comparison src/lib-storage/index/maildir/maildir-sync.c @ 5920:00c5e3cbeaf0 HEAD

Moved index syncing code to its own file.
author Timo Sirainen <tss@iki.fi>
date Sun, 08 Jul 2007 23:28:22 +0300
parents 126b74419f52
children 095b3adc537b
comparison
equal deleted inserted replaced
5919:cc439e4e99cb 5920:00c5e3cbeaf0
175 #include "array.h" 175 #include "array.h"
176 #include "buffer.h" 176 #include "buffer.h"
177 #include "hash.h" 177 #include "hash.h"
178 #include "str.h" 178 #include "str.h"
179 #include "maildir-storage.h" 179 #include "maildir-storage.h"
180 #include "index-sync-changes.h"
181 #include "maildir-uidlist.h" 180 #include "maildir-uidlist.h"
182 #include "maildir-keywords.h"
183 #include "maildir-filename.h" 181 #include "maildir-filename.h"
184 #include "maildir-sync.h" 182 #include "maildir-sync.h"
185 183
186 #include <stdio.h> 184 #include <stdio.h>
187 #include <stddef.h> 185 #include <stddef.h>
198 left. This value is just an optimization to avoid checking the directory 196 left. This value is just an optimization to avoid checking the directory
199 twice unneededly. usually only NFS is the problem case. 1 is the safest 197 twice unneededly. usually only NFS is the problem case. 1 is the safest
200 bet here, but I guess 5 will do just fine too. */ 198 bet here, but I guess 5 will do just fine too. */
201 #define MAILDIR_RENAME_RESCAN_COUNT 5 199 #define MAILDIR_RENAME_RESCAN_COUNT 5
202 200
203 /* After moving 100 mails from new/ to cur/, check if we need to touch the
204 uidlist lock. */
205 #define MAILDIR_SLOW_MOVE_COUNT 100
206 /* readdir() should be pretty fast to do, but check anyway every 10000 mails
207 to see if we need to touch the uidlist lock. */
208 #define MAILDIR_SLOW_CHECK_COUNT 10000
209
210 /* This is mostly to avoid infinite looping when rename() destination already 201 /* This is mostly to avoid infinite looping when rename() destination already
211 exists as the hard link of the file itself. */ 202 exists as the hard link of the file itself. */
212 #define MAILDIR_SCAN_DIR_MAX_COUNT 5 203 #define MAILDIR_SCAN_DIR_MAX_COUNT 5
213 204
214 #define DUPE_LINKS_DELETE_SECS 30 205 #define DUPE_LINKS_DELETE_SECS 30
222 213
223 struct maildir_uidlist_sync_ctx *uidlist_sync_ctx; 214 struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
224 struct maildir_index_sync_context *index_sync_ctx; 215 struct maildir_index_sync_context *index_sync_ctx;
225 }; 216 };
226 217
227 struct maildir_index_sync_context { 218 void maildir_sync_notify(struct maildir_sync_context *ctx)
228 struct maildir_mailbox *mbox;
229 struct maildir_sync_context *maildir_sync_ctx;
230
231 struct mail_index_view *view;
232 struct mail_index_sync_ctx *sync_ctx;
233 struct maildir_keywords_sync_ctx *keywords_sync_ctx;
234 struct mail_index_transaction *trans;
235
236 struct index_sync_changes_context *sync_changes;
237 enum mail_flags flags;
238 ARRAY_TYPE(keyword_indexes) keywords;
239
240 uint32_t seq, uid;
241
242 bool changed;
243 };
244
245 struct maildir_keywords_sync_ctx *
246 maildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
247 {
248 return ctx->keywords_sync_ctx;
249 }
250
251 static int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
252 struct maildir_index_sync_context *ctx)
253 {
254 struct mailbox *box = &mbox->ibox.box;
255
256 if (unlink(path) == 0) {
257 if (box->v.sync_notify != NULL) {
258 box->v.sync_notify(box, ctx->uid,
259 MAILBOX_SYNC_TYPE_EXPUNGE);
260 }
261 mail_index_expunge(ctx->trans, ctx->seq);
262 ctx->changed = TRUE;
263 return 1;
264 }
265 if (errno == ENOENT)
266 return 0;
267
268 mail_storage_set_critical(&mbox->storage->storage,
269 "unlink(%s) failed: %m", path);
270 return -1;
271 }
272
273 static int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
274 struct maildir_index_sync_context *ctx)
275 {
276 struct mailbox *box = &mbox->ibox.box;
277 const char *dir, *fname, *newfname, *newpath;
278 enum mailbox_sync_type sync_type = 0;
279 uint8_t flags8;
280
281 fname = strrchr(path, '/');
282 i_assert(fname != NULL);
283 fname++;
284 dir = t_strdup_until(path, fname);
285
286 /* get the current flags and keywords */
287 maildir_filename_get_flags(ctx->keywords_sync_ctx,
288 fname, &ctx->flags, &ctx->keywords);
289
290 /* apply changes */
291 flags8 = ctx->flags;
292 index_sync_changes_apply(ctx->sync_changes, NULL,
293 &flags8, &ctx->keywords, &sync_type);
294 ctx->flags = flags8;
295
296 /* and try renaming with the new name */
297 newfname = maildir_filename_set_flags(ctx->keywords_sync_ctx, fname,
298 ctx->flags, &ctx->keywords);
299 newpath = t_strconcat(dir, newfname, NULL);
300 if (rename(path, newpath) == 0) {
301 if (box->v.sync_notify != NULL)
302 box->v.sync_notify(box, ctx->uid, sync_type);
303
304 ctx->changed = TRUE;
305 return 1;
306 }
307 if (errno == ENOENT)
308 return 0;
309
310 if (!ENOSPACE(errno) && errno != EACCES) {
311 mail_storage_set_critical(&mbox->storage->storage,
312 "rename(%s, %s) failed: %m", path, newpath);
313 }
314 return -1;
315 }
316
317 static void maildir_sync_notify(struct maildir_sync_context *ctx)
318 { 219 {
319 time_t now; 220 time_t now;
320 221
321 if (ctx == NULL) { 222 if (ctx == NULL) {
322 /* we got here from maildir-save.c. it has no 223 /* we got here from maildir-save.c. it has no
661 } 562 }
662 563
663 return 0; 564 return 0;
664 } 565 }
665 566
666 int maildir_sync_index_begin(struct maildir_mailbox *mbox,
667 struct maildir_index_sync_context **ctx_r)
668 {
669 struct maildir_index_sync_context *ctx;
670 struct mail_index_sync_ctx *sync_ctx;
671 struct mail_index_view *view;
672 struct mail_index_transaction *trans;
673
674 if (mail_index_sync_begin(mbox->ibox.index, &sync_ctx, &view, &trans,
675 (uint32_t)-1, (uoff_t)-1, 0) <= 0) {
676 mail_storage_set_index_error(&mbox->ibox);
677 return -1;
678 }
679
680 ctx = i_new(struct maildir_index_sync_context, 1);
681 ctx->mbox = mbox;
682 ctx->sync_ctx = sync_ctx;
683 ctx->view = view;
684 ctx->trans = trans;
685 ctx->keywords_sync_ctx =
686 maildir_keywords_sync_init(mbox->keywords, mbox->ibox.index);
687
688 ctx->sync_changes = index_sync_changes_init(&mbox->ibox, ctx->sync_ctx,
689 ctx->view, ctx->trans,
690 mbox->ibox.readonly);
691
692 *ctx_r = ctx;
693 return 0;
694 }
695
696 int maildir_sync_index_finish(struct maildir_index_sync_context **_ctx,
697 bool failed, bool cancel)
698 {
699 struct maildir_index_sync_context *ctx = *_ctx;
700 struct maildir_mailbox *mbox = ctx->mbox;
701 int ret = failed ? -1 : 0;
702
703 *_ctx = NULL;
704
705 if (ret < 0 || cancel)
706 mail_index_sync_rollback(&ctx->sync_ctx);
707 else {
708 /* Set syncing_commit=TRUE so that if any sync callbacks try
709 to access mails which got lost (eg. expunge callback trying
710 to open the file which was just unlinked) we don't try to
711 start a second index sync and crash. */
712 mbox->syncing_commit = TRUE;
713 if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
714 mail_storage_set_index_error(&mbox->ibox);
715 ret = -1;
716 } else {
717 mbox->ibox.commit_log_file_seq = 0;
718 mbox->ibox.commit_log_file_offset = 0;
719 }
720 mbox->syncing_commit = FALSE;
721 }
722
723 maildir_keywords_sync_deinit(ctx->keywords_sync_ctx);
724 ctx->keywords_sync_ctx = NULL;
725
726 index_sync_changes_deinit(&ctx->sync_changes);
727 i_free(ctx);
728 return ret;
729 }
730
731 int maildir_sync_index(struct maildir_index_sync_context *ctx,
732 bool partial)
733 {
734 struct maildir_mailbox *mbox = ctx->mbox;
735 struct mail_index_view *view = ctx->view;
736 struct maildir_uidlist_iter_ctx *iter;
737 struct mail_index_transaction *trans = ctx->trans;
738 const struct mail_index_header *hdr;
739 struct mail_index_header empty_hdr;
740 const struct mail_index_record *rec;
741 uint32_t seq, uid, prev_uid;
742 enum maildir_uidlist_rec_flag uflags;
743 const char *filename;
744 ARRAY_TYPE(keyword_indexes) idx_keywords;
745 uint32_t uid_validity, next_uid;
746 uint64_t value;
747 unsigned int changes = 0;
748 int ret = 0;
749 bool expunged, full_rescan = FALSE;
750
751 i_assert(!mbox->syncing_commit);
752 i_assert(maildir_uidlist_is_locked(ctx->mbox->uidlist));
753
754 hdr = mail_index_get_header(view);
755 uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
756 if (uid_validity != hdr->uid_validity &&
757 uid_validity != 0 && hdr->uid_validity != 0) {
758 /* uidvalidity changed and mailbox isn't being initialized,
759 reset mailbox so we can add all messages as new */
760 i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
761 mbox->path, hdr->uid_validity, uid_validity);
762 mail_index_reset(trans);
763
764 memset(&empty_hdr, 0, sizeof(empty_hdr));
765 empty_hdr.next_uid = 1;
766 hdr = &empty_hdr;
767 }
768
769 mbox->syncing_commit = TRUE;
770 seq = prev_uid = 0;
771 t_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
772 t_array_init(&idx_keywords, MAILDIR_MAX_KEYWORDS);
773 iter = maildir_uidlist_iter_init(mbox->uidlist);
774 while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
775 maildir_filename_get_flags(ctx->keywords_sync_ctx, filename,
776 &ctx->flags, &ctx->keywords);
777
778 i_assert(uid > prev_uid);
779 prev_uid = uid;
780
781 /* the private flags are kept only in indexes. don't use them
782 at all even for newly seen mails */
783 ctx->flags &= ~mbox->private_flags_mask;
784
785 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
786 (uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 &&
787 (uflags & MAILDIR_UIDLIST_REC_FLAG_MOVED) == 0) {
788 /* mail is recent for next session as well */
789 ctx->flags |= MAIL_RECENT;
790 }
791
792 __again:
793 ctx->seq = ++seq;
794 ctx->uid = uid;
795
796 if (seq > hdr->messages_count) {
797 if (uid < hdr->next_uid) {
798 /* most likely a race condition: we read the
799 maildir, then someone else expunged messages
800 and committed changes to index. so, this
801 message shouldn't actually exist. mark it
802 racy and check in next sync.
803
804 the difference between this and the later
805 check is that this one happens when messages
806 are expunged from the end */
807 if ((uflags &
808 MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
809 /* partial syncing */
810 continue;
811 }
812 if ((uflags &
813 MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
814 mail_storage_set_critical(
815 &mbox->storage->storage,
816 "Maildir %s sync: "
817 "UID < next_uid "
818 "(%u < %u, file = %s)",
819 mbox->path, uid, hdr->next_uid,
820 filename);
821 mail_index_mark_corrupted(
822 mbox->ibox.index);
823 ret = -1;
824 break;
825 }
826 mbox->dirty_cur_time = ioloop_time;
827 maildir_uidlist_add_flags(mbox->uidlist,
828 filename,
829 MAILDIR_UIDLIST_REC_FLAG_RACING);
830
831 seq--;
832 continue;
833 }
834
835 mail_index_append(trans, uid, &seq);
836 mail_index_update_flags(trans, seq, MODIFY_REPLACE,
837 ctx->flags);
838
839 if (array_count(&ctx->keywords) > 0) {
840 struct mail_keywords *kw;
841
842 kw = mail_index_keywords_create_from_indexes(
843 trans, &ctx->keywords);
844 mail_index_update_keywords(trans, seq,
845 MODIFY_REPLACE, kw);
846 mail_index_keywords_free(&kw);
847 }
848 continue;
849 }
850
851 if (mail_index_lookup(view, seq, &rec) < 0) {
852 mail_storage_set_index_error(&mbox->ibox);
853 ret = -1;
854 break;
855 }
856
857 if (rec->uid < uid) {
858 /* expunged */
859 mail_index_expunge(trans, seq);
860 goto __again;
861 }
862
863 if (rec->uid > uid) {
864 /* most likely a race condition: we read the
865 maildir, then someone else expunged messages and
866 committed changes to index. so, this message
867 shouldn't actually exist. mark it racy and check
868 in next sync. */
869 if ((uflags &
870 MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
871 /* partial syncing */
872 seq--;
873 continue;
874 }
875 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
876 mail_storage_set_critical(
877 &mbox->storage->storage,
878 "Maildir %s sync: "
879 "UID inserted in the middle of mailbox "
880 "(%u > %u, file = %s)",
881 mbox->path, rec->uid, uid, filename);
882 mail_index_mark_corrupted(mbox->ibox.index);
883 ret = -1;
884 break;
885 }
886
887 mbox->dirty_cur_time = ioloop_time;
888 maildir_uidlist_add_flags(mbox->uidlist, filename,
889 MAILDIR_UIDLIST_REC_FLAG_RACING);
890
891 seq--;
892 continue;
893 }
894
895 if (index_sync_changes_read(ctx->sync_changes, rec->uid,
896 &expunged) < 0) {
897 ret = -1;
898 break;
899 }
900
901 if (expunged) {
902 if (maildir_file_do(ctx->mbox, ctx->uid,
903 maildir_expunge, ctx) >= 0) {
904 /* successful expunge */
905 mail_index_expunge(trans, ctx->seq);
906 }
907 if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
908 maildir_sync_notify(ctx->maildir_sync_ctx);
909 continue;
910 }
911
912 /* the private flags are stored only in indexes, keep them */
913 ctx->flags |= rec->flags & mbox->private_flags_mask;
914
915 if ((rec->flags & MAIL_RECENT) != 0) {
916 index_mailbox_set_recent(&mbox->ibox, seq);
917 if (mbox->ibox.keep_recent) {
918 ctx->flags |= MAIL_RECENT;
919 } else {
920 mail_index_update_flags(trans, seq,
921 MODIFY_REMOVE,
922 MAIL_RECENT);
923 }
924 }
925
926 if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
927 /* partial syncing */
928 if ((ctx->flags & MAIL_RECENT) != 0) {
929 /* we last saw this mail in new/, but it's
930 not there anymore. possibly expunged,
931 make sure. */
932 full_rescan = TRUE;
933 }
934 continue;
935 }
936
937 if (index_sync_changes_have(ctx->sync_changes)) {
938 /* apply flag changes to maildir */
939 if (maildir_file_do(ctx->mbox, ctx->uid,
940 maildir_sync_flags, ctx) < 0)
941 ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
942 if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
943 maildir_sync_notify(ctx->maildir_sync_ctx);
944 }
945
946 if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
947 /* we haven't been able to update maildir with this
948 record's flag changes. don't sync them. */
949 continue;
950 }
951
952 if ((ctx->flags & ~MAIL_RECENT) !=
953 (rec->flags & (MAIL_FLAGS_MASK^MAIL_RECENT))) {
954 /* FIXME: this is wrong if there's pending changes in
955 transaction log already. it gets fixed in next sync
956 however.. */
957 mail_index_update_flags(trans, seq, MODIFY_REPLACE,
958 ctx->flags);
959 } else if ((ctx->flags & MAIL_RECENT) == 0 &&
960 (rec->flags & MAIL_RECENT) != 0) {
961 /* just remove recent flag */
962 mail_index_update_flags(trans, seq, MODIFY_REMOVE,
963 MAIL_RECENT);
964 }
965
966 /* update keywords if they have changed */
967 if (mail_index_lookup_keywords(view, seq, &idx_keywords) < 0) {
968 mail_storage_set_index_error(&mbox->ibox);
969 ret = -1;
970 break;
971 }
972 if (!index_keyword_array_cmp(&ctx->keywords, &idx_keywords)) {
973 struct mail_keywords *kw;
974
975 kw = mail_index_keywords_create_from_indexes(
976 trans, &ctx->keywords);
977 mail_index_update_keywords(trans, seq,
978 MODIFY_REPLACE, kw);
979 mail_index_keywords_free(&kw);
980 }
981 }
982 maildir_uidlist_iter_deinit(iter);
983 mbox->syncing_commit = FALSE;
984
985 if (mbox->ibox.box.v.sync_notify != NULL)
986 mbox->ibox.box.v.sync_notify(&mbox->ibox.box, 0, 0);
987
988 if (!partial) {
989 /* expunge the rest */
990 for (seq++; seq <= hdr->messages_count; seq++)
991 mail_index_expunge(trans, seq);
992
993 /* next_uid must be updated only in non-partial syncs since
994 partial syncs don't add the new mails to index. also we'll
995 have to do it here before syncing index records, since after
996 that the uidlist's next_uid value may have changed. */
997 next_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
998 i_assert(next_uid > prev_uid);
999 if (hdr->next_uid < next_uid) {
1000 mail_index_update_header(trans,
1001 offsetof(struct mail_index_header, next_uid),
1002 &next_uid, sizeof(next_uid), FALSE);
1003 }
1004 }
1005
1006 if (ctx->changed)
1007 mbox->dirty_cur_time = ioloop_time;
1008 if (mbox->dirty_cur_time != 0)
1009 mbox->last_dirty_flags |= MAILDIR_DIRTY_CUR;
1010
1011 if (mbox->last_cur_mtime != (time_t)hdr->sync_stamp) {
1012 uint32_t sync_stamp = mbox->last_cur_mtime;
1013
1014 mail_index_update_header(trans,
1015 offsetof(struct mail_index_header, sync_stamp),
1016 &sync_stamp, sizeof(sync_stamp), TRUE);
1017 }
1018
1019 /* FIXME: use a header extension instead of sync_size.. */
1020 value = mbox->last_new_mtime |
1021 ((uint64_t)mbox->last_dirty_flags << 32);
1022 if (value != hdr->sync_size) {
1023 mail_index_update_header(trans,
1024 offsetof(struct mail_index_header, sync_size),
1025 &value, sizeof(value), TRUE);
1026 }
1027
1028 if (hdr->uid_validity == 0) {
1029 /* get the initial uidvalidity */
1030 if (maildir_uidlist_refresh(mbox->uidlist) < 0)
1031 ret = -1;
1032 uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
1033 if (uid_validity == 0) {
1034 uid_validity = ioloop_time;
1035 maildir_uidlist_set_uid_validity(mbox->uidlist,
1036 uid_validity, 0);
1037 }
1038 } else if (uid_validity == 0) {
1039 maildir_uidlist_set_uid_validity(mbox->uidlist,
1040 hdr->uid_validity,
1041 hdr->next_uid);
1042 }
1043
1044 if (uid_validity != hdr->uid_validity && uid_validity != 0) {
1045 mail_index_update_header(trans,
1046 offsetof(struct mail_index_header, uid_validity),
1047 &uid_validity, sizeof(uid_validity), TRUE);
1048 }
1049
1050 return ret < 0 ? -1 : (full_rescan ? 0 : 1);
1051 }
1052
1053 static int maildir_sync_context(struct maildir_sync_context *ctx, bool forced, 567 static int maildir_sync_context(struct maildir_sync_context *ctx, bool forced,
1054 bool sync_last_commit) 568 bool sync_last_commit)
1055 { 569 {
1056 bool new_changed, cur_changed, full_rescan = FALSE; 570 bool new_changed, cur_changed, full_rescan = FALSE;
1057 int ret; 571 int ret;
1124 the trouble? .. */ 638 the trouble? .. */
1125 return ret; 639 return ret;
1126 } 640 }
1127 641
1128 if (!ctx->mbox->syncing_commit) { 642 if (!ctx->mbox->syncing_commit) {
1129 if (maildir_sync_index_begin(ctx->mbox, 643 if (maildir_sync_index_begin(ctx->mbox, ctx,
1130 &ctx->index_sync_ctx) < 0) 644 &ctx->index_sync_ctx) < 0)
1131 return -1; 645 return -1;
1132 ctx->index_sync_ctx->maildir_sync_ctx = ctx;
1133 } 646 }
1134 647
1135 if (new_changed || cur_changed) { 648 if (new_changed || cur_changed) {
1136 /* if we're going to check cur/ dir our current logic requires 649 /* if we're going to check cur/ dir our current logic requires
1137 that new/ dir is checked as well. it's a good idea anyway. */ 650 that new/ dir is checked as well. it's a good idea anyway. */