diff options
Diffstat (limited to 'src/plugins/virtual')
-rw-r--r-- | src/plugins/virtual/Makefile.am | 29 | ||||
-rw-r--r-- | src/plugins/virtual/Makefile.in | 859 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-config.c | 567 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-mail.c | 583 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-plugin.c | 25 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-plugin.h | 7 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-save.c | 153 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-search.c | 206 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-storage.c | 950 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-storage.h | 251 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-sync.c | 1956 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-transaction.c | 87 | ||||
-rw-r--r-- | src/plugins/virtual/virtual-transaction.h | 24 |
13 files changed, 5697 insertions, 0 deletions
diff --git a/src/plugins/virtual/Makefile.am b/src/plugins/virtual/Makefile.am new file mode 100644 index 0000000..9ba873d --- /dev/null +++ b/src/plugins/virtual/Makefile.am @@ -0,0 +1,29 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-imap-storage + +NOPLUGIN_LDFLAGS = +lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version + +module_LTLIBRARIES = \ + lib20_virtual_plugin.la + +lib20_virtual_plugin_la_SOURCES = \ + virtual-config.c \ + virtual-mail.c \ + virtual-plugin.c \ + virtual-search.c \ + virtual-storage.c \ + virtual-save.c \ + virtual-sync.c \ + virtual-transaction.c + +noinst_HEADERS = \ + virtual-plugin.h \ + virtual-storage.h \ + virtual-transaction.h diff --git a/src/plugins/virtual/Makefile.in b/src/plugins/virtual/Makefile.in new file mode 100644 index 0000000..aba6783 --- /dev/null +++ b/src/plugins/virtual/Makefile.in @@ -0,0 +1,859 @@ +# 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/plugins/virtual +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 $(noinst_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +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)$(moduledir)" +LTLIBRARIES = $(module_LTLIBRARIES) +lib20_virtual_plugin_la_LIBADD = +am_lib20_virtual_plugin_la_OBJECTS = virtual-config.lo virtual-mail.lo \ + virtual-plugin.lo virtual-search.lo virtual-storage.lo \ + virtual-save.lo virtual-sync.lo virtual-transaction.lo +lib20_virtual_plugin_la_OBJECTS = \ + $(am_lib20_virtual_plugin_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 = +lib20_virtual_plugin_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib20_virtual_plugin_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +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)/virtual-config.Plo \ + ./$(DEPDIR)/virtual-mail.Plo ./$(DEPDIR)/virtual-plugin.Plo \ + ./$(DEPDIR)/virtual-save.Plo ./$(DEPDIR)/virtual-search.Plo \ + ./$(DEPDIR)/virtual-storage.Plo ./$(DEPDIR)/virtual-sync.Plo \ + ./$(DEPDIR)/virtual-transaction.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 = $(lib20_virtual_plugin_la_SOURCES) +DIST_SOURCES = $(lib20_virtual_plugin_la_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(noinst_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 = +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@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index \ + -I$(top_srcdir)/src/lib-imap-storage + +lib20_virtual_plugin_la_LDFLAGS = -module -avoid-version +module_LTLIBRARIES = \ + lib20_virtual_plugin.la + +lib20_virtual_plugin_la_SOURCES = \ + virtual-config.c \ + virtual-mail.c \ + virtual-plugin.c \ + virtual-search.c \ + virtual-storage.c \ + virtual-save.c \ + virtual-sync.c \ + virtual-transaction.c + +noinst_HEADERS = \ + virtual-plugin.h \ + virtual-storage.h \ + virtual-transaction.h + +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/plugins/virtual/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/plugins/virtual/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): + +install-moduleLTLIBRARIES: $(module_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(moduledir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(moduledir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(moduledir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(moduledir)"; \ + } + +uninstall-moduleLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(module_LTLIBRARIES)'; test -n "$(moduledir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(moduledir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(moduledir)/$$f"; \ + done + +clean-moduleLTLIBRARIES: + -test -z "$(module_LTLIBRARIES)" || rm -f $(module_LTLIBRARIES) + @list='$(module_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}; \ + } + +lib20_virtual_plugin.la: $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_DEPENDENCIES) $(EXTRA_lib20_virtual_plugin_la_DEPENDENCIES) + $(AM_V_CCLD)$(lib20_virtual_plugin_la_LINK) -rpath $(moduledir) $(lib20_virtual_plugin_la_OBJECTS) $(lib20_virtual_plugin_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-config.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-mail.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-plugin.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-save.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-search.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-storage.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-sync.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/virtual-transaction.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 + +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)$(moduledir)"; 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-moduleLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/virtual-config.Plo + -rm -f ./$(DEPDIR)/virtual-mail.Plo + -rm -f ./$(DEPDIR)/virtual-plugin.Plo + -rm -f ./$(DEPDIR)/virtual-save.Plo + -rm -f ./$(DEPDIR)/virtual-search.Plo + -rm -f ./$(DEPDIR)/virtual-storage.Plo + -rm -f ./$(DEPDIR)/virtual-sync.Plo + -rm -f ./$(DEPDIR)/virtual-transaction.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-moduleLTLIBRARIES + +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)/virtual-config.Plo + -rm -f ./$(DEPDIR)/virtual-mail.Plo + -rm -f ./$(DEPDIR)/virtual-plugin.Plo + -rm -f ./$(DEPDIR)/virtual-save.Plo + -rm -f ./$(DEPDIR)/virtual-search.Plo + -rm -f ./$(DEPDIR)/virtual-storage.Plo + -rm -f ./$(DEPDIR)/virtual-sync.Plo + -rm -f ./$(DEPDIR)/virtual-transaction.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-moduleLTLIBRARIES + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libtool clean-moduleLTLIBRARIES \ + 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-moduleLTLIBRARIES install-pdf install-pdf-am \ + 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-moduleLTLIBRARIES + +.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/plugins/virtual/virtual-config.c b/src/plugins/virtual/virtual-config.c new file mode 100644 index 0000000..05a6305 --- /dev/null +++ b/src/plugins/virtual/virtual-config.c @@ -0,0 +1,567 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "crc32.h" +#include "istream.h" +#include "str.h" +#include "unichar.h" +#include "wildcard-match.h" +#include "imap-parser.h" +#include "imap-match.h" +#include "mail-namespace.h" +#include "mail-search-build.h" +#include "mail-search-parser.h" +#include "mailbox-attribute.h" +#include "mailbox-list-iter.h" +#include "imap-metadata.h" +#include "virtual-storage.h" +#include "virtual-plugin.h" + +#include <unistd.h> +#include <fcntl.h> + +struct virtual_parse_context { + struct virtual_mailbox *mbox; + struct istream *input; + + pool_t pool; + string_t *rule; + unsigned int rule_idx; + + char sep; + bool have_wildcards; + bool have_mailbox_defines; +}; + +static struct mail_search_args * +virtual_search_args_parse(const string_t *rule, const char **error_r) +{ + struct istream *input; + struct imap_parser *imap_parser; + const struct imap_arg *args; + struct mail_search_parser *parser; + struct mail_search_args *sargs; + const char *charset = "UTF-8"; + int ret; + + if (str_len(rule) == 0) { + sargs = mail_search_build_init(); + mail_search_build_add_all(sargs); + return sargs; + } + + input = i_stream_create_from_data(str_data(rule), str_len(rule)); + (void)i_stream_read(input); + + imap_parser = imap_parser_create(input, NULL, SIZE_MAX); + ret = imap_parser_finish_line(imap_parser, 0, 0, &args); + if (ret < 0) { + sargs = NULL; + *error_r = t_strdup(imap_parser_get_error(imap_parser, NULL)); + } else { + parser = mail_search_parser_init_imap(args); + if (mail_search_build(mail_search_register_get_imap(), + parser, &charset, &sargs, error_r) < 0) + sargs = NULL; + mail_search_parser_deinit(&parser); + } + + imap_parser_unref(&imap_parser); + i_stream_destroy(&input); + return sargs; +} + +static int +virtual_config_add_rule(struct virtual_parse_context *ctx, const char **error_r) +{ + struct virtual_backend_box *const *bboxes; + struct mail_search_args *search_args; + unsigned int i, count; + + *error_r = NULL; + + if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) { + i_assert(str_len(ctx->rule) == 0); + return 0; + } + + ctx->mbox->search_args_crc32 = + crc32_str_more(ctx->mbox->search_args_crc32, str_c(ctx->rule)); + search_args = virtual_search_args_parse(ctx->rule, error_r); + str_truncate(ctx->rule, 0); + if (search_args == NULL) { + i_assert(*error_r != NULL); + *error_r = t_strconcat("Previous search rule is invalid: ", + *error_r, NULL); + return -1; + } + + /* update at all the mailboxes that were introduced since the previous + rule. */ + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + i_assert(ctx->rule_idx < count); + for (i = ctx->rule_idx; i < count; i++) { + i_assert(bboxes[i]->search_args == NULL); + mail_search_args_ref(search_args); + bboxes[i]->search_args = search_args; + } + mail_search_args_unref(&search_args); + + ctx->rule_idx = array_count(&ctx->mbox->backend_boxes); + return 0; +} + +static int +virtual_config_parse_line(struct virtual_parse_context *ctx, const char *line, + const char **error_r) +{ + struct mail_user *user = ctx->mbox->storage->storage.user; + struct virtual_backend_box *bbox; + const char *p; + bool no_wildcards = FALSE; + + if (*line == ' ' || *line == '\t') { + /* continues the previous search rule */ + if (ctx->rule_idx == array_count(&ctx->mbox->backend_boxes)) { + *error_r = "Search rule without a mailbox"; + return -1; + } + while (*line == ' ' || *line == '\t') line++; + str_append_c(ctx->rule, ' '); + str_append(ctx->rule, line); + return 0; + } + /* if there is no rule yet, it means we want the previous mailboxes + to use the rule that comes later */ + if (str_len(ctx->rule) > 0) { + if (virtual_config_add_rule(ctx, error_r) < 0) + return -1; + } + if (!uni_utf8_str_is_valid(line)) { + *error_r = t_strdup_printf("Mailbox name not UTF-8: %s", + line); + return -1; + } + + /* new mailbox. the search args are added to it later. */ + bbox = p_new(ctx->pool, struct virtual_backend_box, 1); + bbox->virtual_mbox = ctx->mbox; + if (strcasecmp(line, "INBOX") == 0) + line = "INBOX"; + bbox->name = p_strdup(ctx->pool, line); + switch (bbox->name[0]) { + case '+': + bbox->name++; + bbox->clear_recent = TRUE; + break; + case '-': + bbox->name++; + bbox->negative_match = TRUE; + break; + case '!': + /* save messages here */ + if (ctx->mbox->save_bbox != NULL) { + *error_r = "Multiple save mailboxes defined"; + return -1; + } + bbox->name++; + ctx->mbox->save_bbox = bbox; + no_wildcards = TRUE; + break; + } + if (bbox->name[0] == '/') { + /* [+-!]/metadata entry:value */ + if ((p = strchr(bbox->name, ':')) == NULL) { + *error_r = "':' separator missing between metadata entry name and value"; + return -1; + } + bbox->metadata_entry = p_strdup_until(ctx->pool, bbox->name, p++); + bbox->metadata_value = p; + if (!imap_metadata_verify_entry_name(bbox->metadata_entry, error_r)) + return -1; + no_wildcards = TRUE; + } + + if (!no_wildcards && + (strchr(bbox->name, '*') != NULL || + strchr(bbox->name, '%') != NULL)) { + bbox->glob = imap_match_init(ctx->pool, bbox->name, TRUE, ctx->sep); + ctx->have_wildcards = TRUE; + } + if (bbox->metadata_entry == NULL) { + /* now that the prefix characters have been processed, + find the namespace */ + bbox->ns = strcasecmp(bbox->name, "INBOX") == 0 ? + mail_namespace_find_inbox(user->namespaces) : + mail_namespace_find(user->namespaces, bbox->name); + if (bbox->ns == NULL) { + *error_r = t_strdup_printf("Namespace not found for %s", + bbox->name); + return -1; + } + if (strcmp(bbox->name, ctx->mbox->box.vname) == 0) { + *error_r = "Virtual mailbox can't point to itself"; + return -1; + } + ctx->have_mailbox_defines = TRUE; + } + + array_push_back(&ctx->mbox->backend_boxes, &bbox); + return 0; +} + +static void +virtual_mailbox_get_list_patterns(struct virtual_parse_context *ctx) +{ + struct virtual_mailbox *mbox = ctx->mbox; + ARRAY_TYPE(mailbox_virtual_patterns) *dest; + struct mailbox_virtual_pattern pattern; + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + i_zero(&pattern); + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + p_array_init(&mbox->list_include_patterns, ctx->pool, count); + p_array_init(&mbox->list_exclude_patterns, ctx->pool, count); + for (i = 0; i < count; i++) { + if (bboxes[i]->metadata_entry == NULL) + continue; + pattern.ns = bboxes[i]->ns; + pattern.pattern = bboxes[i]->name; + if (bboxes[i]->negative_match) + dest = &mbox->list_include_patterns; + else { + dest = &mbox->list_exclude_patterns; + pattern.pattern++; + } + array_push_back(dest, &pattern); + } +} + +static void +separate_wildcard_mailboxes(struct virtual_mailbox *mbox, + ARRAY_TYPE(virtual_backend_box) *wildcard_boxes, + ARRAY_TYPE(virtual_backend_box) *neg_boxes, + ARRAY_TYPE(virtual_backend_box) *metadata_boxes) +{ + struct virtual_backend_box *const *bboxes; + ARRAY_TYPE(virtual_backend_box) *dest; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + t_array_init(wildcard_boxes, I_MIN(16, count)); + t_array_init(neg_boxes, 4); + t_array_init(metadata_boxes, 4); + for (i = 0; i < count;) { + if (bboxes[i]->metadata_entry != NULL) + dest = metadata_boxes; + else if (bboxes[i]->negative_match) + dest = neg_boxes; + else if (bboxes[i]->glob != NULL) + dest = wildcard_boxes; + else { + dest = NULL; + i++; + } + + if (dest != NULL) { + array_push_back(dest, &bboxes[i]); + array_delete(&mbox->backend_boxes, i, 1); + bboxes = array_get_modifiable(&mbox->backend_boxes, + &count); + } + } +} + +static void virtual_config_copy_expanded(struct virtual_parse_context *ctx, + struct virtual_backend_box *wbox, + const char *name) +{ + struct virtual_backend_box *bbox; + + bbox = p_new(ctx->pool, struct virtual_backend_box, 1); + *bbox = *wbox; + bbox->name = p_strdup(ctx->pool, name); + bbox->glob = NULL; + bbox->wildcard = TRUE; + mail_search_args_ref(bbox->search_args); + array_push_back(&ctx->mbox->backend_boxes, &bbox); +} + +static bool virtual_ns_match(struct mail_namespace *config_ns, + struct mail_namespace *iter_ns) +{ + /* we match only one namespace for each pattern, except with shared + namespaces match also autocreated children */ + if (config_ns == iter_ns) + return TRUE; + if (config_ns->type == iter_ns->type && + (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) == 0 && + (iter_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0) + return TRUE; + if ((iter_ns->flags & NAMESPACE_FLAG_INBOX_USER) != 0 && + (config_ns->flags & NAMESPACE_FLAG_AUTOCREATED) != 0 && + config_ns->prefix_len == 0) { + /* prefix="" namespace was autocreated, so e.g. "*" would match + only that empty namespace. but we want "*" to also match + the inbox=yes namespace, so check it here separately. */ + return TRUE; + } + return FALSE; +} + +static bool virtual_config_match(const struct mailbox_info *info, + ARRAY_TYPE(virtual_backend_box) *boxes_arr, + unsigned int *idx_r) +{ + struct virtual_backend_box *const *boxes; + unsigned int i, count; + + boxes = array_get_modifiable(boxes_arr, &count); + for (i = 0; i < count; i++) { + if (boxes[i]->glob != NULL) { + if (virtual_ns_match(boxes[i]->ns, info->ns) && + imap_match(boxes[i]->glob, + info->vname) == IMAP_MATCH_YES) { + *idx_r = i; + return TRUE; + } + } else { + if (strcmp(boxes[i]->name, info->vname) == 0) { + *idx_r = i; + return TRUE; + } + } + } + return FALSE; +} + +static int virtual_config_box_metadata_match(struct mailbox *box, + struct virtual_backend_box *bbox, + const char **error_r) +{ + struct imap_metadata_transaction *imtrans; + struct mail_attribute_value value; + int ret; + + imtrans = imap_metadata_transaction_begin(box); + ret = imap_metadata_get(imtrans, bbox->metadata_entry, &value); + if (ret < 0) + *error_r = t_strdup(imap_metadata_transaction_get_last_error(imtrans, NULL)); + if (ret > 0) + ret = wildcard_match(value.value, bbox->metadata_value) ? 1 : 0; + if (ret >= 0 && bbox->negative_match) + ret = ret > 0 ? 0 : 1; + (void)imap_metadata_transaction_commit(&imtrans, NULL, NULL); + return ret; +} + +static int +virtual_config_metadata_match(const struct mailbox_info *info, + ARRAY_TYPE(virtual_backend_box) *boxes_arr, + const char **error_r) +{ + struct virtual_backend_box *const *boxes; + struct mailbox *box; + unsigned int i, count; + int ret = 1; + + boxes = array_get_modifiable(boxes_arr, &count); + if (count == 0) + return 1; + + box = mailbox_alloc(info->ns->list, info->vname, MAILBOX_FLAG_READONLY); + for (i = 0; i < count; i++) { + /* break on error or match */ + if ((ret = virtual_config_box_metadata_match(box, boxes[i], error_r)) < 0 || ret > 0) + break; + } + mailbox_free(&box); + return ret; +} + +static int virtual_config_expand_wildcards(struct virtual_parse_context *ctx, + const char **error_r) +{ + const enum mail_namespace_type iter_ns_types = + MAIL_NAMESPACE_TYPE_MASK_ALL; + const enum mailbox_list_iter_flags iter_flags = + MAILBOX_LIST_ITER_RETURN_NO_FLAGS; + struct mail_user *user = ctx->mbox->storage->storage.user; + ARRAY_TYPE(virtual_backend_box) wildcard_boxes, neg_boxes, metadata_boxes; + struct mailbox_list_iterate_context *iter; + struct virtual_backend_box *const *wboxes, *const *boxp; + const char **patterns; + const struct mailbox_info *info; + unsigned int i, j, count; + int ret = 0; + + separate_wildcard_mailboxes(ctx->mbox, &wildcard_boxes, + &neg_boxes, &metadata_boxes); + + /* get patterns we want to list */ + wboxes = array_get_modifiable(&wildcard_boxes, &count); + if (count == 0) { + /* only negative wildcards - doesn't really make sense. + just ignore. */ + return 0; + } + patterns = t_new(const char *, count + 1); + for (i = 0; i < count; i++) + patterns[i] = wboxes[i]->name; + + /* match listed mailboxes to wildcards */ + iter = mailbox_list_iter_init_namespaces(user->namespaces, patterns, + iter_ns_types, iter_flags); + while ((info = mailbox_list_iter_next(iter)) != NULL) { + /* skip non-selectable mailboxes (especially mbox + directories) */ + if ((info->flags & MAILBOX_NOSELECT) != 0) + continue; + if (strcmp(info->vname, ctx->mbox->box.vname) == 0) { + /* don't allow virtual folder to point to itself */ + continue; + } + + if (virtual_config_match(info, &wildcard_boxes, &i) && + !virtual_config_match(info, &neg_boxes, &j) && + virtual_backend_box_lookup_name(ctx->mbox, + info->vname) == NULL) { + ret = virtual_config_metadata_match(info, &metadata_boxes, error_r); + if (ret < 0) + break; + if (ret > 0) { + virtual_config_copy_expanded(ctx, wboxes[i], + info->vname); + } + } + } + for (i = 0; i < count; i++) + mail_search_args_unref(&wboxes[i]->search_args); + array_foreach(&neg_boxes, boxp) + mail_search_args_unref(&(*boxp)->search_args); + array_foreach(&metadata_boxes, boxp) + mail_search_args_unref(&(*boxp)->search_args); + if (mailbox_list_iter_deinit(&iter) < 0) { + *error_r = mailbox_list_get_last_internal_error(user->namespaces->list, NULL); + return -1; + } + return ret < 0 ? -1 : 0; +} + +static void virtual_config_search_args_dup(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + struct mail_search_args *old_args; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + old_args = bboxes[i]->search_args; + bboxes[i]->search_args = mail_search_args_dup(old_args); + mail_search_args_unref(&old_args); + } +} + +int virtual_config_read(struct virtual_mailbox *mbox) +{ + struct mail_storage *storage = mbox->box.storage; + struct virtual_parse_context ctx; + const char *box_path, *path, *line, *error; + unsigned int linenum = 0; + int fd, ret = 0; + + i_array_init(&mbox->backend_boxes, 8); + mbox->search_args_crc32 = (uint32_t)-1; + + box_path = mailbox_get_path(&mbox->box); + path = t_strconcat(box_path, "/"VIRTUAL_CONFIG_FNAME, NULL); + fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == EACCES) { + mailbox_set_critical(&mbox->box, "%s", + mail_error_eacces_msg("open", path)); + } else if (errno != ENOENT) { + mailbox_set_critical(&mbox->box, + "open(%s) failed: %m", path); + } else if (errno == ENOENT) { + mail_storage_set_error(storage, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(mbox->box.vname)); + } else { + mailbox_set_critical(&mbox->box, + "stat(%s) failed: %m", box_path); + } + return -1; + } + + i_zero(&ctx); + ctx.sep = mail_namespaces_get_root_sep(storage->user->namespaces); + ctx.mbox = mbox; + ctx.pool = mbox->box.pool; + ctx.rule = t_str_new(256); + ctx.input = i_stream_create_fd(fd, SIZE_MAX); + i_stream_set_return_partial_line(ctx.input, TRUE); + while ((line = i_stream_read_next_line(ctx.input)) != NULL) { + linenum++; + if (*line == '#') + continue; + if (*line == '\0') + ret = virtual_config_add_rule(&ctx, &error); + else + ret = virtual_config_parse_line(&ctx, line, &error); + if (ret < 0) { + mailbox_set_critical(&mbox->box, + "%s: Error at line %u: %s", + path, linenum, error); + break; + } + } + if (ret == 0) { + ret = virtual_config_add_rule(&ctx, &error); + if (ret < 0) { + mailbox_set_critical(&mbox->box, + "%s: Error at line %u: %s", + path, linenum, error); + } + } + + virtual_mailbox_get_list_patterns(&ctx); + if (ret == 0 && ctx.have_wildcards) { + struct event_reason *reason = + event_reason_begin("virtual:config_read"); + ret = virtual_config_expand_wildcards(&ctx, &error); + if (ret < 0) + mailbox_set_critical(&mbox->box, "%s: %s", path, error); + event_reason_end(&reason); + } + + if (ret == 0 && !ctx.have_mailbox_defines) { + mailbox_set_critical(&mbox->box, + "%s: No mailboxes defined", path); + ret = -1; + } + if (ret == 0) + virtual_config_search_args_dup(mbox); + i_stream_unref(&ctx.input); + i_close_fd(&fd); + return ret; +} + +void virtual_config_free(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + if (!array_is_created(&mbox->backend_boxes)) { + /* mailbox wasn't opened */ + return; + } + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->search_args != NULL) + mail_search_args_unref(&bboxes[i]->search_args); + } + array_free(&mbox->backend_boxes); +} diff --git a/src/plugins/virtual/virtual-mail.c b/src/plugins/virtual/virtual-mail.c new file mode 100644 index 0000000..21459ba --- /dev/null +++ b/src/plugins/virtual/virtual-mail.c @@ -0,0 +1,583 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "index-mail.h" +#include "virtual-storage.h" +#include "virtual-transaction.h" + +struct virtual_mail { + struct index_mail imail; + + enum mail_fetch_field wanted_fields; + struct mailbox_header_lookup_ctx *wanted_headers; + + /* temp_wanted_fields for this mail. Used only when mail doesn't have + a backend mail yet. */ + enum mail_fetch_field delayed_temp_fields; + struct mailbox_header_lookup_ctx *delayed_temp_headers; + + /* currently active mail */ + struct mail *cur_backend_mail; + struct virtual_mail_index_record cur_vrec; + + /* all allocated mails */ + ARRAY(struct mail *) backend_mails; + + /* mail is lost if backend_mail doesn't point to correct mail */ + bool cur_lost:1; +}; + +struct mail * +virtual_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)t->box; + struct virtual_mail *vmail; + pool_t mail_pool, data_pool; + + mail_pool = pool_alloconly_create("vmail", 1024); + data_pool = pool_alloconly_create("virtual index_mail", 512); + vmail = p_new(mail_pool, struct virtual_mail, 1); + vmail->wanted_fields = wanted_fields; + vmail->wanted_headers = wanted_headers; + if (vmail->wanted_headers != NULL) + mailbox_header_lookup_ref(vmail->wanted_headers); + /* Do not pass wanted_fields or wanted_headers to index_mail_init. + It will just cause unwanted behaviour, as we only want these + to be passed to backend mails. */ + index_mail_init(&vmail->imail, t, 0, NULL, mail_pool, data_pool); + vmail->imail.mail.v = virtual_mail_vfuncs; + i_array_init(&vmail->backend_mails, array_count(&mbox->backend_boxes)); + return &vmail->imail.mail.mail; +} + +static void virtual_mail_close(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail **mails; + unsigned int i, count; + + if (mail->seq != 0) { + mailbox_header_lookup_unref(&vmail->delayed_temp_headers); + vmail->delayed_temp_fields = 0; + } + + mails = array_get_modifiable(&vmail->backend_mails, &count); + for (i = 0; i < count; i++) { + struct mail_private *p = (struct mail_private *)mails[i]; + + if (vmail->imail.freeing) + mail_free(&mails[i]); + else + p->v.close(mails[i]); + } + if (vmail->imail.freeing) { + array_free(&vmail->backend_mails); + mailbox_header_lookup_unref(&vmail->wanted_headers); + } + index_mail_close(mail); +} + +static struct mail * +backend_mail_find(struct virtual_mail *vmail, struct mailbox *box) +{ + struct mail *const *mails; + unsigned int i, count; + + mails = array_get(&vmail->backend_mails, &count); + for (i = 0; i < count; i++) { + if (mails[i]->box == box) + return mails[i]; + } + return NULL; +} + +static int backend_mail_get(struct virtual_mail *vmail, + struct mail **backend_mail_r) +{ + struct mail *mail = &vmail->imail.mail.mail; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box; + struct virtual_backend_box *bbox; + + *backend_mail_r = NULL; + + if (vmail->cur_backend_mail != NULL) { + if (vmail->cur_lost) { + mail_set_expunged(&vmail->imail.mail.mail); + return -1; + } + *backend_mail_r = vmail->cur_backend_mail; + return 0; + } + + bbox = virtual_backend_box_lookup(mbox, vmail->cur_vrec.mailbox_id); + i_assert(bbox != NULL); + + vmail->cur_backend_mail = backend_mail_find(vmail, bbox->box); + if (vmail->cur_backend_mail == NULL) { + if (!bbox->box->opened && + virtual_backend_box_open(mbox, bbox) < 0) { + virtual_box_copy_error(mail->box, bbox->box); + return -1; + } + (void)virtual_mail_set_backend_mail(mail, bbox); + i_assert(vmail->cur_backend_mail != NULL); + } + virtual_backend_box_accessed(mbox, bbox); + vmail->cur_lost = !mail_set_uid(vmail->cur_backend_mail, + vmail->cur_vrec.real_uid); + mail->expunged = vmail->cur_lost || vmail->cur_backend_mail->expunged; + if (vmail->cur_lost) { + mail_set_expunged(&vmail->imail.mail.mail); + return -1; + } + /* headers need to be converted to backend-headers, so go through + the virtual add_temp_wanted_fields() again. */ + mail_add_temp_wanted_fields(mail, vmail->delayed_temp_fields, + vmail->delayed_temp_headers); + *backend_mail_r = vmail->cur_backend_mail; + return 0; +} + +struct mail * +virtual_mail_set_backend_mail(struct mail *mail, + struct virtual_backend_box *bbox) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *backend_pmail; + struct mailbox_transaction_context *backend_trans; + struct mailbox_header_lookup_ctx *backend_headers; + + i_assert(bbox->box->opened); + + backend_trans = virtual_transaction_get(mail->transaction, bbox->box); + + backend_headers = vmail->wanted_headers == NULL ? NULL : + mailbox_header_lookup_init(bbox->box, + vmail->wanted_headers->name); + vmail->cur_backend_mail = + mail_alloc(backend_trans, vmail->wanted_fields, backend_headers); + mailbox_header_lookup_unref(&backend_headers); + + backend_pmail = (struct mail_private *)vmail->cur_backend_mail; + backend_pmail->vmail = mail; + array_push_back(&vmail->backend_mails, &vmail->cur_backend_mail); + return vmail->cur_backend_mail; +} + +void virtual_mail_set_unattached_backend_mail(struct mail *mail, + struct mail *backend_mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *backend_pmail; + + vmail->cur_backend_mail = backend_mail; + + backend_pmail = (struct mail_private *)backend_mail; + backend_pmail->vmail = mail; +} + +static void virtual_mail_set_seq(struct mail *mail, uint32_t seq, bool saving) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)mail->box; + const void *data; + + i_assert(!saving); + + mail_index_lookup_ext(mail->transaction->view, seq, + mbox->virtual_ext_id, &data, NULL); + memcpy(&vmail->cur_vrec, data, sizeof(vmail->cur_vrec)); + + index_mail_set_seq(mail, seq, saving); + + vmail->cur_backend_mail = NULL; +} + +static bool virtual_mail_set_uid(struct mail *mail, uint32_t uid) +{ + uint32_t seq; + + if (!mail_index_lookup_seq(mail->transaction->view, uid, &seq)) + return FALSE; + + virtual_mail_set_seq(mail, seq, FALSE); + return TRUE; +} + +static void virtual_mail_set_uid_cache_updates(struct mail *mail, bool set) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + p = (struct mail_private *)backend_mail; + p->v.set_uid_cache_updates(backend_mail, set); +} + +static bool virtual_mail_prefetch(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return TRUE; + p = (struct mail_private *)backend_mail; + return p->v.prefetch(backend_mail); +} + +static int virtual_mail_precache(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + return p->v.precache(backend_mail); +} + +static void +virtual_mail_add_temp_wanted_fields(struct mail *mail, + enum mail_fetch_field fields, + struct mailbox_header_lookup_ctx *headers) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mailbox_header_lookup_ctx *backend_headers, *new_headers; + + if (mail->seq == 0) { + /* No mail set yet. Delay until it is set. */ + vmail->delayed_temp_fields |= fields; + if (vmail->delayed_temp_headers == NULL) + vmail->delayed_temp_headers = headers; + else { + new_headers = mailbox_header_lookup_merge( + vmail->delayed_temp_headers, headers); + mailbox_header_lookup_unref(&vmail->delayed_temp_headers); + vmail->delayed_temp_headers = new_headers; + } + return; + } + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + /* convert header indexes to backend mailbox's header indexes */ + backend_headers = headers == NULL ? NULL : + mailbox_header_lookup_init(backend_mail->box, headers->name); + mail_add_temp_wanted_fields(backend_mail, fields, backend_headers); + mailbox_header_lookup_unref(&backend_headers); +} + +static int +virtual_mail_get_parts(struct mail *mail, struct message_part **parts_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_parts(backend_mail, parts_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_date(struct mail *mail, time_t *date_r, int *timezone_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + int tz; + + if (timezone_r == NULL) + timezone_r = &tz; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_date(backend_mail, date_r, timezone_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_received_date(struct mail *mail, time_t *date_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_received_date(backend_mail, date_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_save_date(struct mail *mail, time_t *date_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + ret = mail_get_save_date(backend_mail, date_r); + if (ret < 0) + virtual_box_copy_error(mail->box, backend_mail->box); + return ret; +} + +static int virtual_mail_get_virtual_mail_size(struct mail *mail, uoff_t *size_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_virtual_size(backend_mail, size_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_physical_size(struct mail *mail, uoff_t *size_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_physical_size(backend_mail, size_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_first_header(struct mail *mail, const char *field, + bool decode_to_utf8, const char **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + ret = p->v.get_first_header(backend_mail, field, + decode_to_utf8, value_r); + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return ret; +} + +static int +virtual_mail_get_headers(struct mail *mail, const char *field, + bool decode_to_utf8, const char *const **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mail_private *p; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + p = (struct mail_private *)backend_mail; + if (p->v.get_headers(backend_mail, field, decode_to_utf8, value_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_header_stream(struct mail *mail, + struct mailbox_header_lookup_ctx *headers, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + struct mailbox_header_lookup_ctx *backend_headers; + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + backend_headers = mailbox_header_lookup_init(backend_mail->box, + headers->name); + ret = mail_get_header_stream(backend_mail, backend_headers, stream_r); + mailbox_header_lookup_unref(&backend_headers); + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_stream(struct mail *mail, bool get_body, + struct message_size *hdr_size, + struct message_size *body_size, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail_private *vp = (struct mail_private *)mail; + struct mail *backend_mail; + const char *reason = t_strdup_printf("virtual mailbox %s: Opened mail UID=%u: %s", + mailbox_get_vname(mail->box), mail->uid, vp->get_stream_reason); + int ret; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + if (get_body) { + ret = mail_get_stream_because(backend_mail, hdr_size, body_size, + reason, stream_r); + } else { + ret = mail_get_hdr_stream_because(backend_mail, hdr_size, + reason, stream_r); + } + + if (ret < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_binary_stream(struct mail *mail, + const struct message_part *part, + bool include_hdr, uoff_t *size_r, + unsigned int *lines_r, bool *binary_r, + struct istream **stream_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + struct mail_private *p = (struct mail_private *)backend_mail; + if (p->v.get_binary_stream(backend_mail, part, include_hdr, + size_r, lines_r, binary_r, stream_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int +virtual_mail_get_special(struct mail *mail, enum mail_fetch_field field, + const char **value_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + if (mail_get_special(backend_mail, field, value_r) < 0) { + virtual_box_copy_error(mail->box, backend_mail->box); + return -1; + } + return 0; +} + +static int virtual_mail_get_backend_mail(struct mail *mail, + struct mail **real_mail_r) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return -1; + + if (mail_get_backend_mail(backend_mail, real_mail_r) < 0) + return -1; + return 0; +} + +static void virtual_mail_update_pop3_uidl(struct mail *mail, const char *uidl) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_update_pop3_uidl(backend_mail, uidl); +} + +static void virtual_mail_expunge(struct mail *mail) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_expunge(backend_mail); +} + +static void +virtual_mail_set_cache_corrupted(struct mail *mail, + enum mail_fetch_field field, + const char *reason) +{ + struct virtual_mail *vmail = (struct virtual_mail *)mail; + struct mail *backend_mail; + + if (backend_mail_get(vmail, &backend_mail) < 0) + return; + mail_set_cache_corrupted(backend_mail, field, reason); +} + +struct mail_vfuncs virtual_mail_vfuncs = { + virtual_mail_close, + index_mail_free, + virtual_mail_set_seq, + virtual_mail_set_uid, + virtual_mail_set_uid_cache_updates, + virtual_mail_prefetch, + virtual_mail_precache, + virtual_mail_add_temp_wanted_fields, + + index_mail_get_flags, + index_mail_get_keywords, + index_mail_get_keyword_indexes, + index_mail_get_modseq, + index_mail_get_pvt_modseq, + virtual_mail_get_parts, + virtual_mail_get_date, + virtual_mail_get_received_date, + virtual_mail_get_save_date, + virtual_mail_get_virtual_mail_size, + virtual_mail_get_physical_size, + virtual_mail_get_first_header, + virtual_mail_get_headers, + virtual_mail_get_header_stream, + virtual_mail_get_stream, + virtual_mail_get_binary_stream, + virtual_mail_get_special, + virtual_mail_get_backend_mail, + index_mail_update_flags, + index_mail_update_keywords, + index_mail_update_modseq, + index_mail_update_pvt_modseq, + virtual_mail_update_pop3_uidl, + virtual_mail_expunge, + virtual_mail_set_cache_corrupted, + NULL, +}; diff --git a/src/plugins/virtual/virtual-plugin.c b/src/plugins/virtual/virtual-plugin.c new file mode 100644 index 0000000..43db7cb --- /dev/null +++ b/src/plugins/virtual/virtual-plugin.c @@ -0,0 +1,25 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "mail-namespace.h" +#include "virtual-storage.h" +#include "virtual-plugin.h" + +const char *virtual_plugin_version = DOVECOT_ABI_VERSION; + +static struct mail_storage_hooks acl_mail_storage_hooks = { + .mailbox_allocated = virtual_backend_mailbox_allocated, + .mailbox_opened = virtual_backend_mailbox_opened +}; + +void virtual_plugin_init(struct module *module ATTR_UNUSED) +{ + mail_storage_class_register(&virtual_storage); + mail_storage_hooks_add(module, &acl_mail_storage_hooks); +} + +void virtual_plugin_deinit(void) +{ + mail_storage_class_unregister(&virtual_storage); + mail_storage_hooks_remove(&acl_mail_storage_hooks); +} diff --git a/src/plugins/virtual/virtual-plugin.h b/src/plugins/virtual/virtual-plugin.h new file mode 100644 index 0000000..39f04bb --- /dev/null +++ b/src/plugins/virtual/virtual-plugin.h @@ -0,0 +1,7 @@ +#ifndef VIRTUAL_PLUGIN_H +#define VIRTUAL_PLUGIN_H + +void virtual_plugin_init(struct module *module); +void virtual_plugin_deinit(void); + +#endif diff --git a/src/plugins/virtual/virtual-save.c b/src/plugins/virtual/virtual-save.c new file mode 100644 index 0000000..9499cd3 --- /dev/null +++ b/src/plugins/virtual/virtual-save.c @@ -0,0 +1,153 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "virtual-transaction.h" +#include "virtual-storage.h" + +struct virtual_save_context { + struct mail_save_context ctx; + struct mail_save_context *backend_save_ctx; + struct mailbox *backend_box; + char *open_errstr; + enum mail_error open_error; +}; + +struct mail_save_context * +virtual_save_alloc(struct mailbox_transaction_context *_t) +{ + struct virtual_transaction_context *t = + (struct virtual_transaction_context *)_t; + struct virtual_mailbox *mbox = (struct virtual_mailbox *)_t->box; + struct mailbox_transaction_context *backend_trans; + struct virtual_save_context *ctx; + const char *errstr; + + if (_t->save_ctx == NULL) { + ctx = i_new(struct virtual_save_context, 1); + ctx->ctx.transaction = &t->t; + _t->save_ctx = &ctx->ctx; + } else { + ctx = (struct virtual_save_context *)_t->save_ctx; + } + + if (mbox->save_bbox != NULL) { + i_assert(ctx->backend_save_ctx == NULL); + i_assert(ctx->open_errstr == NULL); + + if (!mbox->save_bbox->box->opened && + virtual_backend_box_open(mbox, mbox->save_bbox) < 0) { + errstr = mailbox_get_last_error(mbox->save_bbox->box, + &ctx->open_error); + ctx->open_errstr = i_strdup(errstr); + } else { + backend_trans = + virtual_transaction_get(_t, mbox->save_bbox->box); + ctx->backend_save_ctx = mailbox_save_alloc(backend_trans); + } + virtual_backend_box_accessed(mbox, mbox->save_bbox); + } + return _t->save_ctx; +} + +static struct mail_keywords * +virtual_copy_keywords(struct mailbox *src_box, + const struct mail_keywords *src_keywords, + struct mailbox *dest_box) +{ + struct mailbox_status status; + ARRAY_TYPE(keywords) kw_strings; + const char *kw; + unsigned int i; + + if (src_keywords == NULL || src_keywords->count == 0) + return NULL; + + t_array_init(&kw_strings, src_keywords->count + 1); + mailbox_get_open_status(src_box, STATUS_KEYWORDS, &status); + + for (i = 0; i < src_keywords->count; i++) { + kw = array_idx_elem(status.keywords, src_keywords->idx[i]); + array_push_back(&kw_strings, &kw); + } + array_append_zero(&kw_strings); + return mailbox_keywords_create_valid(dest_box, + array_front(&kw_strings)); +} + +int virtual_save_begin(struct mail_save_context *_ctx, struct istream *input) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + struct mail_save_data *mdata = &_ctx->data; + struct mail_keywords *keywords; + + if (ctx->backend_save_ctx == NULL) { + if (ctx->open_errstr != NULL) { + /* mailbox_open() failed */ + mail_storage_set_error(_ctx->transaction->box->storage, + ctx->open_error, ctx->open_errstr); + } else { + mail_storage_set_error(_ctx->transaction->box->storage, + MAIL_ERROR_NOTPOSSIBLE, + "Can't save messages to this virtual mailbox"); + } + return -1; + } + + ctx->backend_box = ctx->backend_save_ctx->transaction->box; + keywords = virtual_copy_keywords(_ctx->transaction->box, mdata->keywords, + ctx->backend_box); + mailbox_save_set_flags(ctx->backend_save_ctx, + mdata->flags | mdata->pvt_flags, + keywords); + if (keywords != NULL) + mail_index_keywords_unref(&keywords); + + mailbox_save_set_received_date(ctx->backend_save_ctx, + mdata->received_date, + mdata->received_tz_offset); + mailbox_save_set_from_envelope(ctx->backend_save_ctx, + mdata->from_envelope); + mailbox_save_set_guid(ctx->backend_save_ctx, mdata->guid); + mailbox_save_set_min_modseq(ctx->backend_save_ctx, mdata->min_modseq); + + virtual_mail_set_unattached_backend_mail(_ctx->dest_mail, + ctx->backend_save_ctx->dest_mail); + return mailbox_save_begin(&ctx->backend_save_ctx, input); +} + +int virtual_save_continue(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + return mailbox_save_continue(ctx->backend_save_ctx); +} + +int virtual_save_finish(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + int ret; + ret = mailbox_save_finish(&ctx->backend_save_ctx); + index_save_context_free(_ctx); + + return ret; +} + +void virtual_save_cancel(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + if (ctx->backend_save_ctx != NULL) + mailbox_save_cancel(&ctx->backend_save_ctx); + i_free_and_null(ctx->open_errstr); + _ctx->unfinished = FALSE; +} + +void virtual_save_free(struct mail_save_context *_ctx) +{ + struct virtual_save_context *ctx = (struct virtual_save_context *)_ctx; + + virtual_save_cancel(_ctx); + mailbox_save_context_deinit(_ctx); + i_free(ctx); +} diff --git a/src/plugins/virtual/virtual-search.c b/src/plugins/virtual/virtual-search.c new file mode 100644 index 0000000..ee04a74 --- /dev/null +++ b/src/plugins/virtual/virtual-search.c @@ -0,0 +1,206 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "mail-search.h" +#include "index-search-private.h" +#include "virtual-storage.h" + + +enum virtual_search_state { + VIRTUAL_SEARCH_STATE_BUILD, + VIRTUAL_SEARCH_STATE_RETURN, + VIRTUAL_SEARCH_STATE_SORT, + VIRTUAL_SEARCH_STATE_SORT_DONE +}; + +struct virtual_search_record { + uint32_t mailbox_id; + uint32_t real_uid; + uint32_t virtual_seq; +}; + +struct virtual_search_context { + union mail_search_module_context module_ctx; + + ARRAY_TYPE(seq_range) result; + struct seq_range_iter result_iter; + ARRAY(struct virtual_search_record) records; + + enum virtual_search_state search_state; + unsigned int next_result_n; + unsigned int next_record_idx; +}; + +static int virtual_search_record_cmp(const struct virtual_search_record *r1, + const struct virtual_search_record *r2) +{ + if (r1->mailbox_id < r2->mailbox_id) + return -1; + if (r1->mailbox_id > r2->mailbox_id) + return 1; + + if (r1->real_uid < r2->real_uid) + return -1; + if (r1->real_uid > r2->real_uid) + return 1; + return 0; +} + +static int mail_search_get_result(struct mail_search_context *ctx) +{ + const struct mail_search_arg *arg; + int ret = 1; + + for (arg = ctx->args->args; arg != NULL; arg = arg->next) { + if (arg->result < 0) + return -1; + if (arg->result == 0) + ret = 0; + } + return ret; +} + +static void virtual_search_get_records(struct mail_search_context *ctx, + struct virtual_search_context *vctx) +{ + struct virtual_mailbox *mbox = + (struct virtual_mailbox *)ctx->transaction->box; + const struct virtual_mail_index_record *vrec; + struct virtual_search_record srec; + const void *data; + int result; + + i_zero(&srec); + while (index_storage_search_next_update_seq(ctx)) { + result = mail_search_get_result(ctx); + i_assert(result != 0); + if (result > 0) { + /* full match, no need to check this any further */ + seq_range_array_add(&vctx->result, ctx->seq); + } else { + /* possible match, save and check later */ + mail_index_lookup_ext(mbox->box.view, ctx->seq, + mbox->virtual_ext_id, + &data, NULL); + vrec = data; + + srec.mailbox_id = vrec->mailbox_id; + srec.real_uid = vrec->real_uid; + srec.virtual_seq = ctx->seq; + array_push_back(&vctx->records, &srec); + } + mail_search_args_reset(ctx->args->args, FALSE); + } + array_sort(&vctx->records, virtual_search_record_cmp); + + ctx->progress_max = array_count(&vctx->records); +} + +struct mail_search_context * +virtual_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) +{ + struct mail_search_context *ctx; + struct virtual_search_context *vctx; + + ctx = index_storage_search_init(t, args, sort_program, + wanted_fields, wanted_headers); + + vctx = i_new(struct virtual_search_context, 1); + vctx->search_state = VIRTUAL_SEARCH_STATE_BUILD; + i_array_init(&vctx->result, 64); + i_array_init(&vctx->records, 64); + MODULE_CONTEXT_SET(ctx, virtual_storage_module, vctx); + + virtual_search_get_records(ctx, vctx); + seq_range_array_iter_init(&vctx->result_iter, &vctx->result); + return ctx; +} + +int virtual_search_deinit(struct mail_search_context *ctx) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + + array_free(&vctx->result); + array_free(&vctx->records); + i_free(vctx); + return index_storage_search_deinit(ctx); +} + +bool virtual_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + struct index_search_context *ictx = (struct index_search_context *)ctx; + uint32_t seq; + + switch (vctx->search_state) { + case VIRTUAL_SEARCH_STATE_BUILD: + if (ctx->sort_program == NULL) + vctx->search_state = VIRTUAL_SEARCH_STATE_SORT; + else + vctx->search_state = VIRTUAL_SEARCH_STATE_RETURN; + return virtual_search_next_nonblock(ctx, mail_r, tryagain_r); + case VIRTUAL_SEARCH_STATE_RETURN: + return index_storage_search_next_nonblock(ctx, mail_r, tryagain_r); + case VIRTUAL_SEARCH_STATE_SORT: + /* the messages won't be returned sorted, so we'll have to + do it ourself */ + while (index_storage_search_next_nonblock(ctx, mail_r, tryagain_r)) + seq_range_array_add(&vctx->result, (*mail_r)->seq); + if (*tryagain_r) + return FALSE; + + vctx->next_result_n = 0; + vctx->search_state = VIRTUAL_SEARCH_STATE_SORT_DONE; + /* fall through */ + case VIRTUAL_SEARCH_STATE_SORT_DONE: + *tryagain_r = FALSE; + if (!seq_range_array_iter_nth(&vctx->result_iter, + vctx->next_result_n, &seq)) + return FALSE; + vctx->next_result_n++; + *mail_r = index_search_get_mail(ictx); + i_assert(*mail_r != NULL); + mail_set_seq(*mail_r, seq); + return TRUE; + } + i_unreached(); +} + +static void search_args_set_full_match(struct mail_search_arg *args) +{ + for (; args != NULL; args = args->next) + args->result = 1; +} + +bool virtual_search_next_update_seq(struct mail_search_context *ctx) +{ + struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx); + const struct virtual_search_record *recs; + unsigned int count; + + recs = array_get(&vctx->records, &count); + if (vctx->next_record_idx < count) { + /* go through potential results first */ + ctx->seq = recs[vctx->next_record_idx++].virtual_seq - 1; + if (!index_storage_search_next_update_seq(ctx)) + i_unreached(); + ctx->progress_cur = vctx->next_record_idx; + return TRUE; + } + + if (ctx->sort_program != NULL && + seq_range_array_iter_nth(&vctx->result_iter, + vctx->next_result_n, &ctx->seq)) { + /* this is known to match fully */ + search_args_set_full_match(ctx->args->args); + vctx->next_result_n++; + return TRUE; + } + return FALSE; +} diff --git a/src/plugins/virtual/virtual-storage.c b/src/plugins/virtual/virtual-storage.c new file mode 100644 index 0000000..f88a279 --- /dev/null +++ b/src/plugins/virtual/virtual-storage.c @@ -0,0 +1,950 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "llist.h" +#include "mkdir-parents.h" +#include "unlink-directory.h" +#include "index-mail.h" +#include "mail-copy.h" +#include "mail-search.h" +#include "mailbox-list-private.h" +#include "virtual-plugin.h" +#include "virtual-transaction.h" +#include "virtual-storage.h" +#include "mailbox-list-notify.h" + +#include <stdio.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +#define VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES 64 + +#define VIRTUAL_BACKEND_CONTEXT(obj) \ + MODULE_CONTEXT_REQUIRE(obj, virtual_backend_storage_module) + +struct virtual_backend_mailbox { + union mailbox_module_context module_ctx; +}; + +extern struct mail_storage virtual_storage; +extern struct mailbox virtual_mailbox; +extern struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs; + +struct virtual_storage_module virtual_storage_module = + MODULE_CONTEXT_INIT(&mail_storage_module_register); +static MODULE_CONTEXT_DEFINE_INIT(virtual_backend_storage_module, + &mail_storage_module_register); + +static bool ns_is_visible(struct mail_namespace *ns) +{ + return (ns->flags & NAMESPACE_FLAG_LIST_PREFIX) != 0 || + (ns->flags & NAMESPACE_FLAG_LIST_CHILDREN) != 0 || + (ns->flags & NAMESPACE_FLAG_HIDDEN) == 0; +} + +static const char *get_user_visible_mailbox_name(struct mailbox *box) +{ + if (ns_is_visible(box->list->ns)) + return box->vname; + else { + return t_strdup_printf("<hidden>%c%s", + mail_namespace_get_sep(box->list->ns), + box->vname); + } +} + +void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src) +{ + const char *name, *str; + enum mail_error error; + + name = get_user_visible_mailbox_name(src); + str = mailbox_get_last_error(src, &error); + + str = t_strdup_printf("%s (for backend mailbox %s)", str, name); + mail_storage_set_error(dest->storage, error, str); +} + +static struct mail_storage *virtual_storage_alloc(void) +{ + struct virtual_storage *storage; + pool_t pool; + + pool = pool_alloconly_create("virtual storage", 1024); + storage = p_new(pool, struct virtual_storage, 1); + storage->storage = virtual_storage; + storage->storage.pool = pool; + p_array_init(&storage->open_stack, pool, 8); + return &storage->storage; +} + +static int +virtual_storage_create(struct mail_storage *_storage, + struct mail_namespace *ns ATTR_UNUSED, + const char **error_r) +{ + struct virtual_storage *storage = (struct virtual_storage *)_storage; + const char *value; + + value = mail_user_plugin_getenv(_storage->user, "virtual_max_open_mailboxes"); + if (value == NULL) + storage->max_open_mailboxes = VIRTUAL_DEFAULT_MAX_OPEN_MAILBOXES; + else if (str_to_uint(value, &storage->max_open_mailboxes) < 0) { + *error_r = "Invalid virtual_max_open_mailboxes setting"; + return -1; + } + return 0; +} + +static void +virtual_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED, + struct mailbox_list_settings *set) +{ + if (set->layout == NULL) + set->layout = MAILBOX_LIST_NAME_FS; + if (set->subscription_fname == NULL) + set->subscription_fname = VIRTUAL_SUBSCRIPTION_FILE_NAME; +} + +struct virtual_backend_box * +virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (strcmp(bboxes[i]->name, name) == 0) + return bboxes[i]; + } + return NULL; +} + +struct virtual_backend_box * +virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + if (mailbox_id == 0) + return NULL; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->mailbox_id == mailbox_id) + return bboxes[i]; + } + return NULL; +} + +static bool virtual_mailbox_is_in_open_stack(struct virtual_storage *storage, + const char *name) +{ + const char *const *names; + unsigned int i, count; + + names = array_get(&storage->open_stack, &count); + for (i = 0; i < count; i++) { + if (strcmp(names[i], name) == 0) + return TRUE; + } + return FALSE; +} + +static int virtual_backend_box_open_failed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + enum mail_error error; + const char *str; + + str = t_strdup_printf( + "Virtual mailbox open failed because of mailbox %s: %s", + get_user_visible_mailbox_name(bbox->box), + mailbox_get_last_error(bbox->box, &error)); + mail_storage_set_error(mbox->box.storage, error, str); + mailbox_free(&bbox->box); + + if (error == MAIL_ERROR_PERM && bbox->wildcard) { + /* this mailbox wasn't explicitly specified. just skip it. */ + return 0; + } + return -1; +} + +static int virtual_backend_box_alloc(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox, + enum mailbox_flags flags) +{ + struct mail_user *user = mbox->storage->storage.user; + struct mail_namespace *ns; + const char *mailbox; + enum mailbox_existence existence; + + i_assert(bbox->box == NULL); + + if (!bbox->clear_recent) + flags &= ENUM_NEGATE(MAILBOX_FLAG_DROP_RECENT); + + mailbox = bbox->name; + ns = mail_namespace_find(user->namespaces, mailbox); + bbox->box = mailbox_alloc(ns->list, mailbox, flags); + MODULE_CONTEXT_SET(bbox->box, virtual_storage_module, bbox); + + if (bbox == mbox->save_bbox) { + /* Assume that the save_bbox exists, whether or not it truly + does. This at least gives a better error message than crash + later on. */ + existence = MAILBOX_EXISTENCE_SELECT; + } else { + if (mailbox_exists(bbox->box, TRUE, &existence) < 0) + return virtual_backend_box_open_failed(mbox, bbox); + } + if (existence != MAILBOX_EXISTENCE_SELECT) { + /* ignore this. it could be intentional. */ + e_debug(mbox->box.event, + "Skipping non-existing mailbox %s", + bbox->box->vname); + mailbox_free(&bbox->box); + return 0; + } + + i_array_init(&bbox->uids, 64); + i_array_init(&bbox->sync_pending_removes, 64); + /* we use modseqs for being able to check quickly if backend mailboxes + have changed. make sure the backend has them enabled. */ + (void)mailbox_enable(bbox->box, MAILBOX_FEATURE_CONDSTORE); + return 1; +} + +static int virtual_mailboxes_open(struct virtual_mailbox *mbox, + enum mailbox_flags flags) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + int ret; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; ) { + ret = virtual_backend_box_alloc(mbox, bboxes[i], flags); + if (ret <= 0) { + if (ret < 0) + break; + array_delete(&mbox->backend_boxes, i, 1); + bboxes = array_get(&mbox->backend_boxes, &count); + } else { + i++; + } + } + if (i == count) + return 0; + else { + /* failed */ + for (; i > 0; i--) { + mailbox_free(&bboxes[i-1]->box); + array_free(&bboxes[i-1]->uids); + } + return -1; + } +} + +static struct mailbox * +virtual_mailbox_alloc(struct mail_storage *_storage, struct mailbox_list *list, + const char *vname, enum mailbox_flags flags) +{ + struct virtual_storage *storage = (struct virtual_storage *)_storage; + struct virtual_mailbox *mbox; + pool_t pool; + + pool = pool_alloconly_create("virtual mailbox", 2048); + mbox = p_new(pool, struct virtual_mailbox, 1); + mbox->box = virtual_mailbox; + mbox->box.pool = pool; + mbox->box.storage = _storage; + mbox->box.list = list; + mbox->box.mail_vfuncs = &virtual_mail_vfuncs; + mbox->box.virtual_vfuncs = &virtual_mailbox_vfuncs; + + index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX); + + mbox->storage = storage; + mbox->virtual_ext_id = (uint32_t)-1; + mbox->virtual_ext2_id = (uint32_t)-1; + mbox->virtual_guid_ext_id = (uint32_t)-1; + return &mbox->box; +} + +void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + + if (bbox->sync_mail != NULL) { + trans = bbox->sync_mail->transaction; + mail_free(&bbox->sync_mail); + (void)mailbox_transaction_commit(&trans); + } +} + +static bool virtual_backend_box_can_close(struct virtual_backend_box *bbox) +{ + if (bbox->box->notify_callback != NULL) { + /* we can close it if notify is set + because we have no need to keep it open + for tracking changes */ + return bbox->notify != NULL; + } + if (array_count(&bbox->sync_pending_removes) > 0) { + /* FIXME: we could probably close this by making + syncing support it? */ + return FALSE; + } + return TRUE; +} + +static bool +virtual_backend_box_close_any_except(struct virtual_mailbox *mbox, + struct virtual_backend_box *except_bbox) +{ + struct virtual_backend_box *bbox; + + /* first try to close a mailbox without any transactions. + we'll also skip any mailbox that has notifications enabled (ideally + these would be handled by mailbox list index) */ + for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) { + i_assert(bbox->box->opened); + + if (bbox != except_bbox && + bbox->box->transaction_count == 0 && + virtual_backend_box_can_close(bbox)) { + i_assert(bbox->sync_mail == NULL); + virtual_backend_box_close(mbox, bbox); + return TRUE; + } + } + + /* next try to close a mailbox that has sync_mail, but no + other transactions */ + for (bbox = mbox->open_backend_boxes_head; bbox != NULL; bbox = bbox->next_open) { + if (bbox != except_bbox && + bbox->sync_mail != NULL && + bbox->box->transaction_count == 1 && + virtual_backend_box_can_close(bbox)) { + virtual_backend_box_sync_mail_unset(bbox); + i_assert(bbox->box->transaction_count == 0); + virtual_backend_box_close(mbox, bbox); + return TRUE; + } + } + return FALSE; +} + +static void virtual_backend_mailbox_close(struct mailbox *box) +{ + struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box); + struct virtual_backend_mailbox *vbox = VIRTUAL_BACKEND_CONTEXT(box); + + if (bbox != NULL && bbox->open_tracked) { + /* we could have gotten here from e.g. mailbox_autocreate() + without going through virtual_mailbox_close() */ + virtual_backend_box_close(bbox->virtual_mbox, bbox); + } + vbox->module_ctx.super.close(box); +} + +void virtual_backend_mailbox_allocated(struct mailbox *box) +{ + struct mailbox_vfuncs *v = box->vlast; + struct virtual_backend_mailbox *vbox; + + vbox = p_new(box->pool, struct virtual_backend_mailbox, 1); + vbox->module_ctx.super = *v; + box->vlast = &vbox->module_ctx.super; + v->close = virtual_backend_mailbox_close; + MODULE_CONTEXT_SET(box, virtual_backend_storage_module, vbox); +} + +void virtual_backend_mailbox_opened(struct mailbox *box) +{ + struct virtual_backend_box *bbox = VIRTUAL_CONTEXT(box); + struct virtual_mailbox *mbox; + + if (bbox == NULL) { + /* not a backend for a virtual mailbox */ + return; + } + i_assert(!bbox->open_tracked); + mbox = bbox->virtual_mbox; + + /* the backend mailbox was already opened. if we didn't get here + from virtual_backend_box_open() we may need to close a mailbox */ + while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes && + virtual_backend_box_close_any_except(mbox, bbox)) + ; + + bbox->open_tracked = TRUE; + mbox->backends_open_count++; + DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); +} + +int virtual_backend_box_open(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + i_assert(!bbox->box->opened); + + /* try to keep the number of open mailboxes below the threshold + before opening the mailbox */ + while (mbox->backends_open_count >= mbox->storage->max_open_mailboxes && + virtual_backend_box_close_any_except(mbox, bbox)) + ; + + return mailbox_open(bbox->box); +} + +void virtual_backend_box_close(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + i_assert(bbox->box->opened); + i_assert(bbox->open_tracked); + + if (bbox->search_result != NULL) + mailbox_search_result_free(&bbox->search_result); + + if (bbox->search_args != NULL && + bbox->search_args_initialized) { + mail_search_args_deinit(bbox->search_args); + bbox->search_args_initialized = FALSE; + } + i_assert(mbox->backends_open_count > 0); + mbox->backends_open_count--; + bbox->open_tracked = FALSE; + + DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); + + /* stop receiving notifications */ + if (bbox->notify_changes_started) + mailbox_notify_changes_stop(bbox->box); + bbox->notify_changes_started = FALSE; + + mailbox_close(bbox->box); +} + +void virtual_backend_box_accessed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox) +{ + DLLIST2_REMOVE_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); + DLLIST2_APPEND_FULL(&mbox->open_backend_boxes_head, + &mbox->open_backend_boxes_tail, bbox, + prev_open, next_open); +} + +static void virtual_mailbox_close_internal(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box **bboxes; + unsigned int i, count; + + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->box == NULL) + continue; + if (bboxes[i]->notify != NULL) + mailbox_list_notify_deinit(&bboxes[i]->notify); + if (bboxes[i]->box->opened) + virtual_backend_box_close(mbox, bboxes[i]); + mailbox_free(&bboxes[i]->box); + if (array_is_created(&bboxes[i]->sync_outside_expunges)) + array_free(&bboxes[i]->sync_outside_expunges); + array_free(&bboxes[i]->sync_pending_removes); + array_free(&bboxes[i]->uids); + } + i_assert(mbox->backends_open_count == 0); +} + +static int +virtual_mailbox_exists(struct mailbox *box, bool auto_boxes ATTR_UNUSED, + enum mailbox_existence *existence_r) +{ + return index_storage_mailbox_exists_full(box, VIRTUAL_CONFIG_FNAME, + existence_r); +} + +static int virtual_mailbox_open(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + bool broken; + int ret = 0; + + if (virtual_mailbox_is_in_open_stack(mbox->storage, box->name)) { + mailbox_set_critical(box, + "Virtual mailbox loops: %s", box->name); + return -1; + } + + if (!array_is_created(&mbox->backend_boxes)) + ret = virtual_config_read(mbox); + if (ret == 0) { + array_push_back(&mbox->storage->open_stack, &box->name); + ret = virtual_mailboxes_open(mbox, box->flags); + array_pop_back(&mbox->storage->open_stack); + } + if (ret == 0) + ret = index_storage_mailbox_open(box, FALSE); + if (ret < 0) { + virtual_mailbox_close_internal(mbox); + return -1; + } + + mbox->virtual_ext_id = + mail_index_ext_register(mbox->box.index, "virtual", 0, + sizeof(struct virtual_mail_index_record), + sizeof(uint32_t)); + mbox->virtual_ext2_id = + mail_index_ext_register(mbox->box.index, "virtual2", 0, 0, 0); + + mbox->virtual_guid_ext_id = + mail_index_ext_register(mbox->box.index, "virtual-guid", GUID_128_SIZE, + 0, 0); + + if (virtual_mailbox_ext_header_read(mbox, box->view, &broken) < 0) { + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); + return -1; + } + + /* if GUID is missing write it here */ + if (guid_128_is_empty(mbox->guid)) { + guid_128_generate(mbox->guid); + struct mail_index_transaction *t = + mail_index_transaction_begin(box->view, 0); + mail_index_update_header_ext(t, mbox->virtual_guid_ext_id, + 0, mbox->guid, GUID_128_SIZE); + if (mail_index_transaction_commit(&t) < 0) { + mailbox_set_critical(box, + "Cannot write GUID for virtual mailbox to index"); + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); + return -1; + } + } + + return 0; +} + +static void virtual_mailbox_close(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + virtual_mailbox_close_internal(mbox); + index_storage_mailbox_close(box); +} + +static void virtual_mailbox_free(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + virtual_config_free(mbox); + index_storage_mailbox_free(box); +} + +static int +virtual_mailbox_create(struct mailbox *box, + const struct mailbox_update *update ATTR_UNUSED, + bool directory ATTR_UNUSED) +{ + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't create virtual mailboxes"); + return -1; +} + +static int +virtual_mailbox_update(struct mailbox *box, + const struct mailbox_update *update ATTR_UNUSED) +{ + mail_storage_set_error(box->storage, MAIL_ERROR_NOTPOSSIBLE, + "Can't update virtual mailboxes"); + return -1; +} + +static int virtual_storage_set_have_guid_flags(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + struct mailbox_status status; + + if (!mbox->box.opened) { + if (mailbox_open(&mbox->box) < 0) + return -1; + } + + mbox->have_guids = TRUE; + mbox->have_save_guids = TRUE; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (mailbox_get_status(bboxes[i]->box, 0, &status) < 0) { + const char *errstr; + enum mail_error error; + + errstr = mailbox_get_last_error(bboxes[i]->box, &error); + if (error == MAIL_ERROR_NOTFOUND) { + /* backend mailbox was just lost - skip it */ + continue; + } + /* Not expected to happen, but we can't return failure + since this could be called from + mailbox_get_open_status() and it would panic. + So just log the error and skip the mailbox. */ + mailbox_set_critical(&mbox->box, + "Virtual mailbox: Failed to get have_guid existence for backend mailbox %s: %s", + mailbox_get_vname(bboxes[i]->box), errstr); + continue; + } + if (!status.have_guids) + mbox->have_guids = FALSE; + if (!status.have_save_guids) + mbox->have_save_guids = FALSE; + } + return 0; +} + +static int +virtual_storage_get_status(struct mailbox *box, + enum mailbox_status_items items, + struct mailbox_status *status_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + if ((items & STATUS_LAST_CACHED_SEQ) != 0) + items |= STATUS_MESSAGES; + + if (index_storage_get_status(box, items, status_r) < 0) + return -1; + + if ((items & STATUS_LAST_CACHED_SEQ) != 0) { + /* Virtual mailboxes have no cached data of their own, so the + current value is always 0. The most important use for this + functionality is for "doveadm index" to do FTS indexing and + it doesn't really matter there if we set this value + correctly or not. So for now just assume that everything is + indexed. */ + status_r->last_cached_seq = status_r->messages; + } + if (!mbox->have_guid_flags_set) { + if (virtual_storage_set_have_guid_flags(mbox) < 0) + return -1; + mbox->have_guid_flags_set = TRUE; + } + + if (mbox->have_guids) + status_r->have_guids = TRUE; + if (mbox->have_save_guids) + status_r->have_save_guids = TRUE; + return 0; +} + +static int +virtual_mailbox_get_metadata(struct mailbox *box, + enum mailbox_metadata_items items, + struct mailbox_metadata *metadata_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + if (index_mailbox_get_metadata(box, items, metadata_r) < 0) + return -1; + i_assert(box->opened); + if ((items & MAILBOX_METADATA_GUID) != 0) { + if (guid_128_is_empty(mbox->guid)) { + mailbox_set_critical(box, "GUID missing for virtual folder"); + return -1; + } + guid_128_copy(metadata_r->guid, mbox->guid); + } + return 0; +} + +static void +virtual_notify_callback(struct mailbox *bbox ATTR_UNUSED, struct mailbox *box) +{ + box->notify_callback(box, box->notify_context); +} + +static void virtual_backend_box_changed(struct virtual_backend_box *bbox) +{ + virtual_notify_callback(bbox->box, &bbox->virtual_mbox->box); +} + +static int virtual_notify_start(struct virtual_backend_box *bbox) +{ + i_assert(bbox->notify == NULL); + if (mailbox_list_notify_init(bbox->box->list, MAILBOX_LIST_NOTIFY_STATUS, + &bbox->notify) < 0) + /* did not support notifications */ + return -1; + mailbox_list_notify_wait(bbox->notify, virtual_backend_box_changed, bbox); + return 0; +} + +static void virtual_notify_changes(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box **bboxp; + + if (box->notify_callback == NULL) { + array_foreach_modifiable(&mbox->backend_boxes, bboxp) { + if ((*bboxp)->notify_changes_started) { + mailbox_notify_changes_stop((*bboxp)->box); + (*bboxp)->notify_changes_started = FALSE; + } + if ((*bboxp)->notify != NULL) + mailbox_list_notify_deinit(&(*bboxp)->notify); + } + return; + } + + array_foreach_modifiable(&mbox->backend_boxes, bboxp) { + if (array_count(&mbox->backend_boxes) == 1 && + (*bboxp)->box->opened) { + /* There's only a single backend mailbox and its + indexes are already opened. Might as well use the + backend directly for notifications. */ + } else { + /* we are already waiting for notifications */ + if ((*bboxp)->notify != NULL) + continue; + /* wait for notifications */ + if (virtual_notify_start(*bboxp) == 0) + continue; + /* it did not work, so open the mailbox and use + alternative method */ + } + + if (!(*bboxp)->box->opened && + virtual_backend_box_open(mbox, *bboxp) < 0) { + /* we can't report error in here, so do it later */ + (*bboxp)->open_failed = TRUE; + continue; + } + mailbox_notify_changes((*bboxp)->box, + virtual_notify_callback, box); + (*bboxp)->notify_changes_started = TRUE; + } +} + +static void +virtual_uidmap_to_uid_array(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *uids_r) +{ + const struct virtual_backend_uidmap *uid; + array_foreach(&bbox->uids, uid) { + seq_range_array_add(uids_r, uid->real_uid); + } +} + +static void +virtual_get_virtual_uids(struct mailbox *box, + struct mailbox *backend_mailbox, + const ARRAY_TYPE(seq_range) *backend_uids, + ARRAY_TYPE(seq_range) *virtual_uids_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *bbox; + const struct virtual_backend_uidmap *uids; + ARRAY_TYPE(seq_range) uid_range; + struct seq_range_iter iter; + unsigned int n, i, count; + uint32_t uid; + + if (mbox->lookup_prev_bbox != NULL && + strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0) + bbox = mbox->lookup_prev_bbox; + else { + bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname); + mbox->lookup_prev_bbox = bbox; + } + if (bbox == NULL) + return; + + uids = array_get(&bbox->uids, &count); i = 0; + + t_array_init(&uid_range, 8); + virtual_uidmap_to_uid_array(bbox, &uid_range); + seq_range_array_intersect(&uid_range, backend_uids); + + seq_range_array_iter_init(&iter, &uid_range); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + while (i < count && uids[i].real_uid < uid) i++; + if (i < count && uids[i].real_uid == uid) { + i_assert(uids[i].virtual_uid > 0); + seq_range_array_add(virtual_uids_r, + uids[i].virtual_uid); + i++; + } + } +} + +static void +virtual_get_virtual_uid_map(struct mailbox *box, + struct mailbox *backend_mailbox, + const ARRAY_TYPE(seq_range) *backend_uids, + ARRAY_TYPE(uint32_t) *virtual_uids_r) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *bbox; + const struct virtual_backend_uidmap *uids; + struct seq_range_iter iter; + unsigned int n, i, count; + uint32_t uid; + + if (mbox->lookup_prev_bbox != NULL && + strcmp(mbox->lookup_prev_bbox->box->vname, backend_mailbox->vname) == 0) + bbox = mbox->lookup_prev_bbox; + else { + bbox = virtual_backend_box_lookup_name(mbox, backend_mailbox->vname); + mbox->lookup_prev_bbox = bbox; + } + if (bbox == NULL) + return; + + uids = array_get(&bbox->uids, &count); i = 0; + seq_range_array_iter_init(&iter, backend_uids); n = 0; + while (seq_range_array_iter_nth(&iter, n++, &uid)) { + while (i < count && uids[i].real_uid < uid) i++; + if (i == count || uids[i].real_uid > uid) { + uint32_t zero = 0; + + array_push_back(virtual_uids_r, &zero); + } else { + i_assert(uids[i].virtual_uid > 0); + array_push_back(virtual_uids_r, &uids[i].virtual_uid); + i++; + } + } +} + +static void +virtual_get_virtual_backend_boxes(struct mailbox *box, + ARRAY_TYPE(mailboxes) *mailboxes, + bool only_with_msgs) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (!only_with_msgs || array_count(&bboxes[i]->uids) > 0) + array_push_back(mailboxes, &bboxes[i]->box); + } +} + +static bool virtual_is_inconsistent(struct mailbox *box) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + + if (mbox->inconsistent) + return TRUE; + + return index_storage_is_inconsistent(box); +} + +static int +virtual_list_index_has_changed(struct mailbox *box ATTR_UNUSED, + struct mail_index_view *list_view ATTR_UNUSED, + uint32_t seq ATTR_UNUSED, bool quick ATTR_UNUSED, + const char **reason_r) +{ + /* we don't have any quick and easy optimizations for tracking + virtual folders. ideally we'd completely disable mailbox list + indexes for them, but this is the easiest way to do it for now. */ + *reason_r = "Virtual indexes always change"; + return 1; +} + +static void +virtual_list_index_update_sync(struct mailbox *box ATTR_UNUSED, + struct mail_index_transaction *trans ATTR_UNUSED, + uint32_t seq ATTR_UNUSED) +{ +} + +struct mail_storage virtual_storage = { + .name = VIRTUAL_STORAGE_NAME, + .class_flags = MAIL_STORAGE_CLASS_FLAG_NOQUOTA | + MAIL_STORAGE_CLASS_FLAG_SECONDARY_INDEX, + + .v = { + NULL, + virtual_storage_alloc, + virtual_storage_create, + index_storage_destroy, + NULL, + virtual_storage_get_list_settings, + NULL, + virtual_mailbox_alloc, + NULL, + NULL, + } +}; + +struct mailbox virtual_mailbox = { + .v = { + index_storage_is_readonly, + index_storage_mailbox_enable, + virtual_mailbox_exists, + virtual_mailbox_open, + virtual_mailbox_close, + virtual_mailbox_free, + virtual_mailbox_create, + virtual_mailbox_update, + index_storage_mailbox_delete, + index_storage_mailbox_rename, + virtual_storage_get_status, + virtual_mailbox_get_metadata, + index_storage_set_subscribed, + index_storage_attribute_set, + index_storage_attribute_get, + index_storage_attribute_iter_init, + index_storage_attribute_iter_next, + index_storage_attribute_iter_deinit, + virtual_list_index_has_changed, + virtual_list_index_update_sync, + virtual_storage_sync_init, + index_mailbox_sync_next, + index_mailbox_sync_deinit, + NULL, + virtual_notify_changes, + virtual_transaction_begin, + virtual_transaction_commit, + virtual_transaction_rollback, + NULL, + virtual_mail_alloc, + virtual_search_init, + virtual_search_deinit, + virtual_search_next_nonblock, + virtual_search_next_update_seq, + index_storage_search_next_match_mail, + virtual_save_alloc, + virtual_save_begin, + virtual_save_continue, + virtual_save_finish, + virtual_save_cancel, + mail_storage_copy, + NULL, + NULL, + NULL, + virtual_is_inconsistent + } +}; + +struct virtual_mailbox_vfuncs virtual_mailbox_vfuncs = { + virtual_get_virtual_uids, + virtual_get_virtual_uid_map, + virtual_get_virtual_backend_boxes +}; diff --git a/src/plugins/virtual/virtual-storage.h b/src/plugins/virtual/virtual-storage.h new file mode 100644 index 0000000..1f34e39 --- /dev/null +++ b/src/plugins/virtual/virtual-storage.h @@ -0,0 +1,251 @@ +#ifndef VIRTUAL_STORAGE_H +#define VIRTUAL_STORAGE_H + +#include "seq-range-array.h" +#include "index-storage.h" + +#define VIRTUAL_STORAGE_NAME "virtual" +#define VIRTUAL_SUBSCRIPTION_FILE_NAME ".virtual-subscriptions" +#define VIRTUAL_CONFIG_FNAME "dovecot-virtual" + +#define VIRTUAL_CONTEXT(obj) \ + MODULE_CONTEXT(obj, virtual_storage_module) +#define VIRTUAL_CONTEXT_REQUIRE(obj) \ + MODULE_CONTEXT_REQUIRE(obj, virtual_storage_module) + +#define VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION 1 + +struct virtual_save_context; + +struct virtual_mail_index_header { + /* Increased by one each time the header is modified */ + uint32_t change_counter; + /* Number of mailbox records following this header. Mailbox names + follow the mailbox records - they have neither NUL terminator nor + padding. */ + uint32_t mailbox_count; + /* Highest used mailbox ID. IDs are never reused. */ + uint32_t highest_mailbox_id; + /* CRC32 of all the search parameters. If it changes, the mailbox is + rebuilt. */ + uint32_t search_args_crc32; +}; + +struct virtual_mail_index_mailbox_record { + /* Unique mailbox ID used as mailbox_id in records. */ + uint32_t id; + /* Length of this mailbox's name. */ + uint32_t name_len; + /* Synced UID validity value */ + uint32_t uid_validity; + /* Next unseen UID */ + uint32_t next_uid; + /* Synced highest modseq value */ + uint64_t highest_modseq; +}; + +struct virtual_mail_index_ext2_header { + /* Version compatibility number: + VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION */ + uint8_t version; + /* Set to sizeof(struct virtual_mail_index_mailbox_ext2_record) when + writing. */ + uint8_t ext_record_size; + /* Set to sizeof(struct virtual_mail_index_ext2_header) when writing. */ + uint16_t hdr_size; + /* Must be the same as virtual_mail_index_header.change_counter. + If not, it means the header was modified by an older Dovecot version + and this extension header should be ignored/rewritten. */ + uint32_t change_counter; +}; + +struct virtual_mail_index_mailbox_ext2_record { + /* Synced GUID value */ + uint8_t guid[16]; +}; + +struct virtual_mail_index_record { + uint32_t mailbox_id; + uint32_t real_uid; +}; + +struct virtual_storage { + struct mail_storage storage; + + /* List of mailboxes while a virtual mailbox is being opened. + Used to track loops. */ + ARRAY_TYPE(const_string) open_stack; + + unsigned int max_open_mailboxes; +}; + +struct virtual_backend_uidmap { + uint32_t real_uid; + /* can be 0 temporarily while syncing before the UID is assigned */ + uint32_t virtual_uid; +}; + +struct virtual_backend_box { + union mailbox_module_context module_ctx; + struct virtual_mailbox *virtual_mbox; + + /* linked list for virtual_mailbox->open_backend_boxes_{head,tail} */ + struct virtual_backend_box *prev_open, *next_open; + + /* Initially zero, updated by syncing */ + uint32_t mailbox_id; + const char *name; + + unsigned int sync_mailbox_idx1; + uint32_t sync_uid_validity; + uint32_t sync_next_uid; + uint64_t sync_highest_modseq; + guid_128_t sync_guid; + /* this value is either 0 or same as sync_highest_modseq. it's kept 0 + when there are pending removes that have yet to be expunged */ + uint64_t ondisk_highest_modseq; + + struct mail_search_args *search_args; + struct mail_search_result *search_result; + + struct mailbox *box; + /* Messages currently included in the virtual mailbox, + sorted by real_uid */ + ARRAY(struct virtual_backend_uidmap) uids; + + /* temporary mail used while syncing */ + struct mail *sync_mail; + /* pending removed UIDs */ + ARRAY_TYPE(seq_range) sync_pending_removes; + /* another process expunged these UIDs. they need to be removed on + next sync. */ + ARRAY_TYPE(seq_range) sync_outside_expunges; + + /* name contains a wildcard, this is a glob for it */ + struct imap_match_glob *glob; + struct mail_namespace *ns; + /* mailbox metadata matching */ + const char *metadata_entry, *metadata_value; + + /* notify context */ + struct mailbox_list_notify *notify; + + bool open_tracked:1; + bool open_failed:1; + bool sync_seen:1; + bool wildcard:1; + bool clear_recent:1; + bool negative_match:1; + bool uids_nonsorted:1; + bool search_args_initialized:1; + bool deleted:1; + bool notify_changes_started:1; /* if the box was opened for notify_changes */ + bool first_sync:1; /* if this is the first sync after bbox was (re-)created */ +}; +ARRAY_DEFINE_TYPE(virtual_backend_box, struct virtual_backend_box *); + +struct virtual_mailbox { + struct mailbox box; + struct virtual_storage *storage; + + uint32_t virtual_ext_id; + uint32_t virtual_ext2_id; + uint32_t virtual_guid_ext_id; + + uint32_t prev_uid_validity; + uint32_t prev_change_counter; + uint32_t highest_mailbox_id; + uint32_t search_args_crc32; + guid_128_t guid; + + struct virtual_backend_box *lookup_prev_bbox; + uint32_t sync_virtual_next_uid; + + /* Mailboxes this virtual mailbox consists of, sorted by mailbox_id */ + ARRAY_TYPE(virtual_backend_box) backend_boxes; + /* backend mailbox where to save messages when saving to this mailbox */ + struct virtual_backend_box *save_bbox; + + /* linked list of open backend mailboxes. head will contain the oldest + accessed mailbox, tail will contain the newest. */ + struct virtual_backend_box *open_backend_boxes_head; + struct virtual_backend_box *open_backend_boxes_tail; + /* number of backend mailboxes that are open currently. */ + unsigned int backends_open_count; + + ARRAY_TYPE(mailbox_virtual_patterns) list_include_patterns; + ARRAY_TYPE(mailbox_virtual_patterns) list_exclude_patterns; + + bool uids_mapped:1; + bool sync_initialized:1; + bool inconsistent:1; + bool have_guid_flags_set:1; + bool have_guids:1; + bool have_save_guids:1; + bool ext_header_rewrite:1; +}; + +extern MODULE_CONTEXT_DEFINE(virtual_storage_module, + &mail_storage_module_register); + +extern struct mail_storage virtual_storage; +extern struct mail_vfuncs virtual_mail_vfuncs; + +int virtual_config_read(struct virtual_mailbox *mbox); +void virtual_config_free(struct virtual_mailbox *mbox); + +int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + bool *broken_r); + +struct virtual_backend_box * +virtual_backend_box_lookup_name(struct virtual_mailbox *mbox, const char *name); +struct virtual_backend_box * +virtual_backend_box_lookup(struct virtual_mailbox *mbox, uint32_t mailbox_id); + +int virtual_backend_box_open(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_close(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_accessed(struct virtual_mailbox *mbox, + struct virtual_backend_box *bbox); +void virtual_backend_box_sync_mail_unset(struct virtual_backend_box *bbox); + +struct mail_search_context * +virtual_search_init(struct mailbox_transaction_context *t, + struct mail_search_args *args, + const enum mail_sort_type *sort_program, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers); +int virtual_search_deinit(struct mail_search_context *ctx); +bool virtual_search_next_nonblock(struct mail_search_context *ctx, + struct mail **mail_r, bool *tryagain_r); +bool virtual_search_next_update_seq(struct mail_search_context *ctx); + +struct mail * +virtual_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers); +struct mail * +virtual_mail_set_backend_mail(struct mail *mail, + struct virtual_backend_box *bbox); +void virtual_mail_set_unattached_backend_mail(struct mail *mail, + struct mail *backend_mail); + +struct mailbox_sync_context * +virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags); + +struct mail_save_context * +virtual_save_alloc(struct mailbox_transaction_context *t); +int virtual_save_begin(struct mail_save_context *ctx, struct istream *input); +int virtual_save_continue(struct mail_save_context *ctx); +int virtual_save_finish(struct mail_save_context *ctx); +void virtual_save_cancel(struct mail_save_context *ctx); +void virtual_save_free(struct mail_save_context *ctx); + +void virtual_box_copy_error(struct mailbox *dest, struct mailbox *src); + +void virtual_backend_mailbox_allocated(struct mailbox *box); +void virtual_backend_mailbox_opened(struct mailbox *box); + +#endif diff --git a/src/plugins/virtual/virtual-sync.c b/src/plugins/virtual/virtual-sync.c new file mode 100644 index 0000000..f8c6989 --- /dev/null +++ b/src/plugins/virtual/virtual-sync.c @@ -0,0 +1,1956 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "ioloop.h" +#include "str.h" +#include "mail-index-modseq.h" +#include "mail-search-build.h" +#include "mailbox-search-result-private.h" +#include "mailbox-recent-flags.h" +#include "index-sync-private.h" +#include "index-search-result.h" +#include "virtual-storage.h" + + +struct virtual_add_record { + struct virtual_mail_index_record rec; + time_t received_date; +}; + +struct virtual_sync_mail { + uint32_t vseq; + struct virtual_mail_index_record vrec; +}; + +struct virtual_sync_context { + struct virtual_mailbox *mbox; + struct mail_index_sync_ctx *index_sync_ctx; + struct mail_index *index; + struct mail_index_view *sync_view; + struct mail_index_transaction *trans; + const char *const *kw_all; + + /* messages expunged within this sync */ + ARRAY_TYPE(seq_range) sync_expunges; + + ARRAY(struct virtual_add_record) all_adds; + + /* all messages in this sync, sorted by mailbox_id + (but unsorted inside it for now, since it doesn't matter) */ + ARRAY(struct virtual_sync_mail) all_mails; + uint32_t all_mails_idx, all_mails_prev_mailbox_id; + + enum mailbox_sync_flags flags; + uint32_t uid_validity; + + bool ext_header_changed:1; + bool expunge_removed:1; + bool index_broken:1; +}; + +static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox); + +static void virtual_sync_set_uidvalidity(struct virtual_sync_context *ctx) +{ + uint32_t uid_validity = ioloop_time; + + mail_index_update_header(ctx->trans, + offsetof(struct mail_index_header, uid_validity), + &uid_validity, sizeof(uid_validity), TRUE); + ctx->uid_validity = uid_validity; +} + +static void virtual_sync_external_flags(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + uint32_t vseq, uint32_t real_uid) +{ + enum mail_flags flags; + const char *const *kw_names; + struct mail_keywords *keywords; + + if (!mail_set_uid(bbox->sync_mail, real_uid)) { + /* we may have reopened the mailbox, which could have + caused the mail to be expunged already. */ + return; + } + + /* copy flags */ + flags = mail_get_flags(bbox->sync_mail); + + /* we don't need to keep recent flags here */ + mail_index_update_flags(ctx->trans, vseq, MODIFY_REPLACE, + flags & ENUM_NEGATE(MAIL_RECENT)); + + /* copy keywords */ + kw_names = mail_get_keywords(bbox->sync_mail); + keywords = mail_index_keywords_create(ctx->index, kw_names); + mail_index_update_keywords(ctx->trans, vseq, MODIFY_REPLACE, keywords); + mail_index_keywords_unref(&keywords); +} + +static int virtual_sync_mail_uid_cmp(const void *p1, const void *p2) +{ + const struct virtual_sync_mail *m1 = p1, *m2 = p2; + + if (m1->vrec.mailbox_id < m2->vrec.mailbox_id) + return -1; + if (m1->vrec.mailbox_id > m2->vrec.mailbox_id) + return 1; + + if (m1->vrec.real_uid < m2->vrec.real_uid) + return -1; + if (m1->vrec.real_uid > m2->vrec.real_uid) + return 1; + /* broken */ + return 0; +} + +static void +virtual_backend_box_sync_mail_set(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + + if (bbox->sync_mail == NULL) { + trans = mailbox_transaction_begin(bbox->box, 0, __func__); + bbox->sync_mail = mail_alloc(trans, 0, NULL); + } +} + +static int bbox_mailbox_id_cmp(struct virtual_backend_box *const *b1, + struct virtual_backend_box *const *b2) +{ + if ((*b1)->mailbox_id < (*b2)->mailbox_id) + return -1; + if ((*b1)->mailbox_id > (*b2)->mailbox_id) + return 1; + return 0; +} + +static int +virtual_sync_get_backend_box(struct virtual_mailbox *mbox, const char *name, + struct virtual_backend_box **bbox_r) +{ + *bbox_r = virtual_backend_box_lookup_name(mbox, name); + if (*bbox_r != NULL || !mbox->sync_initialized) + return 0; + + /* another process just added a new mailbox. + we can't handle this currently. */ + mbox->inconsistent = TRUE; + mail_storage_set_error(mbox->box.storage, MAIL_ERROR_TEMP, t_strdup_printf( + "Backend mailbox '%s' added by another session. " + "Reopen the virtual mailbox.", name)); + return -1; +} + +static bool +virtual_mailbox_ext2_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + const struct virtual_mail_index_header *ext_hdr) +{ + const char *box_path = mailbox_get_path(&mbox->box); + const struct virtual_mail_index_ext2_header *ext2_hdr; + const struct virtual_mail_index_mailbox_ext2_record *ext2_rec; + const void *ext2_data; + size_t ext2_size; + struct virtual_backend_box *bbox; + + mail_index_get_header_ext(view, mbox->virtual_ext2_id, + &ext2_data, &ext2_size); + ext2_hdr = ext2_data; + if (ext2_size == 0) { + /* ext2 is missing - silently add it */ + return FALSE; + } + if (ext2_size < sizeof(*ext2_hdr)) { + i_error("virtual index %s: Invalid ext2 header size: %zu", + box_path, ext2_size); + return FALSE; + } + if (ext2_hdr->hdr_size > ext2_size) { + i_error("virtual index %s: ext2 header size too large: %u > %zu", + box_path, ext2_hdr->hdr_size, ext2_size); + return FALSE; + } + if (ext2_hdr->ext_record_size < sizeof(*ext2_rec)) { + i_error("virtual index %s: Invalid ext2 record size: %u", + box_path, ext2_hdr->ext_record_size); + return FALSE; + } + + if (ext_hdr->change_counter != ext2_hdr->change_counter) { + i_warning("virtual index %s: " + "Extension header change_counter mismatch (%u != %u) - " + "Index was modified by an older version?", + box_path, ext_hdr->change_counter, + ext2_hdr->change_counter); + return FALSE; + } + size_t mailboxes_size = ext2_size - ext2_hdr->hdr_size; + if (mailboxes_size % ext2_hdr->ext_record_size != 0 || + mailboxes_size / ext2_hdr->ext_record_size != ext_hdr->mailbox_count) { + i_error("virtual index %s: Invalid ext2 size: " + "hdr_size=%u record_size=%u total_size=%zu mailbox_count=%u", + box_path, ext2_hdr->hdr_size, ext2_hdr->ext_record_size, + ext2_size, ext_hdr->mailbox_count); + return FALSE; + } + + ext2_rec = CONST_PTR_OFFSET(ext2_data, ext2_hdr->hdr_size); + array_foreach_elem(&mbox->backend_boxes, bbox) { + if (bbox->sync_mailbox_idx1 == 0) + continue; + + guid_128_copy(bbox->sync_guid, + ext2_rec[bbox->sync_mailbox_idx1-1].guid); + } + return TRUE; +} + +int virtual_mailbox_ext_header_read(struct virtual_mailbox *mbox, + struct mail_index_view *view, + bool *broken_r) +{ + const char *box_path = mailbox_get_path(&mbox->box); + const struct virtual_mail_index_header *ext_hdr; + const struct mail_index_header *hdr; + const struct virtual_mail_index_mailbox_record *mailboxes; + struct virtual_backend_box *bbox, **bboxes; + const void *ext_data; + size_t ext_size; + unsigned int i, count, ext_name_offset, ext_mailbox_count; + uint32_t prev_mailbox_id; + int ret = 1; + + *broken_r = FALSE; + + hdr = mail_index_get_header(view); + mail_index_get_header_ext(view, mbox->virtual_ext_id, + &ext_data, &ext_size); + ext_hdr = ext_data; + if (mbox->sync_initialized && + mbox->prev_uid_validity == hdr->uid_validity && + ext_size >= sizeof(*ext_hdr) && + mbox->prev_change_counter == ext_hdr->change_counter) { + /* fully refreshed */ + return 1; + } + + mbox->prev_uid_validity = hdr->uid_validity; + if (ext_hdr == NULL || + mbox->search_args_crc32 != ext_hdr->search_args_crc32) { + mailboxes = NULL; + ext_name_offset = 0; + ext_mailbox_count = 0; + ret = 0; + } else { + const void *guid_data; + size_t guid_size; + mail_index_get_header_ext(view, mbox->virtual_guid_ext_id, + &guid_data, &guid_size); + if (guid_size >= GUID_128_SIZE) + guid_128_copy(mbox->guid, guid_data); + + mbox->prev_change_counter = ext_hdr->change_counter; + mailboxes = (const void *)(ext_hdr + 1); + ext_name_offset = sizeof(*ext_hdr) + + ext_hdr->mailbox_count * sizeof(*mailboxes); + if (ext_name_offset >= ext_size || + ext_hdr->mailbox_count > INT_MAX/sizeof(*mailboxes)) { + i_error("virtual index %s: Broken mailbox_count header", + box_path); + *broken_r = TRUE; + ext_mailbox_count = 0; + ret = 0; + } else { + ext_mailbox_count = ext_hdr->mailbox_count; + } + } + + /* update mailbox backends */ + prev_mailbox_id = 0; + for (i = 0; i < ext_mailbox_count; i++) { + if (mailboxes[i].id > ext_hdr->highest_mailbox_id || + mailboxes[i].id <= prev_mailbox_id) { + i_error("virtual index %s: Broken mailbox id", + box_path); + break; + } + if (mailboxes[i].name_len == 0 || + mailboxes[i].name_len > ext_size) { + i_error("virtual index %s: Broken mailbox name_len", + box_path); + break; + } + if (ext_name_offset + mailboxes[i].name_len > ext_size) { + i_error("virtual index %s: Broken mailbox list", + box_path); + break; + } + T_BEGIN { + const unsigned char *nameptr; + const char *name; + + nameptr = CONST_PTR_OFFSET(ext_data, ext_name_offset); + name = t_strndup(nameptr, mailboxes[i].name_len); + if (virtual_sync_get_backend_box(mbox, name, &bbox) < 0) + ret = -1; + } T_END; + + if (bbox == NULL) { + if (ret < 0) + return -1; + /* mailbox no longer exists. */ + ret = 0; + } else { + bbox->mailbox_id = mailboxes[i].id; + bbox->sync_uid_validity = mailboxes[i].uid_validity; + bbox->ondisk_highest_modseq = + bbox->sync_highest_modseq = + mailboxes[i].highest_modseq; + bbox->sync_next_uid = mailboxes[i].next_uid; + bbox->sync_mailbox_idx1 = i+1; + } + ext_name_offset += mailboxes[i].name_len; + prev_mailbox_id = mailboxes[i].id; + } + if (i < ext_mailbox_count) { + *broken_r = TRUE; + mbox->ext_header_rewrite = TRUE; + ret = 0; + } + if (!*broken_r && ext_mailbox_count > 0) { + if (!virtual_mailbox_ext2_header_read(mbox, view, ext_hdr)) + mbox->ext_header_rewrite = TRUE; + } + + mbox->highest_mailbox_id = ext_hdr == NULL ? 0 : + ext_hdr->highest_mailbox_id; + /* do not mark it initialized if it's broken */ + mbox->sync_initialized = !*broken_r; + + /* assign new mailbox IDs if any are missing */ + bboxes = array_get_modifiable(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->mailbox_id == 0) { + bboxes[i]->mailbox_id = ++mbox->highest_mailbox_id; + ret = 0; + } + } + /* sort the backend mailboxes by mailbox_id. */ + array_sort(&mbox->backend_boxes, bbox_mailbox_id_cmp); + if (ret == 0) + mbox->ext_header_rewrite = TRUE; + return ret; +} + +static void virtual_sync_ext_header_rewrite(struct virtual_sync_context *ctx) +{ + struct virtual_mail_index_header ext_hdr; + struct virtual_mail_index_mailbox_record mailbox; + struct virtual_mail_index_mailbox_ext2_record ext2_rec; + struct virtual_backend_box **bboxes; + buffer_t *buf, *buf2; + const void *ext_data; + size_t ext_size; + unsigned int i, mailbox_pos, name_pos, count; + + bboxes = array_get_modifiable(&ctx->mbox->backend_boxes, &count); + mailbox_pos = sizeof(ext_hdr); + name_pos = mailbox_pos + sizeof(mailbox) * count; + + i_zero(&ext_hdr); + i_zero(&mailbox); + i_zero(&ext2_rec); + + ext_hdr.change_counter = ++ctx->mbox->prev_change_counter; + ext_hdr.mailbox_count = count; + ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id; + ext_hdr.search_args_crc32 = ctx->mbox->search_args_crc32; + + buf = buffer_create_dynamic(default_pool, name_pos + 256); + buffer_append(buf, &ext_hdr, sizeof(ext_hdr)); + + struct virtual_mail_index_ext2_header ext2_hdr = { + .version = VIRTUAL_MAIL_INDEX_EXT2_HEADER_VERSION, + .ext_record_size = sizeof(struct virtual_mail_index_mailbox_ext2_record), + .hdr_size = sizeof(struct virtual_mail_index_ext2_header), + .change_counter = ext_hdr.change_counter, + }; + buf2 = buffer_create_dynamic(default_pool, sizeof(ext2_hdr) + + sizeof(ext2_rec) * count); + buffer_append(buf2, &ext2_hdr, sizeof(ext2_hdr)); + + for (i = 0; i < count; i++) { + i_assert(i == 0 || + bboxes[i]->mailbox_id > bboxes[i-1]->mailbox_id); + + bboxes[i]->sync_mailbox_idx1 = i+1; + mailbox.id = bboxes[i]->mailbox_id; + mailbox.name_len = strlen(bboxes[i]->name); + mailbox.uid_validity = bboxes[i]->sync_uid_validity; + mailbox.highest_modseq = bboxes[i]->ondisk_highest_modseq; + mailbox.next_uid = bboxes[i]->sync_next_uid; + buffer_write(buf, mailbox_pos, &mailbox, sizeof(mailbox)); + buffer_write(buf, name_pos, bboxes[i]->name, mailbox.name_len); + + guid_128_copy(ext2_rec.guid, bboxes[i]->sync_guid); + buffer_append(buf2, &ext2_rec, sizeof(ext2_rec)); + + mailbox_pos += sizeof(mailbox); + name_pos += mailbox.name_len; + + /* repair the value */ + if (ctx->mbox->highest_mailbox_id < mailbox.id) + ctx->mbox->highest_mailbox_id = mailbox.id; + } + if (ctx->mbox->highest_mailbox_id != ext_hdr.highest_mailbox_id) { + ext_hdr.highest_mailbox_id = ctx->mbox->highest_mailbox_id; + buffer_write(buf, 0, &ext_hdr, sizeof(ext_hdr)); + } + i_assert(buf->used == name_pos); + + /* update base extension */ + mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext_id, + &ext_data, &ext_size); + if (ext_size < name_pos) { + mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext_id, + name_pos, + sizeof(struct virtual_mail_index_record), + sizeof(uint32_t)); + } + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + 0, buf->data, name_pos); + + /* update ext2 */ + mail_index_get_header_ext(ctx->sync_view, ctx->mbox->virtual_ext2_id, + &ext_data, &ext_size); + if (ext_size != buf2->used) { + mail_index_ext_resize(ctx->trans, ctx->mbox->virtual_ext2_id, + buf2->used, 0, 0); + } + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + 0, buf2->data, buf2->used); + buffer_free(&buf); + buffer_free(&buf2); +} + +static void virtual_sync_ext_header_update(struct virtual_sync_context *ctx) +{ + struct virtual_mail_index_header ext_hdr; + + if (!ctx->ext_header_changed) + return; + + /* we changed something - update the change counter in header */ + ext_hdr.change_counter = ++ctx->mbox->prev_change_counter; + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + offsetof(struct virtual_mail_index_header, change_counter), + &ext_hdr.change_counter, sizeof(ext_hdr.change_counter)); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + offsetof(struct virtual_mail_index_ext2_header, change_counter), + &ext_hdr.change_counter, sizeof(ext_hdr.change_counter)); +} + +static int virtual_sync_index_rec(struct virtual_sync_context *ctx, + const struct mail_index_sync_rec *sync_rec) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_backend_box *bbox; + const struct virtual_mail_index_record *vrec; + const void *data; + enum mail_flags flags; + struct mail_keywords *keywords; + enum modify_type modify_type; + const char *kw_names[2]; + uint32_t vseq, seq1, seq2; + + switch (sync_rec->type) { + case MAIL_INDEX_SYNC_TYPE_EXPUNGE: + case MAIL_INDEX_SYNC_TYPE_FLAGS: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: + break; + } + if (!mail_index_lookup_seq_range(ctx->sync_view, + sync_rec->uid1, sync_rec->uid2, + &seq1, &seq2)) { + /* already expunged, nothing to do. */ + return 0; + } + + for (vseq = seq1; vseq <= seq2; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id, + &data, NULL); + vrec = data; + + bbox = virtual_backend_box_lookup(ctx->mbox, vrec->mailbox_id); + if (bbox == NULL) + continue; + if (!bbox->box->opened) { + if (virtual_backend_box_open(ctx->mbox, bbox) < 0) { + virtual_box_copy_error(&ctx->mbox->box, + bbox->box); + return -1; + } + } else { + virtual_backend_box_accessed(ctx->mbox, bbox); + } + + virtual_backend_box_sync_mail_set(bbox); + if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) { + /* message is already expunged from backend mailbox. */ + continue; + } + + switch (sync_rec->type) { + case MAIL_INDEX_SYNC_TYPE_EXPUNGE: + mail_expunge(bbox->sync_mail); + break; + case MAIL_INDEX_SYNC_TYPE_FLAGS: + flags = sync_rec->add_flags & MAIL_FLAGS_NONRECENT; + if (flags != 0) { + mail_update_flags(bbox->sync_mail, + MODIFY_ADD, flags); + } + flags = sync_rec->remove_flags & MAIL_FLAGS_NONRECENT; + if (flags != 0) { + mail_update_flags(bbox->sync_mail, + MODIFY_REMOVE, flags); + } + break; + case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: + case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: + kw_names[0] = ctx->kw_all[sync_rec->keyword_idx]; + kw_names[1] = NULL; + keywords = mailbox_keywords_create_valid(bbox->box, + kw_names); + + modify_type = sync_rec->type == + MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD ? + MODIFY_ADD : MODIFY_REMOVE; + mail_update_keywords(bbox->sync_mail, + modify_type, keywords); + mailbox_keywords_unref(&keywords); + break; + } + } + return 0; +} + +static int virtual_sync_index_changes(struct virtual_sync_context *ctx) +{ + const ARRAY_TYPE(keywords) *keywords; + struct mail_index_sync_rec sync_rec; + + keywords = mail_index_get_keywords(ctx->index); + ctx->kw_all = array_count(keywords) == 0 ? NULL : + array_front(keywords); + while (mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) { + if (virtual_sync_index_rec(ctx, &sync_rec) < 0) + return -1; + } + return 0; +} + +static void virtual_sync_index_finish(struct virtual_sync_context *ctx) +{ + struct mailbox *box = &ctx->mbox->box; + const struct mail_index_header *hdr; + struct mail_index_view *view; + uint32_t seq1, seq2; + + view = mail_index_transaction_open_updated_view(ctx->trans); + + hdr = mail_index_get_header(ctx->sync_view); + if (hdr->uid_validity != 0) + ctx->uid_validity = hdr->uid_validity; + else + virtual_sync_set_uidvalidity(ctx); + + /* mark the newly seen messages as recent */ + if (mail_index_lookup_seq_range(view, hdr->first_recent_uid, + (uint32_t)-1, &seq1, &seq2)) { + mailbox_recent_flags_set_seqs(&ctx->mbox->box, view, + seq1, seq2); + } + + mail_index_view_close(&view); + + if (ctx->mbox->ext_header_rewrite) { + /* entire mailbox list needs to be rewritten */ + virtual_sync_ext_header_rewrite(ctx); + } else { + /* update only changed parts in the header */ + virtual_sync_ext_header_update(ctx); + } + + mailbox_sync_notify(box, 0, 0); +} + +static int virtual_sync_backend_box_init(struct virtual_backend_box *bbox) +{ + struct mailbox_transaction_context *trans; + struct mail_search_context *search_ctx; + struct mail *mail; + struct virtual_backend_uidmap uidmap; + enum mailbox_search_result_flags result_flags; + int ret; + + trans = mailbox_transaction_begin(bbox->box, 0, __func__); + + if (!bbox->search_args_initialized) { + mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL); + bbox->search_args_initialized = TRUE; + } + search_ctx = mailbox_search_init(trans, bbox->search_args, NULL, + 0, NULL); + + /* save the result and keep it updated */ + result_flags = MAILBOX_SEARCH_RESULT_FLAG_UPDATE | + MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC; + bbox->search_result = + mailbox_search_result_save(search_ctx, result_flags); + + /* add the found UIDs to uidmap. virtual_uid gets assigned later. */ + i_zero(&uidmap); + array_clear(&bbox->uids); + while (mailbox_search_next(search_ctx, &mail)) { + uidmap.real_uid = mail->uid; + array_push_back(&bbox->uids, &uidmap); + } + ret = mailbox_search_deinit(&search_ctx); + + (void)mailbox_transaction_commit(&trans); + return ret; +} + +static int +virtual_backend_uidmap_bsearch_cmp(const uint32_t *uidp, + const struct virtual_backend_uidmap *uidmap) +{ + return *uidp < uidmap->real_uid ? -1 : + (*uidp > uidmap->real_uid ? 1 : 0); +} + +static void +virtual_sync_mailbox_box_remove(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *removed_uids) +{ + const struct seq_range *uids; + struct virtual_backend_uidmap *uidmap; + unsigned int i, src, dest, uid_count, rec_count; + uint32_t uid, vseq; + + uids = array_get(removed_uids, &uid_count); + if (uid_count == 0) + return; + + /* everything in removed_uids should exist in bbox->uids */ + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + i_assert(rec_count >= uid_count); + + /* find the first uidmap record to be removed */ + if (!array_bsearch_insert_pos(&bbox->uids, &uids[0].seq1, + virtual_backend_uidmap_bsearch_cmp, &src)) + i_unreached(); + + /* remove the unwanted messages */ + dest = src; + for (i = 0; i < uid_count; i++) { + uid = uids[i].seq1; + while (uidmap[src].real_uid != uid) { + uidmap[dest++] = uidmap[src++]; + i_assert(src < rec_count); + } + + for (; uid <= uids[i].seq2; uid++, src++) { + i_assert(src < rec_count); + i_assert(uidmap[src].real_uid == uid); + if (uidmap[src].virtual_uid == 0) { + /* has not been assigned yet */ + continue; + } + if (mail_index_lookup_seq(ctx->sync_view, + uidmap[src].virtual_uid, + &vseq)) + mail_index_expunge(ctx->trans, vseq); + } + } + array_delete(&bbox->uids, dest, src - dest); +} + +static void +virtual_sync_mailbox_box_add(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *added_uids_arr) +{ + const struct seq_range *added_uids; + struct virtual_backend_uidmap *uidmap; + struct virtual_add_record rec; + unsigned int i, src, dest, uid_count, add_count, rec_count; + uint32_t add_uid; + + added_uids = array_get(added_uids_arr, &uid_count); + if (uid_count == 0) + return; + add_count = seq_range_count(added_uids_arr); + + /* none of added_uids should exist in bbox->uids. find the position + of the first inserted index. */ + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + if (rec_count == 0 || + added_uids[0].seq1 > uidmap[rec_count-1].real_uid) { + /* fast path: usually messages are appended */ + dest = rec_count; + } else if (array_bsearch_insert_pos(&bbox->uids, &added_uids[0].seq1, + virtual_backend_uidmap_bsearch_cmp, + &dest)) + i_unreached(); + + /* make space for all added UIDs. */ + if (rec_count == dest) + array_idx_clear(&bbox->uids, dest + add_count-1); + else { + array_copy(&bbox->uids.arr, dest + add_count, + &bbox->uids.arr, dest, rec_count - dest); + } + uidmap = array_get_modifiable(&bbox->uids, &rec_count); + src = dest + add_count; + + /* add/move the UIDs to their correct positions */ + i_zero(&rec); + rec.rec.mailbox_id = bbox->mailbox_id; + for (i = 0; i < uid_count; i++) { + add_uid = added_uids[i].seq1; + while (src < rec_count && uidmap[src].real_uid < add_uid) + uidmap[dest++] = uidmap[src++]; + + for (; add_uid <= added_uids[i].seq2; add_uid++, dest++) { + i_assert(dest < rec_count); + + uidmap[dest].real_uid = add_uid; + uidmap[dest].virtual_uid = 0; + + if (ctx->mbox->uids_mapped) { + rec.rec.real_uid = add_uid; + array_push_back(&ctx->all_adds, &rec); + } + } + } +} + +static void +virtual_sync_mailbox_box_update_flags(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const ARRAY_TYPE(seq_range) *uids_arr) +{ + unsigned int i, uid, vseq; + struct virtual_backend_uidmap *vuid; + struct seq_range_iter iter; + + i = 0; + seq_range_array_iter_init(&iter, uids_arr); + while(seq_range_array_iter_nth(&iter, i++, &uid)) { + vuid = array_bsearch(&bbox->uids, &uid, + virtual_backend_uidmap_bsearch_cmp); + if (vuid == NULL || + vuid->virtual_uid == 0 || + !mail_index_lookup_seq(ctx->sync_view, + vuid->virtual_uid, &vseq)) { + /* the entry has been already removed either by + us or some other session. doesn't matter, + we don't need to update the flags. + + it might also have not yet been assigned a uid + so we don't want to update the flags then either. + */ + continue; + } + virtual_sync_external_flags(ctx, bbox, vseq, + vuid->real_uid); + } +} + +static int virtual_backend_uidmap_cmp(const struct virtual_backend_uidmap *u1, + const struct virtual_backend_uidmap *u2) +{ + if (u1->real_uid < u2->real_uid) + return -1; + if (u1->real_uid > u2->real_uid) + return 1; + return 0; +} + +static void virtual_sync_bbox_uids_sort(struct virtual_backend_box *bbox) +{ + /* the uidmap must be sorted by real_uids */ + array_sort(&bbox->uids, virtual_backend_uidmap_cmp); + bbox->uids_nonsorted = FALSE; +} + +static void virtual_sync_backend_boxes_sort_uids(struct virtual_mailbox *mbox) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + if (bboxes[i]->uids_nonsorted) + virtual_sync_bbox_uids_sort(bboxes[i]); + } +} + +static void +virtual_sync_backend_add_vmsgs_results(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + uint32_t real_uid, + struct mail_search_result *result, + const uint32_t vseq) +{ + struct virtual_backend_uidmap uidmap; + uint32_t vuid, seq; + + mail_index_lookup_uid(ctx->sync_view, vseq, &vuid); + + i_zero(&uidmap); + uidmap.real_uid = real_uid; + uidmap.virtual_uid = vuid; + array_push_back(&bbox->uids, &uidmap); + + if (result == NULL) + ; + else if (mail_index_lookup_seq(bbox->box->view, real_uid, &seq)) + seq_range_array_add(&result->uids, real_uid); + else + seq_range_array_add(&result->removed_uids, real_uid); +} + +static void +virtual_sync_backend_handle_old_vmsgs(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + struct mail_search_result *result) +{ + const struct virtual_mail_index_record *vrec; + const struct virtual_sync_mail *sync_mail, *sync_mails; + const void *data; + uint32_t i, vseq, messages; + + /* find the messages that currently exist in virtual index and add them + to the backend mailbox's list of uids. */ + array_clear(&bbox->uids); + + if (array_is_created(&ctx->all_mails)) { + i_assert(ctx->all_mails_prev_mailbox_id < bbox->mailbox_id); + sync_mails = array_get(&ctx->all_mails, &messages); + for (i = ctx->all_mails_idx; i < messages; i++) { + sync_mail = &sync_mails[i]; + if (sync_mail->vrec.mailbox_id != bbox->mailbox_id) { + if (sync_mail->vrec.mailbox_id < bbox->mailbox_id) { + /* stale mailbox_id, ignore */ + continue; + } + /* Should be in mailbox_id order, + so skip to next box */ + break; + } + + virtual_sync_backend_add_vmsgs_results(ctx, bbox, + sync_mail->vrec.real_uid, result, sync_mail->vseq); + } + ctx->all_mails_idx = i; + ctx->all_mails_prev_mailbox_id = bbox->mailbox_id; + } else { + /* there should be only a single backend mailbox, but in the + existing index there may be stale mailbox_ids that we'll + just skip over. */ + messages = mail_index_view_get_messages_count(ctx->sync_view); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, + ctx->mbox->virtual_ext_id, &data, NULL); + vrec = data; + if (vrec->mailbox_id == bbox->mailbox_id) { + virtual_sync_backend_add_vmsgs_results(ctx, + bbox, vrec->real_uid, result, vseq); + } + } + } + virtual_sync_bbox_uids_sort(bbox); +} + +static int virtual_sync_backend_box_continue(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + const enum mailbox_search_result_flags result_flags = + MAILBOX_SEARCH_RESULT_FLAG_UPDATE | + MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC; + struct mail_index_view *view = bbox->box->view; + struct mail_search_result *result; + ARRAY_TYPE(seq_range) expunged_uids = ARRAY_INIT, removed_uids; + ARRAY_TYPE(seq_range) added_uids, flag_update_uids; + uint64_t modseq, old_highest_modseq; + uint32_t seq, uid, old_msg_count; + + /* initialize the search result from all the existing messages in + virtual index. */ + if (!bbox->search_args_initialized) { + mail_search_args_init(bbox->search_args, bbox->box, FALSE, NULL); + bbox->search_args_initialized = TRUE; + } + result = mailbox_search_result_alloc(bbox->box, bbox->search_args, + result_flags); + mailbox_search_result_initial_done(result); + i_assert(array_count(&result->removed_uids) == 0); + virtual_sync_backend_handle_old_vmsgs(ctx, bbox, result); + if (array_count(&result->removed_uids) > 0) { + /* these are all expunged messages. treat them separately from + "no longer matching messages" (=removed_uids) */ + t_array_init(&expunged_uids, array_count(&result->removed_uids)); + array_append_array(&expunged_uids, &result->removed_uids); + array_clear(&result->removed_uids); + } + + /* get list of changed old messages (messages already once seen by + virtual index), based on modseq changes. (we'll assume all modseq + changes are due to flag changes, which may not be true in future) */ + if (bbox->sync_next_uid <= 1 || + !mail_index_lookup_seq_range(view, 1, bbox->sync_next_uid-1, + &seq, &old_msg_count)) + old_msg_count = 0; + old_highest_modseq = mail_index_modseq_get_highest(view); + + t_array_init(&flag_update_uids, I_MIN(128, old_msg_count)); + if (bbox->sync_highest_modseq < old_highest_modseq) { + for (seq = 1; seq <= old_msg_count; seq++) { + modseq = mail_index_modseq_lookup(view, seq); + if (modseq > bbox->sync_highest_modseq) { + mail_index_lookup_uid(view, seq, &uid); + seq_range_array_add(&flag_update_uids, uid); + } + } + } + + /* update the search result based on the flag changes and + new messages */ + if (index_search_result_update_flags(result, &flag_update_uids) < 0 || + index_search_result_update_appends(result, old_msg_count) < 0) { + mailbox_search_result_free(&result); + return -1; + } + + t_array_init(&removed_uids, 128); + t_array_init(&added_uids, 128); + mailbox_search_result_sync(result, &removed_uids, &added_uids); + if (array_is_created(&expunged_uids)) { + seq_range_array_remove_seq_range(&removed_uids, &expunged_uids); + virtual_sync_mailbox_box_remove(ctx, bbox, &expunged_uids); + } + if (ctx->expunge_removed) + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + else { + /* delayed remove */ + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + } + virtual_sync_mailbox_box_add(ctx, bbox, &added_uids); + virtual_sync_mailbox_box_update_flags(ctx, bbox, &flag_update_uids); + + bbox->search_result = result; + return 0; +} + +static void virtual_sync_drop_existing(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *added_uids) +{ + ARRAY_TYPE(seq_range) drop_uids; + const struct virtual_backend_uidmap *uidmap; + struct seq_range_iter iter; + unsigned int i, n = 0, count; + uint32_t add_uid; + + seq_range_array_iter_init(&iter, added_uids); + if (!seq_range_array_iter_nth(&iter, n++, &add_uid)) + return; + + (void)array_bsearch_insert_pos(&bbox->uids, &add_uid, + virtual_backend_uidmap_bsearch_cmp, &i); + + uidmap = array_get_modifiable(&bbox->uids, &count); + if (i == count) + return; + + t_array_init(&drop_uids, array_count(added_uids)); + for (; i < count; ) { + if (uidmap[i].real_uid < add_uid) { + i++; + continue; + } + if (uidmap[i].real_uid == add_uid) { + seq_range_array_add(&drop_uids, add_uid); + i++; + } + if (!seq_range_array_iter_nth(&iter, n++, &add_uid)) + break; + } + seq_range_array_remove_seq_range(added_uids, &drop_uids); +} + +static void virtual_sync_drop_nonexistent(struct virtual_backend_box *bbox, + ARRAY_TYPE(seq_range) *removed_uids) +{ + ARRAY_TYPE(seq_range) drop_uids; + const struct virtual_backend_uidmap *uidmap; + struct seq_range_iter iter; + unsigned int i, n = 0, count; + uint32_t remove_uid; + bool iter_done = FALSE; + + seq_range_array_iter_init(&iter, removed_uids); + if (!seq_range_array_iter_nth(&iter, n++, &remove_uid)) + return; + + (void)array_bsearch_insert_pos(&bbox->uids, &remove_uid, + virtual_backend_uidmap_bsearch_cmp, &i); + + t_array_init(&drop_uids, array_count(removed_uids)); iter_done = FALSE; + uidmap = array_get_modifiable(&bbox->uids, &count); + for (; i < count; ) { + if (uidmap[i].real_uid < remove_uid) { + i++; + continue; + } + if (uidmap[i].real_uid != remove_uid) + seq_range_array_add(&drop_uids, remove_uid); + else + i++; + if (!seq_range_array_iter_nth(&iter, n++, &remove_uid)) { + iter_done = TRUE; + break; + } + } + if (!iter_done) { + do { + seq_range_array_add(&drop_uids, remove_uid); + } while (seq_range_array_iter_nth(&iter, n++, &remove_uid)); + } + seq_range_array_remove_seq_range(removed_uids, &drop_uids); +} + +static void virtual_sync_mailbox_box_update(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + ARRAY_TYPE(seq_range) removed_uids, added_uids, temp_uids; + unsigned int count1, count2; + + t_array_init(&removed_uids, 128); + t_array_init(&added_uids, 128); + + mailbox_search_result_sync(bbox->search_result, + &removed_uids, &added_uids); + if (array_is_created(&bbox->sync_outside_expunges)) { + seq_range_array_remove_seq_range(&bbox->sync_outside_expunges, + &added_uids); + seq_range_array_merge(&removed_uids, + &bbox->sync_outside_expunges); + array_clear(&bbox->sync_outside_expunges); + } + + virtual_sync_drop_existing(bbox, &added_uids); + virtual_sync_drop_nonexistent(bbox, &removed_uids); + + /* if any of the pending removes came back, we don't want to expunge + them anymore. also since they already exist, remove them from + added_uids. */ + count1 = array_count(&bbox->sync_pending_removes); + count2 = array_count(&added_uids); + if (count1 > 0 && count2 > 0) { + t_array_init(&temp_uids, count1); + array_append_array(&temp_uids, &bbox->sync_pending_removes); + if (seq_range_array_remove_seq_range( + &bbox->sync_pending_removes, &added_uids) > 0) { + seq_range_array_remove_seq_range(&added_uids, + &temp_uids); + } + } + + if (!ctx->expunge_removed) { + /* delay removing messages that don't match the search + criteria, but don't delay removing expunged messages */ + if (array_count(&ctx->sync_expunges) > 0) { + seq_range_array_remove_seq_range(&bbox->sync_pending_removes, + &ctx->sync_expunges); + seq_range_array_remove_seq_range(&removed_uids, + &ctx->sync_expunges); + virtual_sync_mailbox_box_remove(ctx, bbox, + &ctx->sync_expunges); + } + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + } else if (array_count(&bbox->sync_pending_removes) > 0) { + /* remove all current and old */ + seq_range_array_merge(&bbox->sync_pending_removes, + &removed_uids); + virtual_sync_mailbox_box_remove(ctx, bbox, + &bbox->sync_pending_removes); + array_clear(&bbox->sync_pending_removes); + } else { + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + } + virtual_sync_mailbox_box_add(ctx, bbox, &added_uids); +} + +static bool virtual_sync_find_seqs(struct virtual_backend_box *bbox, + const struct mailbox_sync_rec *sync_rec, + unsigned int *idx1_r, + unsigned int *idx2_r) +{ + const struct virtual_backend_uidmap *uidmap; + unsigned int idx, count; + uint32_t uid1, uid2; + + mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1); + mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2); + (void)array_bsearch_insert_pos(&bbox->uids, &uid1, + virtual_backend_uidmap_bsearch_cmp, + &idx); + + uidmap = array_get_modifiable(&bbox->uids, &count); + if (idx == count || uidmap[idx].real_uid > uid2) + return FALSE; + + *idx1_r = idx; + while (idx < count && uidmap[idx].real_uid <= uid2) idx++; + *idx2_r = idx - 1; + return TRUE; +} + +static void virtual_sync_expunge_add(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + const struct mailbox_sync_rec *sync_rec) +{ + struct virtual_backend_uidmap *uidmap; + uint32_t uid1, uid2; + unsigned int i, idx1, count; + + mail_index_lookup_uid(bbox->box->view, sync_rec->seq1, &uid1); + mail_index_lookup_uid(bbox->box->view, sync_rec->seq2, &uid2); + + /* remember only the expunges for messages that + already exist for this mailbox */ + (void)array_bsearch_insert_pos(&bbox->uids, &uid1, + virtual_backend_uidmap_bsearch_cmp, + &idx1); + uidmap = array_get_modifiable(&bbox->uids, &count); + for (i = idx1; i < count; i++) { + if (uidmap[i].real_uid > uid2) + break; + seq_range_array_add(&ctx->sync_expunges, uidmap[i].real_uid); + } +} + +static int virtual_sync_backend_box_sync(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + enum mailbox_sync_flags sync_flags) +{ + struct mailbox_sync_context *sync_ctx; + const struct virtual_backend_uidmap *uidmap; + struct mailbox_sync_rec sync_rec; + struct mailbox_sync_status sync_status; + unsigned int idx1, idx2; + uint32_t vseq, vuid; + + sync_ctx = mailbox_sync_init(bbox->box, sync_flags); + virtual_backend_box_sync_mail_set(bbox); + while (mailbox_sync_next(sync_ctx, &sync_rec)) { + switch (sync_rec.type) { + case MAILBOX_SYNC_TYPE_EXPUNGE: + if (ctx->expunge_removed) { + /* no need to keep track of expunges */ + break; + } + virtual_sync_expunge_add(ctx, bbox, &sync_rec); + break; + case MAILBOX_SYNC_TYPE_FLAGS: + if (!virtual_sync_find_seqs(bbox, &sync_rec, + &idx1, &idx2)) + break; + uidmap = array_front(&bbox->uids); + for (; idx1 <= idx2; idx1++) { + vuid = uidmap[idx1].virtual_uid; + if (vuid == 0) { + /* has not been even assigned yet */ + continue; + } + if (!mail_index_lookup_seq(ctx->sync_view, + vuid, &vseq)) { + /* expunged by another session, + but we haven't yet updated + bbox->uids. */ + continue; + } + virtual_sync_external_flags(ctx, bbox, vseq, + uidmap[idx1].real_uid); + } + break; + case MAILBOX_SYNC_TYPE_MODSEQ: + break; + } + } + if (mailbox_sync_deinit(&sync_ctx, &sync_status) < 0) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + return 0; +} + +static bool +virtual_bbox_mailbox_equals(struct virtual_backend_box *bbox, + const struct mailbox_status *status, + struct mailbox_metadata *metadata, + const char **reason_r) +{ + if (!guid_128_equals(bbox->sync_guid, metadata->guid)) { + *reason_r = t_strdup_printf("GUID changed: %s -> %s", + guid_128_to_string(bbox->sync_guid), + guid_128_to_string(metadata->guid)); + return FALSE; + } + if (bbox->sync_uid_validity != status->uidvalidity) { + *reason_r = t_strdup_printf("UIDVALIDITY changed: %u -> %u", + bbox->sync_uid_validity, status->uidvalidity); + return FALSE; + } + if (bbox->sync_next_uid != status->uidnext) { + *reason_r = t_strdup_printf("UIDNEXT changed: %u -> %u", + bbox->sync_next_uid, status->uidnext); + return FALSE; + } + if (bbox->sync_highest_modseq != status->highest_modseq) { + *reason_r = t_strdup_printf("HIGHESTMODSEQ changed: " + "%"PRIu64" -> %"PRIu64, + bbox->sync_highest_modseq, status->highest_modseq); + return FALSE; + } + return TRUE; +} + +static void virtual_sync_backend_ext_header(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + const unsigned int uidval_pos = + offsetof(struct virtual_mail_index_mailbox_record, + uid_validity); + struct mailbox_status status; + struct virtual_mail_index_mailbox_record mailbox; + struct virtual_mail_index_mailbox_ext2_record ext2; + unsigned int mailbox_offset, ext2_offset; + uint64_t wanted_ondisk_highest_modseq; + struct mailbox_metadata metadata; + const char *reason; + + mailbox_get_open_status(bbox->box, STATUS_UIDVALIDITY | + STATUS_HIGHESTMODSEQ, &status); + wanted_ondisk_highest_modseq = + array_count(&bbox->sync_pending_removes) > 0 ? 0 : + status.highest_modseq; + + if (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID, + &metadata) < 0) { + /* Either a temporary failure or the mailbox was already + deleted. Either way, it doesn't really matter at this point. + We'll just leave the error handling until the next sync. */ + return; + } + + if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason) && + bbox->ondisk_highest_modseq == wanted_ondisk_highest_modseq) + return; + + /* mailbox changed - update extension header */ + bbox->sync_uid_validity = status.uidvalidity; + bbox->sync_highest_modseq = status.highest_modseq; + bbox->ondisk_highest_modseq = wanted_ondisk_highest_modseq; + bbox->sync_next_uid = status.uidnext; + guid_128_copy(bbox->sync_guid, metadata.guid); + + if (ctx->mbox->ext_header_rewrite) { + /* we'll rewrite the entire header later */ + return; + } + + i_zero(&mailbox); + mailbox.uid_validity = bbox->sync_uid_validity; + mailbox.highest_modseq = bbox->ondisk_highest_modseq; + mailbox.next_uid = bbox->sync_next_uid; + + i_zero(&ext2); + guid_128_copy(ext2.guid, bbox->sync_guid); + + i_assert(bbox->sync_mailbox_idx1 > 0); + mailbox_offset = sizeof(struct virtual_mail_index_header) + + (bbox->sync_mailbox_idx1-1) * sizeof(mailbox); + ext2_offset = sizeof(struct virtual_mail_index_ext2_header) + + (bbox->sync_mailbox_idx1-1) * sizeof(ext2); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext_id, + mailbox_offset + uidval_pos, + CONST_PTR_OFFSET(&mailbox, uidval_pos), + sizeof(mailbox) - uidval_pos); + mail_index_update_header_ext(ctx->trans, ctx->mbox->virtual_ext2_id, + ext2_offset, &ext2, sizeof(ext2)); + ctx->ext_header_changed = TRUE; +} + +static void virtual_sync_backend_box_deleted(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + ARRAY_TYPE(seq_range) removed_uids; + const struct virtual_backend_uidmap *uidmap; + + /* delay its full removal until the next time we open the virtual + mailbox. for now just treat it as if it was empty. */ + + t_array_init(&removed_uids, 128); + array_foreach(&bbox->uids, uidmap) + seq_range_array_add(&removed_uids, uidmap->real_uid); + virtual_sync_mailbox_box_remove(ctx, bbox, &removed_uids); + + bbox->deleted = TRUE; +} + +static int +virtual_try_open_and_sync_backend_box(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox, + enum mailbox_sync_flags sync_flags) +{ + int ret = 0; + + if (!bbox->box->opened) + ret = virtual_backend_box_open(ctx->mbox, bbox); + if (ret == 0) + ret = mailbox_sync(bbox->box, sync_flags); + if (ret < 0) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + return 1; +} + +static int virtual_sync_backend_box(struct virtual_sync_context *ctx, + struct virtual_backend_box *bbox) +{ + enum mailbox_sync_flags sync_flags; + struct mailbox_status status; + const char *reason; + int ret; + + if (bbox->deleted) + return 0; + + /* if we already did some changes to index, commit them before + syncing starts. */ + virtual_backend_box_sync_mail_unset(bbox); + + sync_flags = ctx->flags & (MAILBOX_SYNC_FLAG_FULL_READ | + MAILBOX_SYNC_FLAG_FULL_WRITE | + MAILBOX_SYNC_FLAG_FAST); + + if (bbox->search_result == NULL) { + struct mailbox_metadata metadata; + + /* a) first sync in this process. + b) we had auto-closed this backend mailbox. + + first try to quickly check if the mailbox has changed. + if we can do that check from mailbox list index, we don't + even need to open the mailbox. */ + i_assert(array_count(&bbox->sync_pending_removes) == 0); + if (bbox->box->opened || bbox->open_failed) { + /* a) index already opened, refresh it + b) delayed error handling for mailbox_open() + that failed in virtual_notify_changes() */ + if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0) + return ret; + bbox->open_failed = FALSE; + } + + if ((mailbox_get_status(bbox->box, STATUS_UIDVALIDITY | + STATUS_UIDNEXT | STATUS_HIGHESTMODSEQ, + &status) < 0) || + (mailbox_get_metadata(bbox->box, MAILBOX_METADATA_GUID, + &metadata) < 0)) { + if (mailbox_get_last_mail_error(bbox->box) != MAIL_ERROR_NOTFOUND) + return -1; + /* mailbox was deleted */ + virtual_sync_backend_box_deleted(ctx, bbox); + return 0; + } + if (guid_128_is_empty(bbox->sync_guid)) { + /* upgrading from old virtual index */ + guid_128_copy(bbox->sync_guid, metadata.guid); + ctx->mbox->ext_header_rewrite = TRUE; + } + if (virtual_bbox_mailbox_equals(bbox, &status, &metadata, &reason)) { + /* mailbox hasn't changed since we last opened it, + skip it for now. + + we'll still need to create the bbox->uids mapping + using the current index. */ + if (array_count(&bbox->uids) == 0) + virtual_sync_backend_handle_old_vmsgs(ctx, bbox, NULL); + return 0; + } + e_debug(ctx->mbox->box.event, "Backend mailbox %s changed: %s", + bbox->box->vname, reason); + if (!bbox->box->opened) { + /* first time we're opening the index */ + if ((ret = virtual_try_open_and_sync_backend_box(ctx, bbox, sync_flags)) <= 0) + return ret; + } + + virtual_backend_box_sync_mail_set(bbox); + if ((status.uidvalidity != bbox->sync_uid_validity) || + !guid_128_equals(metadata.guid, bbox->sync_guid)) { + /* UID validity or GUID changed since last sync (or + this is the first sync), do a full search */ + bbox->first_sync = TRUE; + ret = virtual_sync_backend_box_init(bbox); + } else { + /* build the initial search using the saved modseq. */ + ret = virtual_sync_backend_box_continue(ctx, bbox); + } + i_assert(bbox->search_result != NULL || ret < 0); + } else { + /* sync using the existing search result */ + i_assert(bbox->box->opened); + i_array_init(&ctx->sync_expunges, 32); + ret = virtual_sync_backend_box_sync(ctx, bbox, sync_flags); + if (ret == 0) T_BEGIN { + virtual_sync_mailbox_box_update(ctx, bbox); + } T_END; + array_free(&ctx->sync_expunges); + } + + virtual_sync_backend_ext_header(ctx, bbox); + return ret; +} + +static void virtual_sync_backend_map_uids(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_sync_mail *vmails; + struct virtual_backend_box *bbox; + struct virtual_backend_uidmap *uidmap = NULL; + struct virtual_add_record add_rec; + const struct virtual_mail_index_record *vrec; + const void *data; + uint32_t i, vseq, vuid, messages; + unsigned int j = 0, uidmap_count = 0; + + messages = mail_index_view_get_messages_count(ctx->sync_view); + if (messages == 0) + return; + + /* sort the messages in current view by their backend mailbox and + real UID */ + vmails = i_new(struct virtual_sync_mail, messages); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, virtual_ext_id, + &data, NULL); + vrec = data; + vmails[vseq-1].vseq = vseq; + vmails[vseq-1].vrec = *vrec; + } + qsort(vmails, messages, sizeof(*vmails), virtual_sync_mail_uid_cmp); + + /* create real mailbox uid -> virtual uid mapping and expunge + messages no longer matching the search rule */ + i_zero(&add_rec); + bbox = NULL; + for (i = 0; i < messages; i++) { + vseq = vmails[i].vseq; + vrec = &vmails[i].vrec; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + /* add the rest of the newly seen messages */ + for (; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (bbox == NULL || bbox->first_sync) { + /* the entire mailbox is lost */ + mail_index_expunge(ctx->trans, vseq); + continue; + } + uidmap = array_get_modifiable(&bbox->uids, + &uidmap_count); + j = 0; + add_rec.rec.mailbox_id = bbox->mailbox_id; + bbox->sync_seen = TRUE; + } + mail_index_lookup_uid(ctx->sync_view, vseq, &vuid); + + /* if virtual record doesn't exist in uidmap, it's expunged */ + for (; j < uidmap_count; j++) { + if (uidmap[j].real_uid >= vrec->real_uid) + break; + + /* newly seen message */ + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + if (j == uidmap_count || uidmap[j].real_uid != vrec->real_uid) + mail_index_expunge(ctx->trans, vseq); + else { + /* exists - update uidmap and flags */ + uidmap[j++].virtual_uid = vuid; + if (bbox->search_result == NULL) { + /* mailbox is completely unchanged since last + sync - no need to sync flags */ + } else { + virtual_sync_external_flags(ctx, bbox, vseq, + vrec->real_uid); + } + } + } + i_free(vmails); + + /* finish adding messages to the last mailbox */ + for (; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } +} + +static void virtual_sync_new_backend_boxes(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + struct virtual_add_record add_rec; + struct virtual_backend_uidmap *uidmap; + unsigned int i, j, count, uidmap_count; + + /* if there are any mailboxes we didn't yet sync, add new messages in + them */ + i_zero(&add_rec); + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + for (i = 0; i < count; i++) { + bboxes[i]->first_sync = FALSE; /* this is the end of the sync */ + + if (bboxes[i]->sync_seen) + continue; + + add_rec.rec.mailbox_id = bboxes[i]->mailbox_id; + uidmap = array_get_modifiable(&bboxes[i]->uids, &uidmap_count); + for (j = 0; j < uidmap_count; j++) { + add_rec.rec.real_uid = uidmap[j].real_uid; + array_push_back(&ctx->all_adds, &add_rec); + } + } +} + +static int virtual_add_record_cmp(const struct virtual_add_record *add1, + const struct virtual_add_record *add2) +{ + if (add1->received_date < add2->received_date) + return -1; + if (add1->received_date > add2->received_date) + return 1; + + /* if they're in same mailbox, we can order them correctly by the UID. + if they're in different mailboxes, ordering by UID doesn't really + help but it doesn't really harm either. */ + if (add1->rec.real_uid < add2->rec.real_uid) + return -1; + if (add1->rec.real_uid > add2->rec.real_uid) + return 1; + + /* two messages in different mailboxes have the same received date + and UID. */ + return 0; +} + +static int virtual_sync_backend_sort_new(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *bbox; + struct virtual_add_record *adds; + const struct virtual_mail_index_record *vrec; + unsigned int i, count; + + /* get all messages' received dates */ + adds = array_get_modifiable(&ctx->all_adds, &count); + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (!bbox->box->opened && + virtual_backend_box_open(ctx->mbox, bbox) < 0) + return -1; + virtual_backend_box_sync_mail_set(bbox); + } + if (!mail_set_uid(bbox->sync_mail, vrec->real_uid)) { + /* we may have reopened the mailbox, which could have + caused the mail to be expunged already. */ + adds[i].received_date = 0; + } else if (mail_get_received_date(bbox->sync_mail, + &adds[i].received_date) < 0) { + if (!bbox->sync_mail->expunged) + return -1; + /* expunged already, just add it somewhere */ + adds[i].received_date = 0; + } + } + + array_sort(&ctx->all_adds, virtual_add_record_cmp); + return 0; +} + +static int virtual_sync_backend_add_new(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_add_record *adds; + struct virtual_backend_box *bbox; + struct virtual_backend_uidmap *uidmap; + const struct mail_index_header *hdr; + const struct virtual_mail_index_record *vrec; + unsigned int i, count, idx; + ARRAY_TYPE(seq_range) saved_uids; + uint32_t vseq, first_uid; + + hdr = mail_index_get_header(ctx->sync_view); + adds = array_get_modifiable(&ctx->all_adds, &count); + if (count == 0) { + ctx->mbox->sync_virtual_next_uid = hdr->next_uid; + return 0; + } + + if (adds[0].rec.mailbox_id == adds[count-1].rec.mailbox_id) { + /* all messages are from a single mailbox. add them in + the same order. */ + } else { + /* sort new messages by received date to get the add order */ + if (virtual_sync_backend_sort_new(ctx) < 0) + return -1; + } + + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (!bbox->box->opened && + virtual_backend_box_open(ctx->mbox, bbox) < 0) + return -1; + virtual_backend_box_sync_mail_set(bbox); + } + + mail_index_append(ctx->trans, 0, &vseq); + mail_index_update_ext(ctx->trans, vseq, virtual_ext_id, + vrec, NULL); + virtual_sync_external_flags(ctx, bbox, vseq, vrec->real_uid); + } + + /* assign UIDs to new messages */ + first_uid = hdr->next_uid; + t_array_init(&saved_uids, 1); + mail_index_append_finish_uids(ctx->trans, first_uid, &saved_uids); + i_assert(seq_range_count(&saved_uids) == count); + + /* update virtual UIDs in uidmap */ + for (bbox = NULL, i = 0; i < count; i++) { + vrec = &adds[i].rec; + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + } + + if (!array_bsearch_insert_pos(&bbox->uids, &vrec->real_uid, + virtual_backend_uidmap_bsearch_cmp, + &idx)) + i_unreached(); + uidmap = array_idx_modifiable(&bbox->uids, idx); + i_assert(uidmap->virtual_uid == 0); + uidmap->virtual_uid = first_uid + i; + } + ctx->mbox->sync_virtual_next_uid = first_uid + i; + return 0; +} + +static int +virtual_sync_apply_existing_appends(struct virtual_sync_context *ctx) +{ + uint32_t virtual_ext_id = ctx->mbox->virtual_ext_id; + struct virtual_backend_box *bbox = NULL; + const struct mail_index_header *hdr; + const struct virtual_mail_index_record *vrec; + struct virtual_backend_uidmap uidmap; + const void *data; + uint32_t seq, seq2; + + if (!ctx->mbox->uids_mapped) + return 0; + + hdr = mail_index_get_header(ctx->sync_view); + if (ctx->mbox->sync_virtual_next_uid >= hdr->next_uid) + return 0; + + /* another process added messages to virtual index. get backend boxes' + uid lists up-to-date by adding the new messages there. */ + if (!mail_index_lookup_seq_range(ctx->sync_view, + ctx->mbox->sync_virtual_next_uid, + (uint32_t)-1, &seq, &seq2)) + return 0; + + i_zero(&uidmap); + for (; seq <= seq2; seq++) { + mail_index_lookup_ext(ctx->sync_view, seq, virtual_ext_id, + &data, NULL); + vrec = data; + uidmap.real_uid = vrec->real_uid; + mail_index_lookup_uid(ctx->sync_view, seq, &uidmap.virtual_uid); + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(ctx->mbox, + vrec->mailbox_id); + if (bbox == NULL) { + mail_index_expunge(ctx->trans, seq); + continue; + } + } + array_push_back(&bbox->uids, &uidmap); + bbox->uids_nonsorted = TRUE; + } + + virtual_sync_backend_boxes_sort_uids(ctx->mbox); + return 0; +} + +static void +virtual_sync_apply_existing_expunges(struct virtual_mailbox *mbox, + struct mailbox_sync_context *sync_ctx) +{ + struct index_mailbox_sync_context *isync_ctx = + (struct index_mailbox_sync_context *)sync_ctx; + struct virtual_backend_box *bbox = NULL; + struct seq_range_iter iter; + const struct virtual_mail_index_record *vrec; + const void *data; + unsigned int n = 0; + uint32_t seq; + + if (isync_ctx->expunges == NULL) + return; + + seq_range_array_iter_init(&iter, isync_ctx->expunges); + while (seq_range_array_iter_nth(&iter, n++, &seq)) { + mail_index_lookup_ext(mbox->box.view, seq, + mbox->virtual_ext_id, &data, NULL); + vrec = data; + + if (bbox == NULL || bbox->mailbox_id != vrec->mailbox_id) { + bbox = virtual_backend_box_lookup(mbox, + vrec->mailbox_id); + if (!array_is_created(&bbox->sync_outside_expunges)) + i_array_init(&bbox->sync_outside_expunges, 32); + } + seq_range_array_add(&bbox->sync_outside_expunges, + vrec->real_uid); + } +} + +static int virtual_sync_mail_mailbox_cmp(const struct virtual_sync_mail *m1, + const struct virtual_sync_mail *m2) +{ + if (m1->vrec.mailbox_id < m2->vrec.mailbox_id) + return -1; + if (m1->vrec.mailbox_id > m2->vrec.mailbox_id) + return 1; + return 0; +} + +static void virtual_sync_bboxes_get_mails(struct virtual_sync_context *ctx) +{ + uint32_t messages, vseq; + const void *mail_data; + const struct virtual_mail_index_record *vrec; + struct virtual_sync_mail *sync_mail; + + messages = mail_index_view_get_messages_count(ctx->sync_view); + i_array_init(&ctx->all_mails, messages); + for (vseq = 1; vseq <= messages; vseq++) { + mail_index_lookup_ext(ctx->sync_view, vseq, + ctx->mbox->virtual_ext_id, &mail_data, NULL); + vrec = mail_data; + sync_mail = array_append_space(&ctx->all_mails); + sync_mail->vseq = vseq; + sync_mail->vrec = *vrec; + } + array_sort(&ctx->all_mails, virtual_sync_mail_mailbox_cmp); +} + +static int virtual_sync_backend_boxes(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + int ret; + + if (virtual_sync_apply_existing_appends(ctx) < 0) + return -1; + + i_array_init(&ctx->all_adds, 128); + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + + /* we have different optimizations depending on whether the virtual + mailbox consists of multiple backend boxes or just one */ + if (count > 1) + virtual_sync_bboxes_get_mails(ctx); + + for (i = 0; i < count; i++) { + if (virtual_sync_backend_box(ctx, bboxes[i]) < 0) { + /* backend failed, copy the error */ + virtual_box_copy_error(&ctx->mbox->box, + bboxes[i]->box); + return -1; + } + } + + if (!ctx->mbox->uids_mapped) { + /* initial sync: assign virtual UIDs to existing messages and + sync all flags */ + ctx->mbox->uids_mapped = TRUE; + virtual_sync_backend_map_uids(ctx); + virtual_sync_new_backend_boxes(ctx); + } + ret = virtual_sync_backend_add_new(ctx); +#ifdef DEBUG + for (i = 0; i < count; i++) { + const struct virtual_backend_uidmap *uidmap; + + array_foreach(&bboxes[i]->uids, uidmap) + i_assert(uidmap->virtual_uid > 0); + } +#endif + array_free(&ctx->all_adds); + if (array_is_created(&ctx->all_mails)) + array_free(&ctx->all_mails); + return ret; +} + +static void virtual_sync_backend_boxes_finish(struct virtual_sync_context *ctx) +{ + struct virtual_backend_box *const *bboxes; + unsigned int i, count; + + bboxes = array_get(&ctx->mbox->backend_boxes, &count); + for (i = 0; i < count; i++) + virtual_backend_box_sync_mail_unset(bboxes[i]); +} + +static int virtual_sync_finish(struct virtual_sync_context *ctx, bool success) +{ + int ret = success ? 0 : -1; + + virtual_sync_backend_boxes_finish(ctx); + if (success) { + if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) { + mailbox_set_index_error(&ctx->mbox->box); + ret = -1; + } + ctx->mbox->ext_header_rewrite = FALSE; + } else { + if (ctx->index_broken) { + /* make sure we don't complain about the same errors + over and over again. */ + if (mail_index_unlink(ctx->index) < 0) { + i_error("virtual index %s: Failed to unlink() " + "broken indexes: %m", + mailbox_get_path(&ctx->mbox->box)); + } + } + mail_index_sync_rollback(&ctx->index_sync_ctx); + } + i_free(ctx); + return ret; +} + +static int virtual_sync(struct virtual_mailbox *mbox, + enum mailbox_sync_flags flags) +{ + struct virtual_sync_context *ctx; + enum mail_index_sync_flags index_sync_flags; + bool broken; + int ret; + + ctx = i_new(struct virtual_sync_context, 1); + ctx->mbox = mbox; + ctx->flags = flags; + ctx->index = mbox->box.index; + /* Removed messages are expunged when + a) EXPUNGE is used + b) Mailbox is being opened (FIX_INCONSISTENT is set) */ + ctx->expunge_removed = + (ctx->flags & (MAILBOX_SYNC_FLAG_EXPUNGE | + MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) != 0; + + index_sync_flags = MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY | + MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES; + if ((mbox->box.flags & MAILBOX_FLAG_DROP_RECENT) != 0) + index_sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT; + + ret = mail_index_sync_begin(ctx->index, &ctx->index_sync_ctx, + &ctx->sync_view, &ctx->trans, + index_sync_flags); + if (ret <= 0) { + if (ret < 0) + mailbox_set_index_error(&mbox->box); + i_free(ctx); + return ret; + } + + ret = virtual_mailbox_ext_header_read(mbox, ctx->sync_view, &broken); + if (ret < 0) + return virtual_sync_finish(ctx, FALSE); + if (broken) + ctx->index_broken = TRUE; + /* apply changes from virtual index to backend mailboxes */ + if (virtual_sync_index_changes(ctx) < 0) + return virtual_sync_finish(ctx, FALSE); + /* update list of UIDs in backend mailboxes */ + if (virtual_sync_backend_boxes(ctx) < 0) + return virtual_sync_finish(ctx, FALSE); + + virtual_sync_index_finish(ctx); + return virtual_sync_finish(ctx, TRUE); +} + +struct mailbox_sync_context * +virtual_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct mailbox_sync_context *sync_ctx; + int ret = 0; + + if (!box->opened) { + if (mailbox_open(box) < 0) + ret = -1; + } + + if (index_mailbox_want_full_sync(&mbox->box, flags) && ret == 0) + ret = virtual_sync(mbox, flags); + + sync_ctx = index_mailbox_sync_init(box, flags, ret < 0); + virtual_sync_apply_existing_expunges(mbox, sync_ctx); + return sync_ctx; +} diff --git a/src/plugins/virtual/virtual-transaction.c b/src/plugins/virtual/virtual-transaction.c new file mode 100644 index 0000000..53ac0a8 --- /dev/null +++ b/src/plugins/virtual/virtual-transaction.c @@ -0,0 +1,87 @@ +/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "virtual-storage.h" +#include "virtual-transaction.h" + +struct mailbox_transaction_context * +virtual_transaction_get(struct mailbox_transaction_context *trans, + struct mailbox *backend_box) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)trans; + struct mailbox_transaction_context *const *bt, *new_bt; + unsigned int i, count; + + bt = array_get(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) { + if (bt[i]->box == backend_box) + return bt[i]; + } + + new_bt = mailbox_transaction_begin(backend_box, trans->flags, __func__); + array_push_back(&vt->backend_transactions, &new_bt); + return new_bt; +} + +struct mailbox_transaction_context * +virtual_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason) +{ + struct virtual_mailbox *mbox = (struct virtual_mailbox *)box; + struct virtual_transaction_context *vt; + + vt = i_new(struct virtual_transaction_context, 1); + i_array_init(&vt->backend_transactions, + array_count(&mbox->backend_boxes)); + index_transaction_init(&vt->t, box, flags, reason); + return &vt->t; +} + +int virtual_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)t; + struct mailbox_transaction_context **bt; + unsigned int i, count; + int ret = 0; + + if (t->save_ctx != NULL) { + virtual_save_free(t->save_ctx); + t->save_ctx = NULL; + } + + bt = array_get_modifiable(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) { + if (mailbox_transaction_commit(&bt[i]) < 0) + ret = -1; + } + array_free(&vt->backend_transactions); + + if (index_transaction_commit(t, changes_r) < 0) + ret = -1; + return ret; +} + +void virtual_transaction_rollback(struct mailbox_transaction_context *t) +{ + struct virtual_transaction_context *vt = + (struct virtual_transaction_context *)t; + struct mailbox_transaction_context **bt; + unsigned int i, count; + + if (t->save_ctx != NULL) { + virtual_save_free(t->save_ctx); + t->save_ctx = NULL; + } + + bt = array_get_modifiable(&vt->backend_transactions, &count); + for (i = 0; i < count; i++) + mailbox_transaction_rollback(&bt[i]); + array_free(&vt->backend_transactions); + + index_transaction_rollback(t); +} diff --git a/src/plugins/virtual/virtual-transaction.h b/src/plugins/virtual/virtual-transaction.h new file mode 100644 index 0000000..a950fcb --- /dev/null +++ b/src/plugins/virtual/virtual-transaction.h @@ -0,0 +1,24 @@ +#ifndef VIRTUAL_TRANSACTION_H +#define VIRTUAL_TRANSACTION_H + +#include "index-storage.h" + +struct virtual_transaction_context { + struct mailbox_transaction_context t; + + ARRAY(struct mailbox_transaction_context *) backend_transactions; +}; + +struct mailbox_transaction_context * +virtual_transaction_get(struct mailbox_transaction_context *trans, + struct mailbox *backend_box); + +struct mailbox_transaction_context * +virtual_transaction_begin(struct mailbox *box, + enum mailbox_transaction_flags flags, + const char *reason); +int virtual_transaction_commit(struct mailbox_transaction_context *t, + struct mail_transaction_commit_changes *changes_r); +void virtual_transaction_rollback(struct mailbox_transaction_context *t); + +#endif |