summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/list
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/list')
-rw-r--r--src/lib-storage/list/Makefile.am45
-rw-r--r--src/lib-storage/list/Makefile.in916
-rw-r--r--src/lib-storage/list/mail-storage-list-index-rebuild.c615
-rw-r--r--src/lib-storage/list/mailbox-list-delete.c489
-rw-r--r--src/lib-storage/list/mailbox-list-delete.h86
-rw-r--r--src/lib-storage/list/mailbox-list-fs-flags.c243
-rw-r--r--src/lib-storage/list/mailbox-list-fs-iter.c833
-rw-r--r--src/lib-storage/list/mailbox-list-fs.c558
-rw-r--r--src/lib-storage/list/mailbox-list-fs.h28
-rw-r--r--src/lib-storage/list/mailbox-list-index-backend.c970
-rw-r--r--src/lib-storage/list/mailbox-list-index-iter.c266
-rw-r--r--src/lib-storage/list/mailbox-list-index-notify.c967
-rw-r--r--src/lib-storage/list/mailbox-list-index-status.c862
-rw-r--r--src/lib-storage/list/mailbox-list-index-storage.h21
-rw-r--r--src/lib-storage/list/mailbox-list-index-sync.c521
-rw-r--r--src/lib-storage/list/mailbox-list-index-sync.h35
-rw-r--r--src/lib-storage/list/mailbox-list-index.c1234
-rw-r--r--src/lib-storage/list/mailbox-list-index.h250
-rw-r--r--src/lib-storage/list/mailbox-list-iter-private.h42
-rw-r--r--src/lib-storage/list/mailbox-list-iter.c1168
-rw-r--r--src/lib-storage/list/mailbox-list-maildir-iter.c524
-rw-r--r--src/lib-storage/list/mailbox-list-maildir.c546
-rw-r--r--src/lib-storage/list/mailbox-list-maildir.h29
-rw-r--r--src/lib-storage/list/mailbox-list-none.c178
-rw-r--r--src/lib-storage/list/mailbox-list-notify-tree.c131
-rw-r--r--src/lib-storage/list/mailbox-list-notify-tree.h27
-rw-r--r--src/lib-storage/list/mailbox-list-subscriptions.c318
-rw-r--r--src/lib-storage/list/mailbox-list-subscriptions.h33
-rw-r--r--src/lib-storage/list/subscription-file.c380
-rw-r--r--src/lib-storage/list/subscription-file.h25
30 files changed, 12340 insertions, 0 deletions
diff --git a/src/lib-storage/list/Makefile.am b/src/lib-storage/list/Makefile.am
new file mode 100644
index 0000000..3a15e8e
--- /dev/null
+++ b/src/lib-storage/list/Makefile.am
@@ -0,0 +1,45 @@
+noinst_LTLIBRARIES = libstorage_list.la
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-fs \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index
+
+libstorage_list_la_SOURCES = \
+ mail-storage-list-index-rebuild.c \
+ mailbox-list-delete.c \
+ mailbox-list-fs.c \
+ mailbox-list-fs-flags.c \
+ mailbox-list-fs-iter.c \
+ mailbox-list-index.c \
+ mailbox-list-index-backend.c \
+ mailbox-list-index-iter.c \
+ mailbox-list-index-notify.c \
+ mailbox-list-index-status.c \
+ mailbox-list-index-sync.c \
+ mailbox-list-iter.c \
+ mailbox-list-maildir.c \
+ mailbox-list-maildir-iter.c \
+ mailbox-list-none.c \
+ mailbox-list-notify-tree.c \
+ mailbox-list-subscriptions.c \
+ subscription-file.c
+
+headers = \
+ mailbox-list-delete.h \
+ mailbox-list-fs.h \
+ mailbox-list-index.h \
+ mailbox-list-index-storage.h \
+ mailbox-list-index-sync.h \
+ mailbox-list-iter-private.h \
+ mailbox-list-maildir.h \
+ mailbox-list-notify-tree.h \
+ mailbox-list-subscriptions.h \
+ subscription-file.h
+
+pkginc_libdir=$(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
diff --git a/src/lib-storage/list/Makefile.in b/src/lib-storage/list/Makefile.in
new file mode 100644
index 0000000..37ec2ca
--- /dev/null
+++ b/src/lib-storage/list/Makefile.in
@@ -0,0 +1,916 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = src/lib-storage/list
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_checktype2.m4 \
+ $(top_srcdir)/m4/ac_typeof.m4 $(top_srcdir)/m4/arc4random.m4 \
+ $(top_srcdir)/m4/blockdev.m4 $(top_srcdir)/m4/c99_vsnprintf.m4 \
+ $(top_srcdir)/m4/clock_gettime.m4 $(top_srcdir)/m4/crypt.m4 \
+ $(top_srcdir)/m4/crypt_xpg6.m4 $(top_srcdir)/m4/dbqlk.m4 \
+ $(top_srcdir)/m4/dirent_dtype.m4 $(top_srcdir)/m4/dovecot.m4 \
+ $(top_srcdir)/m4/fd_passing.m4 $(top_srcdir)/m4/fdatasync.m4 \
+ $(top_srcdir)/m4/flexible_array_member.m4 \
+ $(top_srcdir)/m4/glibc.m4 $(top_srcdir)/m4/gmtime_max.m4 \
+ $(top_srcdir)/m4/gmtime_tm_gmtoff.m4 \
+ $(top_srcdir)/m4/ioloop.m4 $(top_srcdir)/m4/iovec.m4 \
+ $(top_srcdir)/m4/ipv6.m4 $(top_srcdir)/m4/libcap.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/libwrap.m4 \
+ $(top_srcdir)/m4/linux_mremap.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mmap_write.m4 \
+ $(top_srcdir)/m4/mntctl.m4 $(top_srcdir)/m4/modules.m4 \
+ $(top_srcdir)/m4/notify.m4 $(top_srcdir)/m4/nsl.m4 \
+ $(top_srcdir)/m4/off_t_max.m4 $(top_srcdir)/m4/pkg.m4 \
+ $(top_srcdir)/m4/pr_set_dumpable.m4 \
+ $(top_srcdir)/m4/q_quotactl.m4 $(top_srcdir)/m4/quota.m4 \
+ $(top_srcdir)/m4/random.m4 $(top_srcdir)/m4/rlimit.m4 \
+ $(top_srcdir)/m4/sendfile.m4 $(top_srcdir)/m4/size_t_signed.m4 \
+ $(top_srcdir)/m4/sockpeercred.m4 $(top_srcdir)/m4/sql.m4 \
+ $(top_srcdir)/m4/ssl.m4 $(top_srcdir)/m4/st_tim.m4 \
+ $(top_srcdir)/m4/static_array.m4 $(top_srcdir)/m4/test_with.m4 \
+ $(top_srcdir)/m4/time_t.m4 $(top_srcdir)/m4/typeof.m4 \
+ $(top_srcdir)/m4/typeof_dev_t.m4 \
+ $(top_srcdir)/m4/uoff_t_max.m4 $(top_srcdir)/m4/vararg.m4 \
+ $(top_srcdir)/m4/want_apparmor.m4 \
+ $(top_srcdir)/m4/want_bsdauth.m4 \
+ $(top_srcdir)/m4/want_bzlib.m4 \
+ $(top_srcdir)/m4/want_cassandra.m4 \
+ $(top_srcdir)/m4/want_cdb.m4 \
+ $(top_srcdir)/m4/want_checkpassword.m4 \
+ $(top_srcdir)/m4/want_clucene.m4 $(top_srcdir)/m4/want_db.m4 \
+ $(top_srcdir)/m4/want_gssapi.m4 $(top_srcdir)/m4/want_icu.m4 \
+ $(top_srcdir)/m4/want_ldap.m4 $(top_srcdir)/m4/want_lua.m4 \
+ $(top_srcdir)/m4/want_lz4.m4 $(top_srcdir)/m4/want_lzma.m4 \
+ $(top_srcdir)/m4/want_mysql.m4 $(top_srcdir)/m4/want_pam.m4 \
+ $(top_srcdir)/m4/want_passwd.m4 $(top_srcdir)/m4/want_pgsql.m4 \
+ $(top_srcdir)/m4/want_prefetch.m4 \
+ $(top_srcdir)/m4/want_shadow.m4 \
+ $(top_srcdir)/m4/want_sodium.m4 $(top_srcdir)/m4/want_solr.m4 \
+ $(top_srcdir)/m4/want_sqlite.m4 \
+ $(top_srcdir)/m4/want_stemmer.m4 \
+ $(top_srcdir)/m4/want_systemd.m4 \
+ $(top_srcdir)/m4/want_textcat.m4 \
+ $(top_srcdir)/m4/want_unwind.m4 $(top_srcdir)/m4/want_zlib.m4 \
+ $(top_srcdir)/m4/want_zstd.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(pkginc_lib_HEADERS) \
+ $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libstorage_list_la_LIBADD =
+am_libstorage_list_la_OBJECTS = mail-storage-list-index-rebuild.lo \
+ mailbox-list-delete.lo mailbox-list-fs.lo \
+ mailbox-list-fs-flags.lo mailbox-list-fs-iter.lo \
+ mailbox-list-index.lo mailbox-list-index-backend.lo \
+ mailbox-list-index-iter.lo mailbox-list-index-notify.lo \
+ mailbox-list-index-status.lo mailbox-list-index-sync.lo \
+ mailbox-list-iter.lo mailbox-list-maildir.lo \
+ mailbox-list-maildir-iter.lo mailbox-list-none.lo \
+ mailbox-list-notify-tree.lo mailbox-list-subscriptions.lo \
+ subscription-file.lo
+libstorage_list_la_OBJECTS = $(am_libstorage_list_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/mail-storage-list-index-rebuild.Plo \
+ ./$(DEPDIR)/mailbox-list-delete.Plo \
+ ./$(DEPDIR)/mailbox-list-fs-flags.Plo \
+ ./$(DEPDIR)/mailbox-list-fs-iter.Plo \
+ ./$(DEPDIR)/mailbox-list-fs.Plo \
+ ./$(DEPDIR)/mailbox-list-index-backend.Plo \
+ ./$(DEPDIR)/mailbox-list-index-iter.Plo \
+ ./$(DEPDIR)/mailbox-list-index-notify.Plo \
+ ./$(DEPDIR)/mailbox-list-index-status.Plo \
+ ./$(DEPDIR)/mailbox-list-index-sync.Plo \
+ ./$(DEPDIR)/mailbox-list-index.Plo \
+ ./$(DEPDIR)/mailbox-list-iter.Plo \
+ ./$(DEPDIR)/mailbox-list-maildir-iter.Plo \
+ ./$(DEPDIR)/mailbox-list-maildir.Plo \
+ ./$(DEPDIR)/mailbox-list-none.Plo \
+ ./$(DEPDIR)/mailbox-list-notify-tree.Plo \
+ ./$(DEPDIR)/mailbox-list-subscriptions.Plo \
+ ./$(DEPDIR)/subscription-file.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libstorage_list_la_SOURCES)
+DIST_SOURCES = $(libstorage_list_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__installdirs = "$(DESTDIR)$(pkginc_libdir)"
+HEADERS = $(pkginc_lib_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+ACLOCAL_AMFLAGS = @ACLOCAL_AMFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPARMOR_LIBS = @APPARMOR_LIBS@
+AR = @AR@
+AUTH_CFLAGS = @AUTH_CFLAGS@
+AUTH_LIBS = @AUTH_LIBS@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BINARY_CFLAGS = @BINARY_CFLAGS@
+BINARY_LDFLAGS = @BINARY_LDFLAGS@
+BISON = @BISON@
+CASSANDRA_CFLAGS = @CASSANDRA_CFLAGS@
+CASSANDRA_LIBS = @CASSANDRA_LIBS@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CDB_LIBS = @CDB_LIBS@
+CFLAGS = @CFLAGS@
+CLUCENE_CFLAGS = @CLUCENE_CFLAGS@
+CLUCENE_LIBS = @CLUCENE_LIBS@
+COMPRESS_LIBS = @COMPRESS_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPT_LIBS = @CRYPT_LIBS@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DICT_LIBS = @DICT_LIBS@
+DLLIB = @DLLIB@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FLEX = @FLEX@
+FUZZER_CPPFLAGS = @FUZZER_CPPFLAGS@
+FUZZER_LDFLAGS = @FUZZER_LDFLAGS@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KRB5CONFIG = @KRB5CONFIG@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_LIBS = @KRB5_LIBS@
+LD = @LD@
+LDAP_LIBS = @LDAP_LIBS@
+LDFLAGS = @LDFLAGS@
+LD_NO_WHOLE_ARCHIVE = @LD_NO_WHOLE_ARCHIVE@
+LD_WHOLE_ARCHIVE = @LD_WHOLE_ARCHIVE@
+LIBCAP = @LIBCAP@
+LIBDOVECOT = @LIBDOVECOT@
+LIBDOVECOT_COMPRESS = @LIBDOVECOT_COMPRESS@
+LIBDOVECOT_DEPS = @LIBDOVECOT_DEPS@
+LIBDOVECOT_DSYNC = @LIBDOVECOT_DSYNC@
+LIBDOVECOT_LA_LIBS = @LIBDOVECOT_LA_LIBS@
+LIBDOVECOT_LDA = @LIBDOVECOT_LDA@
+LIBDOVECOT_LDAP = @LIBDOVECOT_LDAP@
+LIBDOVECOT_LIBFTS = @LIBDOVECOT_LIBFTS@
+LIBDOVECOT_LIBFTS_DEPS = @LIBDOVECOT_LIBFTS_DEPS@
+LIBDOVECOT_LOGIN = @LIBDOVECOT_LOGIN@
+LIBDOVECOT_LUA = @LIBDOVECOT_LUA@
+LIBDOVECOT_LUA_DEPS = @LIBDOVECOT_LUA_DEPS@
+LIBDOVECOT_SQL = @LIBDOVECOT_SQL@
+LIBDOVECOT_STORAGE = @LIBDOVECOT_STORAGE@
+LIBDOVECOT_STORAGE_DEPS = @LIBDOVECOT_STORAGE_DEPS@
+LIBEXTTEXTCAT_CFLAGS = @LIBEXTTEXTCAT_CFLAGS@
+LIBEXTTEXTCAT_LIBS = @LIBEXTTEXTCAT_LIBS@
+LIBICONV = @LIBICONV@
+LIBICU_CFLAGS = @LIBICU_CFLAGS@
+LIBICU_LIBS = @LIBICU_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBSODIUM_CFLAGS = @LIBSODIUM_CFLAGS@
+LIBSODIUM_LIBS = @LIBSODIUM_LIBS@
+LIBTIRPC_CFLAGS = @LIBTIRPC_CFLAGS@
+LIBTIRPC_LIBS = @LIBTIRPC_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBWRAP_LIBS = @LIBWRAP_LIBS@
+LINKED_STORAGE_LDADD = @LINKED_STORAGE_LDADD@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LUA_CFLAGS = @LUA_CFLAGS@
+LUA_LIBS = @LUA_LIBS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MODULE_LIBS = @MODULE_LIBS@
+MODULE_SUFFIX = @MODULE_SUFFIX@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+NOPLUGIN_LDFLAGS = @NOPLUGIN_LDFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANDOC = @PANDOC@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PIE_CFLAGS = @PIE_CFLAGS@
+PIE_LDFLAGS = @PIE_LDFLAGS@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+QUOTA_LIBS = @QUOTA_LIBS@
+RANLIB = @RANLIB@
+RELRO_LDFLAGS = @RELRO_LDFLAGS@
+RPCGEN = @RPCGEN@
+RUN_TEST = @RUN_TEST@
+SED = @SED@
+SETTING_FILES = @SETTING_FILES@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CFLAGS = @SQLITE_CFLAGS@
+SQLITE_LIBS = @SQLITE_LIBS@
+SQL_CFLAGS = @SQL_CFLAGS@
+SQL_LIBS = @SQL_LIBS@
+SSL_CFLAGS = @SSL_CFLAGS@
+SSL_LIBS = @SSL_LIBS@
+STRIP = @STRIP@
+SYSTEMD_CFLAGS = @SYSTEMD_CFLAGS@
+SYSTEMD_LIBS = @SYSTEMD_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+dict_drivers = @dict_drivers@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moduledir = @moduledir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+rundir = @rundir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+sql_drivers = @sql_drivers@
+srcdir = @srcdir@
+ssldir = @ssldir@
+statedir = @statedir@
+sysconfdir = @sysconfdir@
+systemdservicetype = @systemdservicetype@
+systemdsystemunitdir = @systemdsystemunitdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+noinst_LTLIBRARIES = libstorage_list.la
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-fs \
+ -I$(top_srcdir)/src/lib-mail \
+ -I$(top_srcdir)/src/lib-index \
+ -I$(top_srcdir)/src/lib-imap \
+ -I$(top_srcdir)/src/lib-storage \
+ -I$(top_srcdir)/src/lib-storage/index
+
+libstorage_list_la_SOURCES = \
+ mail-storage-list-index-rebuild.c \
+ mailbox-list-delete.c \
+ mailbox-list-fs.c \
+ mailbox-list-fs-flags.c \
+ mailbox-list-fs-iter.c \
+ mailbox-list-index.c \
+ mailbox-list-index-backend.c \
+ mailbox-list-index-iter.c \
+ mailbox-list-index-notify.c \
+ mailbox-list-index-status.c \
+ mailbox-list-index-sync.c \
+ mailbox-list-iter.c \
+ mailbox-list-maildir.c \
+ mailbox-list-maildir-iter.c \
+ mailbox-list-none.c \
+ mailbox-list-notify-tree.c \
+ mailbox-list-subscriptions.c \
+ subscription-file.c
+
+headers = \
+ mailbox-list-delete.h \
+ mailbox-list-fs.h \
+ mailbox-list-index.h \
+ mailbox-list-index-storage.h \
+ mailbox-list-index-sync.h \
+ mailbox-list-iter-private.h \
+ mailbox-list-maildir.h \
+ mailbox-list-notify-tree.h \
+ mailbox-list-subscriptions.h \
+ subscription-file.h
+
+pkginc_libdir = $(pkgincludedir)
+pkginc_lib_HEADERS = $(headers)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib-storage/list/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/lib-storage/list/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libstorage_list.la: $(libstorage_list_la_OBJECTS) $(libstorage_list_la_DEPENDENCIES) $(EXTRA_libstorage_list_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libstorage_list_la_OBJECTS) $(libstorage_list_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mail-storage-list-index-rebuild.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-delete.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-fs-flags.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-fs-iter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-fs.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index-backend.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index-iter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index-notify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index-status.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index-sync.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-index.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-iter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-maildir-iter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-maildir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-none.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-notify-tree.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mailbox-list-subscriptions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subscription-file.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_libHEADERS: $(pkginc_lib_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_libdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_libdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_libdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_libHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_lib_HEADERS)'; test -n "$(pkginc_libdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_libdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(pkginc_libdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/mail-storage-list-index-rebuild.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-delete.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs-flags.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-backend.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-notify.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-status.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-sync.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-maildir-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-maildir.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-none.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-notify-tree.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-subscriptions.Plo
+ -rm -f ./$(DEPDIR)/subscription-file.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_libHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/mail-storage-list-index-rebuild.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-delete.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs-flags.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-fs.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-backend.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-notify.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-status.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index-sync.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-index.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-maildir-iter.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-maildir.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-none.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-notify-tree.Plo
+ -rm -f ./$(DEPDIR)/mailbox-list-subscriptions.Plo
+ -rm -f ./$(DEPDIR)/subscription-file.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkginc_libHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_libHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-pkginc_libHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/lib-storage/list/mail-storage-list-index-rebuild.c b/src/lib-storage/list/mail-storage-list-index-rebuild.c
new file mode 100644
index 0000000..c55cf82
--- /dev/null
+++ b/src/lib-storage/list/mail-storage-list-index-rebuild.c
@@ -0,0 +1,615 @@
+/* Copyright (c) 2021 Dovecot Authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hash.h"
+#include "guid.h"
+#include "mail-namespace.h"
+#include "str.h"
+#include "str-sanitize.h"
+#include "hex-binary.h"
+#include "randgen.h"
+#include "fs-api.h"
+#include "mail-index.h"
+#include "mailbox-list-index.h"
+#include "mailbox-list-index-sync.h"
+#include "mailbox-tree.h"
+#include "mail-storage-private.h"
+#include "strfuncs.h"
+
+struct mail_storage_list_index_rebuild_mailbox {
+ guid_128_t guid;
+ const char *index_name;
+ const char *storage_name;
+ struct mailbox_list *list;
+};
+
+struct mail_storage_list_index_rebuild_ns {
+ struct mail_namespace *ns;
+ struct mailbox_list_index_sync_context *list_sync_ctx;
+};
+
+struct mail_storage_list_index_rebuild_ctx {
+ struct mail_storage *storage;
+ pool_t pool;
+ HASH_TABLE(char*, struct mail_storage_list_index_rebuild_mailbox *) mailboxes;
+ ARRAY(struct mail_storage_list_index_rebuild_ns) rebuild_namespaces;
+};
+
+static bool
+mail_storage_list_index_rebuild_get_namespaces(struct mail_storage_list_index_rebuild_ctx *ctx)
+{
+ struct mail_namespace *ns;
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns;
+
+ p_array_init(&ctx->rebuild_namespaces, ctx->pool, 4);
+ for (ns = ctx->storage->user->namespaces; ns != NULL; ns = ns->next) {
+ if (ns->storage != ctx->storage ||
+ ns->alias_for != NULL)
+ continue;
+
+ /* ignore any non-INDEX layout */
+ if (strcmp(ns->list->name, MAILBOX_LIST_NAME_INDEX) != 0)
+ continue;
+
+ rebuild_ns = array_append_space(&ctx->rebuild_namespaces);
+ rebuild_ns->ns = ns;
+ }
+
+ return array_count(&ctx->rebuild_namespaces) > 0;
+}
+
+static int rebuild_ns_cmp(const struct mail_storage_list_index_rebuild_ns *ns1,
+ const struct mail_storage_list_index_rebuild_ns *ns2)
+{
+ return strcmp(ns1->ns->prefix, ns2->ns->prefix);
+}
+
+static int
+mail_storage_list_index_rebuild_lock_lists(struct mail_storage_list_index_rebuild_ctx *ctx)
+{
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns;
+
+ /* sort to make sure all processes lock the lists in the same order
+ to avoid deadlocks. this should be the only place that locks more
+ than one list. */
+ array_sort(&ctx->rebuild_namespaces, rebuild_ns_cmp);
+
+ array_foreach_modifiable(&ctx->rebuild_namespaces, rebuild_ns) {
+ if (mailbox_list_index_sync_begin(rebuild_ns->ns->list,
+ &rebuild_ns->list_sync_ctx) < 0) {
+ mail_storage_copy_list_error(ctx->storage,
+ rebuild_ns->ns->list);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static void
+mail_storage_list_index_rebuild_unlock_lists(struct mail_storage_list_index_rebuild_ctx *ctx)
+{
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns;
+
+ array_foreach_modifiable(&ctx->rebuild_namespaces, rebuild_ns) {
+ if (rebuild_ns->list_sync_ctx != NULL)
+ (void)mailbox_list_index_sync_end(&rebuild_ns->list_sync_ctx, TRUE);
+ }
+}
+
+static bool try_get_mailbox_name(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mailbox_list *list, const char *path,
+ const char **name_r)
+{
+ struct mail_index *index =
+ mail_index_alloc(ctx->storage->event, path, MAIL_INDEX_PREFIX);
+ struct mail_index_view *view;
+ uint32_t box_name_hdr_ext_id;
+ bool ret = FALSE;
+ int rc;
+ if ((rc = mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY)) > 0) {
+ if (mail_index_ext_lookup(index, "box-name", &box_name_hdr_ext_id)) {
+ view = mail_index_view_open(index);
+ const void *name_hdr;
+ size_t name_hdr_size;
+ mail_index_get_header_ext(view, box_name_hdr_ext_id,
+ &name_hdr, &name_hdr_size);
+ *name_r = mailbox_name_hdr_decode_storage_name(list,
+ name_hdr, name_hdr_size);
+ ret = TRUE;
+ mail_index_view_close(&view);
+ } else {
+ e_debug(ctx->storage->event,
+ "Cannot find box-name extension in mailbox index at %s", path);
+ }
+ mail_index_close(index);
+ } else if (rc == 0) {
+ e_debug(ctx->storage->event, "Cannot open mailbox index at %s: Not found", path);
+ } else if (rc < 0) {
+ e_debug(ctx->storage->event, "Cannot open mailbox index at %s: %s",
+ path, mail_index_get_error_message(index));
+ }
+ mail_index_free(&index);
+ return ret;
+}
+
+static const char *get_box_name(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mail_storage_list_index_rebuild_mailbox *box)
+{
+ const char *path =
+ t_strdup_printf("%s/%s",
+ mailbox_list_get_root_forced(box->list, MAILBOX_LIST_PATH_TYPE_MAILBOX),
+ guid_128_to_string(box->guid));
+ const char *box_name;
+ bool inbox_ns = (box->list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0;
+
+ if (try_get_mailbox_name(ctx, box->list, path, &box_name)) {
+ /* special case handling */
+ if (inbox_ns && strcmp(box_name, "INBOX") == 0)
+ box_name = "INBOX";
+ e_debug(ctx->storage->event, "Found '%s' from storage %s",
+ box_name, path);
+ } else {
+ e_debug(ctx->storage->event, "Found GUID '%s' from storage %s, "
+ "but could not recover mailbox name",
+ guid_128_to_string(box->guid), path);
+ box_name = t_strdup_printf("%s%s",
+ ctx->storage->lost_mailbox_prefix,
+ guid_128_to_string(box->guid));
+ }
+ return box_name;
+}
+
+static int
+mail_storage_list_index_fill_storage_mailboxes(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mailbox_list *list)
+{
+ struct mail_storage_list_index_rebuild_mailbox *box;
+ struct fs_iter *iter;
+ const char *path, *fname, *error;
+ guid_128_t guid;
+
+ path = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ iter = fs_iter_init_with_event(ctx->storage->mailboxes_fs,
+ ctx->storage->event, path,
+ FS_ITER_FLAG_DIRS | FS_ITER_FLAG_NOCACHE);
+ while ((fname = fs_iter_next(iter)) != NULL) T_BEGIN {
+ if (guid_128_from_string(fname, guid) == 0) {
+ box = p_new(ctx->pool, struct mail_storage_list_index_rebuild_mailbox, 1);
+ guid_128_copy(box->guid, guid);
+ e_debug(ctx->storage->event,
+ "Found GUID '%s' from storage %s",
+ guid_128_to_string(guid), path);
+ char *hk = p_strdup_printf(ctx->pool, "%s%s",
+ list->ns->prefix,
+ guid_128_to_string(guid));
+ box->list = list;
+ hash_table_update(ctx->mailboxes, hk, box);
+ }
+ } T_END;
+
+ if (fs_iter_deinit(&iter, &error) < 0) {
+ mail_storage_set_critical(ctx->storage,
+ "List rebuild: fs_iter_deinit(%s) failed: %s", path,
+ error);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+mail_storage_list_remove_duplicate(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns,
+ struct mailbox *box,
+ struct mail_storage_list_index_rebuild_mailbox *rebuild_box)
+{
+ const char *delete_name, *keep_name;
+
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_INDEX) != 0) {
+ /* we're not using LAYOUT=index. not really supported now,
+ but just ignore that in here. */
+ return 0;
+ }
+ /* we'll need to delete one of these entries. if one of them begins with
+ "lost-", remove it. otherwise just pick one of them randomly. */
+ if (strncmp(box->name, ctx->storage->lost_mailbox_prefix,
+ strlen(ctx->storage->lost_mailbox_prefix)) == 0) {
+ delete_name = box->name;
+ keep_name = rebuild_box->index_name;
+ } else {
+ delete_name = rebuild_box->index_name;
+ keep_name = p_strdup(ctx->pool, box->name);
+ }
+
+ e_debug(ctx->storage->event,
+ "Removing duplicate mailbox '%s' in favor of mailbox '%s'",
+ str_sanitize(delete_name, 128), str_sanitize(keep_name, 128));
+
+ if (mailbox_list_index_sync_delete(rebuild_ns->list_sync_ctx,
+ delete_name, TRUE) < 0) {
+ mail_storage_set_critical(ctx->storage,
+ "List rebuild: Couldn't delete duplicate mailbox list index entry %s: %s",
+ delete_name, mailbox_list_get_last_internal_error(box->list, NULL));
+ return -1;
+ }
+ e_warning(box->event, "List rebuild: Duplicated mailbox GUID %s found - deleting mailbox entry %s (and keeping %s)",
+ guid_128_to_string(rebuild_box->guid), delete_name, keep_name);
+ rebuild_box->index_name = keep_name;
+ return 0;
+}
+
+static int
+mail_storage_list_index_find_indexed_mailbox(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns,
+ const struct mailbox_info *info)
+{
+ struct mail_storage_list_index_rebuild_mailbox *rebuild_box;
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ int ret = 0;
+
+ if ((info->flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) != 0)
+ return 0;
+
+ box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_IGNORE_ACLS);
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) < 0) {
+ mail_storage_set_critical(rebuild_ns->ns->storage,
+ "List rebuild: Couldn't lookup mailbox %s GUID: %s",
+ info->vname, mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ } else {
+ const char *hk = t_strdup_printf("%s%s", info->ns->prefix,
+ guid_128_to_string(metadata.guid));
+ rebuild_box = hash_table_lookup(ctx->mailboxes, hk);
+ if (rebuild_box == NULL) {
+ /* indexed but doesn't exist in storage. shouldn't
+ happen normally, but it'll be created when it gets
+ accessed. */
+ e_debug(box->event,
+ "Mailbox GUID %s exists in list index, but not in storage",
+ guid_128_to_string(metadata.guid));
+ /* Add it there so we can delete the duplicate */
+ char *hk_dup = p_strdup(ctx->pool, hk);
+ rebuild_box = p_new(ctx->pool, struct mail_storage_list_index_rebuild_mailbox, 1);
+ rebuild_box->list = info->ns->list;
+ rebuild_box->index_name = p_strdup(ctx->pool, box->name);
+ guid_128_copy(rebuild_box->guid, metadata.guid);
+ hash_table_insert(ctx->mailboxes, hk_dup, rebuild_box);
+ } else if (rebuild_box->index_name == NULL) {
+ rebuild_box->index_name =
+ p_strdup(ctx->pool, box->name);
+ e_debug(box->event,
+ "Mailbox GUID %s exists in list index and in storage",
+ guid_128_to_string(metadata.guid));
+ } else {
+ /* duplicate GUIDs in index. in theory this could be
+ possible because of mailbox aliases, but we don't
+ support that for now. especially dsync doesn't like
+ duplicates. */
+ if (mail_storage_list_remove_duplicate(ctx, rebuild_ns,
+ box, rebuild_box) < 0)
+ ret = -1;
+ }
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static int
+mail_storage_list_index_find_indexed_mailboxes(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ int ret = 0;
+
+ iter = mailbox_list_iter_init(rebuild_ns->ns->list, "*",
+ MAILBOX_LIST_ITER_RAW_LIST |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES |
+ MAILBOX_LIST_ITER_SKIP_ALIASES);
+ while (ret == 0 && (info = mailbox_list_iter_next(iter)) != NULL) T_BEGIN {
+ ret = mail_storage_list_index_find_indexed_mailbox(ctx, rebuild_ns, info);
+ } T_END;
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ mail_storage_set_critical(rebuild_ns->ns->storage,
+ "List rebuild: Failed to iterate mailboxes: %s",
+ mailbox_list_get_last_internal_error(rebuild_ns->ns->list, NULL));
+ return -1;
+ }
+ return ret;
+}
+
+static int
+mail_storage_list_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ e_debug(box->event, "Attempting to create mailbox");
+ if (mailbox_create(box, update, FALSE) == 0)
+ return 1;
+
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND) {
+ /* if this is because mailbox was marked as deleted,
+ undelete it and retry. */
+ if (mailbox_mark_index_deleted(box, FALSE) < 0)
+ return -1;
+ if (mailbox_create(box, update, FALSE) == 0)
+ return 1;
+ }
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXISTS)
+ return 0;
+ mailbox_set_critical(box,
+ "List rebuild: Couldn't create mailbox %s: %s",
+ mailbox_get_vname(box), mailbox_get_last_internal_error(box, NULL));
+ return -1;
+}
+
+static int
+mail_storage_list_index_try_create(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mailbox_list *list,
+ const uint8_t *guid_p,
+ const char *boxname,
+ bool retry)
+{
+ struct mail_storage *storage = ctx->storage;
+ struct mailbox *box;
+ struct mailbox_update update;
+ string_t *name = t_str_new(128);
+ unsigned char randomness[8];
+ int ret;
+
+ i_zero(&update);
+ guid_128_copy(update.mailbox_guid, guid_p);
+
+ str_append(name, boxname);
+ if (retry) {
+ random_fill(randomness, sizeof(randomness));
+ str_append_c(name, '-');
+ binary_to_hex_append(name, randomness, sizeof(randomness));
+ }
+ /* ignore ACLs to avoid interference */
+ box = mailbox_alloc(list, str_c(name), MAILBOX_FLAG_IGNORE_ACLS);
+ e_debug(box->event, "Mailbox GUID %s exists in storage, but not in list index",
+ guid_128_to_string(guid_p));
+
+ box->corrupted_mailbox_name = TRUE;
+ if ((ret = mail_storage_list_mailbox_create(box, &update)) <= 0)
+ ;
+ else if (mailbox_sync(box, MAILBOX_SYNC_FLAG_FORCE_RESYNC) < 0) {
+ mail_storage_set_critical(storage,
+ "List rebuild: Couldn't force resync on created mailbox %s: %s",
+ str_c(name), mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+ mailbox_free(&box);
+
+ if (ret < 0)
+ return ret;
+
+ /* open a second time to rename the mailbox to its original name,
+ ignore ACLs to avoid interference. */
+ box = mailbox_alloc(list, str_c(name), MAILBOX_FLAG_IGNORE_ACLS);
+ e_debug(box->event, "Attempting to recover original name");
+ if (mailbox_open(box) < 0 &&
+ mailbox_get_last_mail_error(box) != MAIL_ERROR_NOTFOUND) {
+ mail_storage_set_critical(storage,
+ "List rebuild: Couldn't open recovered mailbox %s: %s",
+ str_c(name), mailbox_get_last_internal_error(box, NULL));
+ ret = -1;
+ }
+ mailbox_free(&box);
+ return ret;
+}
+
+static int
+mail_storage_list_index_create(struct mail_storage_list_index_rebuild_ctx *ctx,
+ struct mailbox_list *list,
+ const char *boxname,
+ const uint8_t *guid_p)
+{
+ int i, ret = 0;
+ /* FIXME: we should find out the mailbox's original namespace from the
+ mailbox index's header. */
+ for (i = 0; i < 100; i++) {
+ T_BEGIN {
+ ret = mail_storage_list_index_try_create(ctx, list, guid_p,
+ boxname, i > 0);
+ } T_END;
+ if (ret != 0)
+ return ret;
+ }
+ mail_storage_set_critical(ctx->storage,
+ "List rebuild: Failed to create a new mailbox name for GUID %s - "
+ "everything seems to exist?",
+ guid_128_to_string(guid_p));
+ return -1;
+}
+
+struct mailbox_sort_node {
+ struct mailbox_node node;
+ struct mail_storage_list_index_rebuild_mailbox *box;
+};
+
+static int mail_storage_list_index_add_missing(struct mail_storage_list_index_rebuild_ctx *ctx)
+{
+ struct hash_iterate_context *iter;
+ struct mail_storage_list_index_rebuild_mailbox *box;
+ char *key ATTR_UNUSED;
+ struct mailbox_node *_node;
+ unsigned int num_created = 0;
+ char sep = mail_namespaces_get_root_sep(ctx->storage->user->namespaces);
+ int ret = 0;
+
+ iter = hash_table_iterate_init(ctx->mailboxes);
+ /* we need to sort the boxes so that they end up created in right order
+ in case we have total loss of indexes */
+
+ e_debug(ctx->storage->event, "Sorting mailbox tree");
+ struct mailbox_tree_context *tree =
+ mailbox_tree_init_size(sep, sizeof(struct mailbox_sort_node));
+ while (hash_table_iterate(iter, ctx->mailboxes, &key, &box)) {
+ bool created;
+ const char *name = box->index_name;
+ if (name == NULL)
+ name = get_box_name(ctx, box);
+ const char *vname =
+ t_strconcat(box->list->ns->prefix, name, NULL);
+ _node = mailbox_tree_get(tree, vname, &created);
+ struct mailbox_sort_node *node =
+ container_of(_node, struct mailbox_sort_node, node);
+ node->box = box;
+ }
+ hash_table_iterate_deinit(&iter);
+
+ mailbox_tree_sort(tree);
+
+ struct mailbox_tree_iterate_context *tree_iter =
+ mailbox_tree_iterate_init(tree, NULL, 0);
+ const char *box_name;
+ e_debug(ctx->storage->event, "Recovering lost mailboxes");
+ while ((_node = mailbox_tree_iterate_next(tree_iter, &box_name)) != NULL) {
+ struct mailbox_sort_node *node =
+ container_of(_node, struct mailbox_sort_node, node);
+ /* skip any intermediate levels that might get created
+ into the tree */
+ if (node->box == NULL)
+ continue;
+ /* this node needs to be created */
+ if (node->box->index_name == NULL) {
+ if (mail_storage_list_index_create(ctx, node->box->list,
+ box_name,
+ node->box->guid) < 0)
+ ret = -1;
+ else
+ num_created++;
+ }
+ }
+ mailbox_tree_iterate_deinit(&tree_iter);
+ if (num_created > 0) {
+ e_warning(ctx->storage->event,
+ "Mailbox list rescan found %u lost mailboxes",
+ num_created);
+ }
+ mailbox_tree_deinit(&tree);
+ return ret;
+}
+
+static int mail_storage_list_index_rebuild_ctx(struct mail_storage_list_index_rebuild_ctx *ctx)
+{
+ struct mail_storage_list_index_rebuild_ns *rebuild_ns;
+
+ array_foreach_modifiable(&ctx->rebuild_namespaces, rebuild_ns) {
+ e_debug(ctx->storage->event,
+ "Rebuilding list index for namespace '%s'",
+ rebuild_ns->ns->prefix);
+ if (mail_storage_list_index_fill_storage_mailboxes(ctx, rebuild_ns->ns->list) < 0)
+ return -1;
+ if (mail_storage_list_index_find_indexed_mailboxes(ctx, rebuild_ns) < 0)
+ return -1;
+ }
+
+ /* finish list syncing before creating mailboxes, because
+ mailbox_create() will internally try to re-acquire the lock.
+ (alternatively we could just add the mailbox to the list index
+ directly, but that's could cause problems as well.) */
+ mail_storage_list_index_rebuild_unlock_lists(ctx);
+ if (mail_storage_list_index_add_missing(ctx) < 0)
+ return -1;
+ return 0;
+}
+
+static int mail_storage_list_index_rebuild_int(struct mail_storage *storage)
+{
+ struct mail_storage_list_index_rebuild_ctx ctx;
+ int ret;
+
+ if (storage->mailboxes_fs == NULL) {
+ storage->rebuild_list_index = FALSE;
+ mail_storage_set_critical(storage,
+ "BUG: Can't rebuild mailbox list index: "
+ "Missing mailboxes_fs");
+ return 0;
+ }
+
+ if (storage->rebuilding_list_index)
+ return 0;
+ storage->rebuilding_list_index = TRUE;
+
+ i_zero(&ctx);
+ ctx.storage = storage;
+ ctx.pool = pool_alloconly_create("mailbox list index rebuild", 10240);
+ hash_table_create(&ctx.mailboxes, ctx.pool, 0, str_hash, strcmp);
+
+ /* if no namespaces are found, do nothing */
+ if (!mail_storage_list_index_rebuild_get_namespaces(&ctx)) {
+ hash_table_destroy(&ctx.mailboxes);
+ pool_unref(&ctx.pool);
+ return 0;
+ }
+
+ /* do this operation while keeping mailbox list index locked.
+ this avoids race conditions between other list rebuilds and also
+ makes sure that other processes creating/deleting mailboxes can't
+ cause confusion with race conditions. */
+ struct event_reason *reason =
+ event_reason_begin("storage:mailbox_list_rebuild");
+ if ((ret = mail_storage_list_index_rebuild_lock_lists(&ctx)) == 0)
+ ret = mail_storage_list_index_rebuild_ctx(&ctx);
+ mail_storage_list_index_rebuild_unlock_lists(&ctx);
+ event_reason_end(&reason);
+
+ hash_table_destroy(&ctx.mailboxes);
+ pool_unref(&ctx.pool);
+
+ if (ret == 0)
+ storage->rebuild_list_index = FALSE;
+ storage->rebuilding_list_index = FALSE;
+ return ret;
+}
+
+int mail_storage_list_index_rebuild_and_set_uncorrupted(struct mail_storage *storage)
+{
+ struct mail_namespace *ns;
+ int ret = 0;
+
+ /* If mailbox list index is disabled, stop any attempt already here.
+ This saves some allocations and iterating all namespaces. */
+ if (!storage->set->mailbox_list_index) {
+ storage->rebuild_list_index = FALSE;
+ return 0;
+ }
+
+ if (mail_storage_list_index_rebuild_int(storage) < 0)
+ return -1;
+ for (ns = storage->user->namespaces; ns != NULL; ns = ns->next) {
+ if (ns->storage != storage || ns->alias_for != NULL)
+ continue;
+ if (mailbox_list_index_set_uncorrupted(ns->list) < 0)
+ ret = -1;
+ }
+ return ret;
+}
+
+int mail_storage_list_index_rebuild(struct mail_storage *storage,
+ enum mail_storage_list_index_rebuild_reason reason)
+{
+ /* If mailbox list index is disabled, stop any attempt already here. */
+ if (!storage->set->mailbox_list_index) {
+ storage->rebuild_list_index = FALSE;
+ return 0;
+ }
+
+ switch (reason) {
+ case MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_CORRUPTED:
+ e_warning(storage->event,
+ "Mailbox list index marked corrupted - rescanning");
+ break;
+ case MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_FORCE_RESYNC:
+ e_debug(storage->event,
+ "Mailbox list index rebuild due to force resync");
+ break;
+ case MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_NO_INBOX:
+ e_debug(storage->event,
+ "Mailbox list index rebuild due to no INBOX");
+ break;
+ }
+ return mail_storage_list_index_rebuild_int(storage);
+}
diff --git a/src/lib-storage/list/mailbox-list-delete.c b/src/lib-storage/list/mailbox-list-delete.c
new file mode 100644
index 0000000..ca773ee
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-delete.c
@@ -0,0 +1,489 @@
+/* Copyright (c) 2010-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "hostpid.h"
+#include "randgen.h"
+#include "sleep.h"
+#include "unlink-directory.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-delete.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <unistd.h>
+
+static int
+mailbox_list_check_root_delete(struct mailbox_list *list, const char *name,
+ const char *path)
+{
+ const char *root_dir;
+
+ root_dir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_DIR);
+ if (strcmp(root_dir, path) != 0)
+ return 0;
+
+ if (strcmp(name, "INBOX") == 0 &&
+ (list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
+ "INBOX can't be deleted.");
+ return -1;
+ }
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
+ "Mail storage root can't be deleted.");
+ return -1;
+}
+
+static const char *unique_fname(void)
+{
+ unsigned char randbuf[8];
+
+ random_fill(randbuf, sizeof(randbuf));
+ return t_strdup_printf("%s.%s.%s", my_hostname, my_pid,
+ binary_to_hex(randbuf, sizeof(randbuf)));
+
+}
+
+int mailbox_list_delete_maildir_via_trash(struct mailbox_list *list,
+ const char *name,
+ const char *trash_dir)
+{
+ const char *src, *trash_dest, *error;
+ unsigned int count;
+
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &src) <= 0)
+ i_unreached();
+ if (mailbox_list_check_root_delete(list, name, src) < 0)
+ return -1;
+
+ /* rename the mailbox dir to trash dir, which atomically
+ marks it as being deleted. */
+ count = 0; trash_dest = trash_dir;
+ for (; rename(src, trash_dest) < 0; count++) {
+ if (ENOTFOUND(errno)) {
+ if (trash_dest != trash_dir && count < 5) {
+ /* either the source was just deleted or
+ the trash dir was deleted. */
+ trash_dest = trash_dir;
+ continue;
+ }
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ return -1;
+ }
+ if (errno == EXDEV) {
+ /* can't do this the fast way */
+ return 0;
+ }
+ if (!EDESTDIREXISTS(errno)) {
+ if (mailbox_list_set_error_from_errno(list))
+ return -1;
+ mailbox_list_set_critical(list,
+ "rename(%s, %s) failed: %m", src, trash_dest);
+ return -1;
+ }
+
+ /* trash dir already exists. the reasons for this are:
+
+ a) another process is in the middle of deleting it
+ b) previous process crashed and didn't delete it
+ c) NFS: mailbox was recently deleted, but some connection
+ still has that mailbox open. the directory contains .nfs*
+ files that can't be deleted until the mailbox is closed.
+
+ Because of c) we'll first try to rename the mailbox under
+ the trash directory and only later try to delete the entire
+ trash directory. */
+ if (trash_dir == trash_dest) {
+ trash_dest = t_strconcat(trash_dir, "/",
+ unique_fname(), NULL);
+ } else if (mailbox_list_delete_trash(trash_dest, &error) < 0 &&
+ (errno != ENOTEMPTY || count >= 5)) {
+ mailbox_list_set_critical(list,
+ "unlink_directory(%s) failed: %s", trash_dest, error);
+ return -1;
+ }
+ }
+
+ if (mailbox_list_delete_trash(trash_dir, &error) < 0 &&
+ errno != ENOTEMPTY && errno != EBUSY) {
+ mailbox_list_set_critical(list,
+ "unlink_directory(%s) failed: %s", trash_dir, error);
+
+ /* it's already renamed to trash dir, which means it's
+ deleted as far as the client is concerned. Report
+ success. */
+ }
+ return 1;
+}
+
+int mailbox_list_delete_mailbox_file(struct mailbox_list *list,
+ const char *name, const char *path)
+{
+ /* we can simply unlink() the file */
+ if (unlink(path) == 0)
+ return 0;
+ else if (ENOTFOUND(errno)) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ return -1;
+ } else {
+ if (!mailbox_list_set_error_from_errno(list)) {
+ mailbox_list_set_critical(list,
+ "unlink(%s) failed: %m", path);
+ }
+ return -1;
+ }
+}
+
+int mailbox_list_delete_mailbox_nonrecursive(struct mailbox_list *list,
+ const char *name, const char *path,
+ bool rmdir_path)
+{
+ DIR *dir;
+ struct dirent *d;
+ string_t *full_path;
+ size_t dir_len;
+ const char *error;
+ bool mailbox_dir, unlinked_something = FALSE;
+ int ret = 0;
+
+ if (mailbox_list_check_root_delete(list, name, path) < 0)
+ return -1;
+
+ dir = opendir(path);
+ if (dir == NULL) {
+ if (errno == ENOENT) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else {
+ if (!mailbox_list_set_error_from_errno(list)) {
+ mailbox_list_set_critical(list,
+ "opendir(%s) failed: %m", path);
+ }
+ }
+ return -1;
+ }
+
+ full_path = t_str_new(256);
+ str_append(full_path, path);
+ str_append_c(full_path, '/');
+ dir_len = str_len(full_path);
+
+ for (errno = 0; (d = readdir(dir)) != NULL; errno = 0) {
+ if (d->d_name[0] == '.') {
+ /* skip . and .. */
+ if (d->d_name[1] == '\0')
+ continue;
+ if (d->d_name[1] == '.' && d->d_name[2] == '\0')
+ continue;
+ }
+
+ mailbox_dir = list->v.is_internal_name != NULL &&
+ list->v.is_internal_name(list, d->d_name);
+
+ str_truncate(full_path, dir_len);
+ str_append(full_path, d->d_name);
+
+ if (mailbox_dir) {
+ if (mailbox_list_delete_trash(str_c(full_path), &error) < 0) {
+ mailbox_list_set_critical(list,
+ "unlink_directory(%s) failed: %s",
+ str_c(full_path), error);
+ } else {
+ unlinked_something = TRUE;
+ }
+ continue;
+ }
+
+ /* trying to unlink() a directory gives either EPERM or EISDIR
+ (non-POSIX). it doesn't really work anywhere in practise,
+ so don't bother stat()ing the file first */
+ if (unlink(str_c(full_path)) == 0)
+ unlinked_something = TRUE;
+ else if (errno != ENOENT && !UNLINK_EISDIR(errno)) {
+ mailbox_list_set_critical(list,
+ "unlink(%s) failed: %m", str_c(full_path));
+ ret = -1;
+ } else {
+ /* child directories still exist */
+ rmdir_path = FALSE;
+ }
+ }
+ if (errno != 0) {
+ mailbox_list_set_critical(list, "readdir(%s) failed: %m", path);
+ ret = -1;
+ }
+ if (closedir(dir) < 0) {
+ mailbox_list_set_critical(list, "closedir(%s) failed: %m",
+ path);
+ ret = -1;
+ }
+ if (ret < 0)
+ return -1;
+
+ if (rmdir_path) {
+ unsigned int try_count = 0;
+ int ret = rmdir(path);
+ while (ret < 0 && errno == ENOTEMPTY && try_count++ < 10) {
+ /* We didn't see any child directories, so this is
+ either a race condition or .nfs* files were left
+ lying around. In case it's .nfs* files, retry after
+ waiting a bit. Hopefully all processes keeping those
+ files open will have closed them by then. */
+ i_sleep_msecs(100);
+ ret = rmdir(path);
+ }
+ if (rmdir(path) == 0)
+ unlinked_something = TRUE;
+ else if (errno == ENOENT) {
+ /* race condition with another process, which finished
+ deleting it first. */
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else if (errno != ENOTEMPTY && errno != EEXIST) {
+ mailbox_list_set_critical(list, "rmdir(%s) failed: %m",
+ path);
+ return -1;
+ }
+ }
+
+ if (!unlinked_something) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
+ "Mailbox has children, can't delete it");
+ return -1;
+ }
+ return 0;
+}
+
+static bool mailbox_list_path_is_index(struct mailbox_list *list,
+ enum mailbox_list_path_type type)
+{
+ const char *index_root, *type_root;
+
+ if (type == MAILBOX_LIST_PATH_TYPE_INDEX)
+ return TRUE;
+
+ /* e.g. CONTROL dir could point to the same INDEX dir. */
+ type_root = mailbox_list_get_root_forced(list, type);
+ index_root = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_INDEX);
+ return strcmp(type_root, index_root) == 0;
+}
+
+void mailbox_list_delete_until_root(struct mailbox_list *list, const char *path,
+ enum mailbox_list_path_type type)
+{
+ const char *root_dir, *p;
+ size_t len;
+
+ if (list->set.iter_from_index_dir && !list->set.no_noselect &&
+ mailbox_list_path_is_index(list, type)) {
+ /* Don't auto-rmdir parent index directories with ITERINDEX.
+ Otherwise it'll get us into inconsistent state with a
+ \NoSelect mailbox in the mail directory but not in index
+ directory. */
+ return;
+ }
+
+ root_dir = mailbox_list_get_root_forced(list, type);
+ if (!str_begins(path, root_dir)) {
+ /* mbox workaround: name=child/box, root_dir=mail/.imap/,
+ path=mail/child/.imap/box. we'll want to try to delete
+ the .imap/ part, but no further. */
+ len = strlen(path);
+ while (len > 0 && path[len-1] != '/')
+ len--;
+ if (len == 0)
+ return;
+ len--;
+ while (len > 0 && path[len-1] != '/')
+ len--;
+ if (len == 0)
+ return;
+
+ root_dir = t_strndup(path, len-1);
+ }
+ while (strcmp(path, root_dir) != 0) {
+ if (rmdir(path) < 0 && errno != ENOENT) {
+ if (errno == ENOTEMPTY || errno == EEXIST)
+ return;
+
+ mailbox_list_set_critical(list, "rmdir(%s) failed: %m",
+ path);
+ return;
+ }
+ p = strrchr(path, '/');
+ if (p == NULL)
+ break;
+
+ path = t_strdup_until(path, p);
+ }
+}
+
+void mailbox_list_delete_mailbox_until_root(struct mailbox_list *list,
+ const char *storage_name)
+{
+ enum mailbox_list_path_type types[] = {
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ MAILBOX_LIST_PATH_TYPE_ALT_DIR,
+ MAILBOX_LIST_PATH_TYPE_CONTROL,
+ MAILBOX_LIST_PATH_TYPE_INDEX,
+ MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE,
+ };
+ const char *path;
+
+ for (unsigned int i = 0; i < N_ELEMENTS(types); i++) {
+ if (mailbox_list_get_path(list, storage_name, types[i], &path) > 0)
+ mailbox_list_delete_until_root(list, path, types[i]);
+ }
+}
+
+static int mailbox_list_try_delete(struct mailbox_list *list, const char *name,
+ enum mailbox_list_path_type type)
+{
+ const char *mailbox_path, *index_path, *path, *error;
+ int ret;
+
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) <= 0 ||
+ mailbox_list_get_path(list, name, type, &path) <= 0 ||
+ strcmp(path, mailbox_path) == 0)
+ return 0;
+
+ if (type == MAILBOX_LIST_PATH_TYPE_CONTROL &&
+ mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_INDEX,
+ &index_path) > 0 &&
+ strcmp(index_path, path) == 0) {
+ /* CONTROL dir is the same as INDEX dir, which we already
+ deleted. We don't want to continue especially with
+ iter_from_index_dir=yes, because it could be deleting the
+ index directory. */
+ return 0;
+ }
+
+ /* Note that only ALT currently uses maildir_name in paths.
+ INDEX and CONTROL don't. */
+ if (type != MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX ||
+ *list->set.maildir_name == '\0') {
+ /* this directory may contain also child mailboxes' data.
+ we don't want to delete that. */
+ bool rmdir_path = *list->set.maildir_name != '\0';
+ if (mailbox_list_delete_mailbox_nonrecursive(list, name, path,
+ rmdir_path) == 0)
+ ret = 1;
+ else {
+ enum mail_error error =
+ mailbox_list_get_last_mail_error(list);
+ if (error != MAIL_ERROR_NOTFOUND &&
+ error != MAIL_ERROR_NOTPOSSIBLE)
+ return -1;
+ ret = 0;
+ }
+ } else {
+ if (mailbox_list_delete_trash(path, &error) == 0)
+ ret = 1;
+ else if (errno == ENOTEMPTY)
+ ret = 0;
+ else {
+ mailbox_list_set_critical(list,
+ "unlink_directory(%s) failed: %s", path, error);
+ return -1;
+ }
+ }
+
+ /* Avoid leaving empty parent directories lying around.
+ These parent directories' existence or removal doesn't
+ affect our return value. */
+ mailbox_list_delete_until_root(list, path, type);
+ return ret;
+}
+
+int mailbox_list_delete_finish(struct mailbox_list *list, const char *name)
+{
+ int ret, ret2;
+
+ ret = mailbox_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_INDEX);
+ ret2 = mailbox_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_INDEX_CACHE);
+ if (ret == 0 || ret2 < 0)
+ ret = ret2;
+ ret2 = mailbox_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ if (ret == 0 || ret2 < 0)
+ ret = ret2;
+ ret2 = mailbox_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX);
+ if (ret == 0 || ret2 < 0)
+ ret = ret2;
+ return ret;
+}
+
+int mailbox_list_delete_finish_ret(struct mailbox_list *list,
+ const char *name, bool root_delete_success)
+{
+ int ret2;
+
+ if (!root_delete_success &&
+ mailbox_list_get_last_mail_error(list) != MAIL_ERROR_NOTFOUND) {
+ /* unexpected error - preserve it */
+ return -1;
+ } else if ((ret2 = mailbox_list_delete_finish(list, name)) < 0) {
+ /* unexpected error */
+ return -1;
+ } else if (ret2 > 0) {
+ /* successfully deleted */
+ return 0;
+ } else if (root_delete_success) {
+ /* nothing deleted by us, but root was successfully deleted */
+ return 0;
+ } else {
+ /* nothing deleted by us and the root didn't exist either.
+ make sure the list has the correct error set, since it
+ could have been changed. */
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ return -1;
+ }
+}
+
+int mailbox_list_delete_trash(const char *path, const char **error_r)
+{
+ if (unlink_directory(path, UNLINK_DIRECTORY_FLAG_RMDIR, error_r) < 0) {
+ if (errno == ELOOP) {
+ /* it's a symlink? try just deleting it */
+ if (unlink(path) == 0)
+ return 0;
+ errno = ELOOP;
+ return -1;
+ }
+ return -1;
+ }
+ return 0;
+}
+
+int mailbox_list_delete_symlink_default(struct mailbox_list *list,
+ const char *name)
+{
+ const char *path;
+ int ret;
+
+ ret = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
+ &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+
+ if (unlink(path) == 0)
+ return 0;
+
+ if (errno == ENOENT) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else if (UNLINK_EISDIR(errno)) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE,
+ "Mailbox isn't a symlink");
+ } else {
+ mailbox_list_set_critical(list, "unlink(%s) failed: %m", path);
+ }
+ return -1;
+}
diff --git a/src/lib-storage/list/mailbox-list-delete.h b/src/lib-storage/list/mailbox-list-delete.h
new file mode 100644
index 0000000..9b46638
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-delete.h
@@ -0,0 +1,86 @@
+#ifndef MAILBOX_LIST_DELETE_H
+#define MAILBOX_LIST_DELETE_H
+
+#include "mailbox-list.h"
+
+/* Delete the mailbox atomically by rename()ing it to trash_dir and afterwards
+ recursively deleting the trash_dir. If the rename() fails because trash_dir
+ already exists, the trash_dir is first deleted and rename() is retried.
+
+ Returns 1 if the rename() succeeded. Returns 0 if rename() fails with EXDEV,
+ which means the source and destination are on different filesystems and
+ the rename can never succeeed.
+
+ If the path didn't exist, returns -1 and sets the list error to
+ MAIL_ERROR_NOTFOUND.
+
+ Attempting to delete INBOX or the namespace root returns -1 and sets the
+ list error to MAIL_ERROR_NOTPOSSIBLE.
+
+ Returns -1 and sets the list error on other errors. */
+int mailbox_list_delete_maildir_via_trash(struct mailbox_list *list,
+ const char *name,
+ const char *trash_dir);
+/* Try to unlink() the path. Returns 0 on success. If the path didn't exist,
+ returns -1 and sets the list error to MAIL_ERROR_NOTFOUND.
+ Returns -1 and sets the list error on other errors. */
+int mailbox_list_delete_mailbox_file(struct mailbox_list *list,
+ const char *name, const char *path);
+/* Delete all files from the given path. Also all internal directories
+ (as returned by is_internal_name() check) are recursively deleted.
+ Otherwise directories are left undeleted.
+
+ Returns 0 if anything was unlink()ed and no unexpected errors happened.
+ Also returns 0 if there were no files and the path was successfully
+ rmdir()ed.
+
+ If the path didn't exist, returns -1 and sets the list error to
+ MAIL_ERROR_NOTFOUND.
+
+ If the path exists and has subdirectories, but no files were unlink()ed,
+ returns -1 and sets the list error to MAIL_ERROR_NOTPOSSIBLE.
+
+ Attempting to delete INBOX or the namespace root returns -1 and sets the
+ list error to MAIL_ERROR_NOTPOSSIBLE.
+
+ Returns -1 and sets the list error on other errors. */
+int mailbox_list_delete_mailbox_nonrecursive(struct mailbox_list *list,
+ const char *name, const char *path,
+ bool rmdir_path);
+/* Lookup INDEX, CONTROL and ALT directories for the mailbox and delete them.
+ Returns 1 if anything was unlink()ed or rmdir()ed, 0 if not.
+ Returns -1 and sets the list error on any errors. */
+int mailbox_list_delete_finish(struct mailbox_list *list, const char *name);
+/* Finish mailbox deletion by calling mailbox_list_delete_finish() if needed.
+ Set root_delete_success to TRUE if the mail root directory was successfully
+ deleted, FALSE if not. The list is expected to have a proper error when
+ root_delete_success==FALSE.
+
+ Returns 0 if mailbox deletion should be treated as success. If not, returns
+ -1 and sets the list error if necessary. */
+int mailbox_list_delete_finish_ret(struct mailbox_list *list,
+ const char *name, bool root_delete_success);
+
+/* rmdir() path and its parent directories until the root directory is reached.
+ The root isn't rmdir()ed. */
+void mailbox_list_delete_until_root(struct mailbox_list *list, const char *path,
+ enum mailbox_list_path_type type);
+/* Call mailbox_list_delete_until_root() for all the paths of the mailbox. */
+void mailbox_list_delete_mailbox_until_root(struct mailbox_list *list,
+ const char *storage_name);
+/* Wrapper to unlink_directory(UNLINK_DIRECTORY_FLAG_RMDIR). If it fails due
+ to ELOOP, try to unlink() the path instead. */
+int mailbox_list_delete_trash(const char *path, const char **error_r);
+/* Try to unlink() the path to the mailbox. Returns 0 on success.
+
+ If the path didn't exist, returns -1 and sets the list error to
+ MAIL_ERROR_NOTFOUND.
+
+ If the path is a directory, returns -1 and sets the list error to
+ MAIL_ERROR_NOTPOSSIBLE.
+
+ Returns -1 and sets the list error on other errors. */
+int mailbox_list_delete_symlink_default(struct mailbox_list *list,
+ const char *name);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-fs-flags.c b/src/lib-storage/list/mailbox-list-fs-flags.c
new file mode 100644
index 0000000..3f3bd94
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs-flags.c
@@ -0,0 +1,243 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "mailbox-list-fs.h"
+
+#include <sys/stat.h>
+
+/* Assume that if atime < mtime, there are new mails. If it's good enough for
+ UW-IMAP, it's good enough for us. */
+#define STAT_GET_MARKED_FILE(st) \
+ ((st).st_size == 0 ? MAILBOX_UNMARKED : \
+ (st).st_atime < (st).st_mtime ? MAILBOX_MARKED : MAILBOX_UNMARKED)
+
+static int
+list_is_maildir_mailbox(struct mailbox_list *list, const char *dir,
+ const char *fname, enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags_r)
+{
+ const char *path, *maildir_path;
+ struct stat st, st2;
+ bool mailbox_files;
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ case MAILBOX_LIST_FILE_TYPE_OTHER:
+ /* non-directories aren't valid */
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
+ case MAILBOX_LIST_FILE_TYPE_SYMLINK:
+ break;
+ }
+
+ path = t_strdup_printf("%s/%s", dir, fname);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ } else {
+ /* non-selectable. probably either access denied, or
+ symlink destination not found. don't bother logging
+ errors. */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ if (str_begins(fname, ".nfs")) {
+ /* temporary NFS file */
+ *flags_r |= MAILBOX_NONEXISTENT;
+ } else {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ }
+ return 0;
+ }
+
+ /* ok, we've got a directory. see what we can do about it. */
+
+ /* 1st link is "."
+ 2nd link is ".."
+ 3rd link is either child mailbox or mailbox dir
+ rest of the links are child mailboxes
+
+ if mailboxes are files, then 3+ links are all child mailboxes.
+ */
+ mailbox_files = (list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0;
+ if (st.st_nlink == 2 && !mailbox_files) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+
+ /* we have at least one directory. see if this mailbox is selectable */
+ maildir_path = t_strconcat(path, "/", list->set.maildir_name, NULL);
+ if (stat(maildir_path, &st2) < 0)
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_CHILDREN;
+ else if (!S_ISDIR(st2.st_mode)) {
+ if (mailbox_files) {
+ *flags_r |= st.st_nlink == 2 ?
+ MAILBOX_NOCHILDREN : MAILBOX_CHILDREN;
+ } else {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_CHILDREN;
+ }
+ } else {
+ /* now we know what link count 3 means. */
+ if (st.st_nlink == 3)
+ *flags_r |= MAILBOX_NOCHILDREN;
+ else
+ *flags_r |= MAILBOX_CHILDREN;
+ }
+ *flags_r |= MAILBOX_SELECT;
+ return 1;
+}
+
+static bool
+is_inbox_file(struct mailbox_list *list, const char *path, const char *fname)
+{
+ const char *inbox_path;
+
+ if (strcasecmp(fname, "INBOX") != 0)
+ return FALSE;
+
+ if (mailbox_list_get_path(list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &inbox_path) <= 0)
+ i_unreached();
+ return strcmp(inbox_path, path) == 0;
+}
+
+int fs_list_get_mailbox_flags(struct mailbox_list *list,
+ const char *dir, const char *fname,
+ enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags_r)
+{
+ struct stat st;
+ const char *path;
+
+ *flags_r = 0;
+
+ if (*list->set.maildir_name != '\0' && !list->set.iter_from_index_dir) {
+ /* maildir_name is set: This is the simple case that works for
+ all mail storage formats, because the only thing that
+ matters for existence or child checks is whether the
+ maildir_name exists or not. For example with Maildir this
+ doesn't care whether the "cur" directory exists; as long
+ as the parent maildir_name exists, the Maildir is
+ selectable. */
+ return list_is_maildir_mailbox(list, dir, fname, type, flags_r);
+ }
+ /* maildir_name is not set: Now we (may) need to use storage-specific
+ code to determine whether the mailbox is selectable or if it has
+ children.
+
+ We're here also when iterating from index directory, because even
+ though maildir_name is set, it's not used for index directory.
+ */
+
+ if (!list->set.iter_from_index_dir &&
+ list->v.is_internal_name != NULL &&
+ list->v.is_internal_name(list, fname)) {
+ /* skip internal dirs. For example Maildir's cur/new/tmp */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 0;
+ }
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ /* We know that we're looking at a directory. If the storage
+ uses files, it has to be a \NoSelect directory. */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ /* We know that we're looking at a file. If the storage
+ doesn't use files, it's not a mailbox and we want to skip
+ it. */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* we've done all filtering we can before stat()ing */
+ path = t_strconcat(dir, "/", fname, NULL);
+ if (stat(path, &st) < 0) {
+ if (ENOTFOUND(errno)) {
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ } else if (ENOACCESS(errno)) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ } else {
+ /* non-selectable. probably either access denied, or
+ symlink destination not found. don't bother logging
+ errors. */
+ mailbox_list_set_critical(list, "stat(%s) failed: %m",
+ path);
+ return -1;
+ }
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ if (str_begins(fname, ".nfs")) {
+ /* temporary NFS file */
+ *flags_r |= MAILBOX_NONEXISTENT;
+ return 0;
+ }
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ *flags_r |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
+ return 0;
+ }
+ /* looks like a valid mailbox file */
+ if (is_inbox_file(list, path, fname) &&
+ strcmp(fname, "INBOX") != 0) {
+ /* it's possible for INBOX to have child
+ mailboxes as long as the inbox file itself
+ isn't in <mail root>/INBOX */
+ } else {
+ *flags_r |= MAILBOX_NOINFERIORS;
+ }
+ /* Return mailbox files as always existing. The current
+ mailbox_exists() code would do the same stat() anyway
+ without further checks, so might as well avoid the second
+ stat(). */
+ *flags_r |= MAILBOX_SELECT;
+ *flags_r |= STAT_GET_MARKED_FILE(st);
+ return 1;
+ }
+
+ /* This is a directory */
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ /* We should get here only if type is
+ MAILBOX_LIST_FILE_TYPE_UNKNOWN because the filesystem didn't
+ return the type. Normally this should have already been
+ handled by the MAILBOX_LIST_FILE_TYPE_DIR check above. */
+ *flags_r |= MAILBOX_NOSELECT;
+ return 1;
+ }
+
+ if (list->v.is_internal_name == NULL || list->set.iter_from_index_dir) {
+ /* This mailbox format doesn't use any special directories
+ (e.g. Maildir's cur/new/tmp). In that case we can look at
+ the directory's link count to determine whether there are
+ children or not. The directory's link count equals the
+ number of subdirectories it has. The first two links are
+ for "." and "..".
+
+ link count < 2 can happen with filesystems that don't
+ support link counts. we'll just ignore them for now.. */
+ if (st.st_nlink == 2)
+ *flags_r |= MAILBOX_NOCHILDREN;
+ else if (st.st_nlink > 2)
+ *flags_r |= MAILBOX_CHILDREN;
+ }
+ return 1;
+}
diff --git a/src/lib-storage/list/mailbox-list-fs-iter.c b/src/lib-storage/list/mailbox-list-fs-iter.c
new file mode 100644
index 0000000..b95aabc
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs-iter.c
@@ -0,0 +1,833 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "unichar.h"
+#include "imap-match.h"
+#include "imap-utf7.h"
+#include "mail-storage.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-iter-private.h"
+#include "mailbox-list-fs.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct list_dir_entry {
+ const char *fname;
+ enum mailbox_info_flags info_flags;
+};
+
+struct list_dir_context {
+ struct list_dir_context *parent;
+
+ pool_t pool;
+ const char *storage_name;
+ /* this directory's info flags. */
+ enum mailbox_info_flags info_flags;
+
+ /* all files in this directory */
+ ARRAY(struct list_dir_entry) entries;
+ unsigned int entry_idx;
+};
+
+struct fs_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+
+ const char *const *valid_patterns;
+ /* roots can be either /foo, ~user/bar or baz */
+ ARRAY(const char *) roots;
+ unsigned int root_idx;
+ char sep;
+
+ pool_t info_pool;
+ struct mailbox_info info;
+ /* current directory we're handling */
+ struct list_dir_context *dir;
+
+ bool inbox_found:1;
+ bool inbox_has_children:1;
+ bool listed_prefix_inbox:1;
+};
+
+static int
+fs_get_existence_info_flag(struct fs_list_iterate_context *ctx,
+ const char *vname,
+ enum mailbox_info_flags *info_flags)
+{
+ struct mailbox *box;
+ enum mailbox_flags flags = 0;
+ enum mailbox_existence existence;
+ bool auto_boxes;
+ int ret;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RAW_LIST) != 0)
+ flags |= MAILBOX_FLAG_IGNORE_ACLS;
+ auto_boxes = (ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0;
+ box = mailbox_alloc(ctx->ctx.list, vname, flags);
+ ret = mailbox_exists(box, auto_boxes, &existence);
+ mailbox_free(&box);
+
+ if (ret < 0) {
+ /* this can only be an internal error */
+ mailbox_list_set_internal_error(ctx->ctx.list);
+ return -1;
+ }
+ switch (existence) {
+ case MAILBOX_EXISTENCE_NONE:
+ /* We already found out that this mailbox exists. So this is
+ either a race condition or ACL plugin prevented access to
+ this. In any case treat this as a \NoSelect mailbox so that
+ we'll recurse into its potential children. This is
+ especially important if ACL disabled access to the parent
+ mailbox, but child mailboxes would be accessible. */
+ case MAILBOX_EXISTENCE_NOSELECT:
+ *info_flags |= MAILBOX_NOSELECT;
+ break;
+ case MAILBOX_EXISTENCE_SELECT:
+ *info_flags |= MAILBOX_SELECT;
+ break;
+ }
+ return 0;
+}
+
+static void
+fs_list_rename_invalid(struct fs_list_iterate_context *ctx,
+ const char *storage_name)
+{
+ /* the storage_name is completely invalid, rename it to
+ something more sensible. we could do this for all names that
+ aren't valid mUTF-7, but that might lead to accidents in
+ future when UTF-8 storage names are used */
+ string_t *destname = t_str_new(128);
+ string_t *dest = t_str_new(128);
+ const char *root, *src;
+
+ root = mailbox_list_get_root_forced(ctx->ctx.list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ src = t_strconcat(root, "/", storage_name, NULL);
+
+ if (uni_utf8_get_valid_data((const void *)storage_name,
+ strlen(storage_name), destname))
+ i_unreached(); /* already checked that it was invalid */
+
+ str_append(dest, root);
+ str_append_c(dest, '/');
+ (void)imap_utf8_to_utf7(str_c(destname), dest);
+
+ if (rename(src, str_c(dest)) < 0 && errno != ENOENT)
+ e_error(ctx->ctx.list->ns->user->event,
+ "rename(%s, %s) failed: %m", src, str_c(dest));
+}
+
+static const char *
+dir_get_storage_name(struct list_dir_context *dir, const char *fname)
+{
+ if (*dir->storage_name == '\0') {
+ /* regular root */
+ return fname;
+ } else if (strcmp(dir->storage_name, "/") == 0) {
+ /* full_filesystem_access=yes "/" root */
+ return t_strconcat("/", fname, NULL);
+ } else {
+ /* child */
+ return *fname == '\0' ? dir->storage_name :
+ t_strconcat(dir->storage_name, "/", fname, NULL);
+ }
+}
+
+static int
+dir_entry_get(struct fs_list_iterate_context *ctx, const char *dir_path,
+ struct list_dir_context *dir, const struct dirent *d)
+{
+ const char *storage_name, *vname, *root_dir;
+ struct list_dir_entry *entry;
+ enum imap_match_result match;
+ enum mailbox_info_flags info_flags;
+ int ret;
+
+ /* skip . and .. */
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ return 0;
+
+ if (strcmp(d->d_name, ctx->ctx.list->set.maildir_name) == 0) {
+ /* mail storage's internal directory (e.g. dbox-Mails).
+ this also means that the parent is selectable */
+ dir->info_flags &= ENUM_NEGATE(MAILBOX_NOSELECT);
+ dir->info_flags |= MAILBOX_SELECT;
+ return 0;
+ }
+ if (ctx->ctx.list->set.subscription_fname != NULL &&
+ strcmp(d->d_name, ctx->ctx.list->set.subscription_fname) == 0) {
+ /* if this is the subscriptions file, skip it */
+ root_dir = mailbox_list_get_root_forced(ctx->ctx.list,
+ MAILBOX_LIST_PATH_TYPE_DIR);
+ if (strcmp(root_dir, dir_path) == 0)
+ return 0;
+ }
+
+ /* check the pattern */
+ storage_name = dir_get_storage_name(dir, d->d_name);
+ vname = mailbox_list_get_vname(ctx->ctx.list, storage_name);
+ if (!uni_utf8_str_is_valid(vname)) {
+ fs_list_rename_invalid(ctx, storage_name);
+ /* just skip this in this iteration, we'll see it on the
+ next list */
+ return 0;
+ }
+
+ match = imap_match(ctx->ctx.glob, vname);
+ if (strcmp(d->d_name, "INBOX") == 0 && strcmp(vname, "INBOX") == 0 &&
+ ctx->ctx.list->ns->prefix_len > 0) {
+ /* The glob was matched only against "INBOX", but this
+ directory may hold also prefix/INBOX. Just assume here
+ that it matches and verify later whether it was needed
+ or not. */
+ match = IMAP_MATCH_YES;
+ }
+
+ if ((dir->info_flags & (MAILBOX_CHILDREN | MAILBOX_NOCHILDREN |
+ MAILBOX_NOINFERIORS)) == 0 &&
+ (ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0) {
+ /* we don't know yet if the parent has children. need to figure
+ out if this file is actually a visible mailbox */
+ } else if (match != IMAP_MATCH_YES &&
+ (match & IMAP_MATCH_CHILDREN) == 0) {
+ /* mailbox doesn't match any patterns, we don't care about it */
+ return 0;
+ }
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+ ret = mailbox_list_dirent_is_alias_symlink(ctx->ctx.list,
+ dir_path, d);
+ if (ret != 0)
+ return ret < 0 ? -1 : 0;
+ }
+ ret = ctx->ctx.list->v.
+ get_mailbox_flags(ctx->ctx.list, dir_path, d->d_name,
+ mailbox_list_get_file_type(d), &info_flags);
+ if (ret <= 0)
+ return ret;
+ if (!MAILBOX_INFO_FLAGS_FINISHED(info_flags)) {
+ /* mailbox existence isn't known yet. need to figure it out
+ the hard way. */
+ if (fs_get_existence_info_flag(ctx, vname, &info_flags) < 0)
+ return -1;
+ }
+ if ((info_flags & MAILBOX_NONEXISTENT) != 0)
+ return 0;
+
+ /* mailbox exists - make sure parent knows it has children */
+ dir->info_flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN | MAILBOX_NOINFERIORS);
+ dir->info_flags |= MAILBOX_CHILDREN;
+
+ if (match != IMAP_MATCH_YES && (match & IMAP_MATCH_CHILDREN) == 0) {
+ /* this mailbox didn't actually match any pattern,
+ we just needed to know the children state */
+ return 0;
+ }
+
+ /* entry matched a pattern. we're going to return this. */
+ entry = array_append_space(&dir->entries);
+ entry->fname = p_strdup(dir->pool, d->d_name);
+ entry->info_flags = info_flags;
+ return 0;
+}
+
+static bool
+fs_list_get_storage_path(struct fs_list_iterate_context *ctx,
+ const char *storage_name, const char **path_r)
+{
+ const char *root, *path = storage_name;
+
+ if (*path == '~') {
+ if (!mailbox_list_try_get_absolute_path(ctx->ctx.list, &path)) {
+ /* a) couldn't expand ~user/
+ b) mailbox is under our mail root, we changed
+ path to storage_name */
+ }
+ /* NOTE: the path may have been translated to a storage_name
+ instead of path */
+ }
+ if (*path != '/') {
+ /* non-absolute path. add the mailbox root dir as prefix. */
+ enum mailbox_list_path_type type =
+ ctx->ctx.list->set.iter_from_index_dir ?
+ MAILBOX_LIST_PATH_TYPE_INDEX :
+ MAILBOX_LIST_PATH_TYPE_MAILBOX;
+ if (!mailbox_list_get_root_path(ctx->ctx.list, type, &root))
+ return FALSE;
+ if (ctx->ctx.list->set.iter_from_index_dir &&
+ ctx->ctx.list->set.mailbox_dir_name[0] != '\0') {
+ /* append "mailboxes/" to the index root */
+ root = t_strconcat(root, "/",
+ ctx->ctx.list->set.mailbox_dir_name, NULL);
+ }
+ path = *path == '\0' ? root :
+ t_strconcat(root, "/", path, NULL);
+ }
+ *path_r = path;
+ return TRUE;
+}
+
+static int
+fs_list_dir_read(struct fs_list_iterate_context *ctx,
+ struct list_dir_context *dir)
+{
+ DIR *fsdir;
+ struct dirent *d;
+ const char *path;
+ int ret = 0;
+
+ if (!fs_list_get_storage_path(ctx, dir->storage_name, &path))
+ return 0;
+ if (path == NULL) {
+ /* no mailbox root dir */
+ return 0;
+ }
+
+ fsdir = opendir(path);
+ if (fsdir == NULL) {
+ if (ENOTFOUND(errno)) {
+ /* root) user gave invalid hierarchy, ignore
+ sub) probably just race condition with other client
+ deleting the mailbox. */
+ return 0;
+ }
+ if (errno == EACCES) {
+ /* ignore permission errors */
+ return 0;
+ }
+ mailbox_list_set_critical(ctx->ctx.list,
+ "opendir(%s) failed: %m", path);
+ return -1;
+ }
+ if ((dir->info_flags & (MAILBOX_SELECT | MAILBOX_NOSELECT)) == 0) {
+ /* we don't know if the parent is selectable or not. start with
+ the assumption that it isn't, until we see maildir_name */
+ dir->info_flags |= MAILBOX_NOSELECT;
+ }
+
+ errno = 0;
+ while ((d = readdir(fsdir)) != NULL) T_BEGIN {
+ if (dir_entry_get(ctx, path, dir, d) < 0)
+ ret = -1;
+ errno = 0;
+ } T_END;
+ if (errno != 0) {
+ mailbox_list_set_critical(ctx->ctx.list,
+ "readdir(%s) failed: %m", path);
+ ret = -1;
+ }
+ if (closedir(fsdir) < 0) {
+ mailbox_list_set_critical(ctx->ctx.list,
+ "closedir(%s) failed: %m", path);
+ ret = -1;
+ }
+ return ret;
+}
+
+static struct list_dir_context *
+fs_list_read_dir(struct fs_list_iterate_context *ctx, const char *storage_name,
+ enum mailbox_info_flags info_flags)
+{
+ struct list_dir_context *dir;
+ pool_t pool;
+
+ pool = pool_alloconly_create(MEMPOOL_GROWING"fs iter dir", 256);
+ dir = p_new(pool, struct list_dir_context, 1);
+ dir->pool = pool;
+ dir->storage_name = p_strdup(pool, storage_name);
+ dir->info_flags = info_flags;
+ p_array_init(&dir->entries, pool, 16);
+
+ if (fs_list_dir_read(ctx, dir) < 0)
+ ctx->ctx.failed = TRUE;
+
+ if ((dir->info_flags & (MAILBOX_CHILDREN | MAILBOX_NOCHILDREN |
+ MAILBOX_NOINFERIORS)) == 0) {
+ /* assume this directory has no children */
+ dir->info_flags |= MAILBOX_NOCHILDREN;
+ }
+ return dir;
+}
+
+static bool
+fs_list_get_valid_patterns(struct fs_list_iterate_context *ctx,
+ const char *const *patterns)
+{
+ struct mailbox_list *_list = ctx->ctx.list;
+ ARRAY(const char *) valid_patterns;
+ const char *pattern, *test_pattern, *real_pattern, *error;
+ size_t prefix_len;
+
+ prefix_len = strlen(_list->ns->prefix);
+ p_array_init(&valid_patterns, ctx->ctx.pool, 8);
+ for (; *patterns != NULL; patterns++) {
+ /* check that we're not trying to do any "../../" lists */
+ test_pattern = *patterns;
+ /* skip namespace prefix if possible. this allows using
+ e.g. ~/mail/ prefix and have it pass the pattern
+ validation. */
+ if (strncmp(test_pattern, _list->ns->prefix, prefix_len) == 0)
+ test_pattern += prefix_len;
+ if (!uni_utf8_str_is_valid(test_pattern)) {
+ /* ignore invalid UTF8 patterns */
+ continue;
+ }
+ /* check pattern also when it's converted to use real
+ separators. */
+ real_pattern =
+ mailbox_list_get_storage_name(_list, test_pattern);
+ if (mailbox_list_is_valid_name(_list, test_pattern, &error) &&
+ mailbox_list_is_valid_name(_list, real_pattern, &error)) {
+ pattern = p_strdup(ctx->ctx.pool, *patterns);
+ array_push_back(&valid_patterns, &pattern);
+ }
+ }
+ array_append_zero(&valid_patterns); /* NULL-terminate */
+ ctx->valid_patterns = array_front(&valid_patterns);
+
+ return array_count(&valid_patterns) > 1;
+}
+
+static void fs_list_get_roots(struct fs_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+ char ns_sep = mail_namespace_get_sep(ns);
+ bool full_fs_access =
+ ctx->ctx.list->mail_set->mail_full_filesystem_access;
+ const char *const *patterns, *pattern, *parent, *child;
+ const char *p, *last, *root, *prefix_vname;
+ unsigned int i;
+ size_t parentlen;
+
+ i_assert(*ctx->valid_patterns != NULL);
+
+ /* get the root dirs for all the patterns */
+ p_array_init(&ctx->roots, ctx->ctx.pool, 8);
+ for (patterns = ctx->valid_patterns; *patterns != NULL; patterns++) {
+ pattern = *patterns;
+
+ if (strncmp(pattern, ns->prefix, ns->prefix_len) != 0) {
+ /* typically e.g. prefix=foo/bar/, pattern=foo/%/%
+ we'll use root="" for this.
+
+ it might of course also be pattern=foo/%/prefix/%
+ where we could optimize with root=prefix, but
+ probably too much trouble to implement. */
+ prefix_vname = "";
+ } else {
+ for (p = last = pattern; *p != '\0'; p++) {
+ if (*p == '%' || *p == '*')
+ break;
+ if (*p == ns_sep)
+ last = p;
+ }
+ prefix_vname = t_strdup_until(pattern, last);
+ }
+
+ if (*pattern == ns_sep && full_fs_access) {
+ /* pattern=/something with full filesystem access.
+ (without full filesystem access we want to skip this
+ if namespace prefix begins with separator) */
+ root = "/";
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ ns->prefix_len == 6 &&
+ strcasecmp(prefix_vname, "INBOX") == 0 &&
+ strncasecmp(ns->prefix, pattern, ns->prefix_len) == 0) {
+ /* special case: Namespace prefix is INBOX/ and
+ we just want to see its contents (not the
+ INBOX's children). */
+ root = "";
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+ !ctx->ctx.list->mail_set->mail_shared_explicit_inbox &&
+ (prefix_vname[0] == '\0' ||
+ (strncmp(ns->prefix, prefix_vname, ns->prefix_len-1) == 0 &&
+ prefix_vname[ns->prefix_len-1] == '\0'))) {
+ /* we need to handle ns prefix explicitly here, because
+ getting storage name with
+ mail_shared_explicit_inbox=no would return
+ root=INBOX. (e.g. LIST "" shared/user/box has to
+ return the box when it doesn't exist but
+ shared/user/box/child exists) */
+ root = "";
+ } else {
+ root = mailbox_list_get_storage_name(ctx->ctx.list,
+ prefix_vname);
+ }
+
+ if (*root == '/') {
+ /* /absolute/path */
+ i_assert(full_fs_access);
+ } else if (*root == '~') {
+ /* ~user/path - don't expand the ~user/ path, since we
+ need to be able to convert the path back to vname */
+ i_assert(full_fs_access);
+ } else {
+ /* mailbox name */
+ }
+ root = p_strdup(ctx->ctx.pool, root);
+ array_push_back(&ctx->roots, &root);
+ }
+ /* sort the root dirs so that /foo is before /foo/bar */
+ array_sort(&ctx->roots, i_strcmp_p);
+ /* remove /foo/bar when there already exists /foo parent */
+ for (i = 1; i < array_count(&ctx->roots); ) {
+ parent = array_idx_elem(&ctx->roots, i-1);
+ child = array_idx_elem(&ctx->roots, i);
+ parentlen = strlen(parent);
+ if (str_begins(child, parent) &&
+ (parentlen == 0 ||
+ child[parentlen] == ctx->sep ||
+ child[parentlen] == '\0'))
+ array_delete(&ctx->roots, i, 1);
+ else
+ i++;
+ }
+}
+
+static void fs_list_next_root(struct fs_list_iterate_context *ctx)
+{
+ const char *const *roots;
+ unsigned int count;
+
+ i_assert(ctx->dir == NULL);
+
+ roots = array_get(&ctx->roots, &count);
+ if (ctx->root_idx == count)
+ return;
+ ctx->dir = fs_list_read_dir(ctx, roots[ctx->root_idx],
+ MAILBOX_NOSELECT);
+ ctx->root_idx++;
+}
+
+struct mailbox_list_iterate_context *
+fs_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct fs_list_iterate_context *ctx;
+ pool_t pool;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* we're listing only subscribed mailboxes. we can't optimize
+ it, so just use the generic code. */
+ return mailbox_list_subscriptions_iter_init(_list, patterns,
+ flags);
+ }
+
+ pool = pool_alloconly_create("mailbox list fs iter", 2048);
+ ctx = p_new(pool, struct fs_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = _list;
+ ctx->ctx.flags = flags;
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->info_pool = pool_alloconly_create("fs list", 1024);
+ ctx->sep = mail_namespace_get_sep(_list->ns);
+ ctx->info.ns = _list->ns;
+
+ if (!fs_list_get_valid_patterns(ctx, patterns)) {
+ /* we've only invalid patterns (or INBOX). create a glob
+ anyway to avoid any crashes due to glob being accessed
+ elsewhere */
+ ctx->ctx.glob = imap_match_init(pool, "", TRUE, ctx->sep);
+ return &ctx->ctx;
+ }
+ ctx->ctx.glob = imap_match_init_multiple(pool, ctx->valid_patterns,
+ TRUE, ctx->sep);
+ fs_list_get_roots(ctx);
+ fs_list_next_root(ctx);
+ return &ctx->ctx;
+}
+
+int fs_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct fs_list_iterate_context *ctx =
+ (struct fs_list_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_deinit(_ctx);
+
+ while (ctx->dir != NULL) {
+ struct list_dir_context *dir = ctx->dir;
+
+ ctx->dir = dir->parent;
+ pool_unref(&dir->pool);
+ }
+
+ pool_unref(&ctx->info_pool);
+ pool_unref(&_ctx->pool);
+ return ret;
+}
+
+static void inbox_flags_set(struct fs_list_iterate_context *ctx)
+{
+ /* INBOX is always selectable */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_NOSELECT | MAILBOX_NONEXISTENT);
+
+ if (mail_namespace_is_inbox_noinferiors(ctx->info.ns)) {
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_CHILDREN | MAILBOX_NOCHILDREN);
+ ctx->info.flags |= MAILBOX_NOINFERIORS;
+ }
+}
+
+static const char *
+fs_list_get_inbox_vname(struct fs_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0)
+ return "INBOX";
+ else
+ return p_strconcat(ctx->info_pool, ns->prefix, "INBOX", NULL);
+}
+
+static bool
+list_file_unfound_inbox(struct fs_list_iterate_context *ctx)
+{
+ ctx->info.flags = 0;
+ ctx->info.vname = fs_list_get_inbox_vname(ctx);
+
+ if (mailbox_list_mailbox(ctx->ctx.list, "INBOX", &ctx->info.flags) < 0)
+ ctx->ctx.failed = TRUE;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) != 0 &&
+ (ctx->info.flags & MAILBOX_NONEXISTENT) != 0)
+ return FALSE;
+
+ inbox_flags_set(ctx);
+ if (ctx->inbox_has_children)
+ ctx->info.flags |= MAILBOX_CHILDREN;
+ else {
+ /* we got here because we didn't see INBOX among other mailboxes,
+ which means it has no children. */
+ ctx->info.flags |= MAILBOX_NOCHILDREN;
+ }
+ return TRUE;
+}
+
+static bool
+list_file_is_any_inbox(struct fs_list_iterate_context *ctx,
+ const char *storage_name)
+{
+ const char *path, *inbox_path;
+
+ if (!fs_list_get_storage_path(ctx, storage_name, &path))
+ return FALSE;
+
+ if (mailbox_list_get_path(ctx->ctx.list, "INBOX",
+ MAILBOX_LIST_PATH_TYPE_DIR, &inbox_path) <= 0)
+ i_unreached();
+ return strcmp(path, inbox_path) == 0;
+}
+
+static int
+fs_list_entry(struct fs_list_iterate_context *ctx,
+ const struct list_dir_entry *entry)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+ struct list_dir_context *dir, *subdir = NULL;
+ enum imap_match_result match, child_dir_match;
+ const char *storage_name, *vname, *child_dir_name;
+
+ dir = ctx->dir;
+ storage_name = dir_get_storage_name(dir, entry->fname);
+
+ vname = mailbox_list_get_vname(ctx->ctx.list, storage_name);
+ ctx->info.vname = p_strdup(ctx->info_pool, vname);
+ ctx->info.flags = entry->info_flags;
+
+ match = imap_match(ctx->ctx.glob, ctx->info.vname);
+
+ if (strcmp(ctx->info.vname, "INBOX") == 0 &&
+ ctx->ctx.list->ns->prefix_len > 0) {
+ /* INBOX's children are matched as prefix/INBOX */
+ child_dir_name = t_strdup_printf("%sINBOX", ns->prefix);
+ } else {
+ child_dir_name =
+ t_strdup_printf("%s%c", ctx->info.vname, ctx->sep);
+ }
+ child_dir_match = imap_match(ctx->ctx.glob, child_dir_name);
+ if (child_dir_match == IMAP_MATCH_YES)
+ child_dir_match |= IMAP_MATCH_CHILDREN;
+
+ if ((ctx->info.flags &
+ (MAILBOX_NOCHILDREN | MAILBOX_NOINFERIORS)) != 0) {
+ /* mailbox has no children */
+ } else if ((ctx->info.flags & MAILBOX_CHILDREN) != 0 &&
+ (child_dir_match & IMAP_MATCH_CHILDREN) == 0) {
+ /* mailbox has children, but we don't want to list them */
+ } else if (((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 ||
+ (child_dir_match & IMAP_MATCH_CHILDREN) != 0) &&
+ *entry->fname != '\0') {
+ /* a) mailbox has children and we want to return them
+ b) we don't want to return mailbox's children, but we need
+ to know if it has any */
+ subdir = fs_list_read_dir(ctx, storage_name, entry->info_flags);
+ subdir->parent = dir;
+ ctx->dir = subdir;
+ /* the scanning may have updated the dir's info flags */
+ ctx->info.flags = subdir->info_flags;
+ }
+
+ /* handle INBOXes correctly */
+ if (strcasecmp(ctx->info.vname, "INBOX") == 0 &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* either this is user's INBOX, or it's a naming conflict */
+ if (!list_file_is_any_inbox(ctx, storage_name)) {
+ if (subdir == NULL) {
+ /* no children */
+ } else if ((ctx->ctx.list->flags &
+ MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ if (strcmp(storage_name, "INBOX") == 0) {
+ /* INBOX and its children are in
+ different paths */
+ ctx->inbox_has_children = TRUE;
+ } else {
+ /* naming conflict, skip its
+ children also */
+ ctx->dir = dir;
+ pool_unref(&subdir->pool);
+ }
+ } else if ((ctx->info.flags & MAILBOX_NOINFERIORS) == 0) {
+ /* INBOX itself is \NoInferiors, but this INBOX
+ is a directory, and we can make INBOX have
+ children using it. */
+ ctx->inbox_has_children = TRUE;
+ }
+ return 0;
+ }
+ if (subdir != NULL)
+ ctx->inbox_has_children = TRUE;
+ inbox_flags_set(ctx);
+ ctx->info.vname = "INBOX"; /* always return uppercased */
+ ctx->inbox_found = TRUE;
+ } else if (strcmp(storage_name, "INBOX") == 0 &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* this is <ns prefix>/INBOX. don't return it, unless it has
+ children. */
+ i_assert(*ns->prefix != '\0');
+ if ((ctx->info.flags & MAILBOX_CHILDREN) == 0)
+ return 0;
+ /* although it could be selected with this name,
+ it would be confusing for clients to see the same
+ mails in both INBOX and <ns prefix>/INBOX. */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_SELECT);
+ ctx->info.flags |= MAILBOX_NOSELECT;
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ list_file_is_any_inbox(ctx, storage_name)) {
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* probably mbox inbox file */
+ return 0;
+ }
+ /* shared/user/INBOX */
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_NOSELECT | MAILBOX_NONEXISTENT);
+ ctx->info.flags |= MAILBOX_SELECT;
+ ctx->inbox_found = TRUE;
+ }
+
+ if (match != IMAP_MATCH_YES) {
+ /* mailbox's children may match, but the mailbox itself
+ doesn't */
+ return 0;
+ }
+ if (mailbox_list_iter_try_delete_noselect(&ctx->ctx, &ctx->info, storage_name))
+ return 0;
+ return 1;
+}
+
+static int
+fs_list_next(struct fs_list_iterate_context *ctx)
+{
+ struct list_dir_context *dir;
+ const struct list_dir_entry *entries;
+ unsigned int count;
+ int ret;
+
+ while (ctx->dir != NULL) {
+ /* NOTE: fs_list_entry() may change ctx->dir */
+ entries = array_get(&ctx->dir->entries, &count);
+ while (ctx->dir->entry_idx < count) {
+ p_clear(ctx->info_pool);
+ ret = fs_list_entry(ctx, &entries[ctx->dir->entry_idx++]);
+ if (ret > 0)
+ return 1;
+ if (ret < 0)
+ ctx->ctx.failed = TRUE;
+ entries = array_get(&ctx->dir->entries, &count);
+ }
+
+ dir = ctx->dir;
+ ctx->dir = dir->parent;
+ pool_unref(&dir->pool);
+
+ if (ctx->dir == NULL)
+ fs_list_next_root(ctx);
+ }
+
+ if (ctx->inbox_has_children && ctx->ctx.list->ns->prefix_len > 0 &&
+ !ctx->listed_prefix_inbox) {
+ ctx->info.flags = MAILBOX_CHILDREN | MAILBOX_NOSELECT;
+ ctx->info.vname =
+ p_strconcat(ctx->info_pool,
+ ctx->ctx.list->ns->prefix, "INBOX", NULL);
+ ctx->listed_prefix_inbox = TRUE;
+ if (imap_match(ctx->ctx.glob, ctx->info.vname) == IMAP_MATCH_YES)
+ return 1;
+ }
+ if (!ctx->inbox_found && ctx->ctx.glob != NULL &&
+ (ctx->ctx.list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ imap_match(ctx->ctx.glob,
+ fs_list_get_inbox_vname(ctx)) == IMAP_MATCH_YES) {
+ /* INBOX wasn't seen while listing other mailboxes. It might
+ be located elsewhere. */
+ ctx->inbox_found = TRUE;
+ return list_file_unfound_inbox(ctx) ? 1 : 0;
+ }
+
+ /* finished */
+ return 0;
+}
+
+const struct mailbox_info *
+fs_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct fs_list_iterate_context *ctx =
+ (struct fs_list_iterate_context *)_ctx;
+ int ret;
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_next(_ctx);
+
+ T_BEGIN {
+ ret = fs_list_next(ctx);
+ } T_END;
+
+ if (ret == 0)
+ return mailbox_list_iter_default_next(_ctx);
+ else if (ret < 0)
+ return NULL;
+
+ if (_ctx->list->ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+ !_ctx->list->ns->list->mail_set->mail_shared_explicit_inbox &&
+ strlen(ctx->info.vname) < _ctx->list->ns->prefix_len) {
+ /* shared/user INBOX, IMAP code already lists it */
+ return fs_list_iter_next(_ctx);
+ }
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) {
+ mailbox_list_set_subscription_flags(ctx->ctx.list,
+ ctx->info.vname,
+ &ctx->info.flags);
+ }
+ i_assert(ctx->info.vname != NULL);
+ return &ctx->info;
+}
diff --git a/src/lib-storage/list/mailbox-list-fs.c b/src/lib-storage/list/mailbox-list-fs.c
new file mode 100644
index 0000000..c1aabb9
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs.c
@@ -0,0 +1,558 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "mkdir-parents.h"
+#include "mailbox-log.h"
+#include "subscription-file.h"
+#include "mail-storage.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-fs.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define GLOBAL_TEMP_PREFIX ".temp."
+
+extern struct mailbox_list fs_mailbox_list;
+
+static struct mailbox_list *fs_list_alloc(void)
+{
+ struct fs_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("fs list", 2048);
+
+ list = p_new(pool, struct fs_mailbox_list, 1);
+ list->list = fs_mailbox_list;
+ list->list.pool = pool;
+
+ list->temp_prefix = p_strconcat(pool, GLOBAL_TEMP_PREFIX,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static void fs_list_deinit(struct mailbox_list *_list)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static char fs_list_get_hierarchy_sep(struct mailbox_list *list ATTR_UNUSED)
+{
+ return '/';
+}
+
+static const char *
+fs_list_get_path_to(const struct mailbox_list_settings *set,
+ const char *root_dir, const char *name)
+{
+ if (*set->maildir_name != '\0' && set->index_control_use_maildir_name) {
+ return t_strdup_printf("%s/%s%s/%s", root_dir,
+ set->mailbox_dir_name, name,
+ set->maildir_name);
+ } else {
+ return t_strdup_printf("%s/%s%s", root_dir,
+ set->mailbox_dir_name, name);
+ }
+}
+
+static int
+fs_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ const struct mailbox_list_settings *set = &_list->set;
+ const char *root_dir, *error;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(set, type, path_r) ? 1 : 0;
+ }
+
+ i_assert(mailbox_list_is_valid_name(_list, name, &error));
+
+ if (mailbox_list_try_get_absolute_path(_list, &name)) {
+ if (type == MAILBOX_LIST_PATH_TYPE_INDEX &&
+ *set->index_dir == '\0')
+ return 0;
+ *path_r = name;
+ return 1;
+ }
+
+ root_dir = set->root_dir;
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_DIR:
+ if (*set->maildir_name != '\0') {
+ *path_r = t_strdup_printf("%s/%s%s", set->root_dir,
+ set->mailbox_dir_name, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
+ if (set->alt_dir == NULL)
+ return 0;
+ if (*set->maildir_name != '\0') {
+ /* maildir_name is for the mailbox, caller is asking
+ for the directory name */
+ *path_r = t_strdup_printf("%s/%s%s", set->alt_dir,
+ set->mailbox_dir_name, name);
+ return 1;
+ }
+ root_dir = set->alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ if (set->alt_dir == NULL)
+ return 0;
+ root_dir = set->alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_CONTROL:
+ if (set->control_dir != NULL) {
+ *path_r = fs_list_get_path_to(set, set->control_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_CACHE:
+ if (set->index_cache_dir != NULL) {
+ *path_r = fs_list_get_path_to(set, set->index_cache_dir, name);
+ return 1;
+ }
+ /* fall through */
+ case MAILBOX_LIST_PATH_TYPE_INDEX:
+ if (set->index_dir != NULL) {
+ if (*set->index_dir == '\0')
+ return 0;
+ *path_r = fs_list_get_path_to(set, set->index_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE:
+ if (set->index_pvt_dir == NULL)
+ return 0;
+ *path_r = fs_list_get_path_to(set, set->index_pvt_dir, name);
+ return 1;
+ case MAILBOX_LIST_PATH_TYPE_LIST_INDEX:
+ i_unreached();
+ }
+
+ if (type == MAILBOX_LIST_PATH_TYPE_ALT_DIR ||
+ type == MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX) {
+ /* don't use inbox_path */
+ } else if (strcmp(name, "INBOX") == 0 && set->inbox_path != NULL) {
+ /* If INBOX is a file, index and control directories are
+ located in root directory. */
+ if ((_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0 ||
+ type == MAILBOX_LIST_PATH_TYPE_MAILBOX ||
+ type == MAILBOX_LIST_PATH_TYPE_DIR) {
+ *path_r = set->inbox_path;
+ return 1;
+ }
+ }
+
+ if (root_dir == NULL)
+ return 0;
+ if (*set->maildir_name == '\0') {
+ *path_r = t_strdup_printf("%s/%s%s", root_dir,
+ set->mailbox_dir_name, name);
+ } else {
+ *path_r = t_strdup_printf("%s/%s%s/%s", root_dir,
+ set->mailbox_dir_name, name,
+ set->maildir_name);
+ }
+ return 1;
+}
+
+static const char *
+fs_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+
+ return global ? GLOBAL_TEMP_PREFIX : list->temp_prefix;
+}
+
+static const char *
+fs_list_join_refpattern(struct mailbox_list *_list ATTR_UNUSED,
+ const char *ref, const char *pattern)
+{
+ if (*pattern == '/' || *pattern == '~') {
+ /* pattern overrides reference */
+ } else if (*ref != '\0') {
+ /* merge reference and pattern */
+ pattern = t_strconcat(ref, pattern, NULL);
+ }
+ return pattern;
+}
+
+static int fs_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct fs_mailbox_list *list = (struct fs_mailbox_list *)_list;
+ enum mailbox_list_path_type type;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ type = _list->set.control_dir != NULL ?
+ MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
+
+ path = t_strconcat(mailbox_list_get_root_forced(_list, type),
+ "/", _list->set.subscription_fname, NULL);
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+
+static const char *mailbox_list_fs_get_trash_dir(struct mailbox_list *list)
+{
+ const char *root_dir;
+
+ root_dir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_DIR);
+ return t_strdup_printf("%s/"MAILBOX_LIST_FS_TRASH_DIR_NAME, root_dir);
+}
+
+static int
+fs_list_delete_maildir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *trash_dir;
+ bool rmdir_path;
+ int ret;
+
+ if (*list->set.maildir_name != '\0' &&
+ *list->set.mailbox_dir_name != '\0') {
+ trash_dir = mailbox_list_fs_get_trash_dir(list);
+ ret = mailbox_list_delete_maildir_via_trash(list, name,
+ trash_dir);
+ if (ret < 0)
+ return -1;
+
+ if (ret > 0) {
+ /* try to delete the parent directory */
+ if (mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ if (rmdir(path) < 0 && errno != ENOENT &&
+ errno != ENOTEMPTY && errno != EEXIST) {
+ mailbox_list_set_critical(list,
+ "rmdir(%s) failed: %m", path);
+ }
+ return 0;
+ }
+ }
+
+ rmdir_path = *list->set.maildir_name != '\0';
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) <= 0)
+ i_unreached();
+ return mailbox_list_delete_mailbox_nonrecursive(list, name, path,
+ rmdir_path);
+}
+
+static int fs_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ int ret;
+
+ ret = mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_delete_mailbox_file(list, name, path);
+ } else {
+ ret = fs_list_delete_maildir(list, name);
+ }
+ if (ret == 0 && list->set.no_noselect)
+ mailbox_list_delete_until_root(list, path, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+
+ i_assert(ret <= 0);
+ return mailbox_list_delete_finish_ret(list, name, ret == 0);
+}
+
+static int fs_list_rmdir(struct mailbox_list *list, const char *name,
+ const char *path)
+{
+ guid_128_t dir_sha128;
+
+ if (rmdir(path) < 0)
+ return -1;
+
+ mailbox_name_get_sha128(name, dir_sha128);
+ mailbox_list_add_change(list, MAILBOX_LOG_RECORD_DELETE_DIR,
+ dir_sha128);
+ return 0;
+}
+
+static int fs_list_delete_dir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *child_name, *child_path, *p;
+ char sep;
+ int ret;
+
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ ret = fs_list_rmdir(list, name, path);
+ if (!list->set.iter_from_index_dir) {
+ /* it should exist only in the mail directory */
+ if (ret == 0)
+ return 0;
+ } else if (ret == 0 || errno == ENOENT) {
+ /* the primary list location is the index directory, but it
+ exists in both index and mail directories. */
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_INDEX,
+ &path) <= 0)
+ i_unreached();
+ if (fs_list_rmdir(list, name, path) == 0)
+ return 0;
+ if (ret == 0 && errno == ENOENT) {
+ /* partial existence: exists in _DIR, but not in
+ _INDEX. return success anyway. */
+ return 0;
+ }
+ /* a) both directories didn't exist
+ b) index directory couldn't be rmdir()ed for some reason */
+ }
+
+ if (errno == ENOENT || errno == ENOTDIR) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else if (errno == ENOTEMPTY || errno == EEXIST) {
+ /* mbox workaround: if only .imap/ directory is preventing the
+ deletion, remove it */
+ sep = mailbox_list_get_hierarchy_sep(list);
+ child_name = t_strdup_printf("%s%cchild", name, sep);
+ if (mailbox_list_get_path(list, child_name,
+ MAILBOX_LIST_PATH_TYPE_INDEX,
+ &child_path) > 0 &&
+ str_begins(child_path, path)) {
+ /* drop the "/child" part out. */
+ p = strrchr(child_path, '/');
+ if (rmdir(t_strdup_until(child_path, p)) == 0) {
+ /* try again */
+ if (fs_list_rmdir(list, name, path) == 0)
+ return 0;
+ }
+ }
+
+ mailbox_list_set_error(list, MAIL_ERROR_EXISTS,
+ "Mailbox has children, delete them first");
+ } else {
+ mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path);
+ }
+ return -1;
+}
+
+static int rename_dir(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname,
+ enum mailbox_list_path_type type, bool rmdir_parent)
+{
+ struct stat st;
+ const char *oldpath, *newpath, *p, *oldparent, *newparent;
+
+ if (mailbox_list_get_path(oldlist, oldname, type, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname, type, &newpath) <= 0)
+ return 0;
+
+ if (strcmp(oldpath, newpath) == 0)
+ return 0;
+
+ p = strrchr(oldpath, '/');
+ oldparent = p == NULL ? "/" : t_strdup_until(oldpath, p);
+ p = strrchr(newpath, '/');
+ newparent = p == NULL ? "/" : t_strdup_until(newpath, p);
+
+ if (strcmp(oldparent, newparent) != 0 && stat(oldpath, &st) == 0) {
+ /* make sure the newparent exists */
+ struct mailbox_permissions perm;
+
+ mailbox_list_get_root_permissions(newlist, &perm);
+ if (mkdir_parents_chgrp(newparent, perm.dir_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ if (mailbox_list_set_error_from_errno(oldlist))
+ return -1;
+
+ mailbox_list_set_critical(oldlist,
+ "mkdir_parents(%s) failed: %m", newparent);
+ return -1;
+ }
+ }
+
+ if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ return -1;
+ }
+ if (rmdir_parent && (p = strrchr(oldpath, '/')) != NULL) {
+ oldpath = t_strdup_until(oldpath, p);
+ if (rmdir(oldpath) < 0 && errno != ENOENT &&
+ errno != ENOTEMPTY && errno != EEXIST) {
+ mailbox_list_set_critical(oldlist,
+ "rmdir(%s) failed: %m", oldpath);
+ }
+ }
+
+ /* avoid leaving empty directories lying around */
+ mailbox_list_delete_until_root(oldlist, oldpath, type);
+ return 0;
+}
+
+static int fs_list_rename_mailbox(struct mailbox_list *oldlist,
+ const char *oldname,
+ struct mailbox_list *newlist,
+ const char *newname)
+{
+ struct mail_storage *oldstorage;
+ const char *oldvname, *oldpath, *newpath, *alt_newpath, *root_path, *p;
+ struct stat st;
+ struct mailbox_permissions old_perm, new_perm;
+ bool rmdir_parent = FALSE;
+
+ oldvname = mailbox_list_get_vname(oldlist, oldname);
+ if (mailbox_list_get_storage(&oldlist, oldvname, &oldstorage) < 0)
+ return -1;
+
+ if (mailbox_list_get_path(oldlist, oldname,
+ MAILBOX_LIST_PATH_TYPE_DIR, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_DIR, &newpath) <= 0)
+ i_unreached();
+ if (mailbox_list_get_path(newlist, newname, MAILBOX_LIST_PATH_TYPE_ALT_DIR,
+ &alt_newpath) < 0)
+ i_unreached();
+
+ root_path = mailbox_list_get_root_forced(oldlist, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(oldpath, root_path) == 0) {
+ /* most likely INBOX */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ t_strdup_printf("Renaming %s isn't supported.",
+ oldname));
+ return -1;
+ }
+
+ mailbox_list_get_permissions(oldlist, oldname, &old_perm);
+ mailbox_list_get_permissions(newlist, newname, &new_perm);
+
+ /* if we're renaming under another mailbox, require its permissions
+ to be same as ours. */
+ if (strchr(newname, mailbox_list_get_hierarchy_sep(newlist)) != NULL &&
+ (new_perm.file_create_mode != old_perm.file_create_mode ||
+ new_perm.dir_create_mode != old_perm.dir_create_mode ||
+ new_perm.file_create_gid != old_perm.file_create_gid)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across conflicting "
+ "directory permissions");
+ return -1;
+ }
+
+ /* create the hierarchy */
+ p = strrchr(newpath, '/');
+ if (p != NULL) {
+ p = t_strdup_until(newpath, p);
+ if (mkdir_parents_chgrp(p, new_perm.dir_create_mode,
+ new_perm.file_create_gid,
+ new_perm.file_create_gid_origin) < 0 &&
+ errno != EEXIST) {
+ if (mailbox_list_set_error_from_errno(oldlist))
+ return -1;
+
+ mailbox_list_set_critical(oldlist,
+ "mkdir_parents(%s) failed: %m", p);
+ return -1;
+ }
+ }
+
+ /* first check that the destination mailbox doesn't exist.
+ this is racy, but we need to be atomic and there's hardly any
+ possibility that someone actually tries to rename two mailboxes
+ to same new one */
+ if (lstat(newpath, &st) == 0) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ } else if (errno == ENOTDIR) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Target mailbox doesn't allow inferior mailboxes");
+ return -1;
+ } else if (errno != ENOENT && errno != EACCES) {
+ mailbox_list_set_critical(oldlist, "lstat(%s) failed: %m",
+ newpath);
+ return -1;
+ }
+
+ if (alt_newpath != NULL) {
+ if (stat(alt_newpath, &st) == 0) {
+ /* race condition or a directory left there lying around?
+ safest to just report error. */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ } else if (errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "stat(%s) failed: %m",
+ alt_newpath);
+ return -1;
+ }
+ }
+
+ if (rename(oldpath, newpath) < 0) {
+ if (ENOTFOUND(errno)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(oldlist, oldname));
+ } else if (!mailbox_list_set_error_from_errno(oldlist)) {
+ mailbox_list_set_critical(oldlist,
+ "rename(%s, %s) failed: %m", oldpath, newpath);
+ }
+ return -1;
+ }
+
+ if (alt_newpath != NULL) {
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_ALT_DIR, rmdir_parent);
+ }
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL, rmdir_parent);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX, rmdir_parent);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE, rmdir_parent);
+ return 0;
+}
+
+struct mailbox_list fs_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_FS,
+ .props = 0,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = fs_list_alloc,
+ .deinit = fs_list_deinit,
+ .get_hierarchy_sep = fs_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = fs_list_get_path,
+ .get_temp_prefix = fs_list_get_temp_prefix,
+ .join_refpattern = fs_list_join_refpattern,
+ .iter_init = fs_list_iter_init,
+ .iter_next = fs_list_iter_next,
+ .iter_deinit = fs_list_iter_deinit,
+ .get_mailbox_flags = fs_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = fs_list_set_subscribed,
+ .delete_mailbox = fs_list_delete_mailbox,
+ .delete_dir = fs_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = fs_list_rename_mailbox,
+ }
+};
diff --git a/src/lib-storage/list/mailbox-list-fs.h b/src/lib-storage/list/mailbox-list-fs.h
new file mode 100644
index 0000000..3841890
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-fs.h
@@ -0,0 +1,28 @@
+#ifndef MAILBOX_LIST_FS_H
+#define MAILBOX_LIST_FS_H
+
+#include "mailbox-list-private.h"
+
+/* When doing deletion via renaming it first to trash directory, use this as
+ the trash directory name */
+#define MAILBOX_LIST_FS_TRASH_DIR_NAME "..DOVECOT-TrasH"
+
+struct fs_mailbox_list {
+ struct mailbox_list list;
+
+ const char *temp_prefix;
+};
+
+struct mailbox_list_iterate_context *
+fs_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags);
+int fs_list_iter_deinit(struct mailbox_list_iterate_context *ctx);
+const struct mailbox_info *
+fs_list_iter_next(struct mailbox_list_iterate_context *ctx);
+
+int fs_list_get_mailbox_flags(struct mailbox_list *list,
+ const char *dir, const char *fname,
+ enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-index-backend.c b/src/lib-storage/list/mailbox-list-index-backend.c
new file mode 100644
index 0000000..e088f3b
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-backend.c
@@ -0,0 +1,970 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "hostpid.h"
+#include "str.h"
+#include "mail-index.h"
+#include "subscription-file.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-index-storage.h"
+#include "mailbox-list-index-sync.h"
+
+#include <stdio.h>
+
+#define GLOBAL_TEMP_PREFIX ".temp."
+#define MAILBOX_LIST_INDEX_DEFAULT_ESCAPE_CHAR '^'
+
+struct index_mailbox_list {
+ struct mailbox_list list;
+ const char *temp_prefix;
+
+ const char *create_mailbox_name;
+ guid_128_t create_mailbox_guid;
+};
+
+extern struct mailbox_list index_mailbox_list;
+
+static int
+index_list_rename_mailbox(struct mailbox_list *_oldlist, const char *oldname,
+ struct mailbox_list *_newlist, const char *newname);
+
+static struct mailbox_list *index_list_alloc(void)
+{
+ struct index_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("index list", 2048);
+
+ list = p_new(pool, struct index_mailbox_list, 1);
+ list->list = index_mailbox_list;
+ list->list.pool = pool;
+ list->list.set.storage_name_escape_char = MAILBOX_LIST_INDEX_DEFAULT_ESCAPE_CHAR;
+
+ list->temp_prefix = p_strconcat(pool, GLOBAL_TEMP_PREFIX,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static int index_list_init(struct mailbox_list *_list, const char **error_r)
+{
+ if (!_list->mail_set->mailbox_list_index) {
+ *error_r = "LAYOUT=index requires mailbox_list_index=yes";
+ return -1;
+ }
+ return 0;
+}
+
+static void index_list_deinit(struct mailbox_list *_list)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static char index_list_get_hierarchy_sep(struct mailbox_list *list)
+{
+ char sep = list->ns->set->separator[0];
+
+ if (sep == '\0')
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_SEP;
+ if (sep == list->set.storage_name_escape_char) {
+ /* Separator conflicts with the escape character.
+ Use something else. */
+ if (sep != MAILBOX_LIST_INDEX_HIERARCHY_SEP)
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_SEP;
+ else
+ sep = MAILBOX_LIST_INDEX_HIERARCHY_ALT_SEP;
+ }
+ return sep;
+}
+
+static int
+index_list_get_refreshed_node_seq(struct index_mailbox_list *list,
+ struct mail_index_view *view,
+ const char *name,
+ struct mailbox_list_index_node **node_r,
+ uint32_t *seq_r)
+{
+ unsigned int i;
+
+ *node_r = NULL;
+ *seq_r = 0;
+
+ for (i = 0; i < 2; i++) {
+ *node_r = mailbox_list_index_lookup(&list->list, name);
+ if (*node_r == NULL)
+ return 0;
+ if (mail_index_lookup_seq(view, (*node_r)->uid, seq_r))
+ return 1;
+ /* mailbox was just expunged. refreshing should notice it. */
+ if (mailbox_list_index_refresh_force(&list->list) < 0)
+ return -1;
+ }
+ i_panic("mailbox list index: refreshing doesn't lose expunged uid=%u",
+ (*node_r)->uid);
+ return -1;
+}
+
+static const char *
+index_get_guid_path(struct mailbox_list *_list, const char *root_dir,
+ const guid_128_t mailbox_guid)
+{
+ return t_strdup_printf("%s/%s%s", root_dir,
+ _list->set.mailbox_dir_name,
+ guid_128_to_string(mailbox_guid));
+}
+
+static int
+index_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ struct mail_index_view *view;
+ struct mailbox_list_index_node *node;
+ struct mailbox_status status;
+ guid_128_t mailbox_guid;
+ const char *root_dir, *reason;
+ uint32_t seq;
+ int ret;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(&_list->set, type,
+ path_r) ? 1 : 0;
+ }
+ /* consistently use mailbox_dir_name as part of all mailbox
+ directories (index/control/etc) */
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ type = MAILBOX_LIST_PATH_TYPE_DIR;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ type = MAILBOX_LIST_PATH_TYPE_ALT_DIR;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_LIST_INDEX:
+ i_unreached();
+ default:
+ break;
+ }
+ if (!mailbox_list_set_get_root_path(&_list->set, type, &root_dir))
+ return 0;
+
+ if (list->create_mailbox_name != NULL &&
+ strcmp(list->create_mailbox_name, name) == 0) {
+ *path_r = index_get_guid_path(_list, root_dir,
+ list->create_mailbox_guid);
+ return 1;
+ }
+
+ /* ilist is only required from this point onwards.
+ At least imapc calls index_list_get_path without this context*/
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_list);
+
+ if (ilist->sync_ctx != NULL) {
+ /* we could get here during sync from
+ index_list_mailbox_create_selectable() */
+ view = ilist->sync_ctx->view;
+ node = mailbox_list_index_lookup(&list->list, name);
+ if (node == NULL) {
+ seq = 0;
+ ret = 0;
+ } else if (mail_index_lookup_seq(view, node->uid, &seq)) {
+ ret = 1;
+ } else {
+ i_panic("mailbox list index: lost uid=%u", node->uid);
+ }
+ } else {
+ if (mailbox_list_index_refresh(&list->list) < 0)
+ return -1;
+ view = mail_index_view_open(ilist->index);
+ ret = index_list_get_refreshed_node_seq(list, view, name, &node, &seq);
+ if (ret < 0) {
+ mail_index_view_close(&view);
+ return -1;
+ }
+ }
+ i_assert(ret == 0 || seq != 0);
+ if (ret == 0) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+ ret = -1;
+ } else if (!mailbox_list_index_status(_list, view, seq, 0,
+ &status, mailbox_guid,
+ NULL, &reason) ||
+ guid_128_is_empty(mailbox_guid)) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+ ret = -1;
+ } else {
+ *path_r = index_get_guid_path(_list, root_dir, mailbox_guid);
+ ret = 1;
+ }
+ if (ilist->sync_ctx == NULL)
+ mail_index_view_close(&view);
+ return ret;
+}
+
+static const char *
+index_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+
+ return global ? GLOBAL_TEMP_PREFIX : list->temp_prefix;
+}
+
+static int index_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ path = t_strconcat(_list->set.control_dir != NULL ?
+ _list->set.control_dir : _list->set.root_dir,
+ "/", _list->set.subscription_fname, NULL);
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+static int
+index_list_node_exists(struct index_mailbox_list *list, const char *name,
+ enum mailbox_existence *existence_r)
+{
+ struct mailbox_list_index_node *node;
+
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+
+ if (mailbox_list_index_refresh(&list->list) < 0)
+ return -1;
+
+ node = mailbox_list_index_lookup(&list->list, name);
+ if (node == NULL)
+ return 0;
+
+ if ((node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
+ /* selectable */
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ } else {
+ /* non-selectable */
+ *existence_r = MAILBOX_EXISTENCE_NOSELECT;
+ }
+ return 0;
+}
+
+static int
+index_list_mailbox_create_dir(struct index_mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_node *node;
+ uint32_t seq;
+ bool created;
+ int ret;
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ seq = mailbox_list_index_sync_name(sync_ctx, name, &node, &created);
+ if (created || (node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0) {
+ /* didn't already exist */
+ node->flags = MAILBOX_LIST_INDEX_FLAG_NOSELECT;
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+ ret = 1;
+ } else {
+ /* already existed */
+ ret = 0;
+ }
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int
+index_list_mailbox_create_selectable(struct mailbox *box,
+ const guid_128_t mailbox_guid)
+{
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_record rec;
+ struct mailbox_list_index_node *node;
+ const void *data;
+ bool expunged, created;
+ uint32_t seq;
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ seq = mailbox_list_index_sync_name(sync_ctx, box->name, &node, &created);
+ if (box->corrupted_mailbox_name) {
+ /* an existing mailbox is being created with a "unknown" name.
+ opening the mailbox will hopefully find its real name and
+ rename it. */
+ node->flags |= MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME;
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_ADD,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ }
+ if (!created &&
+ (node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
+ /* already selectable */
+ (void)mailbox_list_index_sync_end(&sync_ctx, TRUE);
+ return 0;
+ }
+
+ mail_index_lookup_ext(sync_ctx->view, seq, ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&rec, data, sizeof(rec));
+ i_assert(guid_128_is_empty(rec.guid));
+
+ /* make it selectable */
+ node->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT |
+ MAILBOX_LIST_INDEX_FLAG_NOINFERIORS);
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+
+ /* set UIDVALIDITY if was set by the storage */
+ if (box->index != NULL) {
+ struct mail_index_view *view;
+
+ view = mail_index_view_open(box->index);
+ if (mail_index_get_header(view)->uid_validity != 0)
+ rec.uid_validity = mail_index_get_header(view)->uid_validity;
+ mail_index_view_close(&view);
+ }
+
+ /* set GUID */
+ memcpy(rec.guid, mailbox_guid, sizeof(rec.guid));
+ mail_index_update_ext(sync_ctx->trans, seq, ilist->ext_id, &rec, NULL);
+
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0) {
+ /* make sure we forget any changes done internally */
+ mailbox_list_index_reset(ilist);
+ return -1;
+ }
+ return 1;
+}
+
+static int
+index_list_mailbox_create(struct mailbox *box,
+ const struct mailbox_update *update, bool directory)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+ struct mailbox_update new_update;
+ enum mailbox_existence existence;
+ int ret;
+
+ /* first do a quick check that it doesn't exist */
+ if (index_list_node_exists(list, box->name, &existence) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ if (existence == MAILBOX_EXISTENCE_NONE && directory) {
+ /* now add the directory to index locked */
+ if ((ret = index_list_mailbox_create_dir(list, box->name)) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ } else if (existence != MAILBOX_EXISTENCE_SELECT && !directory) {
+ /* if no GUID is requested, generate it ourself. set
+ UIDVALIDITY to index sometimes later. */
+ if (update == NULL)
+ i_zero(&new_update);
+ else
+ new_update = *update;
+ if (guid_128_is_empty(new_update.mailbox_guid))
+ guid_128_generate(new_update.mailbox_guid);
+
+ /* create the backend mailbox first before it exists in the
+ list. the mailbox creation wants to use get_path() though,
+ so use a bit kludgy create_mailbox_* variables during the
+ creation to return the path. we'll also support recursively
+ creating more mailboxes in here. */
+ const char *old_name;
+ guid_128_t old_guid;
+
+ old_name = list->create_mailbox_name;
+ guid_128_copy(old_guid, list->create_mailbox_guid);
+
+ list->create_mailbox_name = box->name;
+ guid_128_copy(list->create_mailbox_guid, new_update.mailbox_guid);
+
+ ret = ibox->module_ctx.super.create_box(box, &new_update, FALSE);
+
+ if (ret == 0) {
+ /* backend mailbox was successfully created. now add it
+ to the list. */
+ ret = index_list_mailbox_create_selectable(box, new_update.mailbox_guid);
+ if (ret < 0)
+ mail_storage_copy_list_error(box->storage, box->list);
+ if (ret <= 0) {
+ /* failed to add to list. rollback the backend
+ mailbox creation */
+ bool create_error = ret < 0;
+
+ if (create_error)
+ mail_storage_last_error_push(box->storage);
+ if (mailbox_delete(box) < 0)
+ ret = -1;
+ if (create_error)
+ mail_storage_last_error_pop(box->storage);
+ }
+ }
+ list->create_mailbox_name = old_name;
+ guid_128_copy(list->create_mailbox_guid, old_guid);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox already exists");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+index_list_mailbox_update(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ const char *root_dir, *old_path, *new_path;
+
+ if (mailbox_list_get_path(box->list, box->name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &old_path) <= 0)
+ old_path = NULL;
+
+ if (ibox->module_ctx.super.update_box(box, update) < 0)
+ return -1;
+
+ /* rename the directory */
+ if (!guid_128_is_empty(update->mailbox_guid) && old_path != NULL &&
+ mailbox_list_set_get_root_path(&box->list->set,
+ MAILBOX_LIST_PATH_TYPE_DIR,
+ &root_dir)) {
+ new_path = index_get_guid_path(box->list, root_dir,
+ update->mailbox_guid);
+ if (strcmp(old_path, new_path) == 0)
+ ;
+ else if (rename(old_path, new_path) == 0)
+ ;
+ else if (errno == ENOENT) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
+ return -1;
+ } else {
+ mailbox_set_critical(box, "rename(%s, %s) failed: %m",
+ old_path, new_path);
+ return -1;
+ }
+ }
+
+ mailbox_list_index_update_mailbox_index(box, update);
+ return 0;
+}
+
+static int
+index_list_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ if (auto_boxes && mailbox_is_autocreated(box)) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+
+ struct index_mailbox_list *list =
+ (struct index_mailbox_list *)box->list;
+
+ if (index_list_node_exists(list, box->name, existence_r) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+ return 0;
+}
+
+static bool mailbox_has_corrupted_name(struct mailbox *box)
+{
+ struct mailbox_list_index_node *node;
+
+ if (box->corrupted_mailbox_name)
+ return TRUE;
+
+ node = mailbox_list_index_lookup(box->list, box->name);
+ return node != NULL &&
+ (node->flags & MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME) != 0;
+}
+
+static void index_list_rename_corrupted(struct mailbox *box, const char *newname)
+{
+ if (index_list_rename_mailbox(box->list, box->name,
+ box->list, newname) == 0 ||
+ box->list->error != MAIL_ERROR_EXISTS)
+ return;
+
+ /* mailbox already exists. don't give up yet, just use the newname
+ as prefix and add the "lost-xx" as suffix. */
+ char sep = mailbox_list_get_hierarchy_sep(box->list);
+ const char *oldname = box->name;
+
+ /* oldname should be at the root level, but check for hierarchies
+ anyway to be safe. */
+ const char *p = strrchr(oldname, sep);
+ if (p != NULL)
+ oldname = p+1;
+
+ newname = t_strdup_printf("%s-%s", newname, oldname);
+ (void)index_list_rename_mailbox(box->list, box->name,
+ box->list, newname);
+}
+
+static int index_list_mailbox_open(struct mailbox *box)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ const void *data;
+ const unsigned char *name_hdr;
+ size_t name_hdr_size;
+
+ if (ibox->module_ctx.super.open(box) < 0)
+ return -1;
+
+ if (box->view == NULL) {
+ /* FIXME: dsync-merge is performing a delete in obox - remove
+ this check once dsync-merging is no longer used. */
+ return 0;
+ }
+
+ /* if mailbox name has changed, update it to the header. Use \0
+ as the hierarchy separator in the header. This is to make sure
+ we don't keep rewriting the name just in case some backend switches
+ between separators when accessed different ways. */
+
+ /* Get the current mailbox name with \0 separators and unesacped. */
+ size_t box_name_len;
+ const unsigned char *box_zerosep_name =
+ mailbox_name_hdr_encode(box->list, box->name, &box_name_len);
+
+ /* Does it match what's in the header now? */
+ mail_index_get_header_ext(box->view, box->box_name_hdr_ext_id,
+ &data, &name_hdr_size);
+ name_hdr = data;
+ while (name_hdr_size > 0 && name_hdr[name_hdr_size-1] == '\0') {
+ /* Remove trailing \0 - header doesn't shrink always */
+ name_hdr_size--;
+ }
+ if (name_hdr_size == box_name_len &&
+ memcmp(box_zerosep_name, name_hdr, box_name_len) == 0) {
+ /* Same mailbox name */
+ } else if (!mailbox_has_corrupted_name(box)) {
+ /* Mailbox name changed - update */
+ struct mail_index_transaction *trans =
+ mail_index_transaction_begin(box->view, 0);
+ mail_index_ext_resize_hdr(trans, box->box_name_hdr_ext_id,
+ box_name_len);
+ mail_index_update_header_ext(trans, box->box_name_hdr_ext_id, 0,
+ box_zerosep_name, box_name_len);
+ (void)mail_index_transaction_commit(&trans);
+ } else if (name_hdr_size > 0) {
+ /* Mailbox name is corrupted. Rename it to the previous name. */
+ const char *newname =
+ mailbox_name_hdr_decode_storage_name(
+ box->list, name_hdr, name_hdr_size);
+ index_list_rename_corrupted(box, newname);
+ }
+ return 0;
+}
+
+void mailbox_list_index_backend_sync_init(struct mailbox *box,
+ enum mailbox_sync_flags flags)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+
+ if ((flags & MAILBOX_SYNC_FLAG_FORCE_RESYNC) != 0 &&
+ !ilist->force_resynced) {
+ enum mail_storage_list_index_rebuild_reason reason =
+ MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_FORCE_RESYNC;
+
+ if (box->storage->v.list_index_rebuild != NULL &&
+ box->storage->v.list_index_rebuild(box->storage, reason) < 0)
+ ilist->force_resync_failed = TRUE;
+ /* try to rebuild list index only once - even if it failed */
+ ilist->force_resynced = TRUE;
+ }
+}
+
+int mailbox_list_index_backend_sync_deinit(struct mailbox *box)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+
+ if (ilist->force_resync_failed) {
+ /* fail this only once */
+ ilist->force_resync_failed = FALSE;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+index_list_try_delete(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type)
+{
+ const char *mailbox_path, *path, *error;
+
+ if (mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &mailbox_path) <= 0 ||
+ mailbox_list_get_path(_list, name, type, &path) <= 0 ||
+ strcmp(path, mailbox_path) == 0)
+ return;
+
+ if (*_list->set.maildir_name == '\0' &&
+ (_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ /* this directory may contain also child mailboxes' data.
+ we don't want to delete that. */
+ bool rmdir_path = *_list->set.maildir_name != '\0';
+ if (mailbox_list_delete_mailbox_nonrecursive(_list, name, path,
+ rmdir_path) < 0)
+ return;
+ } else {
+ if (mailbox_list_delete_trash(path, &error) < 0 &&
+ errno != ENOTEMPTY) {
+ mailbox_list_set_critical(_list,
+ "unlink_directory(%s) failed: %s", path, error);
+ }
+ }
+
+ /* avoid leaving empty directories lying around */
+ mailbox_list_delete_until_root(_list, path, type);
+}
+
+static void
+index_list_delete_finish(struct mailbox_list *list, const char *name)
+{
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_INDEX);
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ index_list_try_delete(list, name, MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX);
+}
+
+static int
+index_list_delete_entry(struct index_mailbox_list *list, const char *name,
+ bool delete_selectable)
+{
+ struct mailbox_list_index_sync_context *sync_ctx;
+ int ret;
+
+ if (list->create_mailbox_name != NULL &&
+ strcmp(name, list->create_mailbox_name) == 0) {
+ /* we're rolling back a failed create. if the name exists in the
+ list, it was done by somebody else so we don't want to
+ remove it. */
+ return 0;
+ }
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+ ret = mailbox_list_index_sync_delete(sync_ctx, name, delete_selectable);
+ if (mailbox_list_index_sync_end(&sync_ctx, TRUE) < 0)
+ return -1;
+ return ret;
+}
+
+static int
+index_list_try_delete_nonexistent_parent(struct mailbox_list *_list,
+ const char *name)
+{
+ struct index_mailbox_list *list =
+ container_of(_list, struct index_mailbox_list, list);
+ struct mailbox_list_index_node *node;
+ string_t *full_name;
+ const char *p;
+ char sep = mailbox_list_get_hierarchy_sep(_list);
+
+ if ((p = strrchr(name, sep)) == NULL) {
+ /* No occurrences of the hierarchy separator could be found
+ in the name, so the mailbox has no parents. */
+ return 0;
+ }
+
+ /* Lookup parent node of of given "name" */
+ node = mailbox_list_index_lookup(_list, t_strdup_until(name, p));
+ full_name = t_str_new(32);
+
+ while (node != NULL) {
+ /* Attempt to delete all parent nodes that are NOSELECT or
+ NONEXISTENT */
+ if (node->children != NULL)
+ break;
+ if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0 ||
+ (node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0) {
+ /* The parent mailbox has no other children and is not
+ existant or not selectable, delete it */
+ str_truncate(full_name, 0);
+ mailbox_list_index_node_get_path(node, sep, full_name);
+ if (index_list_delete_entry(list, str_c(full_name), FALSE) < 0)
+ return -1;
+
+ if ((p = strrchr(str_c(full_name), sep)) == NULL) {
+ /* No occurrences of the hierarchy separator
+ could be found in the mailbox that was
+ just deleted. */
+ node = NULL;
+ } else {
+ /* lookup parent node of the node just deleted */
+ str_truncate(full_name, p - str_c(full_name));
+ node = mailbox_list_index_lookup(_list, str_c(full_name));
+ }
+ } else
+ break;
+
+ }
+ return 0;
+}
+
+static int
+index_list_delete_mailbox(struct mailbox_list *_list, const char *name)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ const char *path;
+ int ret;
+
+ /* first delete the mailbox files */
+ ret = mailbox_list_get_path(_list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path);
+ if (ret <= 0)
+ return ret;
+
+ if ((_list->flags & (MAILBOX_LIST_FLAG_NO_MAIL_FILES |
+ MAILBOX_LIST_FLAG_NO_DELETES)) != 0) {
+ ret = 0;
+ } else if ((_list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_delete_mailbox_file(_list, name, path);
+ } else {
+ ret = mailbox_list_delete_mailbox_nonrecursive(_list, name,
+ path, TRUE);
+ }
+
+ if ((ret == 0 || (_list->props & MAILBOX_LIST_PROP_AUTOCREATE_DIRS) != 0) &&
+ (_list->flags & MAILBOX_LIST_FLAG_NO_DELETES) == 0)
+ index_list_delete_finish(_list, name);
+ if (ret == 0) {
+ if (index_list_delete_entry(list, name, TRUE) < 0)
+ return -1;
+ }
+ if (_list->set.no_noselect && ret == 0)
+ (void)index_list_try_delete_nonexistent_parent(_list, name);
+
+ return ret;
+}
+
+static int
+index_list_delete_dir(struct mailbox_list *_list, const char *name)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_list;
+ int ret;
+
+ if ((ret = index_list_delete_entry(list, name, FALSE)) < 0)
+ return -1;
+ if (ret == 0) {
+ mailbox_list_set_error(_list, MAIL_ERROR_EXISTS,
+ "Mailbox has children, delete them first");
+ return -1;
+ }
+ return 0;
+}
+
+static int
+index_list_delete_symlink(struct mailbox_list *_list,
+ const char *name ATTR_UNUSED)
+{
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Symlinks not supported");
+ return -1;
+}
+
+static int
+index_list_rename_mailbox(struct mailbox_list *_oldlist, const char *oldname,
+ struct mailbox_list *_newlist, const char *newname)
+{
+ struct index_mailbox_list *list = (struct index_mailbox_list *)_oldlist;
+ const size_t oldname_len = strlen(oldname);
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mailbox_list_index_record oldrec, newrec;
+ struct mailbox_list_index_node *oldnode, *newnode, *child;
+ const void *data;
+ bool created, expunged;
+ uint32_t oldseq, newseq;
+ int ret;
+
+ if (_oldlist != _newlist) {
+ mailbox_list_set_error(_oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across namespaces.");
+ return -1;
+ }
+
+ if (str_begins(newname, oldname) &&
+ newname[oldname_len] == mailbox_list_get_hierarchy_sep(_newlist)) {
+ mailbox_list_set_error(_oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Can't rename mailbox under itself.");
+ return -1;
+ }
+
+ if (mailbox_list_index_sync_begin(&list->list, &sync_ctx) < 0)
+ return -1;
+
+ oldnode = mailbox_list_index_lookup(&list->list, oldname);
+ if (oldnode == NULL) {
+ (void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
+ mailbox_list_set_error(&list->list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(oldname));
+ return -1;
+ }
+ if (!mail_index_lookup_seq(sync_ctx->view, oldnode->uid, &oldseq))
+ i_panic("mailbox list index: lost uid=%u", oldnode->uid);
+
+ newseq = mailbox_list_index_sync_name(sync_ctx, newname,
+ &newnode, &created);
+ if (!created) {
+ (void)mailbox_list_index_sync_end(&sync_ctx, FALSE);
+ mailbox_list_set_error(&list->list, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ return -1;
+ }
+ i_assert(oldnode != newnode);
+
+ /* copy all the data from old node to new node */
+ newnode->uid = oldnode->uid;
+ newnode->flags = oldnode->flags;
+ newnode->children = oldnode->children; oldnode->children = NULL;
+ for (child = newnode->children; child != NULL; child = child->next)
+ child->parent = newnode;
+
+ /* remove the old node from existence */
+ mailbox_list_index_node_unlink(sync_ctx->ilist, oldnode);
+
+ /* update the old index record to contain the new name_id/parent_uid,
+ then expunge the added index record */
+ mail_index_lookup_ext(sync_ctx->view, oldseq, sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&oldrec, data, sizeof(oldrec));
+
+ mail_index_lookup_ext(sync_ctx->view, newseq, sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&newrec, data, sizeof(newrec));
+
+ oldrec.name_id = newrec.name_id;
+ oldrec.parent_uid = newrec.parent_uid;
+
+ if ((newnode->flags & MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME) != 0) {
+ /* mailbox is renamed - clear away the corruption flag so the
+ new name will be written to the mailbox index header. */
+ newnode->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ mail_index_update_flags(sync_ctx->trans, oldseq, MODIFY_REMOVE,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ }
+ mail_index_update_ext(sync_ctx->trans, oldseq,
+ sync_ctx->ilist->ext_id, &oldrec, NULL);
+ mail_index_expunge(sync_ctx->trans, newseq);
+
+ ret = mailbox_list_index_sync_end(&sync_ctx, TRUE);
+
+ if (_oldlist->set.no_noselect && ret == 0)
+ (void)index_list_try_delete_nonexistent_parent(_oldlist, oldname);
+
+ return ret;
+}
+
+static struct mailbox_list_iterate_context *
+index_list_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct mailbox_list_iterate_context *ctx;
+ pool_t pool;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ return mailbox_list_subscriptions_iter_init(list, patterns,
+ flags);
+ }
+
+ pool = pool_alloconly_create("mailbox list index backend iter", 1024);
+ ctx = p_new(pool, struct mailbox_list_iterate_context, 1);
+ ctx->pool = pool;
+ ctx->list = list;
+ ctx->flags = flags;
+ array_create(&ctx->module_contexts, pool, sizeof(void *), 5);
+ return ctx;
+}
+
+static const struct mailbox_info *
+index_list_iter_next(struct mailbox_list_iterate_context *ctx)
+{
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_next(ctx);
+ return NULL;
+}
+
+static int index_list_iter_deinit(struct mailbox_list_iterate_context *ctx)
+{
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ return mailbox_list_subscriptions_iter_deinit(ctx);
+ pool_unref(&ctx->pool);
+ return 0;
+}
+
+struct mailbox_list index_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_INDEX,
+ .props = MAILBOX_LIST_PROP_NO_ROOT | MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = index_list_alloc,
+ .init = index_list_init,
+ .deinit = index_list_deinit,
+ .get_hierarchy_sep = index_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = index_list_get_path,
+ .get_temp_prefix = index_list_get_temp_prefix,
+ .iter_init = index_list_iter_init,
+ .iter_next = index_list_iter_next,
+ .iter_deinit = index_list_iter_deinit,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = index_list_set_subscribed,
+ .delete_mailbox = index_list_delete_mailbox,
+ .delete_dir = index_list_delete_dir,
+ .delete_symlink = index_list_delete_symlink,
+ .rename_mailbox = index_list_rename_mailbox,
+ }
+};
+
+bool mailbox_list_index_backend_init_mailbox(struct mailbox *box,
+ struct mailbox_vfuncs *v)
+{
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_INDEX) != 0)
+ return TRUE;
+
+ /* NOTE: this is using the same v as
+ mailbox_list_index_status_init_mailbox(), so don't have them
+ accidentally override each others. */
+ v->create_box = index_list_mailbox_create;
+ v->update_box = index_list_mailbox_update;
+ v->exists = index_list_mailbox_exists;
+ v->open = index_list_mailbox_open;
+ return FALSE;
+}
diff --git a/src/lib-storage/list/mailbox-list-index-iter.c b/src/lib-storage/list/mailbox-list-index-iter.c
new file mode 100644
index 0000000..680808e
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-iter.c
@@ -0,0 +1,266 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "imap-match.h"
+#include "mail-storage.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-iter-private.h"
+#include "mailbox-list-index.h"
+
+static bool iter_use_index(struct mailbox_list *list,
+ enum mailbox_list_iter_flags flags)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* for now we don't use indexes when listing subscriptions,
+ because it needs to list also the nonexistent subscribed
+ mailboxes, which don't exist in the index. */
+ return FALSE;
+ }
+ if ((flags & MAILBOX_LIST_ITER_RAW_LIST) != 0 &&
+ ilist->has_backing_store) {
+ /* no indexing wanted with raw lists */
+ return FALSE;
+ }
+ if (mailbox_list_index_refresh(list) < 0 &&
+ ilist->has_backing_store) {
+ /* refresh failed */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_index_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_iterate_context *ctx;
+ pool_t pool;
+ char ns_sep = mail_namespace_get_sep(list->ns);
+
+ if (!iter_use_index(list, flags)) {
+ /* no indexing */
+ return ilist->module_ctx.super.iter_init(list, patterns, flags);
+ }
+
+ pool = pool_alloconly_create("mailbox list index iter", 2048);
+ ctx = p_new(pool, struct mailbox_list_index_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, ns_sep);
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+ ctx->info_pool = pool_alloconly_create("mailbox list index iter info", 128);
+ ctx->ctx.index_iteration = TRUE;
+
+ /* listing mailboxes from index */
+ ctx->info.ns = list->ns;
+ ctx->path = str_new(pool, 128);
+ ctx->next_node = ilist->mailbox_tree;
+ ctx->mailbox_pool = ilist->mailbox_pool;
+ pool_ref(ctx->mailbox_pool);
+ return &ctx->ctx;
+}
+
+static void
+mailbox_list_get_escaped_mailbox_name(struct mailbox_list *list,
+ const char *raw_name,
+ string_t *escaped_name)
+{
+ const char escape_chars[] = {
+ list->set.storage_name_escape_char,
+ mailbox_list_get_hierarchy_sep(list),
+ '\0'
+ };
+ mailbox_list_name_escape(raw_name, escape_chars, escaped_name);
+}
+
+static void
+mailbox_list_index_update_info(struct mailbox_list_index_iterate_context *ctx)
+{
+ struct mailbox_list_index_node *node = ctx->next_node;
+ struct mailbox *box;
+
+ p_clear(ctx->info_pool);
+
+ str_truncate(ctx->path, ctx->parent_len);
+ /* the root directory may have an empty name. in that case we'll still
+ want to insert the separator, so check for non-NULL parent rather
+ than non-empty path. */
+ if (node->parent != NULL) {
+ str_append_c(ctx->path,
+ mailbox_list_get_hierarchy_sep(ctx->ctx.list));
+ }
+ mailbox_list_get_escaped_mailbox_name(ctx->ctx.list, node->raw_name,
+ ctx->path);
+
+ ctx->info.vname = mailbox_list_get_vname(ctx->ctx.list, str_c(ctx->path));
+ ctx->info.flags = node->children != NULL ?
+ MAILBOX_CHILDREN : MAILBOX_NOCHILDREN;
+ if (strcmp(ctx->info.vname, "INBOX") != 0) {
+ /* non-INBOX */
+ ctx->info.vname = p_strdup(ctx->info_pool, ctx->info.vname);
+ } else if (!ctx->prefix_inbox_list) {
+ /* listing INBOX itself */
+ ctx->info.vname = "INBOX";
+ if (mail_namespace_is_inbox_noinferiors(ctx->info.ns)) {
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_CHILDREN |
+ MAILBOX_NOCHILDREN);
+ ctx->info.flags |= MAILBOX_NOINFERIORS;
+ }
+ } else {
+ /* listing INBOX/INBOX */
+ ctx->info.vname = p_strconcat(ctx->info_pool,
+ ctx->ctx.list->ns->prefix, "INBOX", NULL);
+ ctx->info.flags |= MAILBOX_NONEXISTENT;
+ }
+ if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0)
+ ctx->info.flags |= MAILBOX_NONEXISTENT;
+ else if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0)
+ ctx->info.flags |= MAILBOX_NOSELECT;
+ if ((node->flags & MAILBOX_LIST_INDEX_FLAG_NOINFERIORS) != 0)
+ ctx->info.flags |= MAILBOX_NOINFERIORS;
+
+ if ((ctx->ctx.flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) {
+ mailbox_list_set_subscription_flags(ctx->ctx.list,
+ ctx->info.vname,
+ &ctx->info.flags);
+ }
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0) {
+ box = mailbox_alloc(ctx->ctx.list, ctx->info.vname, 0);
+ mailbox_list_index_status_set_info_flags(box, node->uid,
+ &ctx->info.flags);
+ mailbox_free(&box);
+ }
+}
+
+static void
+mailbox_list_index_update_next(struct mailbox_list_index_iterate_context *ctx,
+ bool follow_children)
+{
+ struct mailbox_list_index_node *node = ctx->next_node;
+
+ if (!ctx->prefix_inbox_list && ctx->ctx.list->ns->prefix_len > 0 &&
+ strcmp(node->raw_name, "INBOX") == 0 && node->parent == NULL &&
+ node->children != NULL) {
+ /* prefix/INBOX has children */
+ ctx->prefix_inbox_list = TRUE;
+ return;
+ }
+
+ if (node->children != NULL && follow_children) {
+ ctx->parent_len = str_len(ctx->path);
+ ctx->next_node = node->children;
+ } else {
+ while (node->next == NULL) {
+ node = node->parent;
+ if (node != NULL) T_BEGIN {
+ /* The storage name kept in the iteration context
+ is escaped. To calculate the right truncation
+ margin, the length of the name must be
+ calculated from the escaped storage name and
+ not from node->raw_name. */
+ string_t *escaped_name = t_str_new(64);
+ mailbox_list_get_escaped_mailbox_name(ctx->ctx.list,
+ node->raw_name,
+ escaped_name);
+ ctx->parent_len -= str_len(escaped_name);
+ if (node->parent != NULL)
+ ctx->parent_len--;
+ } T_END;
+ if (node == NULL) {
+ /* last one */
+ ctx->next_node = NULL;
+ return;
+ }
+ }
+ ctx->next_node = node->next;
+ }
+}
+
+static bool
+iter_subscriptions_ok(struct mailbox_list_index_iterate_context *ctx)
+{
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0)
+ return TRUE;
+
+ if ((ctx->info.flags & MAILBOX_SUBSCRIBED) != 0)
+ return TRUE;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 &&
+ (ctx->info.flags & MAILBOX_CHILD_SUBSCRIBED) != 0)
+ return TRUE;
+ return FALSE;
+}
+
+const struct mailbox_info *
+mailbox_list_index_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_ctx->list);
+ if (!_ctx->index_iteration) {
+ /* index isn't being used */
+ return ilist->module_ctx.super.iter_next(_ctx);
+ }
+
+ struct mailbox_list_index_iterate_context *ctx =
+ (struct mailbox_list_index_iterate_context *)_ctx;
+ bool follow_children;
+ enum imap_match_result match;
+
+ /* listing mailboxes from index */
+ while (ctx->next_node != NULL) {
+ T_BEGIN {
+ mailbox_list_index_update_info(ctx);
+ } T_END;
+ match = imap_match(_ctx->glob, ctx->info.vname);
+
+ follow_children = (match & (IMAP_MATCH_YES |
+ IMAP_MATCH_CHILDREN)) != 0;
+ if (match == IMAP_MATCH_YES && iter_subscriptions_ok(ctx)) {
+ /* If this is a) \NoSelect leaf, b) not LAYOUT=index
+ and c) NO-NOSELECT is set, try to rmdir the leaf
+ directores from filesystem. (With LAYOUT=index the
+ \NoSelect mailboxes aren't on the filesystem.) */
+ if (ilist->has_backing_store &&
+ mailbox_list_iter_try_delete_noselect(_ctx, &ctx->info,
+ str_c(ctx->path))) {
+ /* Deleted \NoSelect leaf. Refresh the index
+ later on so it gets removed from the index
+ as well. */
+ mailbox_list_index_refresh_later(_ctx->list);
+ } else {
+ mailbox_list_index_update_next(ctx, TRUE);
+ return &ctx->info;
+ }
+ } else if ((_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0 &&
+ (ctx->info.flags & MAILBOX_CHILD_SUBSCRIBED) == 0) {
+ /* listing only subscriptions, but there are no
+ subscribed children. */
+ follow_children = FALSE;
+ }
+ mailbox_list_index_update_next(ctx, follow_children);
+ }
+ return mailbox_list_iter_default_next(_ctx);
+}
+
+int mailbox_list_index_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_ctx->list);
+ if (!_ctx->index_iteration)
+ return ilist->module_ctx.super.iter_deinit(_ctx);
+
+ struct mailbox_list_index_iterate_context *ctx =
+ (struct mailbox_list_index_iterate_context *)_ctx;
+ int ret = ctx->failed ? -1 : 0;
+
+ pool_unref(&ctx->mailbox_pool);
+ pool_unref(&ctx->info_pool);
+ pool_unref(&_ctx->pool);
+ return ret;
+}
diff --git a/src/lib-storage/list/mailbox-list-index-notify.c b/src/lib-storage/list/mailbox-list-index-notify.c
new file mode 100644
index 0000000..c07e95b
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-notify.c
@@ -0,0 +1,967 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "str.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log-private.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-notify.h"
+#include "mailbox-list-notify-tree.h"
+#include "mailbox-list-index.h"
+
+#include <sys/stat.h>
+
+#define NOTIFY_DELAY_MSECS 500
+
+enum ilist_ext_type {
+ ILIST_EXT_NONE,
+ ILIST_EXT_BASE,
+ ILIST_EXT_MSGS,
+ ILIST_EXT_HIGHESTMODSEQ,
+ ILIST_EXT_UNKNOWN
+};
+
+struct mailbox_list_notify_rename {
+ uint32_t old_uid, new_uid;
+};
+
+struct mailbox_list_inotify_entry {
+ uint32_t uid;
+ guid_128_t guid;
+ bool expunge;
+};
+
+struct mailbox_list_notify_index {
+ struct mailbox_list_notify notify;
+
+ struct mailbox_tree_context *subscriptions;
+ struct mailbox_list_notify_tree *tree;
+ struct mail_index_view *view, *old_view;
+ struct mail_index_view_sync_ctx *sync_ctx;
+ enum ilist_ext_type cur_ext;
+ uint32_t cur_ext_id;
+
+ void (*wait_callback)(void *context);
+ void *wait_context;
+ struct io *io_wait, *io_wait_inbox;
+ struct timeout *to_wait, *to_notify;
+
+ ARRAY_TYPE(seq_range) new_uids, expunged_uids, changed_uids;
+ ARRAY_TYPE(const_string) new_subscriptions, new_unsubscriptions;
+ ARRAY(struct mailbox_list_notify_rename) renames;
+ struct seq_range_iter new_uids_iter, expunged_uids_iter;
+ struct seq_range_iter changed_uids_iter;
+ unsigned int new_uids_n, expunged_uids_n, changed_uids_n;
+ unsigned int rename_idx, subscription_idx, unsubscription_idx;
+
+ struct mailbox_list_notify_rec notify_rec;
+ string_t *rec_name;
+
+ char *list_log_path, *inbox_log_path;
+ struct stat list_last_st, inbox_last_st;
+ struct mailbox *inbox;
+
+ bool initialized:1;
+ bool read_failed:1;
+ bool inbox_event_pending:1;
+};
+
+static const enum mailbox_status_items notify_status_items =
+ STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+ STATUS_UNSEEN | STATUS_HIGHESTMODSEQ;
+
+static enum mailbox_list_notify_event
+mailbox_list_index_get_changed_events(const struct mailbox_notify_node *nnode,
+ const struct mailbox_status *status)
+{
+ enum mailbox_list_notify_event events = 0;
+
+ if (nnode->uidvalidity != status->uidvalidity)
+ events |= MAILBOX_LIST_NOTIFY_UIDVALIDITY;
+ if (nnode->uidnext != status->uidnext)
+ events |= MAILBOX_LIST_NOTIFY_APPENDS;
+ if (nnode->messages > status->messages) {
+ /* NOTE: not entirely reliable, since there could be both
+ expunges and appends.. but it shouldn't make any difference
+ in practise, since anybody interested in expunges is most
+ likely also interested in appends. */
+ events |= MAILBOX_LIST_NOTIFY_EXPUNGES;
+ }
+ if (nnode->unseen != status->unseen)
+ events |= MAILBOX_LIST_NOTIFY_SEEN_CHANGES;
+ if (nnode->highest_modseq < status->highest_modseq)
+ events |= MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES;
+ return events;
+}
+
+static void
+mailbox_notify_node_update_status(struct mailbox_notify_node *nnode,
+ struct mailbox_status *status)
+{
+ nnode->uidvalidity = status->uidvalidity;
+ nnode->uidnext = status->uidnext;
+ nnode->messages = status->messages;
+ nnode->unseen = status->unseen;
+ nnode->highest_modseq = status->highest_modseq;
+}
+
+static void
+mailbox_list_index_notify_init_inbox(struct mailbox_list_notify_index *inotify)
+{
+ inotify->inbox = mailbox_alloc(inotify->notify.list, "INBOX",
+ MAILBOX_FLAG_READONLY);
+ if (mailbox_open(inotify->inbox) < 0)
+ mailbox_free(&inotify->inbox);
+ else
+ inotify->inbox_log_path =
+ i_strconcat(inotify->inbox->index->filepath,
+ ".log", NULL);
+}
+
+int mailbox_list_index_notify_init(struct mailbox_list *list,
+ enum mailbox_list_notify_event mask,
+ struct mailbox_list_notify **notify_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+ struct mailbox_list_notify_index *inotify;
+ const char *index_dir;
+
+ if (ilist == NULL) {
+ /* can't do this without mailbox list indexes */
+ return -1;
+ }
+
+ (void)mailbox_list_index_refresh(list);
+
+ inotify = i_new(struct mailbox_list_notify_index, 1);
+ inotify->notify.list = list;
+ inotify->notify.mask = mask;
+ inotify->view = mail_index_view_open(ilist->index);
+ inotify->old_view = mail_index_view_dup_private(inotify->view);
+ inotify->tree = mailbox_list_notify_tree_init(list);
+ i_array_init(&inotify->new_uids, 8);
+ i_array_init(&inotify->expunged_uids, 8);
+ i_array_init(&inotify->changed_uids, 16);
+ i_array_init(&inotify->renames, 16);
+ i_array_init(&inotify->new_subscriptions, 16);
+ i_array_init(&inotify->new_unsubscriptions, 16);
+ inotify->rec_name = str_new(default_pool, 64);
+ if ((mask & (MAILBOX_LIST_NOTIFY_SUBSCRIBE |
+ MAILBOX_LIST_NOTIFY_UNSUBSCRIBE)) != 0) {
+ (void)mailbox_list_iter_subscriptions_refresh(list);
+ mailbox_tree_sort(list->subscriptions);
+ inotify->subscriptions = mailbox_tree_dup(list->subscriptions);
+ }
+ inotify->list_log_path = i_strdup(ilist->index->log->filepath);
+ if (list->mail_set->mailbox_list_index_include_inbox) {
+ /* INBOX can be handled also using mailbox list index */
+ } else if ((list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) == 0) {
+ /* no INBOX in this namespace */
+ } else if ((mask & MAILBOX_LIST_NOTIFY_STATUS) == 0) {
+ /* not interested in mailbox changes */
+ } else if (mailbox_list_get_path(list, "INBOX", MAILBOX_LIST_PATH_TYPE_INDEX,
+ &index_dir) <= 0) {
+ /* no indexes for INBOX? can't handle it */
+ } else {
+ mailbox_list_index_notify_init_inbox(inotify);
+ }
+
+ *notify_r = &inotify->notify;
+ return 1;
+}
+
+void mailbox_list_index_notify_deinit(struct mailbox_list_notify *notify)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+ bool b;
+
+ if (inotify->inbox != NULL)
+ mailbox_free(&inotify->inbox);
+ if (inotify->subscriptions != NULL)
+ mailbox_tree_deinit(&inotify->subscriptions);
+ io_remove(&inotify->io_wait);
+ io_remove(&inotify->io_wait_inbox);
+ timeout_remove(&inotify->to_wait);
+ timeout_remove(&inotify->to_notify);
+ if (inotify->sync_ctx != NULL)
+ (void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+ mail_index_view_close(&inotify->view);
+ mail_index_view_close(&inotify->old_view);
+ mailbox_list_notify_tree_deinit(&inotify->tree);
+ array_free(&inotify->new_subscriptions);
+ array_free(&inotify->new_unsubscriptions);
+ array_free(&inotify->new_uids);
+ array_free(&inotify->expunged_uids);
+ array_free(&inotify->changed_uids);
+ array_free(&inotify->renames);
+ str_free(&inotify->rec_name);
+ i_free(inotify->list_log_path);
+ i_free(inotify->inbox_log_path);
+ i_free(inotify);
+}
+
+static struct mailbox_list_index_node *
+notify_lookup_guid(struct mailbox_list_notify_index *inotify,
+ struct mail_index_view *view,
+ uint32_t uid, enum mailbox_status_items items,
+ struct mailbox_status *status_r, guid_128_t guid_r)
+{
+ struct mailbox_list_index *ilist =
+ INDEX_LIST_CONTEXT_REQUIRE(inotify->notify.list);
+ struct mailbox_list_index_node *index_node;
+ const char *reason;
+ uint32_t seq;
+
+ if (!mail_index_lookup_seq(view, uid, &seq))
+ return NULL;
+
+ index_node = mailbox_list_index_lookup_uid(ilist, uid);
+ if (index_node == NULL) {
+ /* re-parse the index list using the given view. we could be
+ jumping here between old and new view. */
+ (void)mailbox_list_index_parse(inotify->notify.list,
+ view, FALSE);
+ index_node = mailbox_list_index_lookup_uid(ilist, uid);
+ if (index_node == NULL)
+ return NULL;
+ }
+
+ /* get GUID */
+ i_zero(status_r);
+ memset(guid_r, 0, GUID_128_SIZE);
+ (void)mailbox_list_index_status(inotify->notify.list, view, seq,
+ items, status_r, guid_r, NULL, &reason);
+ return index_node;
+}
+
+static void notify_update_stat(struct mailbox_list_notify_index *inotify,
+ bool stat_list, bool stat_inbox)
+{
+ bool call = FALSE;
+
+ if (stat_list &&
+ stat(inotify->list_log_path, &inotify->list_last_st) < 0 &&
+ errno != ENOENT) {
+ e_error(inotify->notify.list->ns->user->event,
+ "stat(%s) failed: %m", inotify->list_log_path);
+ call = TRUE;
+ }
+ if (inotify->inbox_log_path != NULL && stat_inbox) {
+ if (stat(inotify->inbox_log_path, &inotify->inbox_last_st) < 0 &&
+ errno != ENOENT) {
+ e_error(inotify->notify.list->ns->user->event,
+ "stat(%s) failed: %m", inotify->inbox_log_path);
+ call = TRUE;
+ }
+ }
+ if (call)
+ mailbox_list_index_notify_wait(&inotify->notify, NULL, NULL);
+}
+
+static void
+mailbox_list_index_notify_sync_init(struct mailbox_list_notify_index *inotify)
+{
+ struct mail_index_view_sync_rec sync_rec;
+
+ notify_update_stat(inotify, TRUE, TRUE);
+ (void)mail_index_refresh(inotify->view->index);
+
+ /* sync the view so that map extensions gets updated */
+ inotify->sync_ctx = mail_index_view_sync_begin(inotify->view, 0);
+ mail_transaction_log_view_mark(inotify->view->log_view);
+ while (mail_index_view_sync_next(inotify->sync_ctx, &sync_rec)) ;
+ mail_transaction_log_view_rewind(inotify->view->log_view);
+
+ inotify->cur_ext = ILIST_EXT_NONE;
+ inotify->cur_ext_id = (uint32_t)-1;
+}
+
+static bool notify_ext_rec(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify *notify = &inotify->notify;
+
+ switch (inotify->cur_ext) {
+ case ILIST_EXT_NONE:
+ i_unreached();
+ case ILIST_EXT_BASE:
+ /* UIDVALIDITY changed */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_UIDVALIDITY) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_MSGS:
+ /* APPEND, EXPUNGE, \Seen or \Recent flag change */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_STATUS) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_HIGHESTMODSEQ:
+ /* when this doesn't come with EXT_MSGS update,
+ it can only be a flag change or an explicit
+ modseq change */
+ if ((notify->mask & MAILBOX_LIST_NOTIFY_MODSEQ_CHANGES) == 0)
+ return FALSE;
+ break;
+ case ILIST_EXT_UNKNOWN:
+ return FALSE;
+ }
+ seq_range_array_add(&inotify->changed_uids, uid);
+ return TRUE;
+}
+
+static int
+mailbox_list_index_notify_read_next(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_list_notify *notify = &inotify->notify;
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(notify->list);
+ const struct mail_transaction_header *hdr;
+ const void *data;
+ int ret;
+
+ ret = mail_transaction_log_view_next(inotify->view->log_view,
+ &hdr, &data);
+ if (ret <= 0)
+ return ret;
+
+ if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
+ /* all mailbox index updates are external */
+ return 1;
+ }
+ switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
+ case MAIL_TRANSACTION_APPEND: {
+ /* mailbox added or renamed */
+ const struct mail_index_record *rec, *end;
+
+ if ((notify->mask & (MAILBOX_LIST_NOTIFY_CREATE |
+ MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+ break;
+
+ end = CONST_PTR_OFFSET(data, hdr->size);
+ for (rec = data; rec != end; rec++)
+ seq_range_array_add(&inotify->new_uids, rec->uid);
+ break;
+ }
+ case MAIL_TRANSACTION_EXPUNGE_GUID: {
+ /* mailbox deleted or renamed */
+ const struct mail_transaction_expunge_guid *rec, *end;
+
+ if ((notify->mask & (MAILBOX_LIST_NOTIFY_DELETE |
+ MAILBOX_LIST_NOTIFY_RENAME)) == 0)
+ break;
+
+ end = CONST_PTR_OFFSET(data, hdr->size);
+ for (rec = data; rec != end; rec++)
+ seq_range_array_add(&inotify->expunged_uids, rec->uid);
+ break;
+ }
+ case MAIL_TRANSACTION_EXT_INTRO: {
+ struct mail_index_map *map = inotify->view->map;
+ const struct mail_transaction_ext_intro *rec = data;
+ const struct mail_index_ext *ext = NULL;
+ const char *name;
+ uint32_t ext_map_idx;
+
+ if (!array_is_created(&map->extensions))
+ break;
+ /* we want to know what extension the future
+ ext-rec-updates are changing. we're assuming here that
+ there is only one ext-intro record before those,
+ which is true at least for now. */
+ if (rec->ext_id != (uint32_t)-1 &&
+ rec->ext_id < array_count(&map->extensions)) {
+ /* get extension by id */
+ ext = array_idx(&map->extensions, rec->ext_id);
+ } else if (rec->name_size > 0) {
+ /* by name */
+ name = t_strndup(rec+1, rec->name_size);
+ if (mail_index_map_lookup_ext(map, name, &ext_map_idx))
+ ext = array_idx(&map->extensions, ext_map_idx);
+ }
+ if (ext != NULL) {
+ if (ext->index_idx == ilist->ext_id)
+ inotify->cur_ext = ILIST_EXT_BASE;
+ else if (ext->index_idx == ilist->msgs_ext_id)
+ inotify->cur_ext = ILIST_EXT_MSGS;
+ else if (ext->index_idx == ilist->hmodseq_ext_id)
+ inotify->cur_ext = ILIST_EXT_HIGHESTMODSEQ;
+ else
+ inotify->cur_ext = ILIST_EXT_UNKNOWN;
+ inotify->cur_ext_id = ext->index_idx;
+ }
+ break;
+ }
+ case MAIL_TRANSACTION_EXT_REC_UPDATE: {
+ const struct mail_index_registered_ext *ext;
+ const struct mail_transaction_ext_rec_update *rec;
+ unsigned int i, record_size;
+
+ if (inotify->cur_ext == ILIST_EXT_NONE) {
+ e_error(ilist->index->event,
+ "%s: Missing ext-intro for ext-rec-update",
+ ilist->index->filepath);
+ break;
+ }
+
+ /* the record is padded to 32bits in the transaction log */
+ ext = array_idx(&inotify->view->index->extensions,
+ inotify->cur_ext_id);
+ record_size = (sizeof(*rec) + ext->record_size + 3) & ~3U;
+ for (i = 0; i < hdr->size; i += record_size) {
+ rec = CONST_PTR_OFFSET(data, i);
+
+ if (i + record_size > hdr->size)
+ break;
+ if (!notify_ext_rec(inotify, rec->uid))
+ break;
+ }
+ break;
+ }
+ }
+ return 1;
+}
+
+static int
+mailbox_list_inotify_entry_guid_cmp(const struct mailbox_list_inotify_entry *r1,
+ const struct mailbox_list_inotify_entry *r2)
+{
+ int ret;
+
+ ret = memcmp(r1->guid, r2->guid, sizeof(r1->guid));
+ if (ret != 0)
+ return ret;
+
+ if (r1->expunge == r2->expunge) {
+ /* this really shouldn't happen */
+ return 0;
+ }
+ return r1->expunge ? -1 : 1;
+}
+
+static void
+mailbox_list_index_notify_find_renames(struct mailbox_list_notify_index *inotify)
+{
+ ARRAY(struct mailbox_list_inotify_entry) entries;
+ struct mailbox_status status;
+ struct mailbox_list_notify_rename *rename;
+ struct mailbox_list_inotify_entry *entry;
+ const struct mailbox_list_inotify_entry *e;
+ unsigned int i, count;
+ guid_128_t guid;
+ uint32_t uid;
+
+ /* first get all of the added and expunged GUIDs */
+ t_array_init(&entries, array_count(&inotify->new_uids) +
+ array_count(&inotify->expunged_uids));
+ while (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+ inotify->expunged_uids_n++, &uid)) {
+ if (notify_lookup_guid(inotify, inotify->old_view, uid,
+ 0, &status, guid) != NULL &&
+ !guid_128_is_empty(guid)) {
+ entry = array_append_space(&entries);
+ entry->uid = uid;
+ entry->expunge = TRUE;
+ memcpy(entry->guid, guid, sizeof(entry->guid));
+ }
+ }
+
+ (void)mailbox_list_index_parse(inotify->notify.list,
+ inotify->view, TRUE);
+ while (seq_range_array_iter_nth(&inotify->new_uids_iter,
+ inotify->new_uids_n++, &uid)) {
+ if (notify_lookup_guid(inotify, inotify->view, uid,
+ 0, &status, guid) != NULL &&
+ !guid_128_is_empty(guid)) {
+ entry = array_append_space(&entries);
+ entry->uid = uid;
+ memcpy(entry->guid, guid, sizeof(entry->guid));
+ }
+ }
+
+ /* now sort the entries by GUID and find those that have been both
+ added and expunged */
+ array_sort(&entries, mailbox_list_inotify_entry_guid_cmp);
+
+ e = array_get(&entries, &count);
+ for (i = 1; i < count; i++) {
+ if (e[i-1].expunge && !e[i].expunge &&
+ memcmp(e[i-1].guid, e[i].guid, sizeof(e[i].guid)) == 0) {
+ rename = array_append_space(&inotify->renames);
+ rename->old_uid = e[i-1].uid;
+ rename->new_uid = e[i].uid;
+
+ seq_range_array_remove(&inotify->expunged_uids,
+ rename->old_uid);
+ seq_range_array_remove(&inotify->new_uids,
+ rename->new_uid);
+ }
+ }
+}
+
+static void
+mailbox_list_index_notify_find_subscribes(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_tree_iterate_context *old_iter, *new_iter;
+ struct mailbox_tree_context *old_tree, *new_tree;
+ const char *old_path = NULL, *new_path = NULL;
+ pool_t pool;
+ int ret;
+
+ if (mailbox_list_iter_subscriptions_refresh(inotify->notify.list) < 0)
+ return;
+ mailbox_tree_sort(inotify->notify.list->subscriptions);
+
+ old_tree = inotify->subscriptions;
+ new_tree = mailbox_tree_dup(inotify->notify.list->subscriptions);
+
+ old_iter = mailbox_tree_iterate_init(old_tree, NULL, MAILBOX_SUBSCRIBED);
+ new_iter = mailbox_tree_iterate_init(new_tree, NULL, MAILBOX_SUBSCRIBED);
+
+ pool = mailbox_tree_get_pool(new_tree);
+ for (;;) {
+ if (old_path == NULL) {
+ if (mailbox_tree_iterate_next(old_iter, &old_path) == NULL)
+ old_path = NULL;
+ }
+ if (new_path == NULL) {
+ if (mailbox_tree_iterate_next(new_iter, &new_path) == NULL)
+ new_path = NULL;
+ }
+
+ if (old_path == NULL) {
+ if (new_path == NULL)
+ break;
+ ret = 1;
+ } else if (new_path == NULL)
+ ret = -1;
+ else {
+ ret = strcmp(old_path, new_path);
+ }
+
+ if (ret == 0) {
+ old_path = NULL;
+ new_path = NULL;
+ } else if (ret > 0) {
+ new_path = p_strdup(pool, new_path);
+ array_push_back(&inotify->new_subscriptions,
+ &new_path);
+ new_path = NULL;
+ } else {
+ old_path = p_strdup(pool, old_path);
+ array_push_back(&inotify->new_unsubscriptions,
+ &old_path);
+ old_path = NULL;
+ }
+ }
+ mailbox_tree_iterate_deinit(&old_iter);
+ mailbox_tree_iterate_deinit(&new_iter);
+
+ mailbox_tree_deinit(&inotify->subscriptions);
+ inotify->subscriptions = new_tree;
+}
+
+static void
+mailbox_list_index_notify_reset_iters(struct mailbox_list_notify_index *inotify)
+{
+ seq_range_array_iter_init(&inotify->new_uids_iter,
+ &inotify->new_uids);
+ seq_range_array_iter_init(&inotify->expunged_uids_iter,
+ &inotify->expunged_uids);
+ seq_range_array_iter_init(&inotify->changed_uids_iter,
+ &inotify->changed_uids);
+ inotify->changed_uids_n = 0;
+ inotify->new_uids_n = 0;
+ inotify->expunged_uids_n = 0;
+ inotify->rename_idx = 0;
+ inotify->subscription_idx = 0;
+ inotify->unsubscription_idx = 0;
+}
+
+static void
+mailbox_list_index_notify_read_init(struct mailbox_list_notify_index *inotify)
+{
+ bool b;
+ int ret;
+
+ mailbox_list_index_notify_sync_init(inotify);
+
+ /* read all changes from .log file */
+ while ((ret = mailbox_list_index_notify_read_next(inotify)) > 0) ;
+ inotify->read_failed = ret < 0;
+
+ (void)mail_index_view_sync_commit(&inotify->sync_ctx, &b);
+
+ /* remove changes for already deleted mailboxes */
+ seq_range_array_remove_seq_range(&inotify->new_uids,
+ &inotify->expunged_uids);
+ seq_range_array_remove_seq_range(&inotify->changed_uids,
+ &inotify->expunged_uids);
+ mailbox_list_index_notify_reset_iters(inotify);
+ if (array_count(&inotify->new_uids) > 0 &&
+ array_count(&inotify->expunged_uids) > 0) {
+ mailbox_list_index_notify_find_renames(inotify);
+ mailbox_list_index_notify_reset_iters(inotify);
+ }
+ if (inotify->subscriptions != NULL)
+ mailbox_list_index_notify_find_subscribes(inotify);
+
+ inotify->initialized = TRUE;
+}
+
+static void
+mailbox_list_index_notify_read_deinit(struct mailbox_list_notify_index *inotify)
+{
+ /* save the old view so we can look up expunged records */
+ mail_index_view_close(&inotify->old_view);
+ inotify->old_view = mail_index_view_dup_private(inotify->view);
+
+ array_clear(&inotify->new_subscriptions);
+ array_clear(&inotify->new_unsubscriptions);
+ array_clear(&inotify->new_uids);
+ array_clear(&inotify->expunged_uids);
+ array_clear(&inotify->changed_uids);
+ array_clear(&inotify->renames);
+
+ inotify->initialized = FALSE;
+}
+
+static bool
+mailbox_list_index_notify_lookup(struct mailbox_list_notify_index *inotify,
+ struct mail_index_view *view,
+ uint32_t uid, enum mailbox_status_items items,
+ struct mailbox_status *status_r,
+ struct mailbox_list_notify_rec **rec_r)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+ struct mailbox_list_index_node *index_node;
+ const char *storage_name;
+ char ns_sep = mailbox_list_get_hierarchy_sep(inotify->notify.list);
+
+ i_zero(rec);
+ index_node = notify_lookup_guid(inotify, view, uid,
+ items, status_r, rec->guid);
+ if (index_node == NULL)
+ return FALSE;
+
+ /* get storage_name */
+ str_truncate(inotify->rec_name, 0);
+ mailbox_list_index_node_get_path(index_node, ns_sep, inotify->rec_name);
+ storage_name = str_c(inotify->rec_name);
+
+ rec->storage_name = storage_name;
+ rec->vname = mailbox_list_get_vname(inotify->notify.list,
+ rec->storage_name);
+ *rec_r = rec;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_rename(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ const struct mailbox_list_notify_rename *rename;
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+ const char *old_vname;
+
+ rename = array_idx(&inotify->renames, idx);
+
+ /* lookup the old name */
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+ rename->old_uid, 0, &status, &rec))
+ return FALSE;
+ old_vname = t_strdup(rec->vname);
+
+ /* return using the new name */
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ rename->new_uid, 0, &status, &rec))
+ return FALSE;
+
+ rec->old_vname = old_vname;
+ rec->events = MAILBOX_LIST_NOTIFY_RENAME;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_subscribe(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+
+ i_zero(rec);
+ rec->vname = array_idx_elem(&inotify->new_subscriptions, idx);
+ rec->storage_name = mailbox_list_get_storage_name(inotify->notify.list,
+ rec->vname);
+ rec->events = MAILBOX_LIST_NOTIFY_SUBSCRIBE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_unsubscribe(struct mailbox_list_notify_index *inotify,
+ unsigned int idx)
+{
+ struct mailbox_list_notify_rec *rec = &inotify->notify_rec;
+
+ i_zero(rec);
+ rec->vname = array_idx_elem(&inotify->new_unsubscriptions, idx);
+ rec->storage_name = mailbox_list_get_storage_name(inotify->notify.list,
+ rec->vname);
+ rec->events = MAILBOX_LIST_NOTIFY_UNSUBSCRIBE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_expunge(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->old_view,
+ uid, 0, &status, &rec))
+ return FALSE;
+ rec->events = MAILBOX_LIST_NOTIFY_DELETE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_new(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ uid, 0, &status, &rec))
+ i_unreached();
+ rec->events = MAILBOX_LIST_NOTIFY_CREATE;
+ return TRUE;
+}
+
+static bool
+mailbox_list_index_notify_change(struct mailbox_list_notify_index *inotify,
+ uint32_t uid)
+{
+ struct mailbox_list_notify_rec *rec;
+ struct mailbox_notify_node *nnode, empty_node;
+ struct mailbox_status status;
+
+ if (!mailbox_list_index_notify_lookup(inotify, inotify->view,
+ uid, notify_status_items,
+ &status, &rec)) {
+ /* Mailbox is already deleted. We won't get here if we're
+ tracking MAILBOX_LIST_NOTIFY_DELETE or _RENAME
+ (which update expunged_uids). */
+ return FALSE;
+ }
+
+ /* get the old status */
+ nnode = mailbox_list_notify_tree_lookup(inotify->tree,
+ rec->storage_name);
+ if (nnode == NULL) {
+ /* mailbox didn't exist earlier - report all events as new */
+ i_zero(&empty_node);
+ nnode = &empty_node;
+ }
+ rec->events |= mailbox_list_index_get_changed_events(nnode, &status);
+ /* update internal state */
+ mailbox_notify_node_update_status(nnode, &status);
+ return rec->events != 0;
+}
+
+static bool
+mailbox_list_index_notify_try_next(struct mailbox_list_notify_index *inotify)
+{
+ uint32_t uid;
+
+ /* first show mailbox deletes */
+ if (seq_range_array_iter_nth(&inotify->expunged_uids_iter,
+ inotify->expunged_uids_n++, &uid))
+ return mailbox_list_index_notify_expunge(inotify, uid);
+
+ /* mailbox renames */
+ if (inotify->rename_idx < array_count(&inotify->renames)) {
+ return mailbox_list_index_notify_rename(inotify,
+ inotify->rename_idx++);
+ }
+
+ /* next mailbox creates */
+ if (seq_range_array_iter_nth(&inotify->new_uids_iter,
+ inotify->new_uids_n++, &uid))
+ return mailbox_list_index_notify_new(inotify, uid);
+
+ /* subscribes */
+ if (inotify->subscription_idx < array_count(&inotify->new_subscriptions)) {
+ return mailbox_list_index_notify_subscribe(inotify,
+ inotify->subscription_idx++);
+ }
+ if (inotify->unsubscription_idx < array_count(&inotify->new_unsubscriptions)) {
+ return mailbox_list_index_notify_unsubscribe(inotify,
+ inotify->unsubscription_idx++);
+ }
+
+ /* STATUS updates */
+ while (seq_range_array_iter_nth(&inotify->changed_uids_iter,
+ inotify->changed_uids_n++, &uid)) {
+ if (mailbox_list_index_notify_change(inotify, uid))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static enum mailbox_list_notify_event
+mailbox_list_notify_inbox_get_events(struct mailbox_list_notify_index *inotify)
+{
+ struct mailbox_status old_status, new_status;
+ struct mailbox_notify_node old_nnode;
+
+ mailbox_get_open_status(inotify->inbox, notify_status_items, &old_status);
+ if (mailbox_sync(inotify->inbox, MAILBOX_SYNC_FLAG_FAST) < 0) {
+ e_error(inotify->notify.list->ns->user->event,
+ "Mailbox list index notify: Failed to sync INBOX: %s",
+ mailbox_get_last_internal_error(inotify->inbox, NULL));
+ return 0;
+ }
+ mailbox_get_open_status(inotify->inbox, notify_status_items, &new_status);
+
+ mailbox_notify_node_update_status(&old_nnode, &old_status);
+ return mailbox_list_index_get_changed_events(&old_nnode, &new_status);
+}
+
+int mailbox_list_index_notify_next(struct mailbox_list_notify *notify,
+ const struct mailbox_list_notify_rec **rec_r)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+
+ if (!inotify->initialized)
+ mailbox_list_index_notify_read_init(inotify);
+ if (mailbox_list_index_handle_corruption(notify->list) < 0)
+ return -1;
+
+ while (mailbox_list_index_notify_try_next(inotify)) {
+ if ((inotify->notify_rec.events & inotify->notify.mask) != 0) {
+ *rec_r = &inotify->notify_rec;
+ return 1;
+ } else {
+ /* caller doesn't care about this change */
+ }
+ }
+ if (inotify->inbox_event_pending) {
+ inotify->inbox_event_pending = FALSE;
+ i_zero(&inotify->notify_rec);
+ inotify->notify_rec.vname = "INBOX";
+ inotify->notify_rec.storage_name = "INBOX";
+ inotify->notify_rec.events =
+ mailbox_list_notify_inbox_get_events(inotify);
+ *rec_r = &inotify->notify_rec;
+ return 1;
+ }
+
+ mailbox_list_index_notify_read_deinit(inotify);
+ return inotify->read_failed ? -1 : 0;
+}
+
+static void notify_now_callback(struct mailbox_list_notify_index *inotify)
+{
+ timeout_remove(&inotify->to_notify);
+ inotify->wait_callback(inotify->wait_context);
+}
+
+static void list_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ struct stat list_prev_st = inotify->list_last_st;
+
+ if (inotify->to_notify != NULL) {
+ /* there's a pending notification already -
+ no need to stat() again */
+ return;
+ }
+
+ notify_update_stat(inotify, TRUE, FALSE);
+ if (ST_CHANGED(inotify->list_last_st, list_prev_st)) {
+ /* log has changed. call the callback with a small delay
+ to allow bundling multiple changes together */
+ inotify->to_notify =
+ timeout_add_short(NOTIFY_DELAY_MSECS,
+ notify_now_callback, inotify);
+ }
+}
+
+static void inbox_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ struct stat inbox_prev_st = inotify->inbox_last_st;
+
+ if (inotify->to_notify != NULL && inotify->inbox_event_pending) {
+ /* there's a pending INBOX notification already -
+ no need to stat() again */
+ return;
+ }
+
+ notify_update_stat(inotify, FALSE, TRUE);
+ if (ST_CHANGED(inotify->inbox_last_st, inbox_prev_st))
+ inotify->inbox_event_pending = TRUE;
+ if (inotify->inbox_event_pending && inotify->to_notify == NULL) {
+ /* log has changed. call the callback with a small delay
+ to allow bundling multiple changes together */
+ inotify->to_notify =
+ timeout_add_short(NOTIFY_DELAY_MSECS,
+ notify_now_callback, inotify);
+ }
+}
+
+static void full_notify_callback(struct mailbox_list_notify_index *inotify)
+{
+ list_notify_callback(inotify);
+ inbox_notify_callback(inotify);
+}
+
+void mailbox_list_index_notify_wait(struct mailbox_list_notify *notify,
+ void (*callback)(void *context),
+ void *context)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+ unsigned int check_interval;
+
+ inotify->wait_callback = callback;
+ inotify->wait_context = context;
+
+ if (callback == NULL) {
+ io_remove(&inotify->io_wait);
+ io_remove(&inotify->io_wait_inbox);
+ timeout_remove(&inotify->to_wait);
+ timeout_remove(&inotify->to_notify);
+ } else if (inotify->to_wait == NULL) {
+ (void)io_add_notify(inotify->list_log_path, list_notify_callback,
+ inotify, &inotify->io_wait);
+ /* we need to check for INBOX explicitly, because INBOX changes
+ don't get added to mailbox.list.index.log */
+ if (inotify->inbox_log_path != NULL) {
+ (void)io_add_notify(inotify->inbox_log_path,
+ inbox_notify_callback, inotify,
+ &inotify->io_wait_inbox);
+ }
+ /* check with timeout as well, in case io_add_notify()
+ doesn't work (e.g. NFS) */
+ check_interval = notify->list->mail_set->mailbox_idle_check_interval;
+ i_assert(check_interval > 0);
+ inotify->to_wait = timeout_add(check_interval * 1000,
+ full_notify_callback, inotify);
+ notify_update_stat(inotify, TRUE, TRUE);
+ }
+}
+
+void mailbox_list_index_notify_flush(struct mailbox_list_notify *notify)
+{
+ struct mailbox_list_notify_index *inotify =
+ (struct mailbox_list_notify_index *)notify;
+
+ if (inotify->to_notify == NULL &&
+ notify->list->mail_set->mailbox_idle_check_interval > 0) {
+ /* no pending notification - check if anything had changed */
+ full_notify_callback(inotify);
+ }
+ if (inotify->to_notify != NULL)
+ notify_now_callback(inotify);
+}
diff --git a/src/lib-storage/list/mailbox-list-index-status.c b/src/lib-storage/list/mailbox-list-index-status.c
new file mode 100644
index 0000000..4218ae7
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-status.c
@@ -0,0 +1,862 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "mail-index-modseq.h"
+#include "mailbox-list-index-storage.h"
+#include "mailbox-list-index.h"
+
+#define CACHED_STATUS_ITEMS \
+ (STATUS_MESSAGES | STATUS_UNSEEN | STATUS_RECENT | \
+ STATUS_UIDNEXT | STATUS_UIDVALIDITY | STATUS_HIGHESTMODSEQ)
+
+struct index_list_changes {
+ struct mailbox_status status;
+ guid_128_t guid;
+ uint32_t seq;
+ struct mailbox_index_vsize vsize;
+ uint32_t first_uid;
+
+ bool rec_changed;
+ bool msgs_changed;
+ bool hmodseq_changed;
+ bool vsize_changed;
+ bool first_saved_changed;
+};
+
+struct index_list_storage_module index_list_storage_module =
+ MODULE_CONTEXT_INIT(&mail_storage_module_register);
+
+static int
+index_list_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ struct mail_index_view *view;
+ const struct mail_index_record *rec;
+ enum mailbox_list_index_flags flags;
+ uint32_t seq;
+
+ if (mailbox_list_index_view_open(box, FALSE, &view, &seq) <= 0) {
+ /* failure / not found. fallback to the real storage check
+ just in case to see if the mailbox was just created. */
+ return ibox->module_ctx.super.
+ exists(box, auto_boxes, existence_r);
+ }
+ rec = mail_index_lookup(view, seq);
+ flags = rec->flags;
+ mail_index_view_close(&view);
+
+ if ((flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0)
+ *existence_r = MAILBOX_EXISTENCE_NONE;
+ else if ((flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0)
+ *existence_r = MAILBOX_EXISTENCE_NOSELECT;
+ else
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+}
+
+bool mailbox_list_index_status(struct mailbox_list *list,
+ struct mail_index_view *view,
+ uint32_t seq, enum mailbox_status_items items,
+ struct mailbox_status *status_r,
+ uint8_t *mailbox_guid,
+ struct mailbox_index_vsize *vsize_r,
+ const char **reason_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ const void *data;
+ bool expunged;
+ const char *reason = NULL;
+
+ if ((items & STATUS_UIDVALIDITY) != 0 || mailbox_guid != NULL) {
+ const struct mailbox_list_index_record *rec;
+
+ mail_index_lookup_ext(view, seq, ilist->ext_id,
+ &data, &expunged);
+ rec = data;
+ if (rec == NULL) {
+ if ((items & STATUS_UIDVALIDITY) != 0)
+ reason = "Record for UIDVALIDITY";
+ else
+ reason = "Record for GUID";
+ } else {
+ if ((items & STATUS_UIDVALIDITY) != 0 &&
+ rec->uid_validity == 0)
+ reason = "UIDVALIDITY=0";
+ else
+ status_r->uidvalidity = rec->uid_validity;
+ if (mailbox_guid != NULL)
+ memcpy(mailbox_guid, rec->guid, GUID_128_SIZE);
+ }
+ }
+
+ if ((items & (STATUS_MESSAGES | STATUS_UNSEEN |
+ STATUS_RECENT | STATUS_UIDNEXT)) != 0) {
+ const struct mailbox_list_index_msgs_record *rec;
+
+ mail_index_lookup_ext(view, seq, ilist->msgs_ext_id,
+ &data, &expunged);
+ rec = data;
+ if (rec == NULL)
+ reason = "Record for message counts";
+ else if (rec->uidnext == 0) {
+ reason = "Empty record for message counts";
+ } else {
+ status_r->messages = rec->messages;
+ status_r->unseen = rec->unseen;
+ status_r->recent = rec->recent;
+ status_r->uidnext = rec->uidnext;
+ }
+ }
+ if ((items & STATUS_HIGHESTMODSEQ) != 0) {
+ const uint64_t *rec;
+
+ mail_index_lookup_ext(view, seq, ilist->hmodseq_ext_id,
+ &data, &expunged);
+ rec = data;
+ if (rec == NULL)
+ reason = "Record for HIGHESTMODSEQ";
+ else if (*rec == 0)
+ reason = "HIGHESTMODSEQ=0";
+ else
+ status_r->highest_modseq = *rec;
+ }
+ if (vsize_r != NULL) {
+ mail_index_lookup_ext(view, seq, ilist->vsize_ext_id,
+ &data, &expunged);
+ if (data == NULL)
+ reason = "Record for vsize";
+ else
+ memcpy(vsize_r, data, sizeof(*vsize_r));
+ }
+ *reason_r = reason;
+ return reason == NULL;
+}
+
+static int
+index_list_get_cached_status(struct mailbox *box,
+ enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct mail_index_view *view;
+ const char *reason;
+ uint32_t seq;
+ int ret;
+
+ if (items == 0)
+ return 1;
+
+ if ((items & STATUS_UNSEEN) != 0 &&
+ (mailbox_get_private_flags_mask(box) & MAIL_SEEN) != 0) {
+ /* can't get UNSEEN from list index, since each user has
+ different \Seen flags */
+ return 0;
+ }
+
+ if ((ret = mailbox_list_index_view_open(box, TRUE, &view, &seq)) <= 0)
+ return ret;
+
+ ret = mailbox_list_index_status(box->list, view, seq, items,
+ status_r, NULL, NULL, &reason) ? 1 : 0;
+ if (ret == 0) {
+ e_debug(box->event,
+ "Couldn't get status items from mailbox list index: %s",
+ reason);
+ }
+ mail_index_view_close(&view);
+ return ret;
+}
+
+static int
+index_list_get_status(struct mailbox *box, enum mailbox_status_items items,
+ struct mailbox_status *status_r)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if ((items & ENUM_NEGATE(CACHED_STATUS_ITEMS)) == 0 && !box->opened) {
+ if (index_list_get_cached_status(box, items, status_r) > 0)
+ return 0;
+ /* nonsynced / error, fallback to doing it the slow way */
+ }
+ return ibox->module_ctx.super.get_status(box, items, status_r);
+}
+
+/* Opportunistic function to see ïf we can extract guid from mailbox path */
+static bool index_list_get_guid_from_path(struct mailbox *box, guid_128_t guid_r)
+{
+ const char *path;
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX, &path) <= 0)
+ return FALSE;
+ const char *ptr = strrchr(path, '/');
+ if (ptr == NULL)
+ return FALSE;
+ return guid_128_from_string(ptr + 1, guid_r) == 0;
+}
+
+static int
+index_list_get_cached_guid(struct mailbox *box, guid_128_t guid_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_status status;
+ struct mail_index_view *view;
+ const char *reason;
+ uint32_t seq;
+ int ret;
+
+ /* If using INDEX layout, try determine GUID from mailbox path */
+ if (!ilist->has_backing_store &&
+ index_list_get_guid_from_path(box, guid_r))
+ return 1;
+
+ if (ilist->syncing) {
+ /* syncing wants to know the GUID for a new mailbox. */
+ return 0;
+ }
+
+ if ((ret = mailbox_list_index_view_open(box, FALSE, &view, &seq)) <= 0)
+ return ret;
+
+ ret = mailbox_list_index_status(box->list, view, seq, 0,
+ &status, guid_r, NULL, &reason) ? 1 : 0;
+ if (ret > 0 && guid_128_is_empty(guid_r)) {
+ reason = "GUID is empty";
+ ret = 0;
+ }
+ if (ret == 0) {
+ e_debug(box->event,
+ "Couldn't get GUID from mailbox list index: %s",
+ reason);
+ }
+ mail_index_view_close(&view);
+ return ret;
+}
+
+static int index_list_get_cached_vsize(struct mailbox *box, uoff_t *vsize_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_status status;
+ struct mailbox_index_vsize vsize;
+ struct mail_index_view *view;
+ const char *reason;
+ uint32_t seq;
+ int ret;
+
+ i_assert(!ilist->syncing);
+
+ if ((ret = mailbox_list_index_view_open(box, TRUE, &view, &seq)) <= 0)
+ return ret;
+
+ ret = mailbox_list_index_status(box->list, view, seq,
+ STATUS_MESSAGES | STATUS_UIDNEXT,
+ &status, NULL, &vsize, &reason) ? 1 : 0;
+ if (ret > 0 && status.messages == 0 && status.uidnext > 0) {
+ /* mailbox is empty. its size has to be zero, regardless of
+ what the vsize header says. */
+ vsize.vsize = 0;
+ } else if (ret > 0 && (vsize.highest_uid + 1 != status.uidnext ||
+ vsize.message_count != status.messages)) {
+ /* out of date vsize info */
+ reason = "out of date vsize info";
+ ret = 0;
+ }
+ if (ret > 0)
+ *vsize_r = vsize.vsize;
+ else if (ret == 0) {
+ e_debug(box->event,
+ "Couldn't get vsize from mailbox list index: %s",
+ reason);
+ }
+ mail_index_view_close(&view);
+ return ret;
+}
+
+static int
+index_list_get_cached_first_saved(struct mailbox *box,
+ struct mailbox_index_first_saved *first_saved_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mail_index_view *view;
+ struct mailbox_status status;
+ const char *reason;
+ const void *data;
+ bool expunged;
+ uint32_t seq;
+ int ret;
+
+ i_zero(first_saved_r);
+
+ if ((ret = mailbox_list_index_view_open(box, TRUE, &view, &seq)) <= 0)
+ return ret;
+
+ mail_index_lookup_ext(view, seq, ilist->first_saved_ext_id,
+ &data, &expunged);
+ if (data != NULL)
+ memcpy(first_saved_r, data, sizeof(*first_saved_r));
+ if (first_saved_r->timestamp != 0 && first_saved_r->uid == 0) {
+ /* mailbox was empty the last time we updated this.
+ we'll need to verify if it still is. */
+ if (!mailbox_list_index_status(box->list, view, seq,
+ STATUS_MESSAGES,
+ &status, NULL, NULL, &reason) ||
+ status.messages != 0)
+ first_saved_r->timestamp = 0;
+ }
+ mail_index_view_close(&view);
+ return first_saved_r->timestamp != 0 ? 1 : 0;
+}
+
+static int
+index_list_try_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ enum mailbox_metadata_items noncached_items;
+ int ret;
+
+ i_assert(metadata_r != NULL);
+
+ if (box->opened) {
+ /* if mailbox is already opened, don't bother using the values
+ in mailbox list index. they have a higher chance of being
+ wrong. */
+ return 0;
+ }
+ /* see if we have a chance of fulfilling this without opening
+ the mailbox. */
+ noncached_items = items & ENUM_NEGATE(MAILBOX_METADATA_GUID |
+ MAILBOX_METADATA_VIRTUAL_SIZE |
+ MAILBOX_METADATA_FIRST_SAVE_DATE);
+ if ((noncached_items & MAILBOX_METADATA_PHYSICAL_SIZE) != 0 &&
+ box->mail_vfuncs->get_physical_size ==
+ box->mail_vfuncs->get_virtual_size)
+ noncached_items = items & ENUM_NEGATE(MAILBOX_METADATA_PHYSICAL_SIZE);
+
+ if (noncached_items != 0)
+ return 0;
+
+ if ((items & MAILBOX_METADATA_GUID) != 0) {
+ if ((ret = index_list_get_cached_guid(box, metadata_r->guid)) <= 0)
+ return ret;
+ }
+ if ((items & (MAILBOX_METADATA_VIRTUAL_SIZE |
+ MAILBOX_METADATA_PHYSICAL_SIZE)) != 0) {
+ if ((ret = index_list_get_cached_vsize(box, &metadata_r->virtual_size)) <= 0)
+ return ret;
+ if ((items & MAILBOX_METADATA_PHYSICAL_SIZE) != 0)
+ metadata_r->physical_size = metadata_r->virtual_size;
+ }
+ if ((items & MAILBOX_METADATA_FIRST_SAVE_DATE) != 0) {
+ struct mailbox_index_first_saved first_saved;
+
+ /* start writing first_saved to mailbox list index if it wasn't
+ there already. */
+ box->update_first_saved = TRUE;
+
+ if ((ret = index_list_get_cached_first_saved(box, &first_saved)) <= 0)
+ return ret;
+ metadata_r->first_save_date =
+ first_saved.timestamp == (uint32_t)-1 ? (time_t)-1 :
+ (time_t)first_saved.timestamp;
+ }
+ return 1;
+}
+
+static int
+index_list_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if (index_list_try_get_metadata(box, items, metadata_r) != 0)
+ return 0;
+ return ibox->module_ctx.super.get_metadata(box, items, metadata_r);
+}
+
+static void
+index_list_update_fill_vsize(struct mailbox *box,
+ struct mail_index_view *view,
+ struct index_list_changes *changes_r)
+{
+ const void *data;
+ size_t size;
+
+ mail_index_get_header_ext(view, box->vsize_hdr_ext_id,
+ &data, &size);
+ if (size == sizeof(changes_r->vsize))
+ memcpy(&changes_r->vsize, data, sizeof(changes_r->vsize));
+}
+
+static bool
+index_list_update_fill_changes(struct mailbox *box,
+ struct mail_index_view *list_view,
+ struct index_list_changes *changes_r)
+{
+ struct mailbox_list_index_node *node;
+ struct mail_index_view *view;
+ const struct mail_index_header *hdr;
+ struct mailbox_metadata metadata;
+ uint32_t seq1, seq2;
+
+ i_zero(changes_r);
+
+ node = mailbox_list_index_lookup(box->list, box->name);
+ if (node == NULL)
+ return FALSE;
+ if (!mail_index_lookup_seq(list_view, node->uid, &changes_r->seq))
+ return FALSE;
+
+ /* get STATUS info using the latest data in index.
+ note that for shared mailboxes (with private indexes) this
+ also means that the unseen count is always the owner's
+ count, not what exists in the private index. */
+ view = mail_index_view_open(box->index);
+ hdr = mail_index_get_header(view);
+
+ changes_r->status.messages = hdr->messages_count;
+ changes_r->status.unseen =
+ hdr->messages_count - hdr->seen_messages_count;
+ changes_r->status.uidvalidity = hdr->uid_validity;
+ changes_r->status.uidnext = hdr->next_uid;
+
+ if (!mail_index_lookup_seq_range(view, hdr->first_recent_uid,
+ (uint32_t)-1, &seq1, &seq2))
+ changes_r->status.recent = 0;
+ else
+ changes_r->status.recent = seq2 - seq1 + 1;
+
+ changes_r->status.highest_modseq = mail_index_modseq_get_highest(view);
+ if (changes_r->status.highest_modseq == 0) {
+ /* modseqs not enabled yet, but we can't return 0 */
+ changes_r->status.highest_modseq = 1;
+ }
+ index_list_update_fill_vsize(box, view, changes_r);
+ mail_index_view_close(&view); hdr = NULL;
+
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) == 0)
+ memcpy(changes_r->guid, metadata.guid, sizeof(changes_r->guid));
+ return TRUE;
+}
+
+static void
+index_list_first_saved_update_changes(struct mailbox *box,
+ struct mail_index_view *list_view,
+ struct index_list_changes *changes)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_index_first_saved first_saved;
+ const void *data;
+ bool expunged;
+
+ mail_index_lookup_ext(list_view, changes->seq,
+ ilist->first_saved_ext_id, &data, &expunged);
+ if (data == NULL)
+ i_zero(&first_saved);
+ else
+ memcpy(&first_saved, data, sizeof(first_saved));
+ if (mail_index_view_get_messages_count(box->view) > 0)
+ mail_index_lookup_uid(box->view, 1, &changes->first_uid);
+ if (first_saved.uid == 0 && first_saved.timestamp == 0) {
+ /* it's not in the index yet. we'll set it only if we've
+ just called MAILBOX_METADATA_FIRST_SAVE_DATE. */
+ changes->first_saved_changed = box->update_first_saved;
+ } else {
+ changes->first_saved_changed =
+ changes->first_uid != first_saved.uid;
+ }
+}
+
+static bool
+index_list_has_changed(struct mailbox *box, struct mail_index_view *list_view,
+ struct index_list_changes *changes)
+{
+ struct mailbox_status old_status;
+ struct mailbox_index_vsize old_vsize;
+ const char *reason;
+ guid_128_t old_guid;
+
+ i_zero(&old_status);
+ i_zero(&old_vsize);
+ memset(old_guid, 0, sizeof(old_guid));
+ (void)mailbox_list_index_status(box->list, list_view, changes->seq,
+ CACHED_STATUS_ITEMS,
+ &old_status, old_guid,
+ &old_vsize, &reason);
+
+ changes->rec_changed =
+ old_status.uidvalidity != changes->status.uidvalidity &&
+ changes->status.uidvalidity != 0;
+ if (!guid_128_equals(changes->guid, old_guid) &&
+ !guid_128_is_empty(changes->guid))
+ changes->rec_changed = TRUE;
+
+ if (MAILBOX_IS_NEVER_IN_INDEX(box)) {
+ /* check only UIDVALIDITY and GUID changes for INBOX */
+ return changes->rec_changed;
+ }
+
+ changes->msgs_changed =
+ old_status.messages != changes->status.messages ||
+ old_status.unseen != changes->status.unseen ||
+ old_status.recent != changes->status.recent ||
+ old_status.uidnext != changes->status.uidnext;
+ /* update highest-modseq only if they're ever been used */
+ if (old_status.highest_modseq == changes->status.highest_modseq) {
+ changes->hmodseq_changed = FALSE;
+ } else {
+ changes->hmodseq_changed = TRUE;
+ }
+ if (memcmp(&old_vsize, &changes->vsize, sizeof(old_vsize)) != 0)
+ changes->vsize_changed = TRUE;
+ index_list_first_saved_update_changes(box, list_view, changes);
+
+ return changes->rec_changed || changes->msgs_changed ||
+ changes->hmodseq_changed || changes->vsize_changed ||
+ changes->first_saved_changed;
+}
+
+static void
+index_list_update_first_saved(struct mailbox *box,
+ struct mail_index_transaction *list_trans,
+ const struct index_list_changes *changes)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mailbox_transaction_context *t;
+ struct mail *mail;
+ struct mailbox_index_first_saved first_saved;
+ uint32_t seq, messages_count;
+ time_t save_date;
+ int ret = 0;
+
+ i_zero(&first_saved);
+ first_saved.timestamp = (uint32_t)-1;
+
+ if (changes->first_uid != 0) {
+ t = mailbox_transaction_begin(box, 0, __func__);
+ mail = mail_alloc(t, MAIL_FETCH_SAVE_DATE, NULL);
+ messages_count = mail_index_view_get_messages_count(box->view);
+ for (seq = 1; seq <= messages_count; seq++) {
+ mail_set_seq(mail, seq);
+ if (mail_get_save_date(mail, &save_date) >= 0) {
+ first_saved.uid = mail->uid;
+ first_saved.timestamp = save_date;
+ break;
+ }
+ if (mailbox_get_last_mail_error(box) != MAIL_ERROR_EXPUNGED) {
+ ret = -1;
+ break;
+ }
+ }
+ mail_free(&mail);
+ (void)mailbox_transaction_commit(&t);
+ }
+ if (ret == 0) {
+ mail_index_update_ext(list_trans, changes->seq,
+ ilist->first_saved_ext_id,
+ &first_saved, NULL);
+ }
+}
+
+
+static void
+index_list_update(struct mailbox *box, struct mail_index_view *list_view,
+ struct mail_index_transaction *list_trans,
+ const struct index_list_changes *changes)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+
+ if (changes->rec_changed) {
+ struct mailbox_list_index_record rec;
+ const void *old_data;
+ bool expunged;
+
+ mail_index_lookup_ext(list_view, changes->seq, ilist->ext_id,
+ &old_data, &expunged);
+ i_assert(old_data != NULL);
+ memcpy(&rec, old_data, sizeof(rec));
+
+ if (changes->status.uidvalidity != 0)
+ rec.uid_validity = changes->status.uidvalidity;
+ if (!guid_128_is_empty(changes->guid))
+ memcpy(rec.guid, changes->guid, sizeof(rec.guid));
+ mail_index_update_ext(list_trans, changes->seq, ilist->ext_id,
+ &rec, NULL);
+ }
+
+ if (changes->msgs_changed) {
+ struct mailbox_list_index_msgs_record msgs;
+
+ i_zero(&msgs);
+ msgs.messages = changes->status.messages;
+ msgs.unseen = changes->status.unseen;
+ msgs.recent = changes->status.recent;
+ msgs.uidnext = changes->status.uidnext;
+
+ mail_index_update_ext(list_trans, changes->seq,
+ ilist->msgs_ext_id, &msgs, NULL);
+ }
+ if (changes->hmodseq_changed) {
+ mail_index_update_ext(list_trans, changes->seq,
+ ilist->hmodseq_ext_id,
+ &changes->status.highest_modseq, NULL);
+ }
+ if (changes->vsize_changed) {
+ mail_index_update_ext(list_trans, changes->seq,
+ ilist->vsize_ext_id,
+ &changes->vsize, NULL);
+ }
+ if (changes->first_saved_changed)
+ index_list_update_first_saved(box, list_trans, changes);
+}
+
+static int index_list_update_mailbox(struct mailbox *box)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mail_index_sync_ctx *list_sync_ctx;
+ struct mail_index_view *list_view;
+ struct mail_index_transaction *list_trans;
+ struct index_list_changes changes;
+ int ret;
+
+ i_assert(box->opened);
+
+ if (ilist->syncing || ilist->updating_status)
+ return 0;
+ if (box->deleting) {
+ /* don't update status info while mailbox is being deleted.
+ especially not a good idea if we're rolling back a created
+ mailbox that somebody else had just created */
+ return 0;
+ }
+
+ /* refresh the mailbox list index once. we can't do this again after
+ locking, because it could trigger list syncing. */
+ (void)mailbox_list_index_refresh(box->list);
+
+ /* first do a quick check while unlocked to see if anything changes */
+ list_view = mail_index_view_open(ilist->index);
+ if (!index_list_update_fill_changes(box, list_view, &changes))
+ ret = -1;
+ else if (index_list_has_changed(box, list_view, &changes))
+ ret = 1;
+ else {
+ /* if backend state changed on the last check, update it here
+ now. we probably don't need to bother checking again if the
+ state had changed? */
+ ret = ilist->index_last_check_changed ? 1 : 0;
+ }
+ mail_index_view_close(&list_view);
+ if (ret <= 0) {
+ if (ret < 0)
+ mailbox_list_index_refresh_later(box->list);
+ return 0;
+ }
+
+ /* looks like there are some changes. now lock the list index and do
+ the whole thing all over again while locked. this guarantees
+ that we'll always write the latest state of the mailbox. */
+ if (mail_index_sync_begin(ilist->index, &list_sync_ctx,
+ &list_view, &list_trans, 0) < 0) {
+ mailbox_set_index_error(box);
+ return -1;
+ }
+ /* refresh to latest state of the mailbox now that we're locked */
+ if (mail_index_refresh(box->index) < 0) {
+ mailbox_set_index_error(box);
+ mail_index_sync_rollback(&list_sync_ctx);
+ return -1;
+ }
+
+ if (!index_list_update_fill_changes(box, list_view, &changes))
+ mailbox_list_index_refresh_later(box->list);
+ else {
+ ilist->updating_status = TRUE;
+ if (index_list_has_changed(box, list_view, &changes))
+ index_list_update(box, list_view, list_trans, &changes);
+ if (box->v.list_index_update_sync != NULL &&
+ !MAILBOX_IS_NEVER_IN_INDEX(box)) {
+ box->v.list_index_update_sync(box, list_trans,
+ changes.seq);
+ }
+ ilist->updating_status = FALSE;
+ }
+
+ struct mail_index_sync_rec sync_rec;
+ while (mail_index_sync_next(list_sync_ctx, &sync_rec)) ;
+ if (mail_index_sync_commit(&list_sync_ctx) < 0) {
+ mailbox_set_index_error(box);
+ return -1;
+ }
+ ilist->index_last_check_changed = FALSE;
+ return 0;
+}
+
+void mailbox_list_index_update_mailbox_index(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ struct mail_index_view *list_view;
+ struct mail_index_transaction *list_trans;
+ struct index_list_changes changes;
+ struct mailbox_status status;
+ const char *reason;
+ guid_128_t mailbox_guid;
+ bool guid_changed = FALSE;
+
+ i_zero(&changes);
+ /* update the mailbox list index even if it has some other pending
+ changes. */
+ if (mailbox_list_index_view_open(box, FALSE, &list_view, &changes.seq) <= 0)
+ return;
+
+ guid_128_empty(mailbox_guid);
+ (void)mailbox_list_index_status(box->list, list_view, changes.seq,
+ CACHED_STATUS_ITEMS, &status,
+ mailbox_guid, NULL, &reason);
+
+ if (update->uid_validity != 0) {
+ changes.rec_changed = TRUE;
+ changes.status.uidvalidity = update->uid_validity;
+ }
+ if (!guid_128_equals(update->mailbox_guid, mailbox_guid) &&
+ !guid_128_is_empty(update->mailbox_guid)) {
+ changes.rec_changed = TRUE;
+ memcpy(changes.guid, update->mailbox_guid, sizeof(changes.guid));
+ guid_changed = TRUE;
+ }
+ if (guid_changed ||
+ update->uid_validity != 0 ||
+ update->min_next_uid != 0 ||
+ update->min_first_recent_uid != 0 ||
+ update->min_highest_modseq != 0) {
+ /* reset status counters to 0. let the syncing later figure out
+ their correct values. */
+ changes.msgs_changed = TRUE;
+ changes.hmodseq_changed = TRUE;
+ }
+ list_trans = mail_index_transaction_begin(list_view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ index_list_update(box, list_view, list_trans, &changes);
+ (void)mail_index_transaction_commit(&list_trans);
+ mail_index_view_close(&list_view);
+}
+
+void mailbox_list_index_status_sync_init(struct mailbox *box)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ const struct mail_index_header *hdr;
+
+ hdr = mail_index_get_header(box->view);
+ ibox->pre_sync_log_file_seq = hdr->log_file_seq;
+ ibox->pre_sync_log_file_head_offset = hdr->log_file_head_offset;
+}
+
+void mailbox_list_index_status_sync_deinit(struct mailbox *box)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ const struct mail_index_header *hdr;
+
+ hdr = mail_index_get_header(box->view);
+ if (!ilist->opened &&
+ ibox->pre_sync_log_file_head_offset == hdr->log_file_head_offset &&
+ ibox->pre_sync_log_file_seq == hdr->log_file_seq) {
+ /* List index isn't open and sync changed nothing.
+ Don't bother opening the list index. */
+ return;
+ }
+
+ /* it probably doesn't matter much here if we push/pop the error,
+ but might as well do it. */
+ mail_storage_last_error_push(mailbox_get_storage(box));
+ (void)index_list_update_mailbox(box);
+ mail_storage_last_error_pop(mailbox_get_storage(box));
+}
+
+static int
+index_list_transaction_commit(struct mailbox_transaction_context *t,
+ struct mail_transaction_commit_changes *changes_r)
+{
+ struct mailbox *box = t->box;
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if (ibox->module_ctx.super.transaction_commit(t, changes_r) < 0)
+ return -1;
+ t = NULL;
+
+ /* check all changes here, because e.g. vsize update is _OTHERS */
+ if (changes_r->changes_mask == 0)
+ return 0;
+
+ /* this transaction commit may have been done in error handling path
+ and the caller still wants to access the current error. make sure
+ that whatever we do here won't change the error. */
+ mail_storage_last_error_push(mailbox_get_storage(box));
+ (void)index_list_update_mailbox(box);
+ mail_storage_last_error_pop(mailbox_get_storage(box));
+ return 0;
+}
+
+void mailbox_list_index_status_set_info_flags(struct mailbox *box, uint32_t uid,
+ enum mailbox_info_flags *flags)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(box->list);
+ struct mail_index_view *view;
+ struct mailbox_status status;
+ const char *reason;
+ uint32_t seq;
+ int ret;
+
+ view = mail_index_view_open(ilist->index);
+ if (!mail_index_lookup_seq(view, uid, &seq)) {
+ /* our in-memory tree is out of sync */
+ ret = 1;
+ } else {
+ ret = box->v.list_index_has_changed == NULL ? 0 :
+ box->v.list_index_has_changed(box, view, seq, TRUE,
+ &reason);
+ }
+
+ if (ret != 0) {
+ /* error / not up to date. don't waste time with it. */
+ mail_index_view_close(&view);
+ return;
+ }
+
+ status.recent = 0;
+ (void)mailbox_list_index_status(box->list, view, seq, STATUS_RECENT,
+ &status, NULL, NULL, &reason);
+ mail_index_view_close(&view);
+
+ if (status.recent != 0)
+ *flags |= MAILBOX_MARKED;
+ else
+ *flags |= MAILBOX_UNMARKED;
+}
+
+void mailbox_list_index_status_init_mailbox(struct mailbox_vfuncs *v)
+{
+ v->exists = index_list_exists;
+ v->get_status = index_list_get_status;
+ v->get_metadata = index_list_get_metadata;
+ v->transaction_commit = index_list_transaction_commit;
+}
+
+void mailbox_list_index_status_init_finish(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ ilist->msgs_ext_id = mail_index_ext_register(ilist->index, "msgs", 0,
+ sizeof(struct mailbox_list_index_msgs_record),
+ sizeof(uint32_t));
+
+ ilist->hmodseq_ext_id =
+ mail_index_ext_register(ilist->index, "hmodseq", 0,
+ sizeof(uint64_t), sizeof(uint64_t));
+ ilist->vsize_ext_id =
+ mail_index_ext_register(ilist->index, "vsize", 0,
+ sizeof(struct mailbox_index_vsize), sizeof(uint64_t));
+ ilist->first_saved_ext_id =
+ mail_index_ext_register(ilist->index, "1saved", 0,
+ sizeof(struct mailbox_index_first_saved), sizeof(uint32_t));
+}
diff --git a/src/lib-storage/list/mailbox-list-index-storage.h b/src/lib-storage/list/mailbox-list-index-storage.h
new file mode 100644
index 0000000..6aeec8c
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-storage.h
@@ -0,0 +1,21 @@
+#ifndef MAILBOX_LIST_INDEX_STORAGE_H
+#define MAILBOX_LIST_INDEX_STORAGE_H
+
+#include "mail-storage-private.h"
+
+#define INDEX_LIST_STORAGE_CONTEXT(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, index_list_storage_module)
+
+struct index_list_mailbox {
+ union mailbox_module_context module_ctx;
+
+ uint32_t pre_sync_log_file_seq;
+ uoff_t pre_sync_log_file_head_offset;
+
+ bool have_backend:1;
+};
+
+extern MODULE_CONTEXT_DEFINE(index_list_storage_module,
+ &mail_storage_module_register);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-index-sync.c b/src/lib-storage/list/mailbox-list-index-sync.c
new file mode 100644
index 0000000..2994bb8
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-sync.c
@@ -0,0 +1,521 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "str.h"
+#include "sort.h"
+#include "mail-index.h"
+#include "mail-storage.h"
+#include "mailbox-list-index-sync.h"
+
+static void
+node_lookup_guid(struct mailbox_list_index_sync_context *ctx,
+ const struct mailbox_list_index_node *node, guid_128_t guid_r)
+{
+ struct mailbox *box;
+ struct mailbox_metadata metadata;
+ const char *vname;
+ string_t *str = t_str_new(128);
+ char ns_sep = mailbox_list_get_hierarchy_sep(ctx->list);
+
+ mailbox_list_index_node_get_path(node, ns_sep, str);
+
+ vname = mailbox_list_get_vname(ctx->list, str_c(str));
+ box = mailbox_alloc(ctx->list, vname, 0);
+ if (mailbox_get_metadata(box, MAILBOX_METADATA_GUID, &metadata) == 0)
+ memcpy(guid_r, metadata.guid, GUID_128_SIZE);
+ mailbox_free(&box);
+}
+
+static void
+node_add_to_index(struct mailbox_list_index_sync_context *ctx,
+ const struct mailbox_list_index_node *node, uint32_t *seq_r)
+{
+ struct mailbox_list_index_record irec;
+ uint32_t seq;
+
+ i_zero(&irec);
+ irec.name_id = node->name_id;
+ if (node->parent != NULL)
+ irec.parent_uid = node->parent->uid;
+
+ /* get mailbox GUID if possible. we need to do this early in here to
+ make mailbox rename detection work in NOTIFY */
+ if (ctx->syncing_list) T_BEGIN {
+ node_lookup_guid(ctx, node, irec.guid);
+ } T_END;
+
+ mail_index_append(ctx->trans, node->uid, &seq);
+ mail_index_update_flags(ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_NONEXISTENT);
+ mail_index_update_ext(ctx->trans, seq, ctx->ilist->ext_id, &irec, NULL);
+
+ *seq_r = seq;
+}
+
+static struct mailbox_list_index_node *
+mailbox_list_index_node_add(struct mailbox_list_index_sync_context *ctx,
+ struct mailbox_list_index_node *parent,
+ const char *name, uint32_t *seq_r)
+{
+ struct mailbox_list_index_node *node;
+ char *dup_name;
+ mailbox_list_name_unescape(&name, ctx->list->set.storage_name_escape_char);
+
+ node = p_new(ctx->ilist->mailbox_pool,
+ struct mailbox_list_index_node, 1);
+ node->flags = MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS;
+ /* we don't bother doing name deduplication here, even though it would
+ be possible. */
+ node->raw_name = dup_name = p_strdup(ctx->ilist->mailbox_pool, name);
+ node->name_id = ++ctx->ilist->highest_name_id;
+ node->uid = ctx->next_uid++;
+
+ if (parent != NULL) {
+ node->parent = parent;
+ node->next = parent->children;
+ parent->children = node;
+ } else {
+ node->next = ctx->ilist->mailbox_tree;
+ ctx->ilist->mailbox_tree = node;
+ }
+ hash_table_insert(ctx->ilist->mailbox_hash,
+ POINTER_CAST(node->uid), node);
+ hash_table_insert(ctx->ilist->mailbox_names,
+ POINTER_CAST(node->name_id), dup_name);
+
+ node_add_to_index(ctx, node, seq_r);
+ return node;
+}
+
+uint32_t mailbox_list_index_sync_name(struct mailbox_list_index_sync_context *ctx,
+ const char *name,
+ struct mailbox_list_index_node **node_r,
+ bool *created_r)
+{
+ const char *const *path, *empty_path[] = { "", NULL };
+ struct mailbox_list_index_node *node, *parent;
+ unsigned int i;
+ uint32_t seq = 0;
+
+ path = *name == '\0' ? empty_path :
+ t_strsplit(name, ctx->sep);
+ /* find the last node that exists in the path */
+ node = ctx->ilist->mailbox_tree; parent = NULL;
+ for (i = 0; path[i] != NULL; i++) {
+ node = mailbox_list_index_node_find_sibling(ctx->list, node, path[i]);
+ if (node == NULL)
+ break;
+
+ node->flags |= MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS;
+ parent = node;
+ node = node->children;
+ }
+
+ node = parent;
+ if (path[i] == NULL) {
+ /* the entire path exists */
+ i_assert(node != NULL);
+ if (!mail_index_lookup_seq(ctx->view, node->uid, &seq))
+ i_panic("mailbox list index: lost uid=%u", node->uid);
+ *created_r = FALSE;
+ } else {
+ /* create missing parts of the path */
+ for (; path[i] != NULL; i++) {
+ node = mailbox_list_index_node_add(ctx, node, path[i],
+ &seq);
+ }
+ *created_r = TRUE;
+ }
+
+ *node_r = node;
+ return seq;
+}
+
+static void
+get_existing_name_ids(ARRAY_TYPE(uint32_t) *ids,
+ const struct mailbox_list_index_node *node)
+{
+ for (; node != NULL; node = node->next) {
+ if (node->children != NULL)
+ get_existing_name_ids(ids, node->children);
+ array_push_back(ids, &node->name_id);
+ }
+}
+
+static void
+mailbox_list_index_sync_names(struct mailbox_list_index_sync_context *ctx)
+{
+ struct mailbox_list_index *ilist = ctx->ilist;
+ ARRAY_TYPE(uint32_t) existing_name_ids;
+ buffer_t *hdr_buf;
+ const void *ext_data;
+ size_t ext_size;
+ const char *name;
+ uint32_t id, prev_id = 0;
+
+ /* get all existing name IDs sorted */
+ i_array_init(&existing_name_ids, 64);
+ get_existing_name_ids(&existing_name_ids, ilist->mailbox_tree);
+ array_sort(&existing_name_ids, uint32_cmp);
+
+ hdr_buf = buffer_create_dynamic(default_pool, 1024);
+ buffer_append_zero(hdr_buf, sizeof(struct mailbox_list_index_header));
+
+ /* add existing names to header (with deduplication) */
+ array_foreach_elem(&existing_name_ids, id) {
+ if (id != prev_id) {
+ buffer_append(hdr_buf, &id, sizeof(id));
+ name = hash_table_lookup(ilist->mailbox_names,
+ POINTER_CAST(id));
+ i_assert(name != NULL);
+ buffer_append(hdr_buf, name, strlen(name) + 1);
+ prev_id = id;
+ }
+ }
+ buffer_append_zero(hdr_buf, sizeof(id));
+
+ /* make sure header size is ok in index and update it */
+ mail_index_get_header_ext(ctx->view, ilist->ext_id,
+ &ext_data, &ext_size);
+ if (nearest_power(ext_size) != nearest_power(hdr_buf->used)) {
+ mail_index_ext_resize(ctx->trans, ilist->ext_id,
+ nearest_power(hdr_buf->used),
+ sizeof(struct mailbox_list_index_record),
+ sizeof(uint32_t));
+ }
+ mail_index_update_header_ext(ctx->trans, ilist->ext_id,
+ 0, hdr_buf->data, hdr_buf->used);
+ buffer_free(&hdr_buf);
+ array_free(&existing_name_ids);
+}
+
+static void
+mailbox_list_index_node_clear_exists(struct mailbox_list_index_node *node)
+{
+ while (node != NULL) {
+ if (node->children != NULL)
+ mailbox_list_index_node_clear_exists(node->children);
+
+ node->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS);
+ node = node->next;
+ }
+}
+
+static void
+sync_expunge_nonexistent(struct mailbox_list_index_sync_context *sync_ctx,
+ struct mailbox_list_index_node *node)
+{
+ uint32_t seq;
+
+ while (node != NULL) {
+ if (node->children != NULL)
+ sync_expunge_nonexistent(sync_ctx, node->children);
+
+ if ((node->flags & MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS) == 0) {
+ if (mail_index_lookup_seq(sync_ctx->view, node->uid,
+ &seq))
+ mail_index_expunge(sync_ctx->trans, seq);
+ mailbox_list_index_node_unlink(sync_ctx->ilist, node);
+ }
+ node = node->next;
+ }
+}
+
+int mailbox_list_index_sync_begin(struct mailbox_list *list,
+ struct mailbox_list_index_sync_context **sync_ctx_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_sync_context *sync_ctx;
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+ const struct mail_index_header *hdr;
+ bool fix_attempted = FALSE;
+
+ i_assert(!ilist->syncing);
+
+retry:
+ if (mailbox_list_index_index_open(list) < 0)
+ return -1;
+
+ if (mail_index_sync_begin(ilist->index, &index_sync_ctx, &view, &trans,
+ MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES) < 0) {
+ mailbox_list_index_set_index_error(list);
+ return -1;
+ }
+ mailbox_list_index_reset(ilist);
+
+ /* re-parse mailbox list now that it's refreshed and locked */
+ if (mailbox_list_index_parse(list, view, TRUE) < 0) {
+ mail_index_sync_rollback(&index_sync_ctx);
+ return -1;
+ }
+ if (ilist->call_corruption_callback && !fix_attempted) {
+ /* unlock and resync the index */
+ mail_index_sync_rollback(&index_sync_ctx);
+ if (mailbox_list_index_handle_corruption(list) < 0)
+ return -1;
+ fix_attempted = TRUE;
+ goto retry;
+ }
+
+ sync_ctx = i_new(struct mailbox_list_index_sync_context, 1);
+ sync_ctx->list = list;
+ sync_ctx->ilist = ilist;
+ sync_ctx->sep[0] = mailbox_list_get_hierarchy_sep(list);
+ sync_ctx->orig_highest_name_id = ilist->highest_name_id;
+ sync_ctx->index_sync_ctx = index_sync_ctx;
+ sync_ctx->trans = trans;
+
+ hdr = mail_index_get_header(view);
+ sync_ctx->next_uid = hdr->next_uid;
+
+ if (hdr->uid_validity == 0) {
+ /* first time indexing, set uidvalidity */
+ uint32_t uid_validity = ioloop_time;
+
+ mail_index_update_header(trans,
+ offsetof(struct mail_index_header, uid_validity),
+ &uid_validity, sizeof(uid_validity), TRUE);
+ }
+ sync_ctx->view = mail_index_transaction_open_updated_view(trans);
+ ilist->sync_ctx = sync_ctx;
+ ilist->syncing = TRUE;
+
+ *sync_ctx_r = sync_ctx;
+ return 0;
+}
+
+static int
+mailbox_list_index_sync_list(struct mailbox_list_index_sync_context *sync_ctx)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ enum mailbox_list_index_flags flags;
+ const char *patterns[2];
+ struct mailbox_list_index_node *node;
+ uint32_t seq;
+ bool created;
+
+ /* clear EXISTS-flags, so after sync we know what can be expunged */
+ mailbox_list_index_node_clear_exists(sync_ctx->ilist->mailbox_tree);
+
+ /* don't include autocreated mailboxes in index until they're
+ actually created. this index may be used by multiple users, so
+ we also want to ignore ACLs here. */
+ patterns[0] = "*"; patterns[1] = NULL;
+ iter = sync_ctx->ilist->module_ctx.super.
+ iter_init(sync_ctx->list, patterns,
+ MAILBOX_LIST_ITER_RAW_LIST |
+ MAILBOX_LIST_ITER_NO_AUTO_BOXES);
+
+ sync_ctx->syncing_list = TRUE;
+ while ((info = sync_ctx->ilist->module_ctx.super.iter_next(iter)) != NULL) T_BEGIN {
+ flags = 0;
+ if ((info->flags & MAILBOX_NONEXISTENT) != 0)
+ flags |= MAILBOX_LIST_INDEX_FLAG_NONEXISTENT;
+ if ((info->flags & MAILBOX_NOSELECT) != 0)
+ flags |= MAILBOX_LIST_INDEX_FLAG_NOSELECT;
+ if ((info->flags & MAILBOX_NOINFERIORS) != 0)
+ flags |= MAILBOX_LIST_INDEX_FLAG_NOINFERIORS;
+
+ const char *name = mailbox_list_get_storage_name(info->ns->list,
+ info->vname);
+ if (strcmp(name, "INBOX") == 0 &&
+ strcmp(info->vname, "INBOX") != 0 &&
+ (info->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* prefix/INBOX - don't override INBOX with this */
+ } else {
+ seq = mailbox_list_index_sync_name(sync_ctx, name,
+ &node, &created);
+ node->flags = flags | MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS;
+ mail_index_update_flags(sync_ctx->trans, seq,
+ MODIFY_REPLACE,
+ (enum mail_flags)flags);
+ }
+ } T_END;
+ sync_ctx->syncing_list = FALSE;
+
+ if (sync_ctx->ilist->module_ctx.super.iter_deinit(iter) < 0)
+ return -1;
+
+ /* successfully listed everything, expunge any unseen mailboxes */
+ sync_expunge_nonexistent(sync_ctx, sync_ctx->ilist->mailbox_tree);
+ return 0;
+}
+
+static void
+mailbox_list_index_sync_update_hdr(struct mailbox_list_index_sync_context *sync_ctx)
+{
+ if (sync_ctx->orig_highest_name_id != sync_ctx->ilist->highest_name_id ||
+ sync_ctx->ilist->corrupted_names_or_parents) {
+ /* new names added. this implicitly resets refresh flag */
+ T_BEGIN {
+ mailbox_list_index_sync_names(sync_ctx);
+ } T_END;
+ sync_ctx->ilist->corrupted_names_or_parents = FALSE;
+ } else if (mailbox_list_index_need_refresh(sync_ctx->ilist,
+ sync_ctx->view)) {
+ /* we're synced, reset refresh flag */
+ struct mailbox_list_index_header new_hdr;
+
+ new_hdr.refresh_flag = 0;
+ mail_index_update_header_ext(sync_ctx->trans, sync_ctx->ilist->ext_id,
+ offsetof(struct mailbox_list_index_header, refresh_flag),
+ &new_hdr.refresh_flag, sizeof(new_hdr.refresh_flag));
+ }
+}
+
+static void
+mailbox_list_index_sync_update_corrupted_node(struct mailbox_list_index_sync_context *sync_ctx,
+ struct mailbox_list_index_node *node)
+{
+ struct mailbox_list_index_record irec;
+ uint32_t seq;
+ const void *data;
+ bool expunged;
+
+ if (!mail_index_lookup_seq(sync_ctx->view, node->uid, &seq))
+ return;
+
+ if (node->corrupted_ext) {
+ mail_index_lookup_ext(sync_ctx->view, seq,
+ sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL);
+
+ memcpy(&irec, data, sizeof(irec));
+ irec.name_id = node->name_id;
+ irec.parent_uid = node->parent == NULL ? 0 : node->parent->uid;
+ mail_index_update_ext(sync_ctx->trans, seq,
+ sync_ctx->ilist->ext_id, &irec, NULL);
+ node->corrupted_ext = FALSE;
+ }
+ if (node->corrupted_flags) {
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+ node->corrupted_flags = FALSE;
+ } else if ((node->flags & MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME) != 0) {
+ /* rely on lib-index to drop unnecessary updates */
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_ADD,
+ (enum mail_flags)MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME);
+ }
+}
+
+static void
+mailbox_list_index_sync_update_corrupted_nodes(struct mailbox_list_index_sync_context *sync_ctx,
+ struct mailbox_list_index_node *node)
+{
+ for (; node != NULL; node = node->next) {
+ mailbox_list_index_sync_update_corrupted_node(sync_ctx, node);
+ mailbox_list_index_sync_update_corrupted_nodes(sync_ctx, node->children);
+ }
+}
+
+static void
+mailbox_list_index_sync_update_corrupted(struct mailbox_list_index_sync_context *sync_ctx)
+{
+ if (!sync_ctx->ilist->corrupted_names_or_parents)
+ return;
+
+ mailbox_list_index_sync_update_corrupted_nodes(sync_ctx,
+ sync_ctx->ilist->mailbox_tree);
+}
+
+int mailbox_list_index_sync_end(struct mailbox_list_index_sync_context **_sync_ctx,
+ bool success)
+{
+ struct mailbox_list_index_sync_context *sync_ctx = *_sync_ctx;
+ int ret;
+
+ *_sync_ctx = NULL;
+
+ if (success) {
+ mailbox_list_index_sync_update_corrupted(sync_ctx);
+ mailbox_list_index_sync_update_hdr(sync_ctx);
+ }
+ mail_index_view_close(&sync_ctx->view);
+
+ if (success) {
+ struct mail_index_sync_rec sync_rec;
+ while (mail_index_sync_next(sync_ctx->index_sync_ctx, &sync_rec)) ;
+ if ((ret = mail_index_sync_commit(&sync_ctx->index_sync_ctx)) < 0)
+ mailbox_list_index_set_index_error(sync_ctx->list);
+ } else {
+ mail_index_sync_rollback(&sync_ctx->index_sync_ctx);
+ ret = -1;
+ }
+ sync_ctx->ilist->syncing = FALSE;
+ sync_ctx->ilist->sync_ctx = NULL;
+ i_free(sync_ctx);
+ return ret;
+}
+
+int mailbox_list_index_sync(struct mailbox_list *list, bool refresh)
+{
+ struct mailbox_list_index_sync_context *sync_ctx;
+ int ret = 0;
+
+ if (mailbox_list_index_sync_begin(list, &sync_ctx) < 0)
+ return -1;
+
+ if (!sync_ctx->ilist->has_backing_store) {
+ /* no backing store - we have nothing to sync to */
+ } else if (refresh ||
+ sync_ctx->ilist->call_corruption_callback ||
+ sync_ctx->ilist->corrupted_names_or_parents ||
+ sync_ctx->ilist->highest_name_id == 0 ||
+ !sync_ctx->list->mail_set->mailbox_list_index_very_dirty_syncs) {
+ /* sync the index against the backing store */
+ ret = mailbox_list_index_sync_list(sync_ctx);
+ }
+ return mailbox_list_index_sync_end(&sync_ctx, ret == 0);
+}
+
+int mailbox_list_index_sync_delete(struct mailbox_list_index_sync_context *sync_ctx,
+ const char *name, bool delete_selectable)
+{
+ struct mailbox_list_index_record rec;
+ struct mailbox_list_index_node *node;
+ const void *data;
+ bool expunged;
+ uint32_t seq;
+
+ node = mailbox_list_index_lookup(sync_ctx->list, name);
+ if (node == NULL) {
+ mailbox_list_set_error(sync_ctx->list, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
+ return -1;
+ }
+ if (!mail_index_lookup_seq(sync_ctx->view, node->uid, &seq))
+ i_panic("mailbox list index: lost uid=%u", node->uid);
+ if (delete_selectable) {
+ /* make it at least non-selectable */
+ node->flags = MAILBOX_LIST_INDEX_FLAG_NOSELECT;
+ mail_index_update_flags(sync_ctx->trans, seq, MODIFY_REPLACE,
+ (enum mail_flags)node->flags);
+
+ mail_index_lookup_ext(sync_ctx->view, seq,
+ sync_ctx->ilist->ext_id,
+ &data, &expunged);
+ i_assert(data != NULL && !expunged);
+ memcpy(&rec, data, sizeof(rec));
+ rec.uid_validity = 0;
+ i_zero(&rec.guid);
+ mail_index_update_ext(sync_ctx->trans, seq,
+ sync_ctx->ilist->ext_id, &rec, NULL);
+ }
+ if (node->children != NULL) {
+ /* can't delete this directory before its children,
+ but we may have made it non-selectable already */
+ return 0;
+ }
+
+ /* we can remove the entire node */
+ mail_index_expunge(sync_ctx->trans, seq);
+ mailbox_list_index_node_unlink(sync_ctx->ilist, node);
+ return 1;
+}
diff --git a/src/lib-storage/list/mailbox-list-index-sync.h b/src/lib-storage/list/mailbox-list-index-sync.h
new file mode 100644
index 0000000..db20244
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index-sync.h
@@ -0,0 +1,35 @@
+#ifndef MAILBOX_LIST_INDEX_SYNC_H
+#define MAILBOX_LIST_INDEX_SYNC_H
+
+#include "mailbox-list-index.h"
+
+struct mailbox_list_index_sync_context {
+ struct mailbox_list *list;
+ struct mailbox_list_index *ilist;
+ char sep[2];
+ uint32_t next_uid;
+ uint32_t orig_highest_name_id;
+
+ struct mail_index_sync_ctx *index_sync_ctx;
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+
+ bool syncing_list:1;
+};
+
+int mailbox_list_index_sync_begin(struct mailbox_list *list,
+ struct mailbox_list_index_sync_context **sync_ctx_r);
+int mailbox_list_index_sync_end(struct mailbox_list_index_sync_context **_sync_ctx,
+ bool success);
+int mailbox_list_index_sync(struct mailbox_list *list, bool refresh);
+
+/* Add name to index, return seq in index. */
+uint32_t mailbox_list_index_sync_name(struct mailbox_list_index_sync_context *ctx,
+ const char *name,
+ struct mailbox_list_index_node **node_r,
+ bool *created_r);
+
+int mailbox_list_index_sync_delete(struct mailbox_list_index_sync_context *sync_ctx,
+ const char *name, bool delete_selectable);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-index.c b/src/lib-storage/list/mailbox-list-index.c
new file mode 100644
index 0000000..9c8a718
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index.c
@@ -0,0 +1,1234 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "str.h"
+#include "mail-index-view-private.h"
+#include "mail-storage-hooks.h"
+#include "mail-storage-private.h"
+#include "mailbox-list-index-storage.h"
+#include "mailbox-list-index-sync.h"
+
+#define MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS 1000
+
+/* dovecot.list.index.log doesn't have to be kept for that long. */
+#define MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_SIZE (8*1024)
+#define MAILBOX_LIST_INDEX_LOG_ROTATE_MAX_SIZE (64*1024)
+#define MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_AGE_SECS (5*60)
+#define MAILBOX_LIST_INDEX_LOG2_MAX_AGE_SECS (10*60)
+
+static void mailbox_list_index_init_finish(struct mailbox_list *list);
+
+struct mailbox_list_index_module mailbox_list_index_module =
+ MODULE_CONTEXT_INIT(&mailbox_list_module_register);
+
+void mailbox_list_index_set_index_error(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ mailbox_list_set_internal_error(list);
+ mail_index_reset_error(ilist->index);
+}
+
+static void mailbox_list_index_init_pool(struct mailbox_list_index *ilist)
+{
+ ilist->mailbox_pool = pool_alloconly_create("mailbox list index", 4096);
+ hash_table_create_direct(&ilist->mailbox_names, ilist->mailbox_pool, 0);
+ hash_table_create_direct(&ilist->mailbox_hash, ilist->mailbox_pool, 0);
+}
+
+void mailbox_list_index_reset(struct mailbox_list_index *ilist)
+{
+ hash_table_destroy(&ilist->mailbox_names);
+ hash_table_destroy(&ilist->mailbox_hash);
+ pool_unref(&ilist->mailbox_pool);
+
+ ilist->mailbox_tree = NULL;
+ ilist->highest_name_id = 0;
+ ilist->sync_log_file_seq = 0;
+ ilist->sync_log_file_offset = 0;
+
+ mailbox_list_index_init_pool(ilist);
+}
+
+int mailbox_list_index_index_open(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ const struct mail_storage_settings *set = list->mail_set;
+ enum mail_index_open_flags index_flags;
+ unsigned int lock_timeout;
+
+ if (ilist->opened)
+ return 0;
+
+ if (mailbox_list_mkdir_missing_list_index_root(list) < 0)
+ return -1;
+
+ i_assert(ilist->index != NULL);
+
+ index_flags = mail_storage_settings_to_index_flags(set);
+ if (strcmp(list->name, MAILBOX_LIST_NAME_INDEX) == 0) {
+ /* LAYOUT=index. this is the only location for the mailbox
+ data, so we must never move it into memory. */
+ index_flags |= MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY;
+ }
+ lock_timeout = set->mail_max_lock_timeout == 0 ? UINT_MAX :
+ set->mail_max_lock_timeout;
+
+ if (!mail_index_use_existing_permissions(ilist->index)) {
+ struct mailbox_permissions perm;
+
+ mailbox_list_get_root_permissions(list, &perm);
+ mail_index_set_permissions(ilist->index, perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin);
+ }
+ const struct mail_index_optimization_settings optimize_set = {
+ .log = {
+ .min_size = MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_SIZE,
+ .max_size = MAILBOX_LIST_INDEX_LOG_ROTATE_MAX_SIZE,
+ .min_age_secs = MAILBOX_LIST_INDEX_LOG_ROTATE_MIN_AGE_SECS,
+ .log2_max_age_secs = MAILBOX_LIST_INDEX_LOG2_MAX_AGE_SECS,
+ },
+ };
+ mail_index_set_optimization_settings(ilist->index, &optimize_set);
+
+ mail_index_set_fsync_mode(ilist->index, set->parsed_fsync_mode, 0);
+ mail_index_set_lock_method(ilist->index, set->parsed_lock_method,
+ lock_timeout);
+ if (mail_index_open_or_create(ilist->index, index_flags) < 0) {
+ if (mail_index_move_to_memory(ilist->index) < 0) {
+ /* try opening once more. it should be created
+ directly into memory now, except if it fails with
+ LAYOUT=index backend. */
+ if (mail_index_open_or_create(ilist->index,
+ index_flags) < 0) {
+ mailbox_list_set_internal_error(list);
+ return -1;
+ }
+ }
+ }
+ ilist->opened = TRUE;
+ return 0;
+}
+
+struct mailbox_list_index_node *
+mailbox_list_index_node_find_sibling(const struct mailbox_list *list,
+ struct mailbox_list_index_node *node,
+ const char *name)
+{
+ mailbox_list_name_unescape(&name, list->set.storage_name_escape_char);
+
+ while (node != NULL) {
+ if (strcmp(node->raw_name, name) == 0)
+ return node;
+ node = node->next;
+ }
+ return NULL;
+}
+
+static struct mailbox_list_index_node *
+mailbox_list_index_lookup_real(struct mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_node *node = ilist->mailbox_tree;
+ const char *const *path;
+ unsigned int i;
+ char sep[2];
+
+ if (*name == '\0')
+ return mailbox_list_index_node_find_sibling(list, node, "");
+
+ sep[0] = mailbox_list_get_hierarchy_sep(list); sep[1] = '\0';
+ path = t_strsplit(name, sep);
+ for (i = 0;; i++) {
+ node = mailbox_list_index_node_find_sibling(list, node, path[i]);
+ if (node == NULL || path[i+1] == NULL)
+ break;
+ node = node->children;
+ }
+ return node;
+}
+
+struct mailbox_list_index_node *
+mailbox_list_index_lookup(struct mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index_node *node;
+
+ T_BEGIN {
+ node = mailbox_list_index_lookup_real(list, name);
+ } T_END;
+ return node;
+}
+
+struct mailbox_list_index_node *
+mailbox_list_index_lookup_uid(struct mailbox_list_index *ilist, uint32_t uid)
+{
+ return hash_table_lookup(ilist->mailbox_hash, POINTER_CAST(uid));
+}
+
+void mailbox_list_index_node_get_path(const struct mailbox_list_index_node *node,
+ char sep, string_t *str)
+{
+ if (node->parent != NULL) {
+ mailbox_list_index_node_get_path(node->parent, sep, str);
+ str_append_c(str, sep);
+ }
+ str_append(str, node->raw_name);
+}
+
+void mailbox_list_index_node_unlink(struct mailbox_list_index *ilist,
+ struct mailbox_list_index_node *node)
+{
+ struct mailbox_list_index_node **prev;
+
+ prev = node->parent == NULL ?
+ &ilist->mailbox_tree : &node->parent->children;
+
+ while (*prev != node)
+ prev = &(*prev)->next;
+ *prev = node->next;
+}
+
+static int mailbox_list_index_parse_header(struct mailbox_list_index *ilist,
+ struct mail_index_view *view)
+{
+ const void *data, *name_start, *p;
+ size_t i, len, size;
+ uint32_t id, prev_id = 0;
+ string_t *str;
+ char *name;
+ int ret = 0;
+
+ mail_index_map_get_header_ext(view, view->map, ilist->ext_id, &data, &size);
+ if (size == 0)
+ return 0;
+
+ str = t_str_new(128);
+ for (i = sizeof(struct mailbox_list_index_header); i < size; ) {
+ /* get id */
+ if (i + sizeof(id) > size)
+ return -1;
+ memcpy(&id, CONST_PTR_OFFSET(data, i), sizeof(id));
+ i += sizeof(id);
+
+ if (id <= prev_id) {
+ /* allow extra space in the end as long as last id=0 */
+ return id == 0 ? 0 : -1;
+ }
+ prev_id = id;
+
+ /* get name */
+ p = memchr(CONST_PTR_OFFSET(data, i), '\0', size-i);
+ if (p == NULL)
+ return -1;
+ name_start = CONST_PTR_OFFSET(data, i);
+ len = (const char *)p - (const char *)name_start;
+
+ if (uni_utf8_get_valid_data(name_start, len, str)) {
+ name = p_strndup(ilist->mailbox_pool, name_start, len);
+ } else {
+ /* corrupted index. fix the name. */
+ name = p_strdup(ilist->mailbox_pool, str_c(str));
+ str_truncate(str, 0);
+ ret = -1;
+ }
+
+ i += len + 1;
+
+ /* add id => name to hash table */
+ hash_table_insert(ilist->mailbox_names, POINTER_CAST(id), name);
+ ilist->highest_name_id = id;
+ }
+ i_assert(i == size);
+ return ret;
+}
+
+static void
+mailbox_list_index_generate_name(struct mailbox_list_index *ilist,
+ struct mailbox_list_index_node *node,
+ const char *prefix)
+{
+ guid_128_t guid;
+ char *name;
+
+ i_assert(node->name_id != 0);
+
+ guid_128_generate(guid);
+ name = p_strdup_printf(ilist->mailbox_pool, "%s%s", prefix,
+ guid_128_to_string(guid));
+ node->raw_name = name;
+ node->flags |= MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME;
+
+ hash_table_insert(ilist->mailbox_names,
+ POINTER_CAST(node->name_id), name);
+ if (ilist->highest_name_id < node->name_id)
+ ilist->highest_name_id = node->name_id;
+}
+
+static int mailbox_list_index_node_cmp(const struct mailbox_list_index_node *n1,
+ const struct mailbox_list_index_node *n2)
+{
+ return n1->parent == n2->parent &&
+ strcmp(n1->raw_name, n2->raw_name) == 0 ? 0 : -1;
+}
+
+static unsigned int
+mailbox_list_index_node_hash(const struct mailbox_list_index_node *node)
+{
+ return str_hash(node->raw_name) ^
+ POINTER_CAST_TO(node->parent, unsigned int);
+}
+
+static bool node_has_parent(const struct mailbox_list_index_node *parent,
+ const struct mailbox_list_index_node *node)
+{
+ const struct mailbox_list_index_node *n;
+
+ for (n = parent; n != NULL; n = n->parent) {
+ if (n == node)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int mailbox_list_index_parse_records(struct mailbox_list_index *ilist,
+ struct mail_index_view *view,
+ const char **error_r)
+{
+ struct mailbox_list_index_node *node, *parent;
+ HASH_TABLE(struct mailbox_list_index_node *,
+ struct mailbox_list_index_node *) duplicate_hash;
+ const struct mail_index_record *rec;
+ const struct mailbox_list_index_record *irec;
+ const void *data;
+ bool expunged;
+ uint32_t seq, uid, count;
+ HASH_TABLE(uint8_t *, struct mailbox_list_index_node *) duplicate_guid;
+
+ *error_r = NULL;
+
+ pool_t dup_pool =
+ pool_alloconly_create(MEMPOOL_GROWING"duplicate pool", 2048);
+ hash_table_create(&duplicate_hash, dup_pool, 0,
+ mailbox_list_index_node_hash,
+ mailbox_list_index_node_cmp);
+ count = mail_index_view_get_messages_count(view);
+ if (!ilist->has_backing_store)
+ hash_table_create(&duplicate_guid, dup_pool, 0, guid_128_hash,
+ guid_128_cmp);
+
+ for (seq = 1; seq <= count; seq++) {
+ node = p_new(ilist->mailbox_pool,
+ struct mailbox_list_index_node, 1);
+ rec = mail_index_lookup(view, seq);
+ node->uid = rec->uid;
+ node->flags = rec->flags;
+
+ mail_index_lookup_ext(view, seq, ilist->ext_id,
+ &data, &expunged);
+ if (data == NULL) {
+ *error_r = "Missing list extension data";
+ /* list index is missing, no point trying
+ to do second scan either */
+ count = 0;
+ break;
+ }
+ irec = data;
+
+ node->name_id = irec->name_id;
+ if (node->name_id == 0) {
+ /* invalid name_id - assign a new one */
+ node->name_id = ++ilist->highest_name_id;
+ node->corrupted_ext = TRUE;
+ }
+ node->raw_name = hash_table_lookup(ilist->mailbox_names,
+ POINTER_CAST(irec->name_id));
+ if (node->raw_name == NULL) {
+ *error_r = t_strdup_printf(
+ "name_id=%u not in index header", irec->name_id);
+ if (ilist->has_backing_store)
+ break;
+ /* generate a new name and use it */
+ mailbox_list_index_generate_name(ilist, node, "unknown-");
+ }
+
+ if (!ilist->has_backing_store && guid_128_is_empty(irec->guid) &&
+ (rec->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0) {
+ /* no backing store and mailbox has no GUID.
+ it can't be selectable, but the flag is missing. */
+ node->flags |= MAILBOX_LIST_INDEX_FLAG_NOSELECT;
+ *error_r = t_strdup_printf(
+ "mailbox '%s' (uid=%u) is missing GUID - "
+ "marking it non-selectable", node->raw_name, node->uid);
+ node->corrupted_flags = TRUE;
+ }
+ if (!ilist->has_backing_store && !guid_128_is_empty(irec->guid) &&
+ (rec->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) != 0) {
+ node->flags &= ENUM_NEGATE(MAILBOX_LIST_INDEX_FLAG_NONEXISTENT | MAILBOX_LIST_INDEX_FLAG_NOSELECT);
+ *error_r = t_strdup_printf(
+ "non-selectable mailbox '%s' (uid=%u) already has GUID - "
+ "marking it selectable", node->raw_name, node->uid);
+ node->corrupted_flags = TRUE;
+ }
+
+ if (!ilist->has_backing_store && !guid_128_is_empty(irec->guid)) {
+ struct mailbox_list_index_node *dup_node;
+ uint8_t *guid_p = p_memdup(dup_pool, irec->guid,
+ sizeof(guid_128_t));
+ if ((dup_node = hash_table_lookup(duplicate_guid, guid_p)) != NULL) {
+ *error_r = t_strdup_printf(
+ "duplicate GUID %s for mailbox '%s' and '%s'",
+ guid_128_to_string(guid_p),
+ node->raw_name,
+ dup_node->raw_name);
+ node->corrupted_ext = TRUE;
+ ilist->corrupted_names_or_parents = TRUE;
+ ilist->call_corruption_callback = TRUE;
+ } else {
+ hash_table_insert(duplicate_guid, guid_p, node);
+ }
+ }
+
+ hash_table_insert(ilist->mailbox_hash,
+ POINTER_CAST(node->uid), node);
+ }
+
+ /* do a second scan to create the actual mailbox tree hierarchy.
+ this is needed because the parent_uid may be smaller or higher than
+ the current node's uid */
+ if (*error_r != NULL && ilist->has_backing_store)
+ count = 0;
+ for (seq = 1; seq <= count; seq++) {
+ mail_index_lookup_uid(view, seq, &uid);
+ mail_index_lookup_ext(view, seq, ilist->ext_id,
+ &data, &expunged);
+ irec = data;
+
+ node = mailbox_list_index_lookup_uid(ilist, uid);
+ i_assert(node != NULL);
+
+ if (irec->parent_uid != 0) {
+ /* node should have a parent */
+ parent = mailbox_list_index_lookup_uid(ilist,
+ irec->parent_uid);
+ if (parent == NULL) {
+ *error_r = t_strdup_printf(
+ "parent_uid=%u points to nonexistent record",
+ irec->parent_uid);
+ if (ilist->has_backing_store)
+ break;
+ /* just place it under the root */
+ node->corrupted_ext = TRUE;
+ } else if (node_has_parent(parent, node)) {
+ *error_r = t_strdup_printf(
+ "parent_uid=%u loops to node itself (%s)",
+ uid, node->raw_name);
+ if (ilist->has_backing_store)
+ break;
+ /* just place it under the root */
+ node->corrupted_ext = TRUE;
+ } else {
+ node->parent = parent;
+ node->next = parent->children;
+ parent->children = node;
+ }
+ } else if (strcasecmp(node->raw_name, "INBOX") == 0) {
+ ilist->rebuild_on_missing_inbox = FALSE;
+ }
+ if (hash_table_lookup(duplicate_hash, node) == NULL)
+ hash_table_insert(duplicate_hash, node, node);
+ else {
+ const char *old_name = node->raw_name;
+
+ if (ilist->has_backing_store) {
+ *error_r = t_strdup_printf(
+ "Duplicate mailbox '%s' in index",
+ node->raw_name);
+ break;
+ }
+
+ /* we have only the mailbox list index and this node
+ may have a different GUID, so rename it. */
+ node->corrupted_ext = TRUE;
+ node->name_id = ++ilist->highest_name_id;
+ mailbox_list_index_generate_name(ilist, node,
+ t_strconcat(node->raw_name, "-duplicate-", NULL));
+ *error_r = t_strdup_printf(
+ "Duplicate mailbox '%s' in index, renaming to %s",
+ old_name, node->raw_name);
+ }
+ if (node->parent == NULL) {
+ node->next = ilist->mailbox_tree;
+ ilist->mailbox_tree = node;
+ }
+ }
+ hash_table_destroy(&duplicate_hash);
+ if (!ilist->has_backing_store)
+ hash_table_destroy(&duplicate_guid);
+ pool_unref(&dup_pool);
+ return *error_r == NULL ? 0 : -1;
+}
+
+int mailbox_list_index_parse(struct mailbox_list *list,
+ struct mail_index_view *view, bool force)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ const struct mail_index_header *hdr;
+ const char *error;
+
+ hdr = mail_index_get_header(view);
+ if (!force &&
+ hdr->log_file_seq == ilist->sync_log_file_seq &&
+ hdr->log_file_head_offset == ilist->sync_log_file_offset) {
+ /* nothing changed */
+ return 0;
+ }
+ if ((hdr->flags & MAIL_INDEX_HDR_FLAG_FSCKD) != 0) {
+ mailbox_list_set_critical(list,
+ "Mailbox list index was marked as fsck'd %s", ilist->path);
+ ilist->call_corruption_callback = TRUE;
+ }
+
+ mailbox_list_index_reset(ilist);
+ ilist->sync_log_file_seq = hdr->log_file_seq;
+ ilist->sync_log_file_offset = hdr->log_file_head_offset;
+
+ if (mailbox_list_index_parse_header(ilist, view) < 0) {
+ mailbox_list_set_critical(list,
+ "Corrupted mailbox list index header %s", ilist->path);
+ if (ilist->has_backing_store) {
+ mail_index_mark_corrupted(ilist->index);
+ return -1;
+ }
+ ilist->call_corruption_callback = TRUE;
+ ilist->corrupted_names_or_parents = TRUE;
+ }
+ if (mailbox_list_index_parse_records(ilist, view, &error) < 0) {
+ mailbox_list_set_critical(list,
+ "Corrupted mailbox list index %s: %s",
+ ilist->path, error);
+ if (ilist->has_backing_store) {
+ mail_index_mark_corrupted(ilist->index);
+ return -1;
+ }
+ ilist->call_corruption_callback = TRUE;
+ ilist->corrupted_names_or_parents = TRUE;
+ }
+ return 0;
+}
+
+const unsigned char *
+mailbox_name_hdr_encode(struct mailbox_list *list, const char *storage_name,
+ size_t *name_len_r)
+{
+ const char sep[] = {
+ mailbox_list_get_hierarchy_sep(list),
+ '\0'
+ };
+ const char **name_parts =
+ (const char **)p_strsplit(unsafe_data_stack_pool, storage_name, sep);
+ if (list->set.storage_name_escape_char != '\0') {
+ for (unsigned int i = 0; name_parts[i] != NULL; i++) {
+ mailbox_list_name_unescape(&name_parts[i],
+ list->set.storage_name_escape_char);
+ }
+ }
+
+ string_t *str = t_str_new(64);
+ str_append(str, name_parts[0]);
+ for (unsigned int i = 1; name_parts[i] != NULL; i++) {
+ str_append_c(str, '\0');
+ str_append(str, name_parts[i]);
+ }
+ *name_len_r = str_len(str);
+ return str_data(str);
+}
+
+const char *
+mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
+ const unsigned char *name_hdr,
+ size_t name_hdr_size)
+{
+ const char list_sep = mailbox_list_get_hierarchy_sep(list);
+ const char escape_char = list->set.storage_name_escape_char;
+ string_t *storage_name = t_str_new(name_hdr_size);
+ while (name_hdr_size > 0) {
+ const unsigned char *p = memchr(name_hdr, '\0', name_hdr_size);
+ size_t name_part_len;
+ if (p == NULL) {
+ name_part_len = name_hdr_size;
+ name_hdr_size = 0;
+ } else {
+ name_part_len = p - name_hdr;
+ i_assert(name_hdr_size > name_part_len);
+ name_hdr_size -= name_part_len + 1;
+ }
+
+ if (escape_char == '\0')
+ str_append_data(storage_name, name_hdr, name_part_len);
+ else {
+ const char *name_part =
+ t_strndup(name_hdr, name_part_len);
+ str_append(storage_name,
+ mailbox_list_escape_name_params(name_part,
+ "", '\0', list_sep, escape_char,
+ list->set.maildir_name));
+ }
+
+ if (p != NULL) {
+ name_hdr += name_part_len + 1;
+ str_append_c(storage_name, list_sep);
+ }
+ }
+ return str_c(storage_name);
+}
+
+bool mailbox_list_index_need_refresh(struct mailbox_list_index *ilist,
+ struct mail_index_view *view)
+{
+ const struct mailbox_list_index_header *hdr;
+ const void *data;
+ size_t size;
+
+ if (!ilist->has_backing_store)
+ return FALSE;
+
+ mail_index_get_header_ext(view, ilist->ext_id, &data, &size);
+ hdr = data;
+ return hdr != NULL && hdr->refresh_flag != 0;
+}
+
+int mailbox_list_index_refresh(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ if (ilist->syncing)
+ return 0;
+ if (ilist->last_refresh_timeval.tv_usec == ioloop_timeval.tv_usec &&
+ ilist->last_refresh_timeval.tv_sec == ioloop_timeval.tv_sec) {
+ /* we haven't been to ioloop since last refresh, skip checking
+ it. when we're accessing many mailboxes at once (e.g.
+ opening a virtual mailbox) we don't want to stat/read the
+ index every single time. */
+ return 0;
+ }
+
+ return mailbox_list_index_refresh_force(list);
+}
+
+int mailbox_list_index_refresh_force(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mail_index_view *view;
+ int ret;
+ bool refresh;
+
+ i_assert(!ilist->syncing);
+
+ ilist->last_refresh_timeval = ioloop_timeval;
+ if (mailbox_list_index_index_open(list) < 0)
+ return -1;
+ if (mail_index_refresh(ilist->index) < 0) {
+ mailbox_list_index_set_index_error(list);
+ return -1;
+ }
+
+ view = mail_index_view_open(ilist->index);
+ if ((refresh = mailbox_list_index_need_refresh(ilist, view)) ||
+ ilist->mailbox_tree == NULL) {
+ /* refresh list of mailboxes */
+ ret = mailbox_list_index_sync(list, refresh);
+ } else {
+ ret = mailbox_list_index_parse(list, view, FALSE);
+ }
+ mail_index_view_close(&view);
+
+ if (mailbox_list_index_handle_corruption(list) < 0) {
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mailbox_list_get_last_internal_error(list, &error);
+ mailbox_list_set_error(list, error, t_strdup_printf(
+ "Failed to rebuild mailbox list index: %s", errstr));
+ ret = -1;
+ }
+ return ret;
+}
+
+static void mailbox_list_index_refresh_timeout(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ timeout_remove(&ilist->to_refresh);
+ (void)mailbox_list_index_refresh(list);
+}
+
+void mailbox_list_index_refresh_later(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_header new_hdr;
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+
+ memset(&ilist->last_refresh_timeval, 0,
+ sizeof(ilist->last_refresh_timeval));
+
+ if (!ilist->has_backing_store)
+ return;
+
+ (void)mailbox_list_index_index_open(list);
+
+ view = mail_index_view_open(ilist->index);
+ if (!mailbox_list_index_need_refresh(ilist, view)) {
+ new_hdr.refresh_flag = 1;
+
+ trans = mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ mail_index_update_header_ext(trans, ilist->ext_id,
+ offsetof(struct mailbox_list_index_header, refresh_flag),
+ &new_hdr.refresh_flag, sizeof(new_hdr.refresh_flag));
+ if (mail_index_transaction_commit(&trans) < 0)
+ mail_index_mark_corrupted(ilist->index);
+
+ }
+ mail_index_view_close(&view);
+
+ if (ilist->to_refresh == NULL) {
+ ilist->to_refresh =
+ timeout_add(MAILBOX_LIST_INDEX_REFRESH_DELAY_MSECS,
+ mailbox_list_index_refresh_timeout, list);
+ }
+}
+
+static int
+list_handle_corruption_locked(struct mailbox_list *list,
+ enum mail_storage_list_index_rebuild_reason reason)
+{
+ struct mail_storage *storage;
+ const char *errstr;
+ enum mail_error error;
+
+ array_foreach_elem(&list->ns->all_storages, storage) {
+ if (storage->v.list_index_rebuild == NULL)
+ continue;
+
+ if (storage->v.list_index_rebuild(storage, reason) < 0) {
+ errstr = mail_storage_get_last_internal_error(storage, &error);
+ mailbox_list_set_error(list, error, errstr);
+ return -1;
+ } else {
+ /* FIXME: implement a generic handler that
+ just lists mailbox directories in filesystem
+ and adds the missing ones to the index. */
+ }
+ }
+ return mailbox_list_index_set_uncorrupted(list);
+}
+
+int mailbox_list_index_handle_corruption(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ enum mail_storage_list_index_rebuild_reason reason;
+ int ret;
+
+ if (ilist->call_corruption_callback)
+ reason = MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_CORRUPTED;
+ else if (ilist->rebuild_on_missing_inbox)
+ reason = MAIL_STORAGE_LIST_INDEX_REBUILD_REASON_NO_INBOX;
+ else
+ return 0;
+
+ if (list->disable_rebuild_on_corruption)
+ return 0;
+
+ /* make sure we don't recurse */
+ if (ilist->handling_corruption)
+ return 0;
+ ilist->handling_corruption = TRUE;
+
+ /* Perform the rebuilding locked. Note that if we're here because
+ INBOX wasn't found, this may be because another process is in the
+ middle of creating it. Waiting for the lock here makes sure that
+ we don't start rebuilding before it's finished. In that case the
+ rebuild is a bit unnecessary, but harmless (and avoiding the rebuild
+ just adds extra code complexity). */
+ if (mailbox_list_lock(list) < 0)
+ ret = -1;
+ else {
+ ret = list_handle_corruption_locked(list, reason);
+ mailbox_list_unlock(list);
+ }
+ ilist->handling_corruption = FALSE;
+ return ret;
+}
+
+int mailbox_list_index_set_uncorrupted(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_sync_context *sync_ctx;
+
+ ilist->call_corruption_callback = FALSE;
+ ilist->rebuild_on_missing_inbox = FALSE;
+
+ if (mailbox_list_index_sync_begin(list, &sync_ctx) < 0)
+ return -1;
+
+ mail_index_unset_fscked(sync_ctx->trans);
+ return mailbox_list_index_sync_end(&sync_ctx, TRUE);
+}
+
+bool mailbox_list_index_get_index(struct mailbox_list *list,
+ struct mail_index **index_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+
+ if (ilist == NULL)
+ return FALSE;
+ *index_r = ilist->index;
+ return TRUE;
+}
+
+int mailbox_list_index_view_open(struct mailbox *box, bool require_refreshed,
+ struct mail_index_view **view_r,
+ uint32_t *seq_r)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+ struct mailbox_list_index_node *node;
+ struct mail_index_view *view;
+ const char *reason = NULL;
+ uint32_t seq;
+ int ret;
+
+ if (ilist == NULL) {
+ /* mailbox list indexes aren't enabled */
+ return 0;
+ }
+ if (MAILBOX_IS_NEVER_IN_INDEX(box) && require_refreshed) {
+ /* Optimization: Caller wants the list index to be up-to-date
+ for this mailbox, but this mailbox isn't updated to the list
+ index at all. */
+ return 0;
+ }
+ if (mailbox_list_index_refresh(box->list) < 0) {
+ mail_storage_copy_list_error(box->storage, box->list);
+ return -1;
+ }
+
+ node = mailbox_list_index_lookup(box->list, box->name);
+ if (node == NULL) {
+ /* mailbox not found */
+ e_debug(box->event, "Couldn't open mailbox in list index: "
+ "Mailbox not found");
+ return 0;
+ }
+
+ view = mail_index_view_open(ilist->index);
+ if (mailbox_list_index_need_refresh(ilist, view)) {
+ /* mailbox_list_index_refresh_later() was called.
+ Can't trust the index's contents. */
+ reason = "Refresh-flag set";
+ ret = 1;
+ } else if (!mail_index_lookup_seq(view, node->uid, &seq)) {
+ /* our in-memory tree is out of sync */
+ ret = 1;
+ reason = "Mailbox no longer exists in index";
+ } else if (!require_refreshed) {
+ /* this operation doesn't need the index to be up-to-date */
+ ret = 0;
+ } else {
+ ret = box->v.list_index_has_changed == NULL ? 0 :
+ box->v.list_index_has_changed(box, view, seq, FALSE,
+ &reason);
+ i_assert(ret <= 0 || reason != NULL);
+ }
+
+ if (ret != 0) {
+ /* error / mailbox has changed. we'll need to sync it. */
+ if (ret < 0)
+ mailbox_list_index_refresh_later(box->list);
+ else {
+ i_assert(reason != NULL);
+ e_debug(box->event,
+ "Couldn't open mailbox in list index: %s",
+ reason);
+ ilist->index_last_check_changed = TRUE;
+ }
+ mail_index_view_close(&view);
+ return ret < 0 ? -1 : 0;
+ }
+
+ *view_r = view;
+ *seq_r = seq;
+ return 1;
+}
+
+static void mailbox_list_index_deinit(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ timeout_remove(&ilist->to_refresh);
+ if (ilist->index != NULL) {
+ hash_table_destroy(&ilist->mailbox_hash);
+ hash_table_destroy(&ilist->mailbox_names);
+ pool_unref(&ilist->mailbox_pool);
+ if (ilist->opened)
+ mail_index_close(ilist->index);
+ mail_index_free(&ilist->index);
+ }
+ ilist->module_ctx.super.deinit(list);
+}
+
+static void
+mailbox_list_index_refresh_if_found(struct mailbox_list *list,
+ const char *name, bool selectable)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+ struct mailbox_list_index_node *node;
+
+ if (ilist->syncing)
+ return;
+
+ mailbox_list_last_error_push(list);
+ (void)mailbox_list_index_refresh_force(list);
+ node = mailbox_list_index_lookup(list, name);
+ if (node != NULL &&
+ (!selectable ||
+ (node->flags & (MAILBOX_LIST_INDEX_FLAG_NONEXISTENT |
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT)) == 0)) {
+ /* index is out of sync - refresh */
+ mailbox_list_index_refresh_later(list);
+ }
+ mailbox_list_last_error_pop(list);
+}
+
+static void mailbox_list_index_refresh_if_not_found(struct mailbox_list *list,
+ const char *name)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ if (ilist->syncing)
+ return;
+
+ mailbox_list_last_error_push(list);
+ (void)mailbox_list_index_refresh_force(list);
+ if (mailbox_list_index_lookup(list, name) == NULL) {
+ /* index is out of sync - refresh */
+ mailbox_list_index_refresh_later(list);
+ }
+ mailbox_list_last_error_pop(list);
+}
+
+static int mailbox_list_index_open_mailbox(struct mailbox *box)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if (ibox->module_ctx.super.open(box) < 0) {
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND)
+ mailbox_list_index_refresh_if_found(box->list, box->name, TRUE);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+mailbox_list_index_create_mailbox(struct mailbox *box,
+ const struct mailbox_update *update,
+ bool directory)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if (ibox->module_ctx.super.create_box(box, update, directory) < 0) {
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_EXISTS)
+ mailbox_list_index_refresh_if_not_found(box->list, box->name);
+ return -1;
+ }
+ mailbox_list_index_refresh_later(box->list);
+ return 0;
+}
+
+static int
+mailbox_list_index_update_mailbox(struct mailbox *box,
+ const struct mailbox_update *update)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ if (ibox->module_ctx.super.update_box(box, update) < 0) {
+ if (mailbox_get_last_mail_error(box) == MAIL_ERROR_NOTFOUND)
+ mailbox_list_index_refresh_if_found(box->list, box->name, TRUE);
+ return -1;
+ }
+
+ mailbox_list_index_update_mailbox_index(box, update);
+ return 0;
+}
+
+static int
+mailbox_list_index_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ if (ilist->module_ctx.super.delete_mailbox(list, name) < 0) {
+ if (mailbox_list_get_last_mail_error(list) == MAIL_ERROR_NOTFOUND)
+ mailbox_list_index_refresh_if_found(list, name, FALSE);
+ return -1;
+ }
+ mailbox_list_index_refresh_later(list);
+ return 0;
+}
+
+static int
+mailbox_list_index_delete_dir(struct mailbox_list *list, const char *name)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(list);
+
+ if (ilist->module_ctx.super.delete_dir(list, name) < 0) {
+ if (mailbox_list_get_last_mail_error(list) == MAIL_ERROR_NOTFOUND)
+ mailbox_list_index_refresh_if_found(list, name, FALSE);
+ return -1;
+ }
+ mailbox_list_index_refresh_later(list);
+ return 0;
+}
+
+static int
+mailbox_list_index_rename_mailbox(struct mailbox_list *oldlist,
+ const char *oldname,
+ struct mailbox_list *newlist,
+ const char *newname)
+{
+ struct mailbox_list_index *oldilist = INDEX_LIST_CONTEXT_REQUIRE(oldlist);
+
+ if (oldilist->module_ctx.super.rename_mailbox(oldlist, oldname,
+ newlist, newname) < 0) {
+ if (mailbox_list_get_last_mail_error(oldlist) == MAIL_ERROR_NOTFOUND)
+ mailbox_list_index_refresh_if_found(oldlist, oldname, FALSE);
+ if (mailbox_list_get_last_mail_error(newlist) == MAIL_ERROR_EXISTS)
+ mailbox_list_index_refresh_if_not_found(newlist, newname);
+ return -1;
+ }
+ mailbox_list_index_refresh_later(oldlist);
+ if (oldlist != newlist)
+ mailbox_list_index_refresh_later(newlist);
+ return 0;
+}
+
+static int
+mailbox_list_index_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(_list);
+ struct mail_index_view *view;
+ struct mail_index_transaction *trans;
+ const void *data;
+ size_t size;
+ uint32_t counter;
+
+ if (ilist->module_ctx.super.set_subscribed(_list, name, set) < 0)
+ return -1;
+
+ /* update the "subscriptions changed" counter/timestamp. its purpose
+ is to trigger NOTIFY watcher to handle SubscriptionChange events */
+ if (mailbox_list_index_index_open(_list) < 0)
+ return -1;
+ view = mail_index_view_open(ilist->index);
+ mail_index_get_header_ext(view, ilist->subs_hdr_ext_id, &data, &size);
+ if (size != sizeof(counter))
+ counter = ioloop_time;
+ else {
+ memcpy(&counter, data, size);
+ if (++counter < (uint32_t)ioloop_time)
+ counter = ioloop_time;
+ }
+
+ trans = mail_index_transaction_begin(view,
+ MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL);
+ mail_index_update_header_ext(trans, ilist->subs_hdr_ext_id,
+ 0, &counter, sizeof(counter));
+ (void)mail_index_transaction_commit(&trans);
+ mail_index_view_close(&view);
+ return 0;
+}
+
+static bool mailbox_list_index_is_enabled(struct mailbox_list *list)
+{
+ if (!list->mail_set->mailbox_list_index ||
+ (list->props & MAILBOX_LIST_PROP_NO_LIST_INDEX) != 0)
+ return FALSE;
+
+ i_assert(list->set.list_index_fname != NULL);
+ if (list->set.list_index_fname[0] == '\0')
+ return FALSE;
+ return TRUE;
+}
+
+static void mailbox_list_index_created(struct mailbox_list *list)
+{
+ struct mailbox_list_vfuncs *v = list->vlast;
+ struct mailbox_list_index *ilist;
+ bool has_backing_store;
+
+ /* layout=index doesn't have any backing store */
+ has_backing_store = strcmp(list->name, MAILBOX_LIST_NAME_INDEX) != 0;
+
+ if (!mailbox_list_index_is_enabled(list)) {
+ /* reserve the module context anyway, so syncing code knows
+ that the index is disabled */
+ i_assert(has_backing_store);
+ ilist = NULL;
+ MODULE_CONTEXT_SET(list, mailbox_list_index_module, ilist);
+ return;
+ }
+
+ ilist = p_new(list->pool, struct mailbox_list_index, 1);
+ ilist->module_ctx.super = *v;
+ list->vlast = &ilist->module_ctx.super;
+ ilist->has_backing_store = has_backing_store;
+ ilist->pending_init = TRUE;
+
+ v->deinit = mailbox_list_index_deinit;
+ v->iter_init = mailbox_list_index_iter_init;
+ v->iter_deinit = mailbox_list_index_iter_deinit;
+ v->iter_next = mailbox_list_index_iter_next;
+
+ v->delete_mailbox = mailbox_list_index_delete_mailbox;
+ v->delete_dir = mailbox_list_index_delete_dir;
+ v->rename_mailbox = mailbox_list_index_rename_mailbox;
+ v->set_subscribed = mailbox_list_index_set_subscribed;
+
+ v->notify_init = mailbox_list_index_notify_init;
+ v->notify_next = mailbox_list_index_notify_next;
+ v->notify_deinit = mailbox_list_index_notify_deinit;
+ v->notify_wait = mailbox_list_index_notify_wait;
+ v->notify_flush = mailbox_list_index_notify_flush;
+
+ MODULE_CONTEXT_SET(list, mailbox_list_index_module, ilist);
+
+ if ((list->flags & MAILBOX_LIST_FLAG_SECONDARY) != 0) {
+ /* secondary lists aren't accessible via namespaces, so we
+ need to finish them now. */
+ mailbox_list_index_init_finish(list);
+ }
+}
+
+static void mailbox_list_index_init_finish(struct mailbox_list *list)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(list);
+ const char *dir;
+
+ if (ilist == NULL || !ilist->pending_init)
+ return;
+ ilist->pending_init = FALSE;
+
+ /* we've delayed this part of the initialization so that mbox format
+ can override the index root directory path */
+ if (!mailbox_list_get_root_path(list, MAILBOX_LIST_PATH_TYPE_LIST_INDEX,
+ &dir)) {
+ /* in-memory indexes */
+ dir = NULL;
+ }
+ i_assert(ilist->has_backing_store || dir != NULL);
+
+ i_assert(list->set.list_index_fname != NULL);
+ ilist->path = dir == NULL ? "(in-memory mailbox list index)" :
+ p_strdup_printf(list->pool, "%s/%s", dir, list->set.list_index_fname);
+ ilist->index = mail_index_alloc(list->ns->user->event,
+ dir, list->set.list_index_fname);
+ ilist->rebuild_on_missing_inbox = !ilist->has_backing_store &&
+ (list->ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0;
+
+ ilist->ext_id = mail_index_ext_register(ilist->index, "list",
+ sizeof(struct mailbox_list_index_header),
+ sizeof(struct mailbox_list_index_record),
+ sizeof(uint32_t));
+ ilist->subs_hdr_ext_id = mail_index_ext_register(ilist->index, "subs",
+ sizeof(uint32_t), 0,
+ sizeof(uint32_t));
+ mailbox_list_index_init_pool(ilist);
+
+ mailbox_list_index_status_init_finish(list);
+}
+
+static void
+mailbox_list_index_namespaces_added(struct mail_namespace *namespaces)
+{
+ struct mail_namespace *ns;
+
+ for (ns = namespaces; ns != NULL; ns = ns->next)
+ mailbox_list_index_init_finish(ns->list);
+}
+
+static struct mailbox_sync_context *
+mailbox_list_index_sync_init(struct mailbox *box,
+ enum mailbox_sync_flags flags)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(box);
+
+ mailbox_list_index_status_sync_init(box);
+ if (!ibox->have_backend)
+ mailbox_list_index_backend_sync_init(box, flags);
+ return ibox->module_ctx.super.sync_init(box, flags);
+}
+
+static int
+mailbox_list_index_sync_deinit(struct mailbox_sync_context *ctx,
+ struct mailbox_sync_status *status_r)
+{
+ struct index_list_mailbox *ibox = INDEX_LIST_STORAGE_CONTEXT(ctx->box);
+ struct mailbox *box = ctx->box;
+
+ if (ibox->module_ctx.super.sync_deinit(ctx, status_r) < 0)
+ return -1;
+ ctx = NULL;
+
+ mailbox_list_index_status_sync_deinit(box);
+ if (ibox->have_backend)
+ return mailbox_list_index_backend_sync_deinit(box);
+ else
+ return 0;
+}
+
+static void mailbox_list_index_mailbox_allocated(struct mailbox *box)
+{
+ struct mailbox_vfuncs *v = box->vlast;
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT(box->list);
+ struct index_list_mailbox *ibox;
+
+ if (ilist == NULL)
+ return;
+
+ ibox = p_new(box->pool, struct index_list_mailbox, 1);
+ ibox->module_ctx.super = *v;
+ box->vlast = &ibox->module_ctx.super;
+ MODULE_CONTEXT_SET(box, index_list_storage_module, ibox);
+
+ /* for layout=index these get overridden */
+ v->open = mailbox_list_index_open_mailbox;
+ v->create_box = mailbox_list_index_create_mailbox;
+ v->update_box = mailbox_list_index_update_mailbox;
+
+ /* These are used by both status and backend code, but they can't both
+ be overriding the same function pointer since they share the
+ super pointer. */
+ v->sync_init = mailbox_list_index_sync_init;
+ v->sync_deinit = mailbox_list_index_sync_deinit;
+
+ mailbox_list_index_status_init_mailbox(v);
+ ibox->have_backend = mailbox_list_index_backend_init_mailbox(box, v);
+}
+
+static struct mail_storage_hooks mailbox_list_index_hooks = {
+ .mailbox_list_created = mailbox_list_index_created,
+ .mail_namespaces_added = mailbox_list_index_namespaces_added,
+ .mailbox_allocated = mailbox_list_index_mailbox_allocated
+};
+
+void mailbox_list_index_init(void); /* called in mailbox-list-register.c */
+
+void mailbox_list_index_init(void)
+{
+ mail_storage_hooks_add_internal(&mailbox_list_index_hooks);
+}
diff --git a/src/lib-storage/list/mailbox-list-index.h b/src/lib-storage/list/mailbox-list-index.h
new file mode 100644
index 0000000..0109f5d
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-index.h
@@ -0,0 +1,250 @@
+#ifndef MAILBOX_LIST_INDEX_H
+#define MAILBOX_LIST_INDEX_H
+
+/* Mailbox list index basically contains:
+
+ Header contains ID => name mapping. The name isn't the full mailbox name,
+ but rather each hierarchy level has its own ID and name. For example a
+ mailbox name "foo/bar" (with '/' as separator) would have separate IDs for
+ "foo" and "bar" names.
+
+ The records contain { parent_uid, uid, name_id } field that can be used to
+ build the whole mailbox tree. parent_uid=0 means root, otherwise it's the
+ parent node's uid.
+
+ Each record also contains GUID for each selectable mailbox. If a mailbox
+ is recreated using the same name, its GUID also changes. Note however that
+ the UID doesn't change, because the UID refers to the mailbox name, not to
+ the mailbox itself.
+
+ The records may contain also extensions for allowing mailbox_get_status()
+ to return values directly from the mailbox list index. Storage backends
+ may also add their own extensions to figure out if a record is up to date.
+*/
+
+#include "module-context.h"
+#include "mail-types.h"
+#include "mail-storage.h"
+#include "mailbox-list-private.h"
+
+#include <sys/time.h>
+
+#define MAILBOX_LIST_INDEX_HIERARCHY_SEP '~'
+#define MAILBOX_LIST_INDEX_HIERARCHY_ALT_SEP '^'
+
+#define INDEX_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, mailbox_list_index_module)
+#define INDEX_LIST_CONTEXT_REQUIRE(obj) \
+ MODULE_CONTEXT_REQUIRE(obj, mailbox_list_index_module)
+
+/* Should the STATUS information for this mailbox not be written to the
+ mailbox list index? */
+#define MAILBOX_IS_NEVER_IN_INDEX(box) \
+ ((box)->inbox_any && !(box)->storage->set->mailbox_list_index_include_inbox)
+
+struct mail_index_view;
+struct mailbox_index_vsize;
+struct mailbox_vfuncs;
+
+/* stored in mail_index_record.flags: */
+enum mailbox_list_index_flags {
+ MAILBOX_LIST_INDEX_FLAG_NONEXISTENT = MAIL_DELETED,
+ MAILBOX_LIST_INDEX_FLAG_NOSELECT = MAIL_DRAFT,
+ MAILBOX_LIST_INDEX_FLAG_NOINFERIORS = MAIL_ANSWERED,
+ MAILBOX_LIST_INDEX_FLAG_CORRUPTED_NAME = MAIL_SEEN,
+
+ /* set during syncing for mailboxes that still exist */
+ MAILBOX_LIST_INDEX_FLAG_SYNC_EXISTS = MAIL_FLAGGED
+};
+
+struct mailbox_list_index_header {
+ uint8_t refresh_flag;
+ /* array of { uint32_t id; char name[]; } */
+};
+
+struct mailbox_list_index_record {
+ /* points to given id in header */
+ uint32_t name_id;
+ /* parent mailbox's UID, 0 = root */
+ uint32_t parent_uid;
+
+ /* the following fields are temporarily zero while unknown,
+ also permanently zero for \NoSelect and \Nonexistent mailboxes: */
+
+ guid_128_t guid;
+ uint32_t uid_validity;
+};
+
+struct mailbox_list_index_msgs_record {
+ uint32_t messages;
+ uint32_t unseen;
+ uint32_t recent;
+ uint32_t uidnext;
+};
+
+struct mailbox_list_index_node {
+ struct mailbox_list_index_node *parent;
+ struct mailbox_list_index_node *next;
+ struct mailbox_list_index_node *children;
+
+ uint32_t name_id, uid;
+ enum mailbox_list_index_flags flags;
+ /* extension data is corrupted on disk - need to update it */
+ bool corrupted_ext;
+ /* flags are corrupted on disk - need to update it */
+ bool corrupted_flags;
+ const char *raw_name;
+};
+
+struct mailbox_list_index {
+ union mailbox_list_module_context module_ctx;
+
+ const char *path;
+ struct mail_index *index;
+ uint32_t ext_id, msgs_ext_id, hmodseq_ext_id, subs_hdr_ext_id;
+ uint32_t vsize_ext_id, first_saved_ext_id;
+ struct timeval last_refresh_timeval;
+
+ pool_t mailbox_pool;
+ /* uin32_t id => name */
+ HASH_TABLE(void *, char *) mailbox_names;
+ uint32_t highest_name_id;
+
+ struct mailbox_list_index_sync_context *sync_ctx;
+ uint32_t sync_log_file_seq;
+ uoff_t sync_log_file_offset;
+ uint32_t sync_stamp;
+ struct timeout *to_refresh;
+
+ /* uint32_t uid => node */
+ HASH_TABLE(void *, struct mailbox_list_index_node *) mailbox_hash;
+ struct mailbox_list_index_node *mailbox_tree;
+
+ bool pending_init:1;
+ bool opened:1;
+ bool syncing:1;
+ bool updating_status:1;
+ bool has_backing_store:1;
+ bool index_last_check_changed:1;
+ bool corrupted_names_or_parents:1;
+ bool handling_corruption:1;
+ bool call_corruption_callback:1;
+ bool rebuild_on_missing_inbox:1;
+ bool force_resynced:1;
+ bool force_resync_failed:1;
+};
+
+struct mailbox_list_index_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ pool_t mailbox_pool;
+
+ struct mailbox_info info;
+ pool_t info_pool;
+
+ size_t parent_len;
+ string_t *path;
+ struct mailbox_list_index_node *next_node;
+
+ bool failed:1;
+ bool prefix_inbox_list:1;
+};
+
+extern MODULE_CONTEXT_DEFINE(mailbox_list_index_module,
+ &mailbox_list_module_register);
+
+void mailbox_list_index_set_index_error(struct mailbox_list *list);
+struct mailbox_list_index_node *
+mailbox_list_index_lookup(struct mailbox_list *list, const char *name);
+struct mailbox_list_index_node *
+mailbox_list_index_lookup_uid(struct mailbox_list_index *ilist, uint32_t uid);
+void mailbox_list_index_node_get_path(const struct mailbox_list_index_node *node,
+ char sep, string_t *str);
+void mailbox_list_index_node_unlink(struct mailbox_list_index *ilist,
+ struct mailbox_list_index_node *node);
+
+/* Return mailbox name encoded into box-name header. */
+const unsigned char *
+mailbox_name_hdr_encode(struct mailbox_list *list, const char *storage_name,
+ size_t *name_len_r);
+/* Return mailbox name decoded from box-name header. */
+const char *
+mailbox_name_hdr_decode_storage_name(struct mailbox_list *list,
+ const unsigned char *name_hdr,
+ size_t name_hdr_size);
+
+int mailbox_list_index_index_open(struct mailbox_list *list);
+bool mailbox_list_index_need_refresh(struct mailbox_list_index *ilist,
+ struct mail_index_view *view);
+/* Refresh the index, but only if it hasn't been refreshed "recently"
+ (= within this same ioloop run) */
+int mailbox_list_index_refresh(struct mailbox_list *list);
+/* Refresh the index regardless of when the last refresh was done. */
+int mailbox_list_index_refresh_force(struct mailbox_list *list);
+void mailbox_list_index_refresh_later(struct mailbox_list *list);
+int mailbox_list_index_handle_corruption(struct mailbox_list *list);
+int mailbox_list_index_set_uncorrupted(struct mailbox_list *list);
+
+/* Returns TRUE and index_r if mailbox list index exists, FALSE if not. */
+bool mailbox_list_index_get_index(struct mailbox_list *list,
+ struct mail_index **index_r);
+/* Open mailbox list index's view and get the given mailbox's sequence number
+ in it. If require_refreshed is TRUE, the mailbox must have up-to-date
+ information in the mailbox list index. Returns 1 if ok, 0 if mailbox wasn't
+ found or it wasn't up-to-date as requested, -1 if there was an error. The
+ error is stored to the mailbox storage. */
+int mailbox_list_index_view_open(struct mailbox *box, bool require_refreshed,
+ struct mail_index_view **view_r,
+ uint32_t *seq_r);
+
+struct mailbox_list_index_node *
+mailbox_list_index_node_find_sibling(const struct mailbox_list *list,
+ struct mailbox_list_index_node *node,
+ const char *name);
+void mailbox_list_index_reset(struct mailbox_list_index *ilist);
+int mailbox_list_index_parse(struct mailbox_list *list,
+ struct mail_index_view *view, bool force);
+
+struct mailbox_list_iterate_context *
+mailbox_list_index_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags);
+const struct mailbox_info *
+mailbox_list_index_iter_next(struct mailbox_list_iterate_context *ctx);
+int mailbox_list_index_iter_deinit(struct mailbox_list_iterate_context *ctx);
+
+bool mailbox_list_index_status(struct mailbox_list *list,
+ struct mail_index_view *view,
+ uint32_t seq, enum mailbox_status_items items,
+ struct mailbox_status *status_r,
+ uint8_t *mailbox_guid,
+ struct mailbox_index_vsize *vsize_r,
+ const char **reason_r);
+void mailbox_list_index_status_set_info_flags(struct mailbox *box, uint32_t uid,
+ enum mailbox_info_flags *flags);
+void mailbox_list_index_update_mailbox_index(struct mailbox *box,
+ const struct mailbox_update *update);
+
+int mailbox_list_index_notify_init(struct mailbox_list *list,
+ enum mailbox_list_notify_event mask,
+ struct mailbox_list_notify **notify_r);
+void mailbox_list_index_notify_deinit(struct mailbox_list_notify *notify);
+int mailbox_list_index_notify_next(struct mailbox_list_notify *notify,
+ const struct mailbox_list_notify_rec **rec_r);
+void mailbox_list_index_notify_wait(struct mailbox_list_notify *notify,
+ void (*callback)(void *context),
+ void *context);
+void mailbox_list_index_notify_flush(struct mailbox_list_notify *notify);
+
+void mailbox_list_index_status_init_mailbox(struct mailbox_vfuncs *v);
+bool mailbox_list_index_backend_init_mailbox(struct mailbox *box,
+ struct mailbox_vfuncs *v);
+void mailbox_list_index_status_init_finish(struct mailbox_list *list);
+
+void mailbox_list_index_status_sync_init(struct mailbox *box);
+void mailbox_list_index_status_sync_deinit(struct mailbox *box);
+
+void mailbox_list_index_backend_sync_init(struct mailbox *box,
+ enum mailbox_sync_flags flags);
+int mailbox_list_index_backend_sync_deinit(struct mailbox *box);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-iter-private.h b/src/lib-storage/list/mailbox-list-iter-private.h
new file mode 100644
index 0000000..47de2ce
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-iter-private.h
@@ -0,0 +1,42 @@
+#ifndef MAILBOX_LIST_ITER_PRIVATE_H
+#define MAILBOX_LIST_ITER_PRIVATE_H
+
+#include "mailbox-list-private.h"
+#include "mailbox-list-iter.h"
+#include "mailbox-list-delete.h"
+
+struct autocreate_box {
+ const char *name;
+ const struct mailbox_settings *set;
+ enum mailbox_info_flags flags;
+ bool child_listed;
+};
+
+ARRAY_DEFINE_TYPE(mailbox_settings, struct mailbox_settings *);
+struct mailbox_list_autocreate_iterate_context {
+ unsigned int idx;
+ struct mailbox_info new_info;
+ ARRAY(struct autocreate_box) boxes;
+ ARRAY_TYPE(mailbox_settings) box_sets;
+ ARRAY_TYPE(mailbox_settings) all_ns_box_sets;
+ HASH_TABLE(char *, char *) duplicate_vnames;
+ bool listing_autoboxes:1;
+};
+
+static inline bool
+mailbox_list_iter_try_delete_noselect(struct mailbox_list_iterate_context *ctx,
+ const struct mailbox_info *info,
+ const char *storage_name)
+{
+ if ((info->flags & (MAILBOX_NOSELECT|MAILBOX_NOCHILDREN)) ==
+ (MAILBOX_NOSELECT|MAILBOX_NOCHILDREN) &&
+ ctx->list->set.no_noselect) {
+ /* Try to rmdir() all \NoSelect mailbox leafs and
+ afterwards their parents. */
+ mailbox_list_delete_mailbox_until_root(ctx->list, storage_name);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-iter.c b/src/lib-storage/list/mailbox-list-iter.c
new file mode 100644
index 0000000..4d3d135
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-iter.c
@@ -0,0 +1,1168 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hash.h"
+#include "imap-match.h"
+#include "mail-storage.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-iter-private.h"
+
+enum autocreate_match_result {
+ /* list contains the mailbox */
+ AUTOCREATE_MATCH_RESULT_YES = 0x01,
+ /* list contains children of the mailbox */
+ AUTOCREATE_MATCH_RESULT_CHILDREN = 0x02,
+ /* list contains parents of the mailbox */
+ AUTOCREATE_MATCH_RESULT_PARENT = 0x04
+};
+
+struct ns_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ struct mailbox_list_iterate_context *backend_ctx;
+ struct mail_namespace *namespaces, *cur_ns;
+ struct mailbox_list *error_list;
+ pool_t pool;
+ const char **patterns, **patterns_ns_match;
+ enum mail_namespace_type type_mask;
+
+ struct mailbox_info ns_info;
+ struct mailbox_info inbox_info;
+ const struct mailbox_info *pending_backend_info;
+
+ bool cur_ns_prefix_sent:1;
+ bool inbox_list:1;
+ bool inbox_listed:1;
+ bool inbox_seen:1;
+};
+
+static void mailbox_list_ns_iter_failed(struct ns_list_iterate_context *ctx);
+static bool ns_match_next(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, const char *pattern);
+static int mailbox_list_match_anything(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns,
+ const char *prefix);
+
+static struct mailbox_list_iterate_context mailbox_list_iter_failed;
+
+struct mailbox_list_iterate_context *
+mailbox_list_iter_init(struct mailbox_list *list, const char *pattern,
+ enum mailbox_list_iter_flags flags)
+{
+ const char *patterns[2];
+
+ patterns[0] = pattern;
+ patterns[1] = NULL;
+ return mailbox_list_iter_init_multiple(list, patterns, flags);
+}
+
+int mailbox_list_iter_subscriptions_refresh(struct mailbox_list *list)
+{
+ struct mail_namespace *ns = list->ns;
+
+ if ((ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0) {
+ /* no subscriptions in this namespace. find where they are. */
+ ns = mail_namespace_find_subscribable(ns->user->namespaces,
+ ns->prefix);
+ if (ns == NULL) {
+ /* no subscriptions. avoid crashes by initializing
+ a subscriptions tree. */
+ if (list->subscriptions == NULL) {
+ char sep = mail_namespace_get_sep(list->ns);
+ list->subscriptions = mailbox_tree_init(sep);
+ }
+ return 0;
+ }
+ }
+ return ns->list->v.subscriptions_refresh(ns->list, list);
+}
+
+static struct mailbox_settings *
+mailbox_settings_add_ns_prefix(pool_t pool, struct mail_namespace *ns,
+ struct mailbox_settings *in_set)
+{
+ struct mailbox_settings *out_set;
+
+ if (ns->prefix_len == 0 || strcasecmp(in_set->name, "INBOX") == 0)
+ return in_set;
+
+ out_set = p_new(pool, struct mailbox_settings, 1);
+ *out_set = *in_set;
+ if (*in_set->name == '\0') {
+ /* namespace prefix itself */
+ out_set->name = p_strndup(pool, ns->prefix, ns->prefix_len-1);
+ } else {
+ out_set->name =
+ p_strconcat(pool, ns->prefix, in_set->name, NULL);
+ }
+ return out_set;
+}
+
+static void
+mailbox_list_iter_init_autocreate(struct mailbox_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->list->ns;
+ struct mailbox_list_autocreate_iterate_context *actx;
+ struct mailbox_settings *const *box_sets, *set;
+ struct autocreate_box *autobox;
+ unsigned int i, count;
+
+ if (!array_is_created(&ns->set->mailboxes))
+ return;
+ box_sets = array_get(&ns->set->mailboxes, &count);
+ if (count == 0)
+ return;
+
+ actx = p_new(ctx->pool, struct mailbox_list_autocreate_iterate_context, 1);
+ ctx->autocreate_ctx = actx;
+ hash_table_create(&actx->duplicate_vnames, ctx->pool, 0,
+ str_hash, strcmp);
+
+ /* build the list of mailboxes we need to consider as existing */
+ p_array_init(&actx->boxes, ctx->pool, 16);
+ p_array_init(&actx->box_sets, ctx->pool, 16);
+ p_array_init(&actx->all_ns_box_sets, ctx->pool, 16);
+ for (i = 0; i < count; i++) {
+ if (strcmp(box_sets[i]->autocreate, MAILBOX_SET_AUTO_NO) == 0)
+ continue;
+
+ set = mailbox_settings_add_ns_prefix(ctx->pool,
+ ns, box_sets[i]);
+
+ /* autocreate mailbox belongs to listed namespace */
+ array_push_back(&actx->all_ns_box_sets, &set);
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 ||
+ strcmp(set->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) == 0) {
+ array_push_back(&actx->box_sets, &set);
+ autobox = array_append_space(&actx->boxes);
+ autobox->name = set->name;
+ autobox->set = set;
+ if (strcasecmp(autobox->name, "INBOX") == 0) {
+ /* make sure duplicate INBOX/Inbox/etc.
+ won't get created */
+ autobox->name = "INBOX";
+ }
+ }
+ }
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_iter_init_multiple(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct mailbox_list_iterate_context *ctx;
+
+ i_assert(*patterns != NULL);
+
+ if ((flags & (MAILBOX_LIST_ITER_SELECT_SUBSCRIBED |
+ MAILBOX_LIST_ITER_RETURN_SUBSCRIBED)) != 0) {
+ if (mailbox_list_iter_subscriptions_refresh(list) < 0)
+ return &mailbox_list_iter_failed;
+ }
+
+ ctx = list->v.iter_init(list, patterns, flags);
+ if ((flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0)
+ mailbox_list_iter_init_autocreate(ctx);
+ return ctx;
+}
+
+static bool
+ns_match_simple(struct ns_list_iterate_context *ctx, struct mail_namespace *ns)
+{
+ if ((ctx->type_mask & ns->type) == 0)
+ return FALSE;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+ if (ns->alias_for != NULL)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+ns_is_match_within_ns(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, const char *prefix_without_sep,
+ const char *pattern, enum imap_match_result result)
+{
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) == 0) {
+ switch (result) {
+ case IMAP_MATCH_YES:
+ case IMAP_MATCH_CHILDREN:
+ return TRUE;
+ case IMAP_MATCH_NO:
+ case IMAP_MATCH_PARENT:
+ break;
+ }
+ return FALSE;
+ }
+
+ switch (result) {
+ case IMAP_MATCH_YES:
+ /* allow matching prefix only when it's done without
+ wildcards */
+ if (strcmp(prefix_without_sep, pattern) == 0)
+ return TRUE;
+ break;
+ case IMAP_MATCH_CHILDREN: {
+ /* allow this only if there isn't another namespace
+ with longer prefix that matches this pattern
+ (namespaces are sorted by prefix length) */
+ struct mail_namespace *tmp;
+
+ T_BEGIN {
+ for (tmp = ns->next; tmp != NULL; tmp = tmp->next) {
+ if (ns_match_simple(ctx, tmp) &&
+ ns_match_next(ctx, tmp, pattern))
+ break;
+ }
+ } T_END;
+ if (tmp == NULL)
+ return TRUE;
+ break;
+ }
+ case IMAP_MATCH_NO:
+ case IMAP_MATCH_PARENT:
+ break;
+ }
+ return FALSE;
+}
+
+static bool list_pattern_has_wildcards(const char *pattern)
+{
+ for (; *pattern != '\0'; pattern++) {
+ if (*pattern == '%' || *pattern == '*')
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool ns_match_next(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, const char *pattern)
+{
+ struct imap_match_glob *glob;
+ enum imap_match_result result;
+ const char *prefix_without_sep;
+ size_t len;
+
+ len = ns->prefix_len;
+ if (len > 0 && ns->prefix[len-1] == mail_namespace_get_sep(ns))
+ len--;
+
+ if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX |
+ NAMESPACE_FLAG_LIST_CHILDREN)) == 0) {
+ /* non-listable namespace matches only with exact prefix */
+ if (strncmp(ns->prefix, pattern, ns->prefix_len) != 0)
+ return FALSE;
+ /* with prefix="", list=no we don't want to show anything,
+ except when the client explicitly lists a mailbox without
+ wildcards (e.g. LIST "" mailbox). this is mainly useful
+ for working around client bugs (and supporting a specific
+ IMAP client behavior that's not exactly buggy but not very
+ good IMAP behavior either). */
+ if (ns->prefix_len == 0 && list_pattern_has_wildcards(pattern))
+ return FALSE;
+ }
+
+ prefix_without_sep = t_strndup(ns->prefix, len);
+ if (*prefix_without_sep == '\0')
+ result = IMAP_MATCH_CHILDREN;
+ else {
+ glob = imap_match_init(pool_datastack_create(), pattern,
+ TRUE, mail_namespace_get_sep(ns));
+ result = imap_match(glob, prefix_without_sep);
+ }
+
+ return ns_is_match_within_ns(ctx, ns, prefix_without_sep,
+ pattern, result);
+}
+
+static bool
+mailbox_list_ns_match_patterns(struct ns_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns = ctx->cur_ns;
+ unsigned int i;
+
+ if (!ns_match_simple(ctx, ns))
+ return FALSE;
+
+ /* filter out namespaces whose prefix doesn't match. this same code
+ handles both with and without STAR_WITHIN_NS, so the "without" case
+ is slower than necessary, but this shouldn't matter much */
+ T_BEGIN {
+ for (i = 0; ctx->patterns_ns_match[i] != NULL; i++) {
+ if (ns_match_next(ctx, ns, ctx->patterns_ns_match[i]))
+ break;
+ }
+ } T_END;
+
+ return ctx->patterns_ns_match[i] != NULL;
+}
+
+static bool
+iter_next_try_prefix_pattern(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, const char *pattern)
+{
+ struct imap_match_glob *glob;
+ enum imap_match_result result;
+ const char *prefix_without_sep;
+
+ i_assert(ns->prefix_len > 0);
+
+ if ((ns->flags & (NAMESPACE_FLAG_LIST_PREFIX |
+ NAMESPACE_FLAG_LIST_CHILDREN)) == 0) {
+ /* non-listable namespace matches only with exact prefix */
+ if (strncmp(ns->prefix, pattern, ns->prefix_len) != 0)
+ return FALSE;
+ }
+
+ prefix_without_sep = t_strndup(ns->prefix, ns->prefix_len-1);
+ glob = imap_match_init(pool_datastack_create(), pattern,
+ TRUE, mail_namespace_get_sep(ns));
+ result = imap_match(glob, prefix_without_sep);
+ return result == IMAP_MATCH_YES &&
+ ns_is_match_within_ns(ctx, ns, prefix_without_sep,
+ pattern, result);
+}
+
+static bool
+mailbox_list_ns_prefix_match(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns)
+{
+ unsigned int i;
+ bool ret = FALSE;
+
+ for (i = 0; ctx->patterns_ns_match[i] != NULL; i++) {
+ T_BEGIN {
+ ret = iter_next_try_prefix_pattern(ctx, ns,
+ ctx->patterns_ns_match[i]);
+ } T_END;
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int
+ns_prefix_is_visible(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns)
+{
+ int ret;
+
+ if ((ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0)
+ return 1;
+ if ((ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0) {
+ if ((ret = mailbox_list_match_anything(ctx, ns, ns->prefix)) != 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int
+ns_prefix_has_visible_child_namespace(struct ns_list_iterate_context *ctx,
+ const char *prefix)
+{
+ struct mail_namespace *ns;
+ size_t prefix_len = strlen(prefix);
+ int ret;
+
+ for (ns = ctx->namespaces; ns != NULL; ns = ns->next) {
+ if (ns->prefix_len > prefix_len &&
+ strncmp(ns->prefix, prefix, prefix_len) == 0) {
+ ret = ns_prefix_is_visible(ctx, ns);
+ if (ret != 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static bool
+mailbox_ns_prefix_is_shared_inbox(struct mail_namespace *ns)
+{
+ return ns->type == MAIL_NAMESPACE_TYPE_SHARED &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0 &&
+ !ns->list->mail_set->mail_shared_explicit_inbox;
+}
+
+static bool
+mailbox_is_shared_inbox(struct mail_namespace *ns, const char *vname)
+{
+ return mailbox_ns_prefix_is_shared_inbox(ns) &&
+ strncmp(ns->prefix, vname, ns->prefix_len-1) == 0 &&
+ vname[ns->prefix_len-1] == '\0';
+}
+
+static int
+mailbox_list_match_anything(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, const char *prefix)
+{
+ enum mailbox_list_iter_flags list_flags =
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS;
+ struct mailbox_list_iterate_context *list_iter;
+ const struct mailbox_info *info;
+ const char *pattern;
+ int ret;
+
+ if ((ret = ns_prefix_has_visible_child_namespace(ctx, prefix)) != 0)
+ return ret;
+
+ pattern = t_strconcat(prefix, "%", NULL);
+ list_iter = mailbox_list_iter_init(ns->list, pattern, list_flags);
+ info = mailbox_list_iter_next(list_iter);
+ if (info != NULL && mailbox_ns_prefix_is_shared_inbox(ns) &&
+ mailbox_is_shared_inbox(ns, info->vname)) {
+ /* we don't want to see this, try the next one */
+ info = mailbox_list_iter_next(list_iter);
+ }
+ ret = info != NULL ? 1 : 0;
+ if (mailbox_list_iter_deinit(&list_iter) < 0) {
+ if (ret == 0)
+ ret = -1;
+ }
+ return ret;
+}
+
+static bool
+mailbox_ns_prefix_check_selection_criteria(struct ns_list_iterate_context *ctx)
+{
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ if ((ctx->ns_info.flags & MAILBOX_SUBSCRIBED) != 0)
+ return TRUE;
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 &&
+ (ctx->ns_info.flags & MAILBOX_CHILD_SUBSCRIBED) != 0)
+ return TRUE;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool
+mailbox_list_ns_prefix_return(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *ns, bool has_children)
+{
+ struct mailbox *box;
+ enum mailbox_existence existence;
+ int ret;
+
+ if (strncasecmp(ns->prefix, "INBOX", 5) == 0 &&
+ ns->prefix[5] == mail_namespace_get_sep(ns)) {
+ /* prefix=INBOX/ (or prefix=INBOX/something/) namespace exists.
+ so we can create children to INBOX. */
+ ctx->inbox_info.flags &= ENUM_NEGATE(MAILBOX_NOINFERIORS);
+ }
+
+ if (ns->prefix_len == 0 || !mailbox_list_ns_prefix_match(ctx, ns))
+ return FALSE;
+
+ i_zero(&ctx->ns_info);
+ ctx->ns_info.ns = ns;
+ ctx->ns_info.vname = p_strndup(ctx->pool, ns->prefix,
+ ns->prefix_len-1);
+ if (ns->special_use_mailboxes)
+ ctx->ns_info.flags |= MAILBOX_CHILD_SPECIALUSE;
+
+ if (strcasecmp(ctx->ns_info.vname, "INBOX") == 0) {
+ i_assert(!ctx->inbox_listed);
+ ctx->inbox_listed = TRUE;
+ ctx->ns_info.flags |= ctx->inbox_info.flags | MAILBOX_SELECT;
+ }
+
+ if ((ctx->ctx.flags & (MAILBOX_LIST_ITER_RETURN_SUBSCRIBED |
+ MAILBOX_LIST_ITER_SELECT_SUBSCRIBED)) != 0) {
+ /* Refresh subscriptions first, this won't cause a duplicate
+ call later on as this is only called when the namespace's
+ children definitely don't match */
+ if (mailbox_list_iter_subscriptions_refresh(ns->list) < 0) {
+ mailbox_list_ns_iter_failed(ctx);
+ return FALSE;
+ }
+ mailbox_list_set_subscription_flags(ns->list,
+ ctx->ns_info.vname,
+ &ctx->ns_info.flags);
+ }
+ if (!mailbox_ns_prefix_check_selection_criteria(ctx))
+ return FALSE;
+
+ /* see if the namespace has children */
+ if (has_children)
+ ctx->ns_info.flags |= MAILBOX_CHILDREN;
+ else if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 ||
+ (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0) {
+ /* need to check this explicitly */
+ if ((ret = mailbox_list_match_anything(ctx, ns, ns->prefix)) > 0)
+ ctx->ns_info.flags |= MAILBOX_CHILDREN;
+ else if (ret == 0) {
+ if ((ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 &&
+ !mailbox_ns_prefix_is_shared_inbox(ns)) {
+ /* no children -> not visible */
+ return FALSE;
+ }
+ ctx->ns_info.flags |= MAILBOX_NOCHILDREN;
+ }
+ }
+
+ if ((ctx->ns_info.flags & MAILBOX_SELECT) == 0) {
+ /* see if namespace prefix is selectable */
+ box = mailbox_alloc(ns->list, ctx->ns_info.vname, 0);
+ if (mailbox_exists(box, TRUE, &existence) == 0 &&
+ existence == MAILBOX_EXISTENCE_SELECT)
+ ctx->ns_info.flags |= MAILBOX_SELECT;
+ else
+ ctx->ns_info.flags |= MAILBOX_NONEXISTENT;
+ mailbox_free(&box);
+ }
+ return TRUE;
+}
+
+static void inbox_set_children_flags(struct ns_list_iterate_context *ctx)
+{
+ struct mail_namespace *ns;
+ const char *prefix;
+ int ret;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0)
+ return;
+ if ((ctx->inbox_info.flags & (MAILBOX_CHILDREN | MAILBOX_NOINFERIORS |
+ MAILBOX_NOCHILDREN)) != 0)
+ return;
+
+ ns = mail_namespace_find_prefix(ctx->namespaces, "");
+ if (ns == NULL || (ns->flags & NAMESPACE_FLAG_UNUSABLE) != 0) {
+ /* prefix="" namespace doesn't exist, and neither does
+ anything beginning with prefix=INBOX/ (we checked this
+ earlier). there's no way to create children for INBOX. */
+ ctx->inbox_info.flags |= MAILBOX_NOINFERIORS;
+ return;
+ }
+
+ /* INBOX namespace doesn't exist and we didn't see any children listed
+ for INBOX. this could be because there truly aren't any children,
+ or that the list patterns just didn't match them. */
+ prefix = t_strdup_printf("INBOX%c",
+ mail_namespace_get_sep(ctx->inbox_info.ns));
+ ret = mailbox_list_match_anything(ctx, ctx->inbox_info.ns, prefix);
+ if (ret > 0)
+ ctx->inbox_info.flags |= MAILBOX_CHILDREN;
+ else if (ret == 0)
+ ctx->inbox_info.flags |= MAILBOX_NOCHILDREN;
+}
+
+static void mailbox_list_ns_iter_failed(struct ns_list_iterate_context *ctx)
+{
+ enum mail_error error;
+ const char *errstr;
+
+ if (ctx->cur_ns->list != ctx->error_list) {
+ errstr = mailbox_list_get_last_error(ctx->cur_ns->list, &error);
+ mailbox_list_set_error(ctx->error_list, error, errstr);
+ }
+ ctx->ctx.failed = TRUE;
+}
+
+static bool
+mailbox_list_ns_iter_try_next(struct mailbox_list_iterate_context *_ctx,
+ const struct mailbox_info **info_r)
+{
+ struct ns_list_iterate_context *ctx =
+ (struct ns_list_iterate_context *)_ctx;
+ struct mail_namespace *ns;
+ const struct mailbox_info *info;
+ bool has_children;
+
+ if (ctx->cur_ns == NULL) {
+ if (!ctx->inbox_listed && ctx->inbox_list && !_ctx->failed &&
+ ((_ctx->flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0 ||
+ ctx->inbox_seen)) {
+ /* send delayed INBOX reply */
+ ctx->inbox_listed = TRUE;
+ inbox_set_children_flags(ctx);
+ *info_r = &ctx->inbox_info;
+ return TRUE;
+ }
+ *info_r = NULL;
+ return TRUE;
+ }
+
+ if (ctx->backend_ctx == NULL) {
+ i_assert(ctx->pending_backend_info == NULL);
+ if (!mailbox_list_ns_match_patterns(ctx)) {
+ /* namespace's children don't match the patterns,
+ but the namespace prefix itself might */
+ ns = ctx->cur_ns;
+ ctx->cur_ns = ctx->cur_ns->next;
+ if (mailbox_list_ns_prefix_return(ctx, ns, FALSE)) {
+ *info_r = &ctx->ns_info;
+ return TRUE;
+ }
+ return FALSE;
+ }
+ /* start listing this namespace's mailboxes */
+ ctx->backend_ctx =
+ mailbox_list_iter_init_multiple(ctx->cur_ns->list,
+ ctx->patterns,
+ _ctx->flags);
+ ctx->cur_ns_prefix_sent = FALSE;
+ }
+ if (ctx->pending_backend_info == NULL)
+ info = mailbox_list_iter_next(ctx->backend_ctx);
+ else {
+ info = ctx->pending_backend_info;
+ ctx->pending_backend_info = NULL;
+ }
+ if (!ctx->cur_ns_prefix_sent) {
+ /* delayed sending of namespace prefix */
+ ctx->cur_ns_prefix_sent = TRUE;
+ has_children = info != NULL &&
+ !mailbox_is_shared_inbox(info->ns, info->vname);
+ if (mailbox_list_ns_prefix_return(ctx, ctx->cur_ns,
+ has_children)) {
+ ctx->pending_backend_info = info;
+ *info_r = &ctx->ns_info;
+ return TRUE;
+ }
+ }
+ if (info != NULL) {
+ if (strcasecmp(info->vname, "INBOX") == 0 && ctx->inbox_list) {
+ /* delay sending INBOX reply. we already saved its
+ flags at init stage, except for \Noinferiors
+ and subscription states */
+ ctx->inbox_seen = TRUE;
+ ctx->inbox_info.flags |=
+ (info->flags & (MAILBOX_NOINFERIORS |
+ MAILBOX_SUBSCRIBED |
+ MAILBOX_CHILD_SUBSCRIBED));
+ return FALSE;
+ }
+ if (strncasecmp(info->vname, "INBOX", 5) == 0 &&
+ info->vname[5] == mail_namespace_get_sep(info->ns)) {
+ /* we know now that INBOX has children */
+ ctx->inbox_info.flags |= MAILBOX_CHILDREN;
+ ctx->inbox_info.flags &= ENUM_NEGATE(MAILBOX_NOINFERIORS);
+ }
+ if (info->ns->prefix_len > 0 &&
+ strncmp(info->vname, info->ns->prefix,
+ info->ns->prefix_len-1) == 0 &&
+ info->vname[info->ns->prefix_len-1] == '\0') {
+ /* this is an entry for namespace prefix, which we
+ already returned. (e.g. shared/$user/INBOX entry
+ returned as shared/$user, or when listing
+ subscribed namespace prefix). */
+ return FALSE;
+ }
+
+ *info_r = info;
+ return TRUE;
+ }
+
+ /* finished with this namespace */
+ if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0)
+ mailbox_list_ns_iter_failed(ctx);
+ ctx->cur_ns = ctx->cur_ns->next;
+ return FALSE;
+}
+
+static const struct mailbox_info *
+mailbox_list_ns_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ const struct mailbox_info *info = NULL;
+
+ while (!mailbox_list_ns_iter_try_next(_ctx, &info)) ;
+ return info;
+}
+
+static int
+mailbox_list_ns_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct ns_list_iterate_context *ctx =
+ (struct ns_list_iterate_context *)_ctx;
+ int ret;
+
+ if (ctx->backend_ctx != NULL) {
+ if (mailbox_list_iter_deinit(&ctx->backend_ctx) < 0)
+ mailbox_list_ns_iter_failed(ctx);
+ }
+ ret = _ctx->failed ? -1 : 0;
+ pool_unref(&ctx->pool);
+ return ret;
+}
+
+static const char **
+dup_patterns_without_stars(pool_t pool, const char *const *patterns,
+ unsigned int count)
+{
+ const char **dup;
+ unsigned int i;
+
+ dup = p_new(pool, const char *, count + 1);
+ for (i = 0; i < count; i++) {
+ char *p = p_strdup(pool, patterns[i]);
+ dup[i] = p;
+
+ for (; *p != '\0'; p++) {
+ if (*p == '*')
+ *p = '%';
+ }
+ }
+ return dup;
+}
+
+static bool
+patterns_match_inbox(struct mail_namespace *namespaces,
+ const char *const *patterns)
+{
+ struct mail_namespace *ns = mail_namespace_find_inbox(namespaces);
+ struct imap_match_glob *glob;
+
+ glob = imap_match_init_multiple(pool_datastack_create(), patterns,
+ TRUE, mail_namespace_get_sep(ns));
+ return imap_match(glob, "INBOX") == IMAP_MATCH_YES;
+}
+
+static int inbox_info_init(struct ns_list_iterate_context *ctx,
+ struct mail_namespace *namespaces)
+{
+ enum mailbox_info_flags flags;
+ int ret;
+
+ ctx->inbox_info.vname = "INBOX";
+ ctx->inbox_info.ns = mail_namespace_find_inbox(namespaces);
+ i_assert(ctx->inbox_info.ns != NULL);
+
+ if ((ret = mailbox_list_mailbox(ctx->inbox_info.ns->list, "INBOX", &flags)) > 0)
+ ctx->inbox_info.flags = flags;
+ else if (ret < 0) {
+ ctx->cur_ns = ctx->inbox_info.ns;
+ mailbox_list_ns_iter_failed(ctx);
+ }
+ return ret;
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_iter_init_namespaces(struct mail_namespace *namespaces,
+ const char *const *patterns,
+ enum mail_namespace_type type_mask,
+ enum mailbox_list_iter_flags flags)
+{
+ struct ns_list_iterate_context *ctx;
+ unsigned int i, count;
+ pool_t pool;
+
+ i_assert(namespaces != NULL);
+
+ pool = pool_alloconly_create("mailbox list namespaces", 1024);
+ ctx = p_new(pool, struct ns_list_iterate_context, 1);
+ ctx->pool = pool;
+ ctx->type_mask = type_mask;
+ ctx->ctx.flags = flags;
+ ctx->ctx.list = p_new(pool, struct mailbox_list, 1);
+ ctx->ctx.list->v.iter_next = mailbox_list_ns_iter_next;
+ ctx->ctx.list->v.iter_deinit = mailbox_list_ns_iter_deinit;
+ ctx->namespaces = namespaces;
+ ctx->error_list = namespaces->list;
+
+ count = str_array_length(patterns);
+ ctx->patterns = p_new(pool, const char *, count + 1);
+ for (i = 0; i < count; i++)
+ ctx->patterns[i] = p_strdup(pool, patterns[i]);
+ if (patterns_match_inbox(namespaces, ctx->patterns) &&
+ (flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) {
+ /* we're going to list the INBOX. get its own flags (i.e. not
+ [no]children) immediately, so if we end up seeing something
+ else called INBOX (e.g. namespace prefix) we can show it
+ immediately with the proper flags. */
+ ctx->inbox_list = TRUE;
+ if (inbox_info_init(ctx, namespaces) < 0) {
+ pool_unref(&pool);
+ return &mailbox_list_iter_failed;
+ }
+ }
+
+ if ((flags & MAILBOX_LIST_ITER_STAR_WITHIN_NS) != 0) {
+ /* create copies of patterns with '*' wildcard changed to '%'.
+ this is used only when checking which namespaces to list */
+ ctx->patterns_ns_match =
+ dup_patterns_without_stars(pool, ctx->patterns, count);
+ } else {
+ ctx->patterns_ns_match = ctx->patterns;
+ }
+
+ ctx->cur_ns = namespaces;
+ ctx->ctx.list->ns = namespaces;
+ return &ctx->ctx;
+}
+
+static enum autocreate_match_result
+autocreate_box_match(const ARRAY_TYPE(mailbox_settings) *boxes,
+ struct mail_namespace *ns, const char *name,
+ bool only_subscribed, unsigned int *idx_r)
+{
+ struct mailbox_settings *const *sets;
+ unsigned int i, count;
+ size_t len, name_len = strlen(name);
+ enum autocreate_match_result result = 0;
+ char sep = mail_namespace_get_sep(ns);
+
+ *idx_r = UINT_MAX;
+
+ sets = array_get(boxes, &count);
+ for (i = 0; i < count; i++) {
+ if (only_subscribed &&
+ strcmp(sets[i]->autocreate, MAILBOX_SET_AUTO_SUBSCRIBE) != 0)
+ continue;
+ len = I_MIN(name_len, strlen(sets[i]->name));
+ if (strncmp(name, sets[i]->name, len) != 0)
+ continue;
+
+ if (name[len] == '\0' && sets[i]->name[len] == '\0') {
+ result |= AUTOCREATE_MATCH_RESULT_YES;
+ *idx_r = i;
+ } else if (name[len] == '\0' && sets[i]->name[len] == sep)
+ result |= AUTOCREATE_MATCH_RESULT_CHILDREN;
+ else if (name[len] == sep && sets[i]->name[len] == '\0')
+ result |= AUTOCREATE_MATCH_RESULT_PARENT;
+ }
+ return result;
+}
+
+const struct mailbox_info *
+mailbox_list_iter_autocreate_filter(struct mailbox_list_iterate_context *ctx,
+ const struct mailbox_info *_info)
+{
+ struct mailbox_list_autocreate_iterate_context *actx =
+ ctx->autocreate_ctx;
+ if (actx == NULL || _info == NULL)
+ return _info;
+ actx->new_info = *_info;
+ struct mailbox_info *info = &actx->new_info;
+ enum autocreate_match_result match, match2;
+ unsigned int idx;
+
+ match = autocreate_box_match(&actx->box_sets, ctx->list->ns,
+ info->vname, FALSE, &idx);
+
+ if (!actx->listing_autoboxes) {
+ if ((match & AUTOCREATE_MATCH_RESULT_YES) != 0) {
+ /* we have an exact match in the list.
+ don't list it at the end. */
+ array_delete(&actx->boxes, idx, 1);
+ array_delete(&actx->box_sets, idx, 1);
+ }
+ if ((match & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0 &&
+ hash_table_lookup(actx->duplicate_vnames, info->vname) == NULL) {
+ /* Prevent autocreate-iteration from adding this
+ mailbox as a duplicate. For example we're listing %
+ and we're here because "foo" was found. However,
+ there's also "foo/bar" with auto=create. We're
+ telling here to the autocreate iteration code that
+ "foo" was already found and it doesn't need to add
+ it again. */
+ char *vname = p_strdup(ctx->pool, info->vname);
+ hash_table_insert(actx->duplicate_vnames, vname, vname);
+ }
+ }
+
+ if ((match & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) {
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ info->flags |= MAILBOX_CHILD_SUBSCRIBED;
+ else {
+ info->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ info->flags |= MAILBOX_CHILDREN;
+ }
+ }
+
+ /* make sure the mailbox existence flags are correct. */
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0)
+ match2 = match;
+ else {
+ match2 = autocreate_box_match(&actx->all_ns_box_sets,
+ ctx->list->ns, info->vname,
+ FALSE, &idx);
+ }
+ if ((match2 & AUTOCREATE_MATCH_RESULT_YES) != 0)
+ info->flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT);
+ if ((match2 & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) {
+ info->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ info->flags |= MAILBOX_CHILDREN;
+ }
+
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 &&
+ (ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0) {
+ /* we're listing all mailboxes and want \Subscribed flag */
+ match2 = autocreate_box_match(&actx->all_ns_box_sets,
+ ctx->list->ns, info->vname,
+ TRUE, &idx);
+ if ((match2 & AUTOCREATE_MATCH_RESULT_YES) != 0) {
+ /* mailbox is also marked as autosubscribe */
+ info->flags |= MAILBOX_SUBSCRIBED;
+ }
+ if ((match2 & AUTOCREATE_MATCH_RESULT_CHILDREN) != 0) {
+ /* mailbox also has a children marked as
+ autosubscribe */
+ info->flags |= MAILBOX_CHILD_SUBSCRIBED;
+ }
+ }
+
+ if ((match & AUTOCREATE_MATCH_RESULT_PARENT) != 0) {
+ /* there are autocreate parent boxes.
+ set their children flag states. */
+ struct autocreate_box *autobox;
+ size_t name_len;
+ char sep = mail_namespace_get_sep(ctx->list->ns);
+
+ array_foreach_modifiable(&actx->boxes, autobox) {
+ name_len = strlen(autobox->name);
+ if (!str_begins(info->vname, autobox->name) ||
+ info->vname[name_len] != sep)
+ continue;
+
+ if ((info->flags & MAILBOX_NONEXISTENT) == 0)
+ autobox->flags |= MAILBOX_CHILDREN;
+ if ((info->flags & MAILBOX_SUBSCRIBED) != 0)
+ autobox->flags |= MAILBOX_CHILD_SUBSCRIBED;
+ autobox->child_listed = TRUE;
+ }
+ }
+ return info;
+}
+
+static bool autocreate_iter_autobox(struct mailbox_list_iterate_context *ctx,
+ const struct autocreate_box *autobox)
+{
+ struct mailbox_list_autocreate_iterate_context *actx =
+ ctx->autocreate_ctx;
+ enum imap_match_result match;
+
+ i_zero(&actx->new_info);
+ actx->new_info.ns = ctx->list->ns;
+ actx->new_info.vname = autobox->name;
+ actx->new_info.flags = autobox->flags;
+
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0)
+ actx->new_info.flags |= MAILBOX_SUBSCRIBED;
+
+ if ((actx->new_info.flags & MAILBOX_CHILDREN) == 0) {
+ if ((ctx->list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0 &&
+ ctx->list->set.maildir_name[0] == '\0') {
+ /* mailbox format using files (e.g. mbox)
+ without DIRNAME specified */
+ actx->new_info.flags |= MAILBOX_NOINFERIORS;
+ } else {
+ actx->new_info.flags |= MAILBOX_NOCHILDREN;
+ }
+ }
+
+ match = imap_match(ctx->glob, actx->new_info.vname);
+ if (match == IMAP_MATCH_YES) {
+ actx->new_info.special_use =
+ *autobox->set->special_use == '\0' ? NULL :
+ autobox->set->special_use;
+ return TRUE;
+ }
+ if ((match & IMAP_MATCH_PARENT) != 0 && !autobox->child_listed) {
+ enum mailbox_info_flags old_flags = actx->new_info.flags;
+ char sep = mail_namespace_get_sep(ctx->list->ns);
+ const char *p;
+ char *vname;
+
+ /* e.g. autocreate=foo/bar and we're listing % */
+ actx->new_info.flags = MAILBOX_NONEXISTENT |
+ (old_flags & (MAILBOX_CHILDREN |
+ MAILBOX_CHILD_SUBSCRIBED));
+ if ((old_flags & MAILBOX_NONEXISTENT) == 0) {
+ actx->new_info.flags |= MAILBOX_CHILDREN;
+ actx->new_info.flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ }
+ if ((old_flags & MAILBOX_SUBSCRIBED) != 0)
+ actx->new_info.flags |= MAILBOX_CHILD_SUBSCRIBED;
+ do {
+ p = strrchr(actx->new_info.vname, sep);
+ i_assert(p != NULL);
+ actx->new_info.vname = vname =
+ p_strdup_until(ctx->pool,
+ actx->new_info.vname, p);
+ match = imap_match(ctx->glob, actx->new_info.vname);
+ } while (match != IMAP_MATCH_YES);
+
+ if (hash_table_lookup(actx->duplicate_vnames, vname) == NULL) {
+ hash_table_insert(actx->duplicate_vnames, vname, vname);
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static const struct mailbox_info *
+mailbox_list_iter_next_call(struct mailbox_list_iterate_context *ctx)
+{
+ const struct mailbox_info *info;
+ const struct mailbox_settings *set;
+
+ info = ctx->list->v.iter_next(ctx);
+ if (info == NULL)
+ return NULL;
+
+ ctx->list->ns->flags |= NAMESPACE_FLAG_USABLE;
+ if ((ctx->flags & MAILBOX_LIST_ITER_RETURN_SPECIALUSE) != 0) {
+ set = mailbox_settings_find(ctx->list->ns, info->vname);
+ if (set != NULL && *set->special_use != '\0') {
+ ctx->specialuse_info = *info;
+ ctx->specialuse_info.special_use =
+ *set->special_use == '\0' ? NULL :
+ set->special_use;
+ info = &ctx->specialuse_info;
+ }
+ }
+
+ return mailbox_list_iter_autocreate_filter(ctx, info);
+}
+
+const struct mailbox_info *
+mailbox_list_iter_default_next(struct mailbox_list_iterate_context *ctx)
+{
+ struct mailbox_list_autocreate_iterate_context *actx =
+ ctx->autocreate_ctx;
+ const struct autocreate_box *autoboxes, *autobox;
+ unsigned int count;
+
+ if (actx == NULL)
+ return NULL;
+
+ /* do not drop boxes anymore */
+ actx->listing_autoboxes = TRUE;
+
+ /* list missing mailboxes */
+ autoboxes = array_get(&actx->boxes, &count);
+ while (actx->idx < count) {
+ autobox = &autoboxes[actx->idx++];
+ if (autocreate_iter_autobox(ctx, autobox))
+ return &actx->new_info;
+ }
+ i_assert(array_count(&actx->boxes) == array_count(&actx->box_sets));
+ return NULL;
+}
+
+static bool
+special_use_selection(struct mailbox_list_iterate_context *ctx,
+ const struct mailbox_info *info)
+{
+ if ((ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0 &&
+ (ctx->flags & MAILBOX_LIST_ITER_SELECT_SPECIALUSE) != 0) {
+ /* LIST (SPECIAL-USE RECURSIVEMATCH) used. for now we support
+ this only for namespace prefixes */
+ if ((info->flags & MAILBOX_CHILD_SPECIALUSE) != 0)
+ return TRUE;
+ }
+ return (ctx->flags & MAILBOX_LIST_ITER_SELECT_SPECIALUSE) == 0 ||
+ info->special_use != NULL;
+}
+
+const struct mailbox_info *
+mailbox_list_iter_next(struct mailbox_list_iterate_context *ctx)
+{
+ const struct mailbox_info *info;
+
+ if (ctx == &mailbox_list_iter_failed)
+ return NULL;
+ do {
+ T_BEGIN {
+ info = mailbox_list_iter_next_call(ctx);
+ } T_END;
+ } while (info != NULL && !special_use_selection(ctx, info));
+ return info;
+}
+
+int mailbox_list_iter_deinit(struct mailbox_list_iterate_context **_ctx)
+{
+ struct mailbox_list_iterate_context *ctx = *_ctx;
+
+ *_ctx = NULL;
+
+ if (ctx == &mailbox_list_iter_failed)
+ return -1;
+ if (ctx->autocreate_ctx != NULL)
+ hash_table_destroy(&ctx->autocreate_ctx->duplicate_vnames);
+ return ctx->list->v.iter_deinit(ctx);
+}
+
+static void node_fix_parents(struct mailbox_node *node)
+{
+ /* If we happened to create any of the parents, we need to mark them
+ nonexistent. */
+ node = node->parent;
+ for (; node != NULL; node = node->parent) {
+ if ((node->flags & MAILBOX_MATCHED) == 0)
+ node->flags |= MAILBOX_NONEXISTENT;
+ }
+}
+
+static void
+mailbox_list_iter_update_real(struct mailbox_list_iter_update_context *ctx,
+ const char *name)
+{
+ struct mail_namespace *ns = ctx->iter_ctx->list->ns;
+ struct mailbox_node *node;
+ enum mailbox_info_flags create_flags, always_flags;
+ enum imap_match_result match;
+ const char *p;
+ bool created, add_matched;
+
+ create_flags = MAILBOX_NOCHILDREN;
+ always_flags = ctx->leaf_flags;
+ add_matched = TRUE;
+
+ for (;;) {
+ created = FALSE;
+ match = imap_match(ctx->glob, name);
+ if (match == IMAP_MATCH_YES) {
+ node = ctx->update_only ?
+ mailbox_tree_lookup(ctx->tree_ctx, name) :
+ mailbox_tree_get(ctx->tree_ctx, name, &created);
+ if (created) {
+ node->flags = create_flags;
+ if (create_flags != 0)
+ node_fix_parents(node);
+ }
+ if (node != NULL) {
+ if (!ctx->update_only && add_matched)
+ node->flags |= MAILBOX_MATCHED;
+ if ((always_flags & MAILBOX_CHILDREN) != 0)
+ node->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ node->flags |= always_flags;
+ }
+ /* We don't want to show the parent mailboxes unless
+ something else matches them, but if they are matched
+ we want to show them having child subscriptions */
+ add_matched = FALSE;
+ } else {
+ if ((match & IMAP_MATCH_PARENT) == 0)
+ break;
+ /* We've a (possibly) non-subscribed parent mailbox
+ which has a subscribed child mailbox. Make sure we
+ return the parent mailbox. */
+ }
+
+ if (!ctx->match_parents)
+ break;
+
+ /* see if parent matches */
+ p = strrchr(name, mail_namespace_get_sep(ns));
+ if (p == NULL)
+ break;
+
+ name = t_strdup_until(name, p);
+ create_flags |= MAILBOX_NONEXISTENT;
+ create_flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ always_flags = MAILBOX_CHILDREN | ctx->parent_flags;
+ }
+}
+
+void mailbox_list_iter_update(struct mailbox_list_iter_update_context *ctx,
+ const char *name)
+{
+ T_BEGIN {
+ mailbox_list_iter_update_real(ctx, name);
+ } T_END;
+}
diff --git a/src/lib-storage/list/mailbox-list-maildir-iter.c b/src/lib-storage/list/mailbox-list-maildir-iter.c
new file mode 100644
index 0000000..df5fdd7
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-maildir-iter.c
@@ -0,0 +1,524 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "ioloop.h"
+#include "unlink-directory.h"
+#include "unichar.h"
+#include "imap-match.h"
+#include "imap-utf7.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-maildir.h"
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+struct maildir_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+
+ const char *dir;
+ char prefix_char;
+
+ struct mailbox_tree_context *tree_ctx;
+ struct mailbox_tree_iterate_context *tree_iter;
+
+ struct mailbox_info info;
+};
+
+static void node_fix_parents(struct mailbox_node *node)
+{
+ /* Fix parent nodes' children states. also if we happened to create any
+ of the parents, we need to mark them nonexistent. */
+ node = node->parent;
+ for (; node != NULL; node = node->parent) {
+ if ((node->flags & MAILBOX_MATCHED) == 0)
+ node->flags |= MAILBOX_NONEXISTENT;
+
+ node->flags |= MAILBOX_CHILDREN;
+ node->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ }
+}
+
+static void
+maildir_fill_parents(struct maildir_list_iterate_context *ctx,
+ struct imap_match_glob *glob, bool update_only,
+ const char *vname)
+{
+ struct mail_namespace *ns = ctx->ctx.list->ns;
+ struct mailbox_node *node;
+ const char *p;
+ size_t vname_len = strlen(vname);
+ bool created;
+ char ns_sep = mail_namespace_get_sep(ns);
+
+ while ((p = strrchr(vname, ns_sep)) != NULL) {
+ vname = t_strdup_until(vname, p);
+ if (imap_match(glob, vname) != IMAP_MATCH_YES)
+ continue;
+
+ if (ns->prefix_len > 0 && vname_len == ns->prefix_len-1 &&
+ strncmp(vname, ns->prefix, ns->prefix_len - 1) == 0 &&
+ vname[ns->prefix_len-1] == ns_sep) {
+ /* don't return matches to namespace prefix itself */
+ continue;
+ }
+
+ created = FALSE;
+ node = update_only ?
+ mailbox_tree_lookup(ctx->tree_ctx, vname) :
+ mailbox_tree_get(ctx->tree_ctx, vname, &created);
+ if (node != NULL) {
+ if (created) {
+ /* we haven't yet seen this mailbox,
+ but we might see it later */
+ node->flags = MAILBOX_NONEXISTENT;
+ }
+ if (!update_only)
+ node->flags |= MAILBOX_MATCHED;
+ node->flags |= MAILBOX_CHILDREN;
+ node->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ node_fix_parents(node);
+ }
+ }
+}
+
+static void maildir_set_children(struct maildir_list_iterate_context *ctx,
+ const char *vname)
+{
+ struct mailbox_node *node;
+ const char *p;
+ char hierarchy_sep;
+
+ hierarchy_sep = mail_namespace_get_sep(ctx->ctx.list->ns);
+
+ /* mark the first existing parent as containing children */
+ while ((p = strrchr(vname, hierarchy_sep)) != NULL) {
+ vname = t_strdup_until(vname, p);
+
+ node = mailbox_tree_lookup(ctx->tree_ctx, vname);
+ if (node != NULL) {
+ node->flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ node->flags |= MAILBOX_CHILDREN;
+ break;
+ }
+ }
+}
+
+static int
+maildir_fill_inbox(struct maildir_list_iterate_context *ctx,
+ struct imap_match_glob *glob, const char *inbox_name,
+ bool update_only)
+{
+ struct mailbox_node *node;
+ enum mailbox_info_flags flags;
+ enum imap_match_result match;
+ bool created;
+ int ret;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_NO_AUTO_BOXES) == 0) {
+ /* always show INBOX */
+ } else {
+ /* INBOX may be Maildir root or completely elsewhere.
+ show it only if it has already been created */
+ ret = mailbox_list_mailbox(ctx->ctx.list, "INBOX", &flags);
+ if (ret < 0)
+ return -1;
+ if ((flags & MAILBOX_NONEXISTENT) != 0)
+ update_only = TRUE;
+ }
+
+ if (update_only) {
+ node = mailbox_tree_lookup(ctx->tree_ctx, inbox_name);
+ if (node != NULL)
+ node->flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT);
+ return 0;
+ }
+
+ /* add the INBOX only if it matches the patterns */
+ match = imap_match(glob, inbox_name);
+ if (match == IMAP_MATCH_PARENT)
+ maildir_fill_parents(ctx, glob, FALSE, inbox_name);
+ else if (match == IMAP_MATCH_YES) {
+ node = mailbox_tree_get(ctx->tree_ctx, inbox_name, &created);
+ if (created)
+ node->flags = MAILBOX_NOCHILDREN;
+ else
+ node->flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT);
+ node->flags |= MAILBOX_MATCHED;
+ }
+ return 0;
+}
+
+static bool
+maildir_get_type(const char *dir, const char *fname,
+ enum mailbox_list_file_type *type_r,
+ enum mailbox_info_flags *flags)
+{
+ const char *path;
+ struct stat st;
+
+ path = *fname == '\0' ? dir :
+ t_strdup_printf("%s/%s", dir, fname);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ /* just deleted? */
+ *flags |= MAILBOX_NONEXISTENT;
+ } else {
+ *flags |= MAILBOX_NOSELECT;
+ }
+ return FALSE;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ *type_r = MAILBOX_LIST_FILE_TYPE_DIR;
+ return TRUE;
+ } else {
+ if (str_begins(fname, ".nfs"))
+ *flags |= MAILBOX_NONEXISTENT;
+ else
+ *flags |= MAILBOX_NOSELECT;
+ return FALSE;
+ }
+}
+
+int maildir_list_get_mailbox_flags(struct mailbox_list *list,
+ const char *dir, const char *fname,
+ enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags_r)
+{
+ *flags_r = 0;
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ case MAILBOX_LIST_FILE_TYPE_OTHER:
+ break;
+ case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
+ case MAILBOX_LIST_FILE_TYPE_SYMLINK:
+ /* need to check with stat() to be sure */
+ if (!list->mail_set->maildir_stat_dirs && *fname != '\0' &&
+ strcmp(list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0 &&
+ !str_begins(fname, ".nfs")) {
+ /* just assume it's a valid mailbox */
+ return 1;
+ }
+
+ if (!maildir_get_type(dir, fname, &type, flags_r))
+ return 0;
+ break;
+ }
+
+ switch (type) {
+ case MAILBOX_LIST_FILE_TYPE_DIR:
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 0;
+ }
+ break;
+ case MAILBOX_LIST_FILE_TYPE_FILE:
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) == 0) {
+ *flags_r |= MAILBOX_NOSELECT;
+ return 0;
+ }
+ break;
+ case MAILBOX_LIST_FILE_TYPE_OTHER:
+ *flags_r |= MAILBOX_NOSELECT;
+ return 0;
+
+ case MAILBOX_LIST_FILE_TYPE_UNKNOWN:
+ case MAILBOX_LIST_FILE_TYPE_SYMLINK:
+ i_unreached();
+ }
+ if (*fname != '\0') {
+ /* this tells maildir storage code that it doesn't need to
+ see if cur/ exists, because just the existence of .dir/
+ assumes that the mailbox exists. */
+ *flags_r |= MAILBOX_SELECT;
+ }
+ return 1;
+}
+
+static bool maildir_delete_trash_dir(struct maildir_list_iterate_context *ctx,
+ const char *fname)
+{
+ const char *path, *error;
+ struct stat st;
+
+ if (fname[1] != ctx->prefix_char || ctx->prefix_char == '\0' ||
+ strcmp(fname+2, MAILBOX_LIST_MAILDIR_TRASH_DIR_NAME) != 0)
+ return FALSE;
+
+ /* this directory is in the middle of being deleted, or the process
+ trying to delete it had died. delete it ourself if it's been there
+ longer than one hour. */
+ path = t_strdup_printf("%s/%s", ctx->dir, fname);
+ if (stat(path, &st) == 0 &&
+ st.st_mtime < ioloop_time - 3600)
+ (void)mailbox_list_delete_trash(path, &error);
+
+ return TRUE;
+}
+
+static int
+maildir_fill_readdir_entry(struct maildir_list_iterate_context *ctx,
+ struct imap_match_glob *glob, const struct dirent *d,
+ bool update_only)
+{
+ struct mailbox_list *list = ctx->ctx.list;
+ const char *fname, *storage_name, *vname;
+ enum mailbox_info_flags flags;
+ enum imap_match_result match;
+ struct mailbox_node *node;
+ bool created;
+ int ret;
+
+ fname = d->d_name;
+ if (fname[0] == ctx->prefix_char)
+ storage_name = fname + 1;
+ else {
+ if (ctx->prefix_char != '\0' || fname[0] == '.')
+ return 0;
+ storage_name = fname;
+ }
+
+ /* skip . and .. */
+ if (fname[0] == '.' &&
+ (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0')))
+ return 0;
+
+ vname = mailbox_list_get_vname(list, storage_name);
+ if (!uni_utf8_str_is_valid(vname)) {
+ /* the storage_name is completely invalid, rename it to
+ something more sensible. we could do this for all names that
+ aren't valid mUTF-7, but that might lead to accidents in
+ future when UTF-8 storage names are used */
+ const char *src = t_strdup_printf("%s/%s", ctx->dir, fname);
+ string_t *destvname = t_str_new(128);
+ string_t *dest = t_str_new(128);
+
+ if (uni_utf8_get_valid_data((const void *)fname,
+ strlen(fname), destvname))
+ i_unreached(); /* already checked that it was invalid */
+
+ str_append(dest, ctx->dir);
+ str_append_c(dest, '/');
+ (void)imap_utf8_to_utf7(str_c(destvname), dest);
+
+ if (rename(src, str_c(dest)) < 0 && errno != ENOENT)
+ e_error(ctx->ctx.list->ns->user->event,
+ "rename(%s, %s) failed: %m", src, str_c(dest));
+ /* just skip this in this iteration, we'll see it on the
+ next list */
+ return 0;
+ }
+
+ /* make sure the pattern matches */
+ match = imap_match(glob, vname);
+ if ((match & (IMAP_MATCH_YES | IMAP_MATCH_PARENT)) == 0)
+ return 0;
+
+ /* check if this is an actual mailbox */
+ if (maildir_delete_trash_dir(ctx, fname))
+ return 0;
+
+ if ((ctx->ctx.flags & MAILBOX_LIST_ITER_SKIP_ALIASES) != 0) {
+ ret = mailbox_list_dirent_is_alias_symlink(list, ctx->dir, d);
+ if (ret != 0)
+ return ret < 0 ? -1 : 0;
+ }
+ T_BEGIN {
+ ret = list->v.get_mailbox_flags(list, ctx->dir, fname,
+ mailbox_list_get_file_type(d), &flags);
+ } T_END;
+ if (ret <= 0)
+ return ret;
+
+ /* we know the children flags ourself, so ignore if any of
+ them were set. */
+ flags &= ENUM_NEGATE(MAILBOX_NOINFERIORS | MAILBOX_CHILDREN | MAILBOX_NOCHILDREN);
+
+ if ((match & IMAP_MATCH_PARENT) != 0)
+ maildir_fill_parents(ctx, glob, update_only, vname);
+ else {
+ created = FALSE;
+ node = update_only ?
+ mailbox_tree_lookup(ctx->tree_ctx, vname) :
+ mailbox_tree_get(ctx->tree_ctx, vname, &created);
+
+ if (node != NULL) {
+ if (created)
+ node->flags = MAILBOX_NOCHILDREN;
+ else
+ node->flags &= ENUM_NEGATE(MAILBOX_NONEXISTENT);
+ if (!update_only)
+ node->flags |= MAILBOX_MATCHED;
+ node->flags |= flags;
+ node_fix_parents(node);
+ } else {
+ i_assert(update_only);
+ maildir_set_children(ctx, vname);
+ }
+ }
+ return 0;
+}
+
+static int
+maildir_fill_readdir(struct maildir_list_iterate_context *ctx,
+ struct imap_match_glob *glob, bool update_only)
+{
+ struct mailbox_list *list = ctx->ctx.list;
+ struct mail_namespace *ns = list->ns;
+ DIR *dirp;
+ struct dirent *d;
+ const char *vname;
+ int ret = 0;
+
+ dirp = opendir(ctx->dir);
+ if (dirp == NULL) {
+ if (errno == EACCES) {
+ mailbox_list_set_critical(list, "%s",
+ mail_error_eacces_msg("opendir", ctx->dir));
+ } else if (errno != ENOENT) {
+ mailbox_list_set_critical(list,
+ "opendir(%s) failed: %m", ctx->dir);
+ return -1;
+ }
+ return 0;
+ }
+
+ while ((d = readdir(dirp)) != NULL) {
+ T_BEGIN {
+ ret = maildir_fill_readdir_entry(ctx, glob, d,
+ update_only);
+ } T_END;
+ if (ret < 0)
+ break;
+ }
+
+ if (closedir(dirp) < 0) {
+ mailbox_list_set_critical(list, "readdir(%s) failed: %m",
+ ctx->dir);
+ return -1;
+ }
+ if (ret < 0)
+ return -1;
+
+ if ((ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0) {
+ /* make sure INBOX is listed */
+ return maildir_fill_inbox(ctx, glob, "INBOX", update_only);
+ } else if ((ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
+ /* show shared INBOX. */
+ vname = mailbox_list_get_vname(ns->list, "INBOX");
+ return maildir_fill_inbox(ctx, glob, vname, update_only);
+ } else {
+ return 0;
+ }
+}
+
+struct mailbox_list_iterate_context *
+maildir_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+ struct maildir_list_iterate_context *ctx;
+ pool_t pool;
+ char ns_sep = mail_namespace_get_sep(_list->ns);
+ int ret;
+
+ pool = pool_alloconly_create("mailbox list maildir iter", 1024);
+ ctx = p_new(pool, struct maildir_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = _list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, ns_sep);
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->tree_ctx = mailbox_tree_init(ns_sep);
+ ctx->info.ns = _list->ns;
+ ctx->prefix_char = strcmp(_list->name, MAILBOX_LIST_NAME_IMAPDIR) == 0 ?
+ '\0' : list->sep;
+
+ if (_list->set.iter_from_index_dir)
+ ctx->dir = _list->set.index_dir;
+ else
+ ctx->dir = _list->set.root_dir;
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0) {
+ /* Listing only subscribed mailboxes.
+ Flags are set later if needed. */
+ bool default_nonexistent =
+ (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0;
+
+ mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree_ctx,
+ default_nonexistent);
+ }
+
+ if ((flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0 ||
+ (flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) == 0) {
+ /* Add/update mailbox list with flags */
+ bool update_only =
+ (flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) != 0;
+
+ T_BEGIN {
+ ret = maildir_fill_readdir(ctx, ctx->ctx.glob,
+ update_only);
+ } T_END;
+ if (ret < 0) {
+ ctx->ctx.failed = TRUE;
+ return &ctx->ctx;
+ }
+ }
+
+ ctx->tree_iter = mailbox_tree_iterate_init(ctx->tree_ctx, NULL,
+ MAILBOX_MATCHED);
+ return &ctx->ctx;
+}
+
+int maildir_list_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct maildir_list_iterate_context *ctx =
+ (struct maildir_list_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ if (ctx->tree_iter != NULL)
+ mailbox_tree_iterate_deinit(&ctx->tree_iter);
+ mailbox_tree_deinit(&ctx->tree_ctx);
+ pool_unref(&ctx->ctx.pool);
+ return ret;
+}
+
+const struct mailbox_info *
+maildir_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct maildir_list_iterate_context *ctx =
+ (struct maildir_list_iterate_context *)_ctx;
+ struct mailbox_node *node;
+
+ if (_ctx->failed)
+ return NULL;
+
+ node = mailbox_tree_iterate_next(ctx->tree_iter, &ctx->info.vname);
+ if (node == NULL)
+ return mailbox_list_iter_default_next(_ctx);
+
+ ctx->info.flags = node->flags;
+ if (strcmp(ctx->info.vname, "INBOX") == 0 &&
+ mail_namespace_is_inbox_noinferiors(ctx->info.ns)) {
+ i_assert((ctx->info.flags & MAILBOX_NOCHILDREN) != 0);
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_NOCHILDREN);
+ ctx->info.flags |= MAILBOX_NOINFERIORS;
+ }
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_SUBSCRIBED) != 0 &&
+ (_ctx->flags & MAILBOX_LIST_ITER_SELECT_SUBSCRIBED) == 0) {
+ /* we're listing all mailboxes but we want to know
+ \Subscribed flags */
+ mailbox_list_set_subscription_flags(_ctx->list, ctx->info.vname,
+ &ctx->info.flags);
+ }
+ return &ctx->info;
+}
diff --git a/src/lib-storage/list/mailbox-list-maildir.c b/src/lib-storage/list/mailbox-list-maildir.c
new file mode 100644
index 0000000..265f3d0
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-maildir.c
@@ -0,0 +1,546 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "hostpid.h"
+#include "eacces-error.h"
+#include "mkdir-parents.h"
+#include "str.h"
+#include "subscription-file.h"
+#include "mailbox-list-subscriptions.h"
+#include "mailbox-list-delete.h"
+#include "mailbox-list-maildir.h"
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#define MAILDIR_GLOBAL_TEMP_PREFIX "temp."
+#define IMAPDIR_GLOBAL_TEMP_PREFIX ".temp."
+
+extern struct mailbox_list maildir_mailbox_list;
+extern struct mailbox_list imapdir_mailbox_list;
+
+static struct mailbox_list *maildir_list_alloc(void)
+{
+ struct maildir_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("maildir++ list", 2048);
+ list = p_new(pool, struct maildir_mailbox_list, 1);
+ list->list = maildir_mailbox_list;
+ list->list.pool = pool;
+ list->sep = '.';
+
+ list->global_temp_prefix = MAILDIR_GLOBAL_TEMP_PREFIX;
+ list->temp_prefix = p_strconcat(pool, list->global_temp_prefix,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static struct mailbox_list *imapdir_list_alloc(void)
+{
+ struct maildir_mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("imapdir list", 1024);
+ list = p_new(pool, struct maildir_mailbox_list, 1);
+ list->list = imapdir_mailbox_list;
+ list->list.pool = pool;
+ list->sep = '.';
+
+ list->global_temp_prefix = IMAPDIR_GLOBAL_TEMP_PREFIX;
+ list->temp_prefix = p_strconcat(pool, list->global_temp_prefix,
+ my_hostname, ".", my_pid, ".", NULL);
+ return &list->list;
+}
+
+static void maildir_list_deinit(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ pool_unref(&list->list.pool);
+}
+
+static const char *
+maildir_list_get_dirname_path(struct mailbox_list *list, const char *dir,
+ const char *name)
+{
+ if (*name == '\0')
+ return dir;
+ else if (strcmp(list->name, imapdir_mailbox_list.name) == 0)
+ return t_strdup_printf("%s/%s", dir, name);
+
+ return t_strdup_printf("%s/%c%s", dir,
+ mailbox_list_get_hierarchy_sep(list), name);
+}
+
+static const char *
+maildir_list_get_absolute_path(struct mailbox_list *list, const char *name)
+{
+ const char *p;
+
+ if (!mailbox_list_try_get_absolute_path(list, &name)) {
+ /* fallback to using as ~name */
+ return name;
+ }
+
+ p = strrchr(name, '/');
+ if (p == NULL)
+ return name;
+ return maildir_list_get_dirname_path(list, t_strdup_until(name, p),
+ p+1);
+}
+
+static char maildir_list_get_hierarchy_sep(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ return list->sep;
+}
+
+static int
+maildir_list_get_path(struct mailbox_list *_list, const char *name,
+ enum mailbox_list_path_type type, const char **path_r)
+{
+ const char *root_dir;
+
+ if (name == NULL) {
+ /* return root directories */
+ return mailbox_list_set_get_root_path(&_list->set, type,
+ path_r) ? 1 : 0;
+ }
+
+ if (_list->mail_set->mail_full_filesystem_access &&
+ (*name == '/' || *name == '~')) {
+ *path_r = maildir_list_get_absolute_path(_list, name);
+ return 1;
+ }
+
+ root_dir = _list->set.root_dir;
+ switch (type) {
+ case MAILBOX_LIST_PATH_TYPE_DIR:
+ case MAILBOX_LIST_PATH_TYPE_MAILBOX:
+ break;
+ case MAILBOX_LIST_PATH_TYPE_ALT_DIR:
+ case MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX:
+ if (_list->set.alt_dir == NULL)
+ return 0;
+ root_dir = _list->set.alt_dir;
+ break;
+ case MAILBOX_LIST_PATH_TYPE_CONTROL:
+ if (_list->set.control_dir != NULL) {
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.control_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_CACHE:
+ if (_list->set.index_cache_dir != NULL) {
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_cache_dir, name);
+ return 1;
+ }
+ /* fall through */
+ case MAILBOX_LIST_PATH_TYPE_INDEX:
+ if (_list->set.index_dir != NULL) {
+ if (*_list->set.index_dir == '\0')
+ return 0;
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_dir, name);
+ return 1;
+ }
+ break;
+ case MAILBOX_LIST_PATH_TYPE_INDEX_PRIVATE:
+ if (_list->set.index_pvt_dir == NULL)
+ return 0;
+ *path_r = maildir_list_get_dirname_path(_list,
+ _list->set.index_pvt_dir, name);
+ return 1;
+ case MAILBOX_LIST_PATH_TYPE_LIST_INDEX:
+ i_unreached();
+ }
+
+ if (type == MAILBOX_LIST_PATH_TYPE_ALT_DIR ||
+ type == MAILBOX_LIST_PATH_TYPE_ALT_MAILBOX) {
+ /* don't use inbox_path */
+ } else if (strcmp(name, "INBOX") == 0 && _list->set.inbox_path != NULL) {
+ *path_r = _list->set.inbox_path;
+ return 1;
+ }
+
+ *path_r = maildir_list_get_dirname_path(_list, root_dir, name);
+ return 1;
+}
+
+static const char *
+maildir_list_get_temp_prefix(struct mailbox_list *_list, bool global)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+
+ return global ? list->global_temp_prefix : list->temp_prefix;
+}
+
+static int maildir_list_set_subscribed(struct mailbox_list *_list,
+ const char *name, bool set)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+ const char *path;
+
+ if (_list->set.subscription_fname == NULL) {
+ mailbox_list_set_error(_list, MAIL_ERROR_NOTPOSSIBLE,
+ "Subscriptions not supported");
+ return -1;
+ }
+
+ path = t_strconcat(_list->set.control_dir != NULL ?
+ _list->set.control_dir : _list->set.root_dir,
+ "/", _list->set.subscription_fname, NULL);
+
+ return subsfile_set_subscribed(_list, path, list->temp_prefix,
+ name, set);
+}
+
+static const char *
+mailbox_list_maildir_get_trash_dir(struct mailbox_list *_list)
+{
+ struct maildir_mailbox_list *list =
+ (struct maildir_mailbox_list *)_list;
+ const char *root_dir;
+
+ root_dir = mailbox_list_get_root_forced(_list, MAILBOX_LIST_PATH_TYPE_DIR);
+ return t_strdup_printf("%s/%c%c"MAILBOX_LIST_MAILDIR_TRASH_DIR_NAME,
+ root_dir, list->sep, list->sep);
+}
+
+static int
+maildir_list_delete_maildir(struct mailbox_list *list, const char *name)
+{
+ const char *path, *trash_dir;
+ int ret = 0;
+
+ trash_dir = mailbox_list_maildir_get_trash_dir(list);
+ ret = mailbox_list_delete_maildir_via_trash(list, name, trash_dir);
+ if (ret < 0)
+ return -1;
+
+ if (ret == 0) {
+ /* we could actually use just unlink_directory()
+ but error handling is easier this way :) */
+ if (mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path) <= 0)
+ i_unreached();
+ if (mailbox_list_delete_mailbox_nonrecursive(list, name,
+ path, TRUE) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int
+maildir_list_delete_mailbox(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ int ret;
+
+ if ((list->flags & MAILBOX_LIST_FLAG_MAILBOX_FILES) != 0) {
+ ret = mailbox_list_get_path(list, name,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+ ret = mailbox_list_delete_mailbox_file(list, name, path);
+ } else {
+ ret = maildir_list_delete_maildir(list, name);
+ }
+
+ i_assert(ret <= 0);
+ return mailbox_list_delete_finish_ret(list, name, ret == 0);
+}
+
+static int maildir_list_delete_dir(struct mailbox_list *list, const char *name)
+{
+ const char *path;
+ struct stat st;
+
+ /* with maildir++ there aren't any non-selectable mailboxes.
+ we'll always fail. */
+ if (mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR,
+ &path) <= 0)
+ i_unreached();
+ if (stat(path, &st) == 0) {
+ mailbox_list_set_error(list, MAIL_ERROR_EXISTS,
+ "Mailbox exists");
+ } else if (errno == ENOENT || errno == ENOTDIR) {
+ mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(list, name));
+ } else {
+ mailbox_list_set_critical(list, "stat(%s) failed: %m", path);
+ }
+ return -1;
+}
+
+static int rename_dir(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname,
+ enum mailbox_list_path_type type)
+{
+ const char *oldpath, *newpath;
+
+ if (mailbox_list_get_path(oldlist, oldname, type, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname, type, &newpath) <= 0)
+ return 0;
+
+ if (strcmp(oldpath, newpath) == 0)
+ return 0;
+
+ if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+maildir_rename_children(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ struct mailbox_list_iterate_context *iter;
+ const struct mailbox_info *info;
+ ARRAY(const char *) names_arr;
+ const char *pattern, *oldpath, *newpath, *old_childname, *new_childname;
+ const char *const *names, *old_vname, *new_vname;
+ unsigned int i, count;
+ size_t old_vnamelen;
+ pool_t pool;
+ char old_ns_sep;
+ int ret;
+
+ ret = 0;
+
+ /* first get the list of the children and save them to memory, because
+ we can't rely on readdir() not skipping files while the directory
+ is being modified. this doesn't protect against modifications by
+ other processes though. */
+ pool = pool_alloconly_create("Maildir++ children list", 1024);
+ i_array_init(&names_arr, 64);
+
+ old_vname = mailbox_list_get_vname(oldlist, oldname);
+ old_vnamelen = strlen(old_vname);
+
+ new_vname = mailbox_list_get_vname(newlist, newname);
+
+ old_ns_sep = mail_namespace_get_sep(oldlist->ns);
+ pattern = t_strdup_printf("%s%c*", old_vname, old_ns_sep);
+ iter = mailbox_list_iter_init(oldlist, pattern,
+ MAILBOX_LIST_ITER_RETURN_NO_FLAGS |
+ MAILBOX_LIST_ITER_RAW_LIST);
+ while ((info = mailbox_list_iter_next(iter)) != NULL) {
+ const char *name;
+
+ /* verify that the prefix matches, otherwise we could have
+ problems with mailbox names containing '%' and '*' chars */
+ if (strncmp(info->vname, old_vname, old_vnamelen) == 0 &&
+ info->vname[old_vnamelen] == old_ns_sep) {
+ name = p_strdup(pool, info->vname + old_vnamelen);
+ array_push_back(&names_arr, &name);
+ }
+ }
+ if (mailbox_list_iter_deinit(&iter) < 0) {
+ ret = -1;
+ names = NULL; count = 0;
+ } else {
+ names = array_get(&names_arr, &count);
+ }
+
+ for (i = 0; i < count; i++) {
+ old_childname = mailbox_list_get_storage_name(oldlist,
+ t_strconcat(old_vname, names[i], NULL));
+ if (strcmp(old_childname, new_vname) == 0) {
+ /* When doing RENAME "a" "a.b" we see "a.b" here.
+ We don't want to rename it anymore to "a.b.b". */
+ continue;
+ }
+
+ new_childname = mailbox_list_get_storage_name(newlist,
+ t_strconcat(new_vname, names[i], NULL));
+ if (mailbox_list_get_path(oldlist, old_childname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &newpath) <= 0)
+ i_unreached();
+
+ /* FIXME: it's possible to merge two mailboxes if either one of
+ them doesn't have existing root mailbox. 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 mailboxes have
+ identically named child mailbox they conflict. Just ignore
+ those and leave them under the old mailbox. */
+ if (rename(oldpath, newpath) == 0 || EDESTDIREXISTS(errno))
+ ret = 1;
+ else {
+ mailbox_list_set_critical(oldlist,
+ "rename(%s, %s) failed: %m", oldpath, newpath);
+ ret = -1;
+ break;
+ }
+
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+ (void)rename_dir(oldlist, old_childname, newlist, new_childname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE);
+ }
+ array_free(&names_arr);
+ pool_unref(&pool);
+
+ return ret;
+}
+
+static int
+maildir_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
+ struct mailbox_list *newlist, const char *newname)
+{
+ const char *oldpath, *newpath, *root_path;
+ int ret;
+ bool found;
+
+ /* NOTE: it's possible to rename a nonexistent mailbox which has
+ children. In that case we should ignore the rename() error. */
+ if (mailbox_list_get_path(oldlist, oldname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &oldpath) <= 0 ||
+ mailbox_list_get_path(newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX, &newpath) <= 0)
+ i_unreached();
+
+ root_path = mailbox_list_get_root_forced(oldlist,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (strcmp(oldpath, root_path) == 0) {
+ /* most likely INBOX */
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ t_strdup_printf("Renaming %s isn't supported.",
+ oldname));
+ return -1;
+ }
+
+ /* if we're renaming under another mailbox, require its permissions
+ to be same as ours. */
+ if (strchr(newname, mailbox_list_get_hierarchy_sep(newlist)) != NULL) {
+ struct mailbox_permissions old_perm, new_perm;
+
+ mailbox_list_get_permissions(oldlist, oldname, &old_perm);
+ mailbox_list_get_permissions(newlist, newname, &new_perm);
+
+ if ((new_perm.file_create_mode != old_perm.file_create_mode ||
+ new_perm.dir_create_mode != old_perm.dir_create_mode ||
+ new_perm.file_create_gid != old_perm.file_create_gid)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Renaming not supported across conflicting "
+ "directory permissions");
+ return -1;
+ }
+ }
+
+
+ ret = rename(oldpath, newpath);
+ if (ret == 0 || errno == ENOENT) {
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_CONTROL);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX);
+ (void)rename_dir(oldlist, oldname, newlist, newname,
+ MAILBOX_LIST_PATH_TYPE_INDEX_CACHE);
+
+ found = ret == 0;
+ T_BEGIN {
+ ret = maildir_rename_children(oldlist, oldname,
+ newlist, newname);
+ } T_END;
+ if (ret < 0)
+ return -1;
+ if (!found && ret == 0) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTFOUND,
+ T_MAILBOX_LIST_ERR_NOT_FOUND(oldlist, oldname));
+ return -1;
+ }
+
+ return 0;
+ }
+
+ if (EDESTDIREXISTS(errno)) {
+ mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
+ "Target mailbox already exists");
+ } else {
+ mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
+ oldpath, newpath);
+ }
+ return -1;
+}
+
+struct mailbox_list maildir_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS,
+ .props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
+ MAILBOX_LIST_PROP_NO_NOSELECT |
+ MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = maildir_list_alloc,
+ .deinit = maildir_list_deinit,
+ .get_hierarchy_sep = maildir_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = maildir_list_get_path,
+ .get_temp_prefix = maildir_list_get_temp_prefix,
+ .iter_init = maildir_list_iter_init,
+ .iter_next = maildir_list_iter_next,
+ .iter_deinit = maildir_list_iter_deinit,
+ .get_mailbox_flags = maildir_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = maildir_list_set_subscribed,
+ .delete_mailbox = maildir_list_delete_mailbox,
+ .delete_dir = maildir_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = maildir_list_rename_mailbox,
+ }
+};
+
+struct mailbox_list imapdir_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_IMAPDIR,
+ .props = MAILBOX_LIST_PROP_NO_MAILDIR_NAME |
+ MAILBOX_LIST_PROP_NO_ALT_DIR |
+ MAILBOX_LIST_PROP_NO_NOSELECT |
+ MAILBOX_LIST_PROP_NO_INTERNAL_NAMES,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = imapdir_list_alloc,
+ .deinit = maildir_list_deinit,
+ .get_hierarchy_sep = maildir_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = maildir_list_get_path,
+ .get_temp_prefix = maildir_list_get_temp_prefix,
+ .iter_init = maildir_list_iter_init,
+ .iter_next = maildir_list_iter_next,
+ .iter_deinit = maildir_list_iter_deinit,
+ .get_mailbox_flags = maildir_list_get_mailbox_flags,
+ .subscriptions_refresh = mailbox_list_subscriptions_refresh,
+ .set_subscribed = maildir_list_set_subscribed,
+ .delete_mailbox = maildir_list_delete_mailbox,
+ .delete_dir = maildir_list_delete_dir,
+ .delete_symlink = mailbox_list_delete_symlink_default,
+ .rename_mailbox = maildir_list_rename_mailbox,
+ }
+};
diff --git a/src/lib-storage/list/mailbox-list-maildir.h b/src/lib-storage/list/mailbox-list-maildir.h
new file mode 100644
index 0000000..3760378
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-maildir.h
@@ -0,0 +1,29 @@
+#ifndef MAILBOX_LIST_MAILDIR_H
+#define MAILBOX_LIST_MAILDIR_H
+
+#include "mailbox-list-private.h"
+
+/* When doing deletion via renaming it first to trash directory, use this as
+ the trash directory name */
+#define MAILBOX_LIST_MAILDIR_TRASH_DIR_NAME "DOVECOT-TRASHED"
+
+struct maildir_mailbox_list {
+ struct mailbox_list list;
+
+ const char *global_temp_prefix, *temp_prefix;
+ char sep;
+};
+
+struct mailbox_list_iterate_context *
+maildir_list_iter_init(struct mailbox_list *_list, const char *const *patterns,
+ enum mailbox_list_iter_flags flags);
+int maildir_list_iter_deinit(struct mailbox_list_iterate_context *ctx);
+const struct mailbox_info *
+maildir_list_iter_next(struct mailbox_list_iterate_context *ctx);
+
+int maildir_list_get_mailbox_flags(struct mailbox_list *list,
+ const char *dir, const char *fname,
+ enum mailbox_list_file_type type,
+ enum mailbox_info_flags *flags);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-none.c b/src/lib-storage/list/mailbox-list-none.c
new file mode 100644
index 0000000..fb5c608
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-none.c
@@ -0,0 +1,178 @@
+/* Copyright (c) 2006-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "imap-match.h"
+#include "mailbox-list-private.h"
+
+#define GLOBAL_TEMP_PREFIX ".temp."
+
+struct noop_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ struct mailbox_info inbox_info;
+ bool list_inbox:1;
+};
+
+extern struct mailbox_list none_mailbox_list;
+
+static struct mailbox_list *none_list_alloc(void)
+{
+ struct mailbox_list *list;
+ pool_t pool;
+
+ pool = pool_alloconly_create("none list", 2048);
+
+ list = p_new(pool, struct mailbox_list, 1);
+ *list = none_mailbox_list;
+ list->props = MAILBOX_LIST_PROP_NO_LIST_INDEX;
+ list->pool = pool;
+ return list;
+}
+
+static void none_list_deinit(struct mailbox_list *list)
+{
+ pool_unref(&list->pool);
+}
+
+static char none_list_get_hierarchy_sep(struct mailbox_list *list ATTR_UNUSED)
+{
+ return '/';
+}
+
+static int
+none_list_get_path(struct mailbox_list *list ATTR_UNUSED,
+ const char *name ATTR_UNUSED,
+ enum mailbox_list_path_type type ATTR_UNUSED,
+ const char **path_r ATTR_UNUSED)
+{
+ return 0;
+}
+
+static const char *
+none_list_get_temp_prefix(struct mailbox_list *list ATTR_UNUSED,
+ bool global ATTR_UNUSED)
+{
+ return GLOBAL_TEMP_PREFIX;
+}
+
+static int
+none_list_subscriptions_refresh(struct mailbox_list *src_list ATTR_UNUSED,
+ struct mailbox_list *dest_list ATTR_UNUSED)
+{
+ return 0;
+}
+
+static int none_list_set_subscribed(struct mailbox_list *list,
+ const char *name ATTR_UNUSED,
+ bool set ATTR_UNUSED)
+{
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Not supported");
+ return -1;
+}
+
+static int none_list_delete_mailbox(struct mailbox_list *list,
+ const char *name ATTR_UNUSED)
+{
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Not supported");
+ return -1;
+}
+
+static int none_list_delete_dir(struct mailbox_list *list,
+ const char *name ATTR_UNUSED)
+{
+ mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, "Not supported");
+ return -1;
+}
+
+static int
+none_list_rename_mailbox(struct mailbox_list *oldlist,
+ const char *oldname ATTR_UNUSED,
+ struct mailbox_list *newlist ATTR_UNUSED,
+ const char *newname ATTR_UNUSED)
+{
+ mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
+ "Not supported");
+ return -1;
+}
+
+static struct mailbox_list_iterate_context *
+none_list_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct noop_list_iterate_context *ctx;
+ pool_t pool;
+
+ pool = pool_alloconly_create("mailbox list none iter", 1024);
+ ctx = p_new(pool, struct noop_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE,
+ mail_namespace_get_sep(list->ns));
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+ if ((list->ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 &&
+ imap_match(ctx->ctx.glob, "INBOX") == IMAP_MATCH_YES) {
+ ctx->list_inbox = TRUE;
+ ctx->inbox_info.ns = list->ns;
+ ctx->inbox_info.vname = "INBOX";
+ }
+ return &ctx->ctx;
+}
+
+static int
+none_list_iter_deinit(struct mailbox_list_iterate_context *ctx)
+{
+ pool_unref(&ctx->pool);
+ return 0;
+}
+
+static const struct mailbox_info *
+none_list_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct noop_list_iterate_context *ctx =
+ (struct noop_list_iterate_context *)_ctx;
+
+ if (ctx->list_inbox) {
+ ctx->list_inbox = FALSE;
+ return &ctx->inbox_info;
+ }
+ return NULL;
+}
+
+static int
+none_list_get_mailbox_flags(struct mailbox_list *list ATTR_UNUSED,
+ const char *dir ATTR_UNUSED,
+ const char *fname ATTR_UNUSED,
+ enum mailbox_list_file_type type ATTR_UNUSED,
+ enum mailbox_info_flags *flags)
+{
+ *flags = MAILBOX_NONEXISTENT;
+ return 0;
+}
+
+struct mailbox_list none_mailbox_list = {
+ .name = MAILBOX_LIST_NAME_NONE,
+ .props = MAILBOX_LIST_PROP_NO_ROOT,
+ .mailbox_name_max_length = MAILBOX_LIST_NAME_MAX_LENGTH,
+
+ .v = {
+ .alloc = none_list_alloc,
+ .deinit = none_list_deinit,
+ .get_hierarchy_sep = none_list_get_hierarchy_sep,
+ .get_vname = mailbox_list_default_get_vname,
+ .get_storage_name = mailbox_list_default_get_storage_name,
+ .get_path = none_list_get_path,
+ .get_temp_prefix = none_list_get_temp_prefix,
+ .iter_init = none_list_iter_init,
+ .iter_next = none_list_iter_next,
+ .iter_deinit = none_list_iter_deinit,
+ .get_mailbox_flags = none_list_get_mailbox_flags,
+ .subscriptions_refresh = none_list_subscriptions_refresh,
+ .set_subscribed = none_list_set_subscribed,
+ .delete_mailbox = none_list_delete_mailbox,
+ .delete_dir = none_list_delete_dir,
+ .delete_symlink = none_list_delete_dir,
+ .rename_mailbox = none_list_rename_mailbox,
+ }
+};
diff --git a/src/lib-storage/list/mailbox-list-notify-tree.c b/src/lib-storage/list/mailbox-list-notify-tree.c
new file mode 100644
index 0000000..6152dbb
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-notify-tree.c
@@ -0,0 +1,131 @@
+/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "mail-index.h"
+#include "mail-storage.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-index.h"
+#include "mailbox-list-notify-tree.h"
+
+struct mailbox_list_notify_tree {
+ struct mailbox_list *list;
+ struct mailbox_tree_context *mailboxes;
+
+ struct mail_index_view *view;
+ bool failed;
+};
+
+static void
+mailbox_list_notify_node_get_status(struct mailbox_list_notify_tree *tree,
+ struct mailbox_notify_node *nnode)
+{
+ struct mailbox_status status;
+ const char *reason;
+ uint32_t seq;
+
+ if (!mail_index_lookup_seq(tree->view, nnode->index_uid, &seq))
+ return;
+
+ i_zero(&status);
+ (void)mailbox_list_index_status(tree->list, tree->view, seq,
+ STATUS_UIDVALIDITY | STATUS_UIDNEXT | STATUS_MESSAGES |
+ STATUS_UNSEEN | STATUS_HIGHESTMODSEQ, &status, nnode->guid,
+ NULL, &reason);
+ nnode->uidvalidity = status.uidvalidity;
+ nnode->uidnext = status.uidnext;
+ nnode->messages = status.messages;
+ nnode->unseen = status.unseen;
+ nnode->highest_modseq = status.highest_modseq;
+}
+
+static void
+mailbox_list_notify_node_build(struct mailbox_list_notify_tree *tree,
+ struct mailbox_list_index_node *index_node,
+ string_t *path)
+{
+ struct mailbox_node *node;
+ struct mailbox_notify_node *nnode;
+ size_t prefix_len;
+ bool created;
+
+ str_append(path, index_node->raw_name);
+
+ node = mailbox_tree_get(tree->mailboxes, str_c(path), &created);
+ nnode = (struct mailbox_notify_node *)node;
+ nnode->index_uid = index_node->uid;
+
+ if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NONEXISTENT) != 0)
+ node->flags = MAILBOX_NONEXISTENT;
+ else if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NOSELECT) != 0)
+ node->flags = MAILBOX_NOSELECT;
+ else {
+ node->flags = 0;
+ mailbox_list_notify_node_get_status(tree, nnode);
+ }
+ if ((index_node->flags & MAILBOX_LIST_INDEX_FLAG_NOINFERIORS) != 0)
+ node->flags |= MAILBOX_NOINFERIORS;
+
+ if (index_node->children != NULL) {
+ str_append_c(path, mailbox_list_get_hierarchy_sep(tree->list));
+ prefix_len = str_len(path);
+ index_node = index_node->children;
+ for (; index_node != NULL; index_node = index_node->next) {
+ str_truncate(path, prefix_len);
+ mailbox_list_notify_node_build(tree, index_node, path);
+ }
+ }
+}
+
+static void
+mailbox_list_notify_tree_build(struct mailbox_list_notify_tree *tree)
+{
+ struct mailbox_list_index *ilist = INDEX_LIST_CONTEXT_REQUIRE(tree->list);
+ struct mailbox_list_index_node *index_node;
+ string_t *path = t_str_new(128);
+
+ if (mailbox_list_index_refresh(tree->list) < 0)
+ tree->failed = TRUE;
+
+ tree->view = mail_index_view_open(ilist->index);
+ index_node = ilist->mailbox_tree;
+ for (; index_node != NULL; index_node = index_node->next) {
+ str_truncate(path, 0);
+ mailbox_list_notify_node_build(tree, index_node, path);
+ }
+ mail_index_view_close(&tree->view);
+}
+
+struct mailbox_list_notify_tree *
+mailbox_list_notify_tree_init(struct mailbox_list *list)
+{
+ struct mailbox_list_notify_tree *tree;
+
+ tree = i_new(struct mailbox_list_notify_tree, 1);
+ tree->list = list;
+ tree->mailboxes =
+ mailbox_tree_init_size(mailbox_list_get_hierarchy_sep(list),
+ sizeof(struct mailbox_notify_node));
+ mailbox_list_notify_tree_build(tree);
+ return tree;
+}
+
+void mailbox_list_notify_tree_deinit(struct mailbox_list_notify_tree **_tree)
+{
+ struct mailbox_list_notify_tree *tree = *_tree;
+
+ *_tree = NULL;
+
+ mailbox_tree_deinit(&tree->mailboxes);
+ i_free(tree);
+}
+
+struct mailbox_notify_node *
+mailbox_list_notify_tree_lookup(struct mailbox_list_notify_tree *tree,
+ const char *storage_name)
+{
+ struct mailbox_node *node;
+
+ node = mailbox_tree_lookup(tree->mailboxes, storage_name);
+ return (struct mailbox_notify_node *)node;
+}
diff --git a/src/lib-storage/list/mailbox-list-notify-tree.h b/src/lib-storage/list/mailbox-list-notify-tree.h
new file mode 100644
index 0000000..3666ac8
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-notify-tree.h
@@ -0,0 +1,27 @@
+#ifndef MAILBOX_LIST_NOTIFY_TREE_H
+#define MAILBOX_LIST_NOTIFY_TREE_H
+
+#include "mailbox-tree.h"
+
+struct mailbox_notify_node {
+ struct mailbox_node node;
+
+ guid_128_t guid;
+ uint32_t index_uid;
+
+ uint32_t uidvalidity;
+ uint32_t uidnext;
+ uint32_t messages;
+ uint32_t unseen;
+ uint64_t highest_modseq;
+};
+
+struct mailbox_list_notify_tree *
+mailbox_list_notify_tree_init(struct mailbox_list *list);
+void mailbox_list_notify_tree_deinit(struct mailbox_list_notify_tree **tree);
+
+struct mailbox_notify_node *
+mailbox_list_notify_tree_lookup(struct mailbox_list_notify_tree *tree,
+ const char *storage_name);
+
+#endif
diff --git a/src/lib-storage/list/mailbox-list-subscriptions.c b/src/lib-storage/list/mailbox-list-subscriptions.c
new file mode 100644
index 0000000..2e27c96
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-subscriptions.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "array.h"
+#include "unichar.h"
+#include "imap-match.h"
+#include "subscription-file.h"
+#include "mailbox-tree.h"
+#include "mailbox-list-private.h"
+#include "mailbox-list-subscriptions.h"
+
+#include <sys/stat.h>
+
+struct subscriptions_mailbox_list_iterate_context {
+ struct mailbox_list_iterate_context ctx;
+ struct mailbox_tree_context *tree;
+ struct mailbox_tree_iterate_context *iter;
+ struct mailbox_info info;
+};
+
+static int
+mailbox_list_subscription_fill_one(struct mailbox_list *list,
+ struct mailbox_list *src_list,
+ const char *name)
+{
+ struct mail_namespace *ns, *default_ns = list->ns;
+ struct mail_namespace *namespaces = default_ns->user->namespaces;
+ struct mailbox_node *node;
+ const char *vname, *ns_name, *error;
+ size_t len;
+ bool created;
+
+ /* default_ns is whatever namespace we're currently listing.
+ if we have e.g. prefix="" and prefix=pub/ namespaces with
+ pub/ namespace having subscriptions=no, we want to:
+
+ 1) when listing "" namespace we want to skip over any names
+ that begin with pub/. */
+ if (src_list->ns->prefix_len == 0)
+ ns_name = name;
+ else {
+ /* we could have two-level namespace: ns/ns2/ */
+ ns_name = t_strconcat(src_list->ns->prefix, name, NULL);
+ }
+ ns = mail_namespace_find_unsubscribable(namespaces, ns_name);
+ if (ns != NULL && ns != default_ns) {
+ if (ns->prefix_len > 0)
+ return 0;
+ /* prefix="" namespace=no : catching this is basically the
+ same as not finding any namespace. */
+ ns = NULL;
+ }
+
+ /* 2) when listing pub/ namespace, skip over entries that don't
+ begin with pub/. */
+ if (ns == NULL &&
+ (default_ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) == 0)
+ return 0;
+
+ /* When listing shared namespace's subscriptions, we need to
+ autocreate all the visible child namespaces. their subscriptions
+ are listed later. */
+ if (ns != NULL && mail_namespace_is_shared_user_root(ns)) {
+ /* we'll need to get the namespace autocreated.
+ one easy way is to just ask to join a reference and
+ pattern */
+ (void)mailbox_list_join_refpattern(ns->list, ns_name, "");
+ }
+
+ /* When listing pub/ namespace, skip over the namespace
+ prefix in the name. the rest of the name is storage_name. */
+ if (ns == NULL)
+ ns = default_ns;
+ else if (strncmp(ns_name, ns->prefix, ns->prefix_len) == 0) {
+ ns_name += ns->prefix_len;
+ name = ns_name;
+ } else {
+ /* "pub" entry - this shouldn't be possible normally, because
+ it should be saved as "pub/", but handle it anyway */
+ i_assert(strncmp(ns_name, ns->prefix, ns->prefix_len-1) == 0 &&
+ ns_name[ns->prefix_len-1] == '\0');
+ name = "";
+ /* ns_name = ""; */
+ }
+
+ len = strlen(name);
+ if (len > 0 && name[len-1] == mail_namespace_get_sep(ns)) {
+ /* entry ends with hierarchy separator, remove it.
+ this exists mainly for backwards compatibility with old
+ Dovecot versions and non-Dovecot software that added them */
+ name = t_strndup(name, len-1);
+ }
+
+ if (!mailbox_list_is_valid_name(list, name, &error)) {
+ /* we'll only get into trouble if we show this */
+ return -1;
+ } else {
+ vname = mailbox_list_get_vname(list, name);
+ if (!uni_utf8_str_is_valid(vname))
+ return -1;
+ node = mailbox_tree_get(list->subscriptions, vname, &created);
+ node->flags = MAILBOX_SUBSCRIBED;
+ }
+ return 0;
+}
+
+int mailbox_list_subscriptions_refresh(struct mailbox_list *src_list,
+ struct mailbox_list *dest_list)
+{
+ struct subsfile_list_context *subsfile_ctx;
+ struct stat st;
+ enum mailbox_list_path_type type;
+ const char *path, *name;
+ char sep;
+ int ret;
+
+ /* src_list is subscriptions=yes, dest_list is subscriptions=no
+ (or the same as src_list) */
+ i_assert((src_list->ns->flags & NAMESPACE_FLAG_SUBSCRIPTIONS) != 0);
+
+ if (dest_list->subscriptions == NULL) {
+ sep = mail_namespace_get_sep(src_list->ns);
+ dest_list->subscriptions = mailbox_tree_init(sep);
+ }
+
+ type = src_list->set.control_dir != NULL ?
+ MAILBOX_LIST_PATH_TYPE_CONTROL : MAILBOX_LIST_PATH_TYPE_DIR;
+ if (!mailbox_list_get_root_path(src_list, type, &path) ||
+ src_list->set.subscription_fname == NULL) {
+ /* no subscriptions (e.g. pop3c) */
+ return 0;
+ }
+ path = t_strconcat(path, "/", src_list->set.subscription_fname, NULL);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT) {
+ /* no subscriptions */
+ mailbox_tree_clear(dest_list->subscriptions);
+ dest_list->subscriptions_mtime = 0;
+ return 0;
+ }
+ mailbox_list_set_critical(dest_list, "stat(%s) failed: %m",
+ path);
+ return -1;
+ }
+ if (st.st_mtime == dest_list->subscriptions_mtime &&
+ st.st_mtime < dest_list->subscriptions_read_time-1) {
+ /* we're up to date */
+ return 0;
+ }
+
+ mailbox_tree_clear(dest_list->subscriptions);
+ dest_list->subscriptions_read_time = ioloop_time;
+
+ subsfile_ctx = subsfile_list_init(dest_list, path);
+ if (subsfile_list_fstat(subsfile_ctx, &st) == 0)
+ dest_list->subscriptions_mtime = st.st_mtime;
+ while ((name = subsfile_list_next(subsfile_ctx)) != NULL) T_BEGIN {
+ T_BEGIN {
+ ret = mailbox_list_subscription_fill_one(dest_list,
+ src_list, name);
+ } T_END;
+ if (ret < 0) {
+ e_warning(dest_list->ns->user->event,
+ "Subscriptions file %s: "
+ "Removing invalid entry: %s",
+ path, name);
+ (void)subsfile_set_subscribed(src_list, path,
+ mailbox_list_get_temp_prefix(src_list),
+ name, FALSE);
+
+ }
+ } T_END;
+
+ if (subsfile_list_deinit(&subsfile_ctx) < 0) {
+ dest_list->subscriptions_mtime = (time_t)-1;
+ return -1;
+ }
+ return 0;
+}
+
+void mailbox_list_set_subscription_flags(struct mailbox_list *list,
+ const char *vname,
+ enum mailbox_info_flags *flags)
+{
+ struct mailbox_node *node;
+
+ *flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+
+ node = mailbox_tree_lookup(list->subscriptions, vname);
+ if (node != NULL) {
+ *flags |= node->flags & MAILBOX_SUBSCRIBED;
+
+ /* the only reason why node might have a child is if one of
+ them is subscribed */
+ if (node->children != NULL)
+ *flags |= MAILBOX_CHILD_SUBSCRIBED;
+ }
+}
+
+void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
+ struct mailbox_tree_context *tree,
+ bool default_nonexistent)
+{
+ struct mailbox_list_iter_update_context update_ctx;
+ struct mailbox_tree_iterate_context *iter;
+ const char *name;
+
+ i_zero(&update_ctx);
+ update_ctx.iter_ctx = ctx;
+ update_ctx.tree_ctx = tree;
+ update_ctx.glob = ctx->glob;
+ update_ctx.leaf_flags = MAILBOX_SUBSCRIBED;
+ if (default_nonexistent)
+ update_ctx.leaf_flags |= MAILBOX_NONEXISTENT;
+ update_ctx.parent_flags = MAILBOX_CHILD_SUBSCRIBED;
+ update_ctx.match_parents =
+ (ctx->flags & MAILBOX_LIST_ITER_SELECT_RECURSIVEMATCH) != 0;
+
+ iter = mailbox_tree_iterate_init(ctx->list->subscriptions, NULL,
+ MAILBOX_SUBSCRIBED);
+ while (mailbox_tree_iterate_next(iter, &name) != NULL)
+ mailbox_list_iter_update(&update_ctx, name);
+ mailbox_tree_iterate_deinit(&iter);
+}
+
+struct mailbox_list_iterate_context *
+mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx;
+ pool_t pool;
+ char sep = mail_namespace_get_sep(list->ns);
+
+ pool = pool_alloconly_create("mailbox list subscriptions iter", 1024);
+ ctx = p_new(pool, struct subscriptions_mailbox_list_iterate_context, 1);
+ ctx->ctx.pool = pool;
+ ctx->ctx.list = list;
+ ctx->ctx.flags = flags;
+ ctx->ctx.glob = imap_match_init_multiple(pool, patterns, TRUE, sep);
+ array_create(&ctx->ctx.module_contexts, pool, sizeof(void *), 5);
+
+ ctx->tree = mailbox_tree_init(sep);
+ mailbox_list_subscriptions_fill(&ctx->ctx, ctx->tree, FALSE);
+
+ ctx->info.ns = list->ns;
+ /* the tree usually has only those entries we want to iterate through,
+ but there are also non-matching root entries (e.g. "LSUB foo/%" will
+ include the "foo"), which we'll drop with MAILBOX_MATCHED. */
+ ctx->iter = mailbox_tree_iterate_init(ctx->tree, NULL, MAILBOX_MATCHED);
+ return &ctx->ctx;
+}
+
+const struct mailbox_info *
+mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *_ctx)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx =
+ (struct subscriptions_mailbox_list_iterate_context *)_ctx;
+ struct mailbox_list *list = _ctx->list;
+ struct mailbox_node *node;
+ enum mailbox_info_flags subs_flags;
+ const char *vname, *storage_name, *error;
+ int ret;
+
+ node = mailbox_tree_iterate_next(ctx->iter, &vname);
+ if (node == NULL)
+ return mailbox_list_iter_default_next(_ctx);
+
+ ctx->info.vname = vname;
+ subs_flags = node->flags & (MAILBOX_SUBSCRIBED |
+ MAILBOX_CHILD_SUBSCRIBED);
+
+ if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0 &&
+ (_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) == 0) {
+ /* don't care about flags, just return it */
+ ctx->info.flags = subs_flags;
+ return &ctx->info;
+ }
+
+ storage_name = mailbox_list_get_storage_name(list, vname);
+ if (!mailbox_list_is_valid_name(list, storage_name, &error)) {
+ /* broken entry in subscriptions file */
+ ctx->info.flags = MAILBOX_NONEXISTENT;
+ } else if (mailbox_list_mailbox(list, storage_name,
+ &ctx->info.flags) < 0) {
+ ctx->info.flags = 0;
+ _ctx->failed = TRUE;
+ } else if ((_ctx->flags & MAILBOX_LIST_ITER_RETURN_CHILDREN) != 0 &&
+ (ctx->info.flags & (MAILBOX_CHILDREN |
+ MAILBOX_NOCHILDREN)) == 0) {
+ ret = mailbox_has_children(list, storage_name);
+ if (ret < 0)
+ _ctx->failed = TRUE;
+ else if (ret == 0)
+ ctx->info.flags |= MAILBOX_NOCHILDREN;
+ else
+ ctx->info.flags |= MAILBOX_CHILDREN;
+
+ }
+
+ ctx->info.flags &= ENUM_NEGATE(MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+ ctx->info.flags |=
+ node->flags & (MAILBOX_SUBSCRIBED | MAILBOX_CHILD_SUBSCRIBED);
+ return &ctx->info;
+}
+
+int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *_ctx)
+{
+ struct subscriptions_mailbox_list_iterate_context *ctx =
+ (struct subscriptions_mailbox_list_iterate_context *)_ctx;
+ int ret = _ctx->failed ? -1 : 0;
+
+ mailbox_tree_iterate_deinit(&ctx->iter);
+ mailbox_tree_deinit(&ctx->tree);
+ pool_unref(&_ctx->pool);
+ return ret;
+}
diff --git a/src/lib-storage/list/mailbox-list-subscriptions.h b/src/lib-storage/list/mailbox-list-subscriptions.h
new file mode 100644
index 0000000..283012d
--- /dev/null
+++ b/src/lib-storage/list/mailbox-list-subscriptions.h
@@ -0,0 +1,33 @@
+#ifndef MAILBOX_LIST_SUBSCRIPTIONS_H
+#define MAILBOX_LIST_SUBSCRIPTIONS_H
+
+#include "mailbox-list-iter.h"
+
+struct mailbox_tree_context;
+struct mailbox_list_iterate_context;
+
+int mailbox_list_subscriptions_refresh(struct mailbox_list *src_list,
+ struct mailbox_list *dest_list);
+
+/* Set MAILBOX_SUBSCRIBED and MAILBOX_CHILD_SUBSCRIBED flags,
+ clearing them if they already are there when they shouldn't. */
+void mailbox_list_set_subscription_flags(struct mailbox_list *list,
+ const char *vname,
+ enum mailbox_info_flags *flags);
+
+/* Add subscriptions matching the iteration to the given tree */
+void mailbox_list_subscriptions_fill(struct mailbox_list_iterate_context *ctx,
+ struct mailbox_tree_context *tree,
+ bool default_nonexistent);
+
+/* Iterate through subscriptions, call mailbox_list.get_mailbox_flags()
+ if necessary for mailboxes to get their flags. */
+struct mailbox_list_iterate_context *
+mailbox_list_subscriptions_iter_init(struct mailbox_list *list,
+ const char *const *patterns,
+ enum mailbox_list_iter_flags flags);
+const struct mailbox_info *
+mailbox_list_subscriptions_iter_next(struct mailbox_list_iterate_context *ctx);
+int mailbox_list_subscriptions_iter_deinit(struct mailbox_list_iterate_context *ctx);
+
+#endif
diff --git a/src/lib-storage/list/subscription-file.c b/src/lib-storage/list/subscription-file.c
new file mode 100644
index 0000000..4c5094a
--- /dev/null
+++ b/src/lib-storage/list/subscription-file.c
@@ -0,0 +1,380 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "istream.h"
+#include "ostream.h"
+#include "nfs-workarounds.h"
+#include "mkdir-parents.h"
+#include "file-dotlock.h"
+#include "mailbox-list-private.h"
+#include "subscription-file.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#define SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT NFS_ESTALE_RETRY_COUNT
+#define SUBSCRIPTION_FILE_LOCK_TIMEOUT 120
+#define SUBSCRIPTION_FILE_CHANGE_TIMEOUT 30
+
+struct subsfile_list_context {
+ struct mailbox_list *list;
+ struct istream *input;
+ char *path;
+ string_t *name;
+
+ unsigned int version;
+ bool failed;
+};
+
+static const char version2_header[] = "V\t2\n\n";
+
+static void subsread_set_syscall_error(struct mailbox_list *list,
+ const char *function, const char *path)
+{
+ if (errno == EACCES && !event_want_debug_log(list->ns->user->event)) {
+ mailbox_list_set_error(list, MAIL_ERROR_PERM,
+ "No permission to read subscriptions");
+ } else {
+ mailbox_list_set_critical(list,
+ "%s failed with subscription file %s: %m",
+ function, path);
+ }
+}
+
+static void subswrite_set_syscall_error(struct mailbox_list *list,
+ const char *function, const char *path)
+{
+ if (errno == EACCES && !event_want_debug_log(list->ns->user->event)) {
+ mailbox_list_set_error(list, MAIL_ERROR_PERM,
+ "No permission to modify subscriptions");
+ } else {
+ mailbox_list_set_critical(list,
+ "%s failed with subscription file %s: %m",
+ function, path);
+ }
+}
+
+static void
+subsfile_list_read_header(struct mailbox_list *list, struct istream *input,
+ unsigned int *version_r)
+{
+ const unsigned char version2_header_len = strlen(version2_header);
+ const unsigned char *data;
+ size_t size;
+ int ret;
+
+ *version_r = 0;
+
+ ret = i_stream_read_bytes(input, &data, &size, version2_header_len);
+ if (ret < 0) {
+ i_assert(ret == -1);
+ if (input->stream_errno != 0)
+ subswrite_set_syscall_error(list, "read()", i_stream_get_name(input));
+ return;
+ }
+ if (ret > 0 &&
+ memcmp(data, version2_header, version2_header_len) == 0) {
+ *version_r = 2;
+ i_stream_skip(input, version2_header_len);
+ }
+}
+
+static const char *next_line(struct mailbox_list *list, const char *path,
+ struct istream *input, bool *failed_r,
+ bool ignore_estale)
+{
+ const char *line;
+
+ *failed_r = FALSE;
+
+ while ((line = i_stream_next_line(input)) == NULL) {
+ switch (i_stream_read(input)) {
+ case -1:
+ if (input->stream_errno != 0 &&
+ (input->stream_errno != ESTALE || !ignore_estale)) {
+ subswrite_set_syscall_error(list,
+ "read()", path);
+ *failed_r = TRUE;
+ }
+ return NULL;
+ case -2:
+ /* mailbox name too large */
+ mailbox_list_set_critical(list,
+ "Subscription file %s contains lines longer "
+ "than %u characters", path,
+ (unsigned int)list->mailbox_name_max_length);
+ *failed_r = TRUE;
+ return NULL;
+ }
+ }
+
+ return line;
+}
+
+int subsfile_set_subscribed(struct mailbox_list *list, const char *path,
+ const char *temp_prefix, const char *name,
+ bool set)
+{
+ const struct mail_storage_settings *mail_set = list->mail_set;
+ struct dotlock_settings dotlock_set;
+ struct dotlock *dotlock;
+ struct mailbox_permissions perm;
+ const char *line, *dir, *fname, *escaped_name;
+ struct istream *input = NULL;
+ struct ostream *output;
+ int fd_in, fd_out;
+ enum mailbox_list_path_type type;
+ bool found, changed = FALSE, failed = FALSE;
+ unsigned int version = 2;
+
+ if (strcasecmp(name, "INBOX") == 0)
+ name = "INBOX";
+
+ i_zero(&dotlock_set);
+ dotlock_set.use_excl_lock = mail_set->dotlock_use_excl;
+ dotlock_set.nfs_flush = mail_set->mail_nfs_storage;
+ dotlock_set.temp_prefix = temp_prefix;
+ dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT;
+ dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT;
+
+ mailbox_list_get_root_permissions(list, &perm);
+ fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
+ perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin, &dotlock);
+ if (fd_out == -1 && errno == ENOENT) {
+ /* directory hasn't been created yet. */
+ type = list->set.control_dir != NULL ?
+ MAILBOX_LIST_PATH_TYPE_CONTROL :
+ MAILBOX_LIST_PATH_TYPE_DIR;
+ fname = strrchr(path, '/');
+ if (fname != NULL) {
+ dir = t_strdup_until(path, fname);
+ if (mailbox_list_mkdir_root(list, dir, type) < 0)
+ return -1;
+ }
+ fd_out = file_dotlock_open_group(&dotlock_set, path, 0,
+ perm.file_create_mode,
+ perm.file_create_gid,
+ perm.file_create_gid_origin,
+ &dotlock);
+ }
+ if (fd_out == -1) {
+ if (errno == EAGAIN) {
+ mailbox_list_set_error(list, MAIL_ERROR_TEMP,
+ "Timeout waiting for subscription file lock");
+ } else {
+ subswrite_set_syscall_error(list, "file_dotlock_open()",
+ path);
+ }
+ return -1;
+ }
+
+ fd_in = nfs_safe_open(path, O_RDONLY);
+ if (fd_in == -1 && errno != ENOENT) {
+ subswrite_set_syscall_error(list, "open()", path);
+ file_dotlock_delete(&dotlock);
+ return -1;
+ }
+ if (fd_in != -1) {
+ input = i_stream_create_fd_autoclose(&fd_in, list->mailbox_name_max_length+1);
+ i_stream_set_return_partial_line(input, TRUE);
+ subsfile_list_read_header(list, input, &version);
+ }
+
+ found = FALSE;
+ output = o_stream_create_fd_file(fd_out, 0, FALSE);
+ o_stream_cork(output);
+ if (version >= 2)
+ o_stream_nsend_str(output, version2_header);
+ if (version < 2 || name[0] == '\0')
+ escaped_name = name;
+ else {
+ const char *const *tmp;
+ char separators[2];
+ string_t *str = t_str_new(64);
+
+ separators[0] = mailbox_list_get_hierarchy_sep(list);
+ separators[1] = '\0';
+ tmp = t_strsplit(name, separators);
+ str_append_tabescaped(str, *tmp);
+ for (tmp++; *tmp != NULL; tmp++) {
+ str_append_c(str, '\t');
+ str_append_tabescaped(str, *tmp);
+ }
+ escaped_name = str_c(str);
+ }
+ if (input != NULL) {
+ while ((line = next_line(list, path, input,
+ &failed, FALSE)) != NULL) {
+ if (strcmp(line, escaped_name) == 0) {
+ found = TRUE;
+ if (!set) {
+ changed = TRUE;
+ continue;
+ }
+ }
+
+ o_stream_nsend_str(output, line);
+ o_stream_nsend(output, "\n", 1);
+ }
+ i_stream_destroy(&input);
+ }
+
+ if (!failed && set && !found) {
+ /* append subscription */
+ line = t_strconcat(escaped_name, "\n", NULL);
+ o_stream_nsend_str(output, line);
+ changed = TRUE;
+ }
+
+ if (changed && !failed) {
+ if (o_stream_finish(output) < 0) {
+ subswrite_set_syscall_error(list, "write()", path);
+ failed = TRUE;
+ } else if (mail_set->parsed_fsync_mode != FSYNC_MODE_NEVER) {
+ if (fsync(fd_out) < 0) {
+ subswrite_set_syscall_error(list, "fsync()",
+ path);
+ failed = TRUE;
+ }
+ }
+ } else {
+ o_stream_abort(output);
+ }
+ o_stream_destroy(&output);
+
+ if (failed || !changed) {
+ if (file_dotlock_delete(&dotlock) < 0) {
+ subswrite_set_syscall_error(list,
+ "file_dotlock_delete()", path);
+ failed = TRUE;
+ }
+ } else {
+ enum dotlock_replace_flags flags =
+ DOTLOCK_REPLACE_FLAG_VERIFY_OWNER;
+ if (file_dotlock_replace(&dotlock, flags) < 0) {
+ subswrite_set_syscall_error(list,
+ "file_dotlock_replace()", path);
+ failed = TRUE;
+ }
+ }
+ return failed ? -1 : (changed ? 1 : 0);
+}
+
+struct subsfile_list_context *
+subsfile_list_init(struct mailbox_list *list, const char *path)
+{
+ struct subsfile_list_context *ctx;
+ int fd;
+
+ ctx = i_new(struct subsfile_list_context, 1);
+ ctx->list = list;
+
+ fd = nfs_safe_open(path, O_RDONLY);
+ if (fd == -1) {
+ if (errno != ENOENT) {
+ subsread_set_syscall_error(list, "open()", path);
+ ctx->failed = TRUE;
+ }
+ } else {
+ ctx->input = i_stream_create_fd_autoclose(&fd,
+ list->mailbox_name_max_length+1);
+ i_stream_set_return_partial_line(ctx->input, TRUE);
+ subsfile_list_read_header(ctx->list, ctx->input, &ctx->version);
+ }
+ ctx->path = i_strdup(path);
+ ctx->name = str_new(default_pool, 128);
+ return ctx;
+}
+
+int subsfile_list_deinit(struct subsfile_list_context **_ctx)
+{
+ struct subsfile_list_context *ctx = *_ctx;
+ int ret = ctx->failed ? -1 : 0;
+
+ *_ctx = NULL;
+
+ i_stream_destroy(&ctx->input);
+ str_free(&ctx->name);
+ i_free(ctx->path);
+ i_free(ctx);
+ return ret;
+}
+
+int subsfile_list_fstat(struct subsfile_list_context *ctx, struct stat *st_r)
+{
+ const struct stat *st;
+
+ if (ctx->failed)
+ return -1;
+
+ if (i_stream_stat(ctx->input, FALSE, &st) < 0) {
+ ctx->failed = TRUE;
+ return -1;
+ }
+ *st_r = *st;
+ return 0;
+}
+
+static const char *
+subsfile_list_unescaped(struct subsfile_list_context *ctx, const char *line)
+{
+ const char *p;
+
+ str_truncate(ctx->name, 0);
+ while ((p = strchr(line, '\t')) != NULL) {
+ str_append_tabunescaped(ctx->name, line, p-line);
+ str_append_c(ctx->name, mailbox_list_get_hierarchy_sep(ctx->list));
+ line = p+1;
+ }
+ str_append_tabunescaped(ctx->name, line, strlen(line));
+ return str_c(ctx->name);
+}
+
+const char *subsfile_list_next(struct subsfile_list_context *ctx)
+{
+ const char *line;
+ unsigned int i;
+ int fd;
+
+ if (ctx->failed || ctx->input == NULL)
+ return NULL;
+
+ for (i = 0;; i++) {
+ line = next_line(ctx->list, ctx->path, ctx->input, &ctx->failed,
+ i < SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT);
+ if (ctx->input->stream_errno != ESTALE ||
+ i == SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT)
+ break;
+
+ /* Reopen the subscription file and re-send everything.
+ this isn't the optimal behavior, but it's allowed by
+ IMAP and this way we don't have to read everything into
+ memory or try to play any guessing games. */
+ i_stream_destroy(&ctx->input);
+
+ fd = nfs_safe_open(ctx->path, O_RDONLY);
+ if (fd == -1) {
+ /* In case of ENOENT all the subscriptions got lost.
+ Just return end of subscriptions list in that
+ case. */
+ if (errno != ENOENT) {
+ subsread_set_syscall_error(ctx->list, "open()",
+ ctx->path);
+ ctx->failed = TRUE;
+ }
+ return NULL;
+ }
+
+ ctx->input = i_stream_create_fd_autoclose(&fd,
+ ctx->list->mailbox_name_max_length+1);
+ i_stream_set_return_partial_line(ctx->input, TRUE);
+ }
+
+ if (ctx->version > 1 && line != NULL)
+ line = subsfile_list_unescaped(ctx, line);
+ return line;
+}
diff --git a/src/lib-storage/list/subscription-file.h b/src/lib-storage/list/subscription-file.h
new file mode 100644
index 0000000..fbaa0a3
--- /dev/null
+++ b/src/lib-storage/list/subscription-file.h
@@ -0,0 +1,25 @@
+#ifndef SUBSCRIPTION_FILE_H
+#define SUBSCRIPTION_FILE_H
+
+struct stat;
+struct mailbox_list;
+
+/* Initialize new subscription file listing. */
+struct subsfile_list_context *
+subsfile_list_init(struct mailbox_list *list, const char *path);
+/* Deinitialize subscription file listing. Returns 0 if ok, or -1 if some
+ error occurred while listing. */
+int subsfile_list_deinit(struct subsfile_list_context **ctx);
+
+/* Call fstat() for subscription file */
+int subsfile_list_fstat(struct subsfile_list_context *ctx, struct stat *st_r);
+
+/* Returns the next subscribed mailbox, or NULL. */
+const char *subsfile_list_next(struct subsfile_list_context *ctx);
+
+/* Returns 1 if subscribed, 0 if no changes done, -1 if error. */
+int subsfile_set_subscribed(struct mailbox_list *list, const char *path,
+ const char *temp_prefix, const char *name,
+ bool set);
+
+#endif