diff options
Diffstat (limited to 'src/lib-storage/list')
30 files changed, 12168 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..9a78a90 --- /dev/null +++ b/src/lib-storage/list/Makefile.in @@ -0,0 +1,916 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 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..1e4f462 --- /dev/null +++ b/src/lib-storage/list/mail-storage-list-index-rebuild.c @@ -0,0 +1,510 @@ +/* Copyright (c) 2021 Dovecot Authors, see the included COPYING file */ + +#include "lib.h" +#include "hash.h" +#include "guid.h" +#include "str.h" +#include "str-sanitize.h" +#include "hex-binary.h" +#include "randgen.h" +#include "fs-api.h" +#include "mailbox-list-index.h" +#include "mailbox-list-index-sync.h" +#include "mail-storage-private.h" + +struct mail_storage_list_index_rebuild_mailbox { + guid_128_t guid; + const char *index_name; +}; + +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; + struct mailbox_list *first_list; + HASH_TABLE(uint8_t *, 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; + + /* track first list */ + if (ctx->first_list == NULL) + ctx->first_list = ns->list; + + 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 int +mail_storage_list_index_fill_storage_mailboxes(struct mail_storage_list_index_rebuild_ctx *ctx) +{ + struct mail_storage_list_index_rebuild_mailbox *box; + struct fs_iter *iter; + const char *path, *fname, *error; + guid_128_t guid; + uint8_t *guid_p; + + path = mailbox_list_get_root_forced(ctx->first_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) { + if (guid_128_from_string(fname, guid) < 0) + continue; + + box = p_new(ctx->pool, struct mail_storage_list_index_rebuild_mailbox, 1); + guid_128_copy(box->guid, guid); + guid_p = box->guid; + hash_table_update(ctx->mailboxes, guid_p, box); + } + + 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; + const uint8_t *guid_p; + 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 { + guid_p = metadata.guid; + rebuild_box = hash_table_lookup(ctx->mailboxes, guid_p); + 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(guid_p)); + } 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(guid_p)); + } 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, + const uint8_t *guid_p, + bool retry) +{ + struct mail_storage *storage = ctx->storage; + struct mailbox_list *list; + struct mailbox *box; + struct mailbox_update update; + enum mailbox_existence existence; + string_t *name = t_str_new(128); + unsigned char randomness[8]; + int ret; + + /* FIXME: we should find out the mailbox's original namespace from the + mailbox index's header. */ + list = ctx->first_list; + + i_zero(&update); + guid_128_copy(update.mailbox_guid, guid_p); + + str_printfa(name, "%s%s%s", list->ns->prefix, + storage->lost_mailbox_prefix, guid_128_to_string(guid_p)); + 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 (mailbox_exists(box, FALSE, &existence) < 0) { + mail_storage_set_critical(storage, + "List rebuild: Couldn't lookup mailbox %s existence: %s", + str_c(name), mailbox_get_last_internal_error(box, NULL)); + ret = -1; + } else if (existence != MAILBOX_EXISTENCE_NONE) { + ret = 0; + } else 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, + const uint8_t *guid_p) +{ + int i, ret = 0; + + for (i = 0; i < 100; i++) { + T_BEGIN { + ret = mail_storage_list_index_try_create(ctx, guid_p, 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; +} + +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; + uint8_t *guid_p; + unsigned int num_created = 0; + int ret = 0; + + iter = hash_table_iterate_init(ctx->mailboxes); + while (hash_table_iterate(iter, ctx->mailboxes, &guid_p, &box)) T_BEGIN { + if (box->index_name == NULL) { + if (mail_storage_list_index_create(ctx, guid_p) < 0) + ret = -1; + else + num_created++; + } + } T_END; + hash_table_iterate_deinit(&iter); + if (num_created > 0) { + e_warning(ctx->storage->event, + "Mailbox list rescan found %u lost mailboxes", + num_created); + } + 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; + + if (mail_storage_list_index_fill_storage_mailboxes(ctx) < 0) + return -1; + + 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_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, guid_128_hash, guid_128_cmp); + + /* 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; + } + + /* Only perform this for INDEX layout */ + if (strcmp(ctx.first_list->name, MAILBOX_LIST_NAME_INDEX) == 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); + } else + ret = 0; + + 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..15dac41 --- /dev/null +++ b/src/lib-storage/list/mailbox-list-index-backend.c @@ -0,0 +1,979 @@ +/* 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. */ + char sep = mailbox_list_get_hierarchy_sep(box->list); + char *box_zerosep_name = t_strdup_noconst(box->name); + size_t box_name_len = strlen(box_zerosep_name); + for (size_t i = 0; i < box_name_len; i++) { + if (box_zerosep_name[i] == sep) + box_zerosep_name[i] = '\0'; + } + + /* 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. */ + char sep = mailbox_list_get_hierarchy_sep(box->list); + char *newname = t_malloc0(name_hdr_size + 1); + memcpy(newname, name_hdr, name_hdr_size); + for (size_t i = 0; i < name_hdr_size; i++) { + if (newname[i] == '\0') + newname[i] = sep; + } + + 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..be8b265 --- /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) { + /* 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--; + } + 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..2ba8861 --- /dev/null +++ b/src/lib-storage/list/mailbox-list-index.c @@ -0,0 +1,1168 @@ +/* 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; +} + +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..a2ca620 --- /dev/null +++ b/src/lib-storage/list/mailbox-list-index.h @@ -0,0 +1,240 @@ +#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); + +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..4098d8f --- /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 |