maildir-sync.c revision 0e2686dfe29a18772fa4026bad53e2c7c560403f
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen/* Copyright (C) 2004 Timo Sirainen */
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen/*
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen Here's a description of how we handle Maildir synchronization and
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen it's problems:
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen We want to be as efficient as we can. The most efficient way to
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen check if changes have occured is to stat() the new/ and cur/
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen directories and uidlist file - if their mtimes haven't changed,
1098fc409a45e7603701dc94635927a673bee0c1Timo Sirainen there's no changes and we don't need to do anything.
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen Problem 1: Multiple changes can happen within a single second -
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen nothing guarantees that once we synced it, someone else didn't just
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen then make a modification. Such modifications wouldn't get noticed
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen until a new modification occured later.
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen Problem 2: Syncing cur/ directory is much more costly than syncing
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen new/. Moving mails from new/ to cur/ will always change mtime of
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen cur/ causing us to sync it as well.
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen Problem 3: We may not be able to move mail from new/ to cur/
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen because we're out of quota, or simply because we're accessing a
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen read-only mailbox.
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen MAILDIR_SYNC_SECS
72e9e7ad158101d46860b42c4080e894485c78c3Timo Sirainen -----------------
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen Several checks below use MAILDIR_SYNC_SECS, which should be maximum
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen clock drift between all computers accessing the maildir (eg. via
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen NFS), rounded up to next second. Our default is 1 second, since
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen everyone should be using NTP.
72e9e7ad158101d46860b42c4080e894485c78c3Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen Note that setting it to 0 works only if there's only one computer
4823da41d112ff9f5e8f088b0e60d1636e01ff92Timo Sirainen accessing the maildir. It's practically impossible to make two
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen clocks _exactly_ synchronized.
afa6ac39d1d6df246d4e7352288c2a0388276a24Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen It might be possible to only use file server's clock by looking at
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen the atime field, but I don't know how well that would actually work.
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen cur directory
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen -------------
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
72e9e7ad158101d46860b42c4080e894485c78c3Timo Sirainen We have dirty_cur_time variable which is set to cur/ directory's
4823da41d112ff9f5e8f088b0e60d1636e01ff92Timo Sirainen mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen synchronized the directory.
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen When dirty_cur_time is non-zero, we don't synchronize the cur/
4823da41d112ff9f5e8f088b0e60d1636e01ff92Timo Sirainen directory until
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen a) cur/'s mtime changes
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen b) opening a mail fails with ENOENT
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen c) time() > dirty_cur_time + MAILDIR_SYNC_SECS
9945ad9e528188521d876b80f08a648072ffa207Timo Sirainen
4823da41d112ff9f5e8f088b0e60d1636e01ff92Timo Sirainen This allows us to modify the maildir multiple times without having
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen to sync it at every change. The sync will eventually be done to
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen make sure we didn't miss any external changes.
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen The dirty_cur_time is set when:
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen - we change message flags
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen - we expunge messages
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen - we move mail from new/ to cur/
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen - we sync cur/ directory and it's mtime is >= time() - MAILDIR_SYNC_SECS
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen It's unset when we do the final syncing, ie. when mtime is
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen older than time() - MAILDIR_SYNC_SECS.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen new directory
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen -------------
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen it. dirty_cur_time-like feature might save us a few syncs, but
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen that might break a client which saves a mail in one connection and
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen tries to fetch it in another one. new/ directory is almost always
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen empty, so syncing it should be very fast anyway. Actually this can
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen still happen if we sync only new/ dir while another client is also
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen moving mails from it to cur/ - it takes us a while to see them.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen That's pretty unlikely to happen however, and only way to fix it
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen would be to always synchronize cur/ after new/.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen Normally we move all mails from new/ to cur/ whenever we sync it. If
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen it's not possible for some reason, we mark the mail with "probably
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen exists in new/ directory" flag.
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen If rename() still fails because of ENOSPC or EDQUOT, we still save
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen the flag changes in index with dirty-flag on. When moving the mail
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen to cur/ directory, or when we notice it's already moved there, we
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen apply the flag changes to the filename, rename it and remove the
1171f0abf442638bac1827bb24a0b6b8eb682a82Timo Sirainen dirty flag. If there's dirty flags, this should be tried every time
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen after expunge or when closing the mailbox.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
dd4b5f14b71b01a84af942e720a2d6e5f15ee1a7Timo Sirainen uidlist
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen -------
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen This file contains UID <-> filename mappings. It's updated only when
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen new mail arrives, so it may contain filenames that have already been
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen deleted. Updating is done by getting uidlist.lock file, writing the
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen whole uidlist into it and rename()ing it over the old uidlist. This
d5ac54ef50db16b50689b5c8b7bb64d344190832Timo Sirainen means there's no need to lock the file for reading.
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen Whenever uidlist is rewritten, it's mtime must be larger than the old
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen one's. Use utime() before rename() if needed. Note that inode checking
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen wouldn't have been sufficient as inode numbers can be reused.
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen This file is usually read the first time you need to know filename for
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen given UID. After that it's not re-read unless new mails come that we
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen don't know about.
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen broken clients
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen --------------
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen Originally the middle identifier in Maildir filename was specified
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen only as <process id>_<delivery counter>. That however created a
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen problem with randomized PIDs which made it possible that the same
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen PID was reused within one second.
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen So if within one second a mail was delivered, MUA moved it to cur/
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen and another mail was delivered by a new process using same PID as
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen the first one, we likely ended up overwriting the first mail when
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen the second mail was moved over it.
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen Nowadays everyone should be giving a bit more specific identifier,
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen for example include microseconds in it which Dovecot does.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen There's a simple way to prevent this from happening in some cases:
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen Don't move the mail from new/ to cur/ if it's mtime is >= time() -
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen MAILDIR_SYNC_SECS. The second delivery's link() call then fails
325d4ad220bd13f6d176391d962a0e33c856a7f6Timo Sirainen because the file is already in new/, and it will then use a
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen different filename. There's a few problems with this however:
b20fb5b1df9d604a7541f5118fc5b4b466d211efTimo Sirainen
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen - it requires extra stat() call which is unneeded extra I/O
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen - another MUA might still move the mail to cur/
df4018ae2f0a95be602f724ca70df7e0e3bd6a7dTimo Sirainen - if first file's flags are modified by either Dovecot or another
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen MUA, it's moved to cur/ (you _could_ just do the dirty-flagging
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen but that'd be ugly)
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen Because this is useful only for very few people and it requires
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen extra I/O, I decided not to implement this. It should be however
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen quite easy to do since we need to be able to deal with files in new/
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen in any case.
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen It's also possible to never accidentally overwrite a mail by using
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen link() + unlink() rather than rename(). This however isn't very
7c95b03620a03a43dd72d39608cea5fc77393ad6Timo Sirainen good idea as it introduces potential race conditions when multiple
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen clients are accessing the mailbox:
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen Trying to move the same mail from new/ to cur/ at the same time:
a40d26f83af808a0ea1e212c001d682a96d870b0Timo Sirainen
811f2e26d9782d9cb99fdf82e18ffa0a77564fe2Timo Sirainen a) Client 1 uses slightly different filename than client 2,
811f2e26d9782d9cb99fdf82e18ffa0a77564fe2Timo Sirainen for example one sets read-flag on but the other doesn't.
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen You have the same mail duplicated now.
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
4bbee99b3aef449a9a2a11a5b5cf1ca486915c49Timo Sirainen b) Client 3 sees the mail between Client 1's and 2's link() calls
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen and changes it's flag. You have the same mail duplicated now.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen And it gets worse when they're unlink()ing in cur/ directory:
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen c) Client 1 changes mails's flag and client 2 changes it back
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen between 1's link() and unlink(). The mail is now expunged.
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen d) If you try to deal with the duplicates by unlink()ing another
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen one of them, you might end up unlinking both of them.
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen So, what should we do then if we notice a duplicate? First of all,
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen it might not be a duplicate at all, readdir() might have just
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen returned it twice because it was just renamed. What we should do is
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen create a completely new base name for it and rename() it to that.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen If the call fails with ENOENT, it only means that it wasn't a
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen duplicate after all.
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen*/
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen#include "lib.h"
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen#include "ioloop.h"
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen#include "buffer.h"
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#include "hash.h"
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen#include "str.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "maildir-storage.h"
aff7542e1d2f48b030560a4f01096a2cc3f671ceTimo Sirainen#include "maildir-uidlist.h"
aff7542e1d2f48b030560a4f01096a2cc3f671ceTimo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainen#include <stdio.h>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include <stddef.h>
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen#include <unistd.h>
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen#include <dirent.h>
aff7542e1d2f48b030560a4f01096a2cc3f671ceTimo Sirainen#include <sys/stat.h>
aff7542e1d2f48b030560a4f01096a2cc3f671ceTimo Sirainen
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen#define MAILDIR_SYNC_SECS 1
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen#define MAILDIR_FILENAME_FLAG_FOUND 128
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainenstruct maildir_sync_context {
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct index_mailbox *ibox;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen const char *new_dir, *cur_dir;
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen int partial;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen};
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainenstruct maildir_index_sync_context {
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct index_mailbox *ibox;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct mail_index_view *view;
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen struct mail_index_sync_ctx *sync_ctx;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct mail_index_transaction *trans;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen struct mail_index_sync_rec sync_rec;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen uint32_t seq;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen int dirty_state;
4edf90751f075cc6ab3d6f53fc78b656efa80922Timo Sirainen};
a40d26f83af808a0ea1e212c001d682a96d870b0Timo Sirainen
992a13add4eea0810e4db0f042a595dddf85536aTimo Sirainenstatic int maildir_expunge(struct index_mailbox *ibox, const char *path,
7416b94f38e82381abd9cee660efdcf3e7b773afTimo Sirainen void *context __attr_unused__)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen if (unlink(path) == 0) {
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen ibox->dirty_cur_time = ioloop_time;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen return 1;
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen }
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (errno == ENOENT)
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen return 0;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen mail_storage_set_critical(ibox->box.storage,
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen "unlink(%s) failed: %m", path);
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen return -1;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen}
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainenstatic int maildir_sync_flags(struct index_mailbox *ibox, const char *path,
aff7542e1d2f48b030560a4f01096a2cc3f671ceTimo Sirainen void *context)
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen{
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen struct maildir_index_sync_context *ctx = context;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen const char *newpath;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen enum mail_flags flags;
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen uint8_t flags8;
fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8Timo Sirainen keywords_mask_t keywords;
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen ctx->dirty_state = 0;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen (void)maildir_filename_get_flags(path, &flags, keywords);
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen flags8 = flags;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen mail_index_sync_flags_apply(&ctx->sync_rec, &flags8, keywords);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen newpath = maildir_filename_set_flags(path, flags8, keywords);
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (rename(path, newpath) == 0) {
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if ((flags8 & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen ctx->dirty_state = -1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen ibox->dirty_cur_time = ioloop_time;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return 1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen }
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (errno == ENOENT)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return 0;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (ENOSPACE(errno) || errno == EACCES) {
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen memset(keywords, 0, sizeof(keywords));
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_ADD,
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen MAIL_INDEX_MAIL_FLAG_DIRTY, keywords);
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen ctx->dirty_state = 1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return 1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen }
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen mail_storage_set_critical(ibox->box.storage,
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen "rename(%s, %s) failed: %m", path, newpath);
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return -1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen}
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainenstatic int maildir_sync_record(struct index_mailbox *ibox,
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen struct maildir_index_sync_context *ctx)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen{
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen struct mail_index_sync_rec *sync_rec = &ctx->sync_rec;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen struct mail_index_view *view = ctx->view;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen uint32_t seq, seq1, seq2, uid;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen switch (sync_rec->type) {
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen case MAIL_INDEX_SYNC_TYPE_APPEND:
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen break;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen /* make it go through sequences to avoid looping through huge
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen holes in UID range */
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (mail_index_lookup_uid_range(view, sync_rec->uid1,
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen sync_rec->uid2,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen &seq1, &seq2) < 0)
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen return -1;
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (seq1 == 0)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen break;
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen for (seq = seq1; seq <= seq2; seq++) {
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen if (mail_index_lookup_uid(view, seq, &uid) < 0)
e015e2f7e7f48874495f9df8b0dd192b7ffcb5ccTimo Sirainen return -1;
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen if (maildir_file_do(ibox, uid, maildir_expunge,
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen NULL) < 0)
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen return -1;
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen }
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen break;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen case MAIL_INDEX_SYNC_TYPE_FLAGS:
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (mail_index_lookup_uid_range(view, sync_rec->uid1,
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen sync_rec->uid2,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen &seq1, &seq2) < 0)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return -1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (seq1 == 0)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen break;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen for (ctx->seq = seq1; ctx->seq <= seq2; ctx->seq++) {
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (mail_index_lookup_uid(view, ctx->seq, &uid) < 0)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return -1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (maildir_file_do(ibox, uid,
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen maildir_sync_flags, ctx) < 0)
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen return -1;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen if (ctx->dirty_state < 0) {
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen /* flag isn't dirty anymore */
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen keywords_mask_t keywords;
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen memset(keywords, 0, sizeof(keywords));
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen mail_index_update_flags(ctx->trans, ctx->seq,
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen MODIFY_REMOVE,
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen MAIL_INDEX_MAIL_FLAG_DIRTY, keywords);
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen }
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen }
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen break;
d22390f33eedbd2413debabc0662dde5241b1aa6Timo Sirainen }
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen return 0;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen}
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenint maildir_sync_last_commit(struct index_mailbox *ibox)
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen{
13c6532dc104d23061e6901783ceb1ff8872c206Timo Sirainen struct maildir_index_sync_context ctx;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen uint32_t seq;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen uoff_t offset;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen int ret;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (ibox->commit_log_file_seq == 0)
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen return 0;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen memset(&ctx, 0, sizeof(ctx));
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ctx.ibox = ibox;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ibox->syncing_commit = TRUE;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ret = mail_index_sync_begin(ibox->index, &ctx.sync_ctx, &ctx.view,
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ibox->commit_log_file_seq,
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ibox->commit_log_file_offset, FALSE);
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (ret > 0) {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ctx.trans = mail_index_transaction_begin(ctx.view, FALSE);
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainen while ((ret = mail_index_sync_next(ctx.sync_ctx,
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen &ctx.sync_rec)) > 0) {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (maildir_sync_record(ibox, &ctx) < 0) {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ret = -1;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen break;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen }
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen }
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen if (mail_index_transaction_commit(ctx.trans, &seq, &offset) < 0)
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen ret = -1;
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen if (mail_index_sync_commit(ctx.sync_ctx) < 0)
4b231ca0bbe3b536acbd350101e183441ce0247aTimo Sirainen ret = -1;
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen }
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen ibox->syncing_commit = FALSE;
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen if (ret == 0) {
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen ibox->commit_log_file_seq = 0;
365435901c22df7e5838f574c950b0e32e77f78aTimo Sirainen ibox->commit_log_file_offset = 0;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen } else {
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen mail_storage_set_index_error(ibox);
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen }
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen return ret;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen}
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainenstatic struct maildir_sync_context *
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainenmaildir_sync_context_new(struct index_mailbox *ibox)
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen{
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen struct maildir_sync_context *ctx;
a817fdcc43aedf423e2134091d5f83f91d64bcc9Timo Sirainen
a817fdcc43aedf423e2134091d5f83f91d64bcc9Timo Sirainen ctx = t_new(struct maildir_sync_context, 1);
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ctx->ibox = ibox;
6fdf8b5e4e71a69f5974f59eec2b8c19bc421fe2Timo Sirainen ctx->new_dir = t_strconcat(ibox->path, "/new", NULL);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx->cur_dir = t_strconcat(ibox->path, "/cur", NULL);
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen return ctx;
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen}
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen
58be9d6bcc3800f5b3d76a064ee767fbe31a5a8aTimo Sirainenstatic void maildir_sync_deinit(struct maildir_sync_context *ctx)
8aacc9e7c84f8376822823ec98c2f551d4919b2eTimo Sirainen{
if (ctx->uidlist_sync_ctx != NULL)
(void)maildir_uidlist_sync_deinit(ctx->uidlist_sync_ctx);
}
static int maildir_fix_duplicate(struct index_mailbox *ibox, const char *dir,
const char *old_fname)
{
const char *new_fname, *old_path, *new_path;
int ret = 0;
t_push();
old_path = t_strconcat(dir, "/", old_fname, NULL);
new_fname = maildir_generate_tmp_filename(&ioloop_timeval);
new_path = t_strconcat(ibox->path, "/new/", new_fname, NULL);
if (rename(old_path, new_path) == 0) {
i_warning("Fixed duplicate in %s: %s -> %s",
ibox->path, old_fname, new_fname);
} else if (errno != ENOENT) {
mail_storage_set_critical(ibox->box.storage,
"rename(%s, %s) failed: %m", old_path, new_path);
ret = -1;
}
t_pop();
return ret;
}
static int maildir_scan_dir(struct maildir_sync_context *ctx, int new_dir)
{
struct mail_storage *storage = ctx->ibox->box.storage;
const char *dir;
DIR *dirp;
string_t *src, *dest;
struct dirent *dp;
enum maildir_uidlist_rec_flag flags;
int move_new, ret = 1;
dir = new_dir ? ctx->new_dir : ctx->cur_dir;
dirp = opendir(dir);
if (dirp == NULL) {
mail_storage_set_critical(storage,
"opendir(%s) failed: %m", dir);
return -1;
}
t_push();
src = t_str_new(1024);
dest = t_str_new(1024);
move_new = new_dir && !mailbox_is_readonly(&ctx->ibox->box) &&
!ctx->ibox->keep_recent;
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.')
continue;
ret = maildir_uidlist_sync_next_pre(ctx->uidlist_sync_ctx,
dp->d_name);
if (ret == 0) {
/* new file and we couldn't lock uidlist, check this
later in next sync. */
if (new_dir)
ctx->ibox->last_new_mtime = 0;
else
ctx->ibox->dirty_cur_time = ioloop_time;
continue;
}
if (ret < 0)
break;
flags = 0;
if (move_new) {
str_truncate(src, 0);
str_truncate(dest, 0);
str_printfa(src, "%s/%s", ctx->new_dir, dp->d_name);
str_printfa(dest, "%s/%s", ctx->cur_dir, dp->d_name);
if (strchr(dp->d_name, ':') == NULL)
str_append(dest, ":2,");
if (rename(str_c(src), str_c(dest)) == 0) {
/* we moved it - it's \Recent for us */
ctx->ibox->dirty_cur_time = ioloop_time;
flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
} else if (ENOTFOUND(errno)) {
/* someone else moved it already */
flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED;
} else if (ENOSPACE(errno)) {
/* not enough disk space, leave here */
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
move_new = FALSE;
} else {
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
mail_storage_set_critical(storage,
"rename(%s, %s) failed: %m",
str_c(src), str_c(dest));
}
} else if (new_dir) {
flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT;
}
ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
dp->d_name, flags);
if (ret <= 0) {
if (ret < 0)
break;
/* possibly duplicate - try fixing it */
if (maildir_fix_duplicate(ctx->ibox,
dir, dp->d_name) < 0) {
ret = -1;
break;
}
}
}
if (closedir(dirp) < 0) {
mail_storage_set_critical(storage,
"closedir(%s) failed: %m", dir);
}
t_pop();
return ret < 0 ? -1 : 0;
}
static int maildir_sync_quick_check(struct maildir_sync_context *ctx,
int *new_changed_r, int *cur_changed_r)
{
struct index_mailbox *ibox = ctx->ibox;
struct stat st;
time_t new_mtime, cur_mtime;
*new_changed_r = *cur_changed_r = FALSE;
if (stat(ctx->new_dir, &st) < 0) {
mail_storage_set_critical(ibox->box.storage,
"stat(%s) failed: %m", ctx->new_dir);
return -1;
}
new_mtime = st.st_mtime;
if (stat(ctx->cur_dir, &st) < 0) {
mail_storage_set_critical(ibox->box.storage,
"stat(%s) failed: %m", ctx->cur_dir);
return -1;
}
cur_mtime = st.st_mtime;
if (ibox->dirty_cur_time == 0) {
/* cur stamp is kept in index, we don't have to sync if
someone else has done it and updated the index. make sure
we have a fresh index with latest sync_stamp. */
struct mail_index_view *view;
const struct mail_index_header *hdr;
if (mail_index_refresh(ibox->index) < 0) {
mail_storage_set_index_error(ibox);
return -1;
}
view = mail_index_view_open(ibox->index);
if (mail_index_get_header(view, &hdr) < 0) {
mail_index_view_close(view);
mail_storage_set_index_error(ibox);
return -1;
}
ibox->last_cur_mtime = hdr->sync_stamp;
mail_index_view_close(view);
}
if (new_mtime != ibox->last_new_mtime ||
new_mtime >= ibox->last_new_sync_time - MAILDIR_SYNC_SECS) {
*new_changed_r = TRUE;
ibox->last_new_mtime = new_mtime;
ibox->last_new_sync_time = ioloop_time;
}
if (cur_mtime != ibox->last_cur_mtime ||
(ibox->dirty_cur_time != 0 &&
ioloop_time - ibox->dirty_cur_time > MAILDIR_SYNC_SECS)) {
/* cur/ changed, or delayed cur/ check */
*cur_changed_r = TRUE;
ibox->last_cur_mtime = cur_mtime;
ibox->dirty_cur_time =
cur_mtime >= ioloop_time - MAILDIR_SYNC_SECS ?
cur_mtime : 0;
}
return 0;
}
int maildir_sync_index(struct index_mailbox *ibox, int partial)
{
struct maildir_index_sync_context sync_ctx;
struct maildir_uidlist_iter_ctx *iter;
struct mail_index_transaction *trans;
struct mail_index_view *view;
const struct mail_index_header *hdr;
const struct mail_index_record *rec;
uint32_t seq, uid;
enum maildir_uidlist_rec_flag uflags;
const char *filename;
enum mail_flags flags;
keywords_mask_t keywords;
uint32_t uid_validity, next_uid;
int ret;
memset(&sync_ctx, 0, sizeof(sync_ctx));
sync_ctx.ibox = ibox;
if (mail_index_sync_begin(ibox->index, &sync_ctx.sync_ctx, &view,
(uint32_t)-1, (uoff_t)-1, FALSE) <= 0) {
mail_storage_set_index_error(ibox);
return -1;
}
sync_ctx.view = view;
ret = mail_index_get_header(view, &hdr);
i_assert(ret == 0); /* view is locked, can't happen */
uid_validity = maildir_uidlist_get_uid_validity(ibox->uidlist);
if (uid_validity != hdr->uid_validity &&
uid_validity != 0 && hdr->uid_validity != 0) {
/* uidvalidity changed and mailbox isn't being initialized,
index must be rebuilt */
mail_storage_set_critical(ibox->box.storage,
"Maildir %s sync: UIDVALIDITY changed (%u -> %u)",
ibox->path, hdr->uid_validity, uid_validity);
mail_index_mark_corrupted(ibox->index);
(void)mail_index_sync_rollback(sync_ctx.sync_ctx);
return -1;
}
trans = mail_index_transaction_begin(view, FALSE);
sync_ctx.trans = trans;
seq = 0;
iter = maildir_uidlist_iter_init(ibox->uidlist);
while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
maildir_filename_get_flags(filename, &flags, keywords);
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_MOVED) == 0) {
/* mail is recent for next session as well */
flags |= MAIL_RECENT;
}
__again:
seq++;
if (seq > hdr->messages_count) {
if (uid < hdr->next_uid) {
/* most likely a race condition: we read the
maildir, then someone else expunged messages
and committed changes to index. so, this
message shouldn't actually exist. mark it
racy and check in next sync.
the difference between this and the later
check is that this one happens when messages
are expunged from the end */
if ((uflags &
MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
continue;
}
if ((uflags &
MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
mail_storage_set_critical(
ibox->box.storage,
"Maildir %s sync: "
"UID < next_uid "
"(%u < %u, file = %s)",
ibox->path, uid, hdr->next_uid,
filename);
mail_index_mark_corrupted(ibox->index);
ret = -1;
break;
}
ibox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(ibox->uidlist,
filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
mail_index_append(trans, uid, &seq);
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
flags, keywords);
continue;
}
if (mail_index_lookup(view, seq, &rec) < 0) {
ret = -1;
break;
}
if (rec->uid < uid) {
/* expunged */
mail_index_expunge(trans, seq);
goto __again;
}
if (rec->uid > uid) {
/* most likely a race condition: we read the
maildir, then someone else expunged messages and
committed changes to index. so, this message
shouldn't actually exist. mark it racy and check
in next sync. */
if ((uflags &
MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
seq--;
continue;
}
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) != 0) {
mail_storage_set_critical(ibox->box.storage,
"Maildir %s sync: "
"UID inserted in the middle of mailbox "
"(%u > %u, file = %s)",
ibox->path, rec->uid, uid, filename);
mail_index_mark_corrupted(ibox->index);
ret = -1;
break;
}
ibox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(ibox->uidlist, filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
if ((rec->flags & MAIL_RECENT) != 0) {
index_mailbox_set_recent(ibox, seq);
if (ibox->keep_recent) {
flags |= MAIL_RECENT;
} else {
mail_index_update_flags(trans, seq,
MODIFY_REMOVE,
MAIL_RECENT, keywords);
}
}
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
continue;
}
if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
/* we haven't been able to update maildir with this
record's flag changes. don't sync them. */
continue;
}
if (((uint8_t)flags & ~MAIL_RECENT) !=
(rec->flags & (MAIL_FLAGS_MASK^MAIL_RECENT)) ||
memcmp(keywords, rec->keywords,
INDEX_KEYWORDS_BYTE_COUNT) != 0) {
/* FIXME: this is wrong if there's pending changes in
transaction log already. it gets fixed in next sync
however.. */
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
flags, keywords);
} else if ((flags & MAIL_RECENT) == 0 &&
(rec->flags & MAIL_RECENT) != 0) {
/* just remove recent flag */
memset(keywords, 0, sizeof(keywords));
mail_index_update_flags(trans, seq, MODIFY_REMOVE,
MAIL_RECENT, keywords);
}
}
maildir_uidlist_iter_deinit(iter);
if (!partial) {
/* expunge the rest */
for (seq++; seq <= hdr->messages_count; seq++)
mail_index_expunge(trans, seq);
}
/* now, sync the index */
ibox->syncing_commit = TRUE;
while ((ret = mail_index_sync_next(sync_ctx.sync_ctx,
&sync_ctx.sync_rec)) > 0) {
if (maildir_sync_record(ibox, &sync_ctx) < 0) {
ret = -1;
break;
}
}
ibox->syncing_commit = FALSE;
if (ibox->dirty_cur_time == 0 &&
ibox->last_cur_mtime != (time_t)hdr->sync_stamp) {
uint32_t sync_stamp = ibox->last_cur_mtime;
mail_index_update_header(trans,
offsetof(struct mail_index_header, sync_stamp),
&sync_stamp, sizeof(sync_stamp));
}
if (hdr->uid_validity == 0) {
/* get the initial uidvalidity */
if (maildir_uidlist_update(ibox->uidlist) < 0)
ret = -1;
uid_validity = maildir_uidlist_get_uid_validity(ibox->uidlist);
if (uid_validity == 0) {
uid_validity = ioloop_time;
maildir_uidlist_set_uid_validity(ibox->uidlist,
uid_validity);
}
} else if (uid_validity == 0) {
maildir_uidlist_set_uid_validity(ibox->uidlist,
hdr->uid_validity);
}
if (uid_validity != hdr->uid_validity && uid_validity != 0) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, uid_validity),
&uid_validity, sizeof(uid_validity));
}
next_uid = maildir_uidlist_get_next_uid(ibox->uidlist);
if (next_uid != 0 && hdr->next_uid != next_uid) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, next_uid),
&next_uid, sizeof(next_uid));
}
if (ret < 0) {
mail_index_transaction_rollback(trans);
mail_index_sync_rollback(sync_ctx.sync_ctx);
} else {
uint32_t seq;
uoff_t offset;
if (mail_index_transaction_commit(trans, &seq, &offset) < 0)
ret = -1;
else if (seq != 0) {
ibox->commit_log_file_seq = seq;
ibox->commit_log_file_offset = offset;
}
if (mail_index_sync_commit(sync_ctx.sync_ctx) < 0)
ret = -1;
}
if (ret == 0) {
ibox->commit_log_file_seq = 0;
ibox->commit_log_file_offset = 0;
} else {
mail_storage_set_index_error(ibox);
}
return ret;
}
static int maildir_sync_context(struct maildir_sync_context *ctx, int forced)
{
int ret, new_changed, cur_changed;
if (!forced) {
if (maildir_sync_quick_check(ctx, &new_changed, &cur_changed) < 0)
return -1;
if (!new_changed && !cur_changed)
return 0;
} else {
new_changed = cur_changed = TRUE;
}
/* we have to lock uidlist immediately, otherwise there's race
conditions with other processes who might write older maildir
file list into uidlist.
alternative would be to lock it when new files are found, but
the directory scans _must_ be restarted then.
if we got here through maildir_sync_last_commit(), we can't sync
index as it's already being synced. so, don't try locking uidlist
either, we only want to find new filename for some mail.
*/
if (!ctx->ibox->syncing_commit) {
if ((ret = maildir_uidlist_try_lock(ctx->ibox->uidlist)) < 0)
return ret;
if (ret == 0 && !forced) {
/* we didn't get a lock, don't do syncing unless we
really want to check for expunges or renames. new
files won't be added. */
return 0;
}
}
ctx->partial = !cur_changed;
ctx->uidlist_sync_ctx =
maildir_uidlist_sync_init(ctx->ibox->uidlist, ctx->partial);
if (maildir_scan_dir(ctx, TRUE) < 0)
return -1;
if (cur_changed) {
if (maildir_scan_dir(ctx, FALSE) < 0)
return -1;
}
/* finish uidlist syncing, but keep it still locked */
maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
if (!ctx->ibox->syncing_commit) {
if (maildir_sync_index(ctx->ibox, ctx->partial) < 0)
return -1;
}
ret = maildir_uidlist_sync_deinit(ctx->uidlist_sync_ctx);
ctx->uidlist_sync_ctx = NULL;
return ret;
}
int maildir_storage_sync_force(struct index_mailbox *ibox)
{
struct maildir_sync_context *ctx;
int ret;
ctx = maildir_sync_context_new(ibox);
ret = maildir_sync_context(ctx, TRUE);
maildir_sync_deinit(ctx);
return ret;
}
struct mailbox_sync_context *
maildir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
{
struct index_mailbox *ibox = (struct index_mailbox *)box;
struct maildir_sync_context *ctx;
int ret = 0;
if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
ibox->sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <= ioloop_time) {
ibox->sync_last_check = ioloop_time;
ctx = maildir_sync_context_new(ibox);
ret = maildir_sync_context(ctx, FALSE);
maildir_sync_deinit(ctx);
}
return index_mailbox_sync_init(box, flags, ret < 0);
}