maildir-sync.c revision 6f66e585998aa88a4b0ccad531d329a103325d57
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (C) 2004 Timo Sirainen */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen/*
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen Here's a description of how we handle Maildir synchronization and
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen it's problems:
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen
0536ccb51d41e3078c3a9fa33e509fb4b2420f95Timo Sirainen We want to be as efficient as we can. The most efficient way to
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen check if changes have occurred is to stat() the new/ and cur/
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen directories and uidlist file - if their mtimes haven't changed,
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen there's no changes and we don't need to do anything.
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen Problem 1: Multiple changes can happen within a single second -
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen nothing guarantees that once we synced it, someone else didn't just
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen then make a modification. Such modifications wouldn't get noticed
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen until a new modification occurred later.
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen Problem 2: Syncing cur/ directory is much more costly than syncing
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen new/. Moving mails from new/ to cur/ will always change mtime of
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen cur/ causing us to sync it as well.
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen Problem 3: We may not be able to move mail from new/ to cur/
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen because we're out of quota, or simply because we're accessing a
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen read-only mailbox.
8cb72c59d5ea4e9e5f638d7ec840bb853f5a188eTimo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen MAILDIR_SYNC_SECS
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen -----------------
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen Several checks below use MAILDIR_SYNC_SECS, which should be maximum
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen clock drift between all computers accessing the maildir (eg. via
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen NFS), rounded up to next second. Our default is 1 second, since
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen everyone should be using NTP.
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen Note that setting it to 0 works only if there's only one computer
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen accessing the maildir. It's practically impossible to make two
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen clocks _exactly_ synchronized.
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen It might be possible to only use file server's clock by looking at
cd56a23e21f1df3f79648cf07e2f4385e2fadebbTimo Sirainen the atime field, but I don't know how well that would actually work.
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen cur directory
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen -------------
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen We have dirty_cur_time variable which is set to cur/ directory's
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen mtime when it's >= time() - MAILDIR_SYNC_SECS and we _think_ we have
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen synchronized the directory.
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen When dirty_cur_time is non-zero, we don't synchronize the cur/
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen directory until
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen a) cur/'s mtime changes
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen b) opening a mail fails with ENOENT
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen c) time() > dirty_cur_time + MAILDIR_SYNC_SECS
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen This allows us to modify the maildir multiple times without having
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen to sync it at every change. The sync will eventually be done to
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen make sure we didn't miss any external changes.
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen The dirty_cur_time is set when:
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen - we change message flags
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen - we expunge messages
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen - we move mail from new/ to cur/
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen - we sync cur/ directory and it's mtime is >= time() - MAILDIR_SYNC_SECS
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen It's unset when we do the final syncing, ie. when mtime is
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen older than time() - MAILDIR_SYNC_SECS.
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen new directory
5ac0b0bf32898c63da086ae169674ecac151a31eTimo Sirainen -------------
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen If new/'s mtime is >= time() - MAILDIR_SYNC_SECS, always synchronize
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen it. dirty_cur_time-like feature might save us a few syncs, but
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen that might break a client which saves a mail in one connection and
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen tries to fetch it in another one. new/ directory is almost always
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen empty, so syncing it should be very fast anyway. Actually this can
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen still happen if we sync only new/ dir while another client is also
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen moving mails from it to cur/ - it takes us a while to see them.
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen That's pretty unlikely to happen however, and only way to fix it
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen would be to always synchronize cur/ after new/.
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen Normally we move all mails from new/ to cur/ whenever we sync it. If
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen it's not possible for some reason, we mark the mail with "probably
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen exists in new/ directory" flag.
1a0ece3e873e3864269ed7eaed957dc10c56d25fTimo Sirainen
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen If rename() still fails because of ENOSPC or EDQUOT, we still save
c28f6aa0b70af4811c9ace9114fe827c2f503455Timo Sirainen the flag changes in index with dirty-flag on. When moving the mail
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen to cur/ directory, or when we notice it's already moved there, we
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen apply the flag changes to the filename, rename it and remove the
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen dirty flag. If there's dirty flags, this should be tried every time
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen after expunge or when closing the mailbox.
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen uidlist
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen -------
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen This file contains UID <-> filename mappings. It's updated only when
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen new mail arrives, so it may contain filenames that have already been
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen deleted. Updating is done by getting uidlist.lock file, writing the
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen whole uidlist into it and rename()ing it over the old uidlist. This
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen means there's no need to lock the file for reading.
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen Whenever uidlist is rewritten, it's mtime must be larger than the old
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen one's. Use utime() before rename() if needed. Note that inode checking
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen wouldn't have been sufficient as inode numbers can be reused.
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen This file is usually read the first time you need to know filename for
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen given UID. After that it's not re-read unless new mails come that we
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen don't know about.
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen broken clients
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen --------------
862ec874f9373e3e499e237d3b9f71fdf1413feeTimo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen Originally the middle identifier in Maildir filename was specified
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen only as <process id>_<delivery counter>. That however created a
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen problem with randomized PIDs which made it possible that the same
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen PID was reused within one second.
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen So if within one second a mail was delivered, MUA moved it to cur/
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen and another mail was delivered by a new process using same PID as
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen the first one, we likely ended up overwriting the first mail when
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen the second mail was moved over it.
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen Nowadays everyone should be giving a bit more specific identifier,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen for example include microseconds in it which Dovecot does.
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen There's a simple way to prevent this from happening in some cases:
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen Don't move the mail from new/ to cur/ if it's mtime is >= time() -
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen MAILDIR_SYNC_SECS. The second delivery's link() call then fails
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen because the file is already in new/, and it will then use a
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen different filename. There's a few problems with this however:
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen
0a49b316fc729e5d57268ffa63c7122ac73f994cTimo Sirainen - it requires extra stat() call which is unneeded extra I/O
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen - another MUA might still move the mail to cur/
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen - if first file's flags are modified by either Dovecot or another
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen MUA, it's moved to cur/ (you _could_ just do the dirty-flagging
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen but that'd be ugly)
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen Because this is useful only for very few people and it requires
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen extra I/O, I decided not to implement this. It should be however
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen quite easy to do since we need to be able to deal with files in new/
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen in any case.
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen It's also possible to never accidentally overwrite a mail by using
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen link() + unlink() rather than rename(). This however isn't very
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen good idea as it introduces potential race conditions when multiple
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen clients are accessing the mailbox:
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen Trying to move the same mail from new/ to cur/ at the same time:
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen a) Client 1 uses slightly different filename than client 2,
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen for example one sets read-flag on but the other doesn't.
0892446b45c195461bb7be6599f02d97e1e2c9b2Timo Sirainen You have the same mail duplicated now.
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen b) Client 3 sees the mail between Client 1's and 2's link() calls
a2738cdb6d2733fb3e186331d68009421a19ea00Timo Sirainen and changes it's flag. You have the same mail duplicated now.
d66ef20c30fee728899ee168c75fcc5ff8fbdac1Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen And it gets worse when they're unlink()ing in cur/ directory:
d66ef20c30fee728899ee168c75fcc5ff8fbdac1Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen c) Client 1 changes mails's flag and client 2 changes it back
9e095dd6a77097356aca8216356d4d71ef1bea45Timo Sirainen between 1's link() and unlink(). The mail is now expunged.
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen d) If you try to deal with the duplicates by unlink()ing another
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen one of them, you might end up unlinking both of them.
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
0892446b45c195461bb7be6599f02d97e1e2c9b2Timo Sirainen So, what should we do then if we notice a duplicate? First of all,
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen it might not be a duplicate at all, readdir() might have just
7af4788b402346c94496095dd819f95ce03fe431Timo Sirainen returned it twice because it was just renamed. What we should do is
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen create a completely new base name for it and rename() it to that.
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen If the call fails with ENOENT, it only means that it wasn't a
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen duplicate after all.
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen*/
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "lib.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "ioloop.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "array.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "buffer.h"
d66ef20c30fee728899ee168c75fcc5ff8fbdac1Timo Sirainen#include "hash.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "str.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "maildir-storage.h"
c24ef531ca58abad996482f5c2e8992be9ae8981Timo Sirainen#include "maildir-uidlist.h"
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen#include "maildir-keywords.h"
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen#include "maildir-sync.h"
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen#include <stdio.h>
0a0cd45a633112a2ae6aad801c1e6afe53ab95deTimo Sirainen#include <stddef.h>
e4c81823af1fc43ca3f2ce9eb4af7fc8f57b13a5Timo Sirainen#include <unistd.h>
e4c81823af1fc43ca3f2ce9eb4af7fc8f57b13a5Timo Sirainen#include <dirent.h>
2524ef7b34965a1b1895d6140fd8296bf57c78d2Timo Sirainen#include <sys/stat.h>
0892446b45c195461bb7be6599f02d97e1e2c9b2Timo Sirainen
e4c81823af1fc43ca3f2ce9eb4af7fc8f57b13a5Timo Sirainen#define MAILDIR_FILENAME_FLAG_FOUND 128
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen/* When rename()ing many files from new/ to cur/, it's possible that next
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen readdir() skips some files. we don't of course wish to lose them, so we
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen go and rescan the new/ directory again from beginning until no files are
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen left. This value is just an optimization to avoid checking the directory
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen twice unneededly. usually only NFS is the problem case. 1 is the safest
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen bet here, but I guess 5 will do just fine too. */
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen#define MAILDIR_RENAME_RESCAN_COUNT 5
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenstruct maildir_sync_context {
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct maildir_mailbox *mbox;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen const char *new_dir, *cur_dir;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen bool partial;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct maildir_index_sync_context *index_sync_ctx;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen};
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenstruct maildir_index_sync_context {
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct maildir_mailbox *mbox;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct mail_index_view *view;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct mail_index_sync_ctx *sync_ctx;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen struct mail_index_transaction *trans;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen ARRAY_DEFINE(sync_recs, struct mail_index_sync_rec);
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen uint32_t seq;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen int dirty_state;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen};
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenstruct maildir_keywords_sync_ctx *
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenmaildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen{
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen return ctx->keywords_sync_ctx;
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen}
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainenint maildir_filename_get_flags(struct maildir_keywords_sync_ctx *ctx,
61e6367a259e2473f33df42fda8ceeb3b8b48416Timo Sirainen const char *fname, enum mail_flags *flags_r,
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen ARRAY_TYPE(keyword_indexes) *keywords_r)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen const char *info;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen array_clear(keywords_r);
20344c0e814139e3c365fbb9287478f91512089eTimo Sirainen *flags_r = 0;
20344c0e814139e3c365fbb9287478f91512089eTimo Sirainen
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen info = strchr(fname, MAILDIR_INFO_SEP);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (info == NULL || info[1] != '2' || info[2] != MAILDIR_FLAGS_SEP)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return 0;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen for (info += 3; *info != '\0' && *info != MAILDIR_FLAGS_SEP; info++) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen switch (*info) {
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen case 'R': /* replied */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen *flags_r |= MAIL_ANSWERED;
20344c0e814139e3c365fbb9287478f91512089eTimo Sirainen break;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen case 'S': /* seen */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen *flags_r |= MAIL_SEEN;
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen break;
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen case 'T': /* trashed */
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen *flags_r |= MAIL_DELETED;
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen break;
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen case 'D': /* draft */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen *flags_r |= MAIL_DRAFT;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen break;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen case 'F': /* flagged */
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen *flags_r |= MAIL_FLAGGED;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen break;
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen default:
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen if (*info >= MAILDIR_KEYWORD_FIRST &&
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen *info <= MAILDIR_KEYWORD_LAST) {
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen int idx;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen idx = maildir_keywords_char_idx(ctx, *info);
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen if (idx < 0) {
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen /* unknown keyword. */
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen break;
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen }
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen array_append(keywords_r,
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen (unsigned int *)&idx, 1);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen break;
e3fc1874694a8ddba9552ec23f9952f74f33d1d5Timo Sirainen }
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
601f5f14c6cde28f0e0c6ca7c5d735315d3d48dfTimo Sirainen /* unknown flag - ignore */
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen break;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen }
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen return 1;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen}
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainenstatic void
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainenmaildir_filename_append_keywords(struct maildir_keywords_sync_ctx *ctx,
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen ARRAY_TYPE(keyword_indexes) *keywords,
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen string_t *str)
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen{
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen const unsigned int *indexes;
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen unsigned int i, count;
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen char chr;
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen indexes = array_get(keywords, &count);
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen for (i = 0; i < count; i++) {
602a0434db30d8e3292d1c161a803d96a879a74fTimo Sirainen chr = maildir_keywords_idx_char(ctx, indexes[i]);
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen if (chr != '\0')
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen str_append_c(str, chr);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen}
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainenconst char *maildir_filename_set_flags(struct maildir_keywords_sync_ctx *ctx,
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen const char *fname, enum mail_flags flags,
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen ARRAY_TYPE(keyword_indexes) *keywords)
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen{
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen string_t *flags_str;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen enum mail_flags flags_left;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen const char *info, *oldflags;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen int nextflag;
4334b9b032298defd4d3906f5357698ff016ead0Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen /* remove the old :info from file name, and get the old flags */
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen info = strrchr(fname, MAILDIR_INFO_SEP);
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen if (info != NULL && strrchr(fname, '/') > info)
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen info = NULL;
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen oldflags = "";
c9bf63e9094761767a63ac6b189bcf60bcffdc44Timo Sirainen if (info != NULL) {
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen fname = t_strdup_until(fname, info);
548e394330621952db0f03dd667b70184c4a37b6Timo Sirainen if (info[1] == '2' && info[2] == MAILDIR_FLAGS_SEP)
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen oldflags = info+3;
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen }
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen /* insert the new flags between old flags. flags must be sorted by
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen their ASCII code. unknown flags are kept. */
dd62b77c932d1b518f2a3e4bf80e36542becc256Timo Sirainen flags_str = t_str_new(256);
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen str_append(flags_str, fname);
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen str_append(flags_str, MAILDIR_FLAGS_FULL_SEP);
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen flags_left = flags;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen for (;;) {
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen /* skip all known flags */
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen while (*oldflags == 'D' || *oldflags == 'F' ||
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen *oldflags == 'R' || *oldflags == 'S' ||
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen *oldflags == 'T' ||
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen (*oldflags >= MAILDIR_KEYWORD_FIRST &&
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen *oldflags <= MAILDIR_KEYWORD_LAST))
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen oldflags++;
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen nextflag = *oldflags == '\0' || *oldflags == MAILDIR_FLAGS_SEP ?
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen 256 : (unsigned char) *oldflags;
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen if ((flags_left & MAIL_DRAFT) && nextflag > 'D') {
7e1f68ad71d3485f1882142837b01f7a98ca8467Timo Sirainen str_append_c(flags_str, 'D');
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen flags_left &= ~MAIL_DRAFT;
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen }
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen if ((flags_left & MAIL_FLAGGED) && nextflag > 'F') {
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen str_append_c(flags_str, 'F');
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen flags_left &= ~MAIL_FLAGGED;
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen }
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen if ((flags_left & MAIL_ANSWERED) && nextflag > 'R') {
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen str_append_c(flags_str, 'R');
a3c197999dfe2b0c8ea38cb77cfa5e95026005c0Timo Sirainen flags_left &= ~MAIL_ANSWERED;
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen }
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen if ((flags_left & MAIL_SEEN) && nextflag > 'S') {
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen str_append_c(flags_str, 'S');
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen flags_left &= ~MAIL_SEEN;
923115fd382904fa13bb09bf307bf2835b52df60Timo Sirainen }
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen if ((flags_left & MAIL_DELETED) && nextflag > 'T') {
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen str_append_c(flags_str, 'T');
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen flags_left &= ~MAIL_DELETED;
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen }
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen if (keywords != NULL && array_is_created(keywords) &&
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen nextflag > MAILDIR_KEYWORD_FIRST) {
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen maildir_filename_append_keywords(ctx, keywords,
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen flags_str);
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen keywords = NULL;
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen }
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen if (*oldflags == '\0' || *oldflags == MAILDIR_FLAGS_SEP)
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen break;
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen str_append_c(flags_str, *oldflags);
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen oldflags++;
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen }
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen if (*oldflags == MAILDIR_FLAGS_SEP) {
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen /* another flagset, we don't know about these, just keep them */
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen while (*oldflags != '\0')
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen str_append_c(flags_str, *oldflags++);
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen }
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen return str_c(flags_str);
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen}
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainenstatic int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen void *context __attr_unused__)
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen{
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen if (unlink(path) == 0) {
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen mbox->dirty_cur_time = ioloop_time;
e0c3d5460d1cc0c440cb7723c8c2eef8d0afe9b9Timo Sirainen return 1;
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen }
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen if (errno == ENOENT)
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen return 0;
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen mail_storage_set_critical(STORAGE(mbox->storage),
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen "unlink(%s) failed: %m", path);
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen return -1;
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen}
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainenstatic int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct maildir_index_sync_context *ctx)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen const struct mail_index_sync_rec *recs;
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen const char *dir, *fname, *newfname, *newpath;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen enum mail_flags flags;
0611067f385a37773800225256dcd5cf6aa34212Timo Sirainen ARRAY_TYPE(keyword_indexes) keywords;
df16c7e87511fed827e6890a2a47d13ca48716deTimo Sirainen unsigned int i, count;
df16c7e87511fed827e6890a2a47d13ca48716deTimo Sirainen uint8_t flags8;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
0611067f385a37773800225256dcd5cf6aa34212Timo Sirainen ctx->dirty_state = 0;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen fname = strrchr(path, '/');
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen i_assert(fname != NULL);
b7651d283ca261015ef3c445f1f27f340f0864e2Timo Sirainen fname++;
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen dir = t_strdup_until(path, fname);
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen t_array_init(&keywords, 16);
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen (void)maildir_filename_get_flags(ctx->keywords_sync_ctx,
7b42d6cbee8186195d8c5e66078043a0fa1f25c1Timo Sirainen fname, &flags, &keywords);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen flags8 = flags;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen recs = array_get_modifiable(&ctx->sync_recs, &count);
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen for (i = 0; i < count; i++) {
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen if (recs[i].uid1 != ctx->seq)
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen break;
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen switch (recs[i].type) {
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen case MAIL_INDEX_SYNC_TYPE_FLAGS:
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen mail_index_sync_flags_apply(&recs[i], &flags8);
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen break;
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
c0b1543512bc3e0a3a9f526056a3678a07ce32f5Timo Sirainen mail_index_sync_keywords_apply(&recs[i], &keywords);
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen break;
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen case MAIL_INDEX_SYNC_TYPE_APPEND:
51e1a1c280ccb461a15827f7987d09cb9708b6e3Timo Sirainen case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen i_unreached();
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen break;
a0b6b441fc679e562e79be0fb2819ffc24ab5b74Timo Sirainen }
1036ad17ac837a451f6b045cac504d3efa2edb8eTimo Sirainen }
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen
89e195dfb5c4b0efd9b9f459771a4467674e5b1fTimo Sirainen newfname = maildir_filename_set_flags(ctx->keywords_sync_ctx,
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen fname, flags8, &keywords);
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen newpath = t_strconcat(dir, newfname, NULL);
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen if (rename(path, newpath) == 0) {
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen if ((flags8 & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0)
6f08b98ac63c25b747120d0c8f8e319b4e26ab0fTimo Sirainen ctx->dirty_state = -1;
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen mbox->dirty_cur_time = ioloop_time;
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen return 1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen if (errno == ENOENT)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return 0;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (ENOSPACE(errno) || errno == EACCES) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen mail_index_update_flags(ctx->trans, ctx->seq, MODIFY_ADD,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen MAIL_INDEX_MAIL_FLAG_DIRTY);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen ctx->dirty_state = 1;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen return 1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen mail_storage_set_critical(STORAGE(mbox->storage),
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen "rename(%s, %s) failed: %m", path, newpath);
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen return -1;
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen}
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainenstatic int
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainenmaildir_sync_record_commit_until(struct maildir_index_sync_context *ctx,
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen uint32_t last_seq)
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen{
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen struct mail_index_sync_rec *recs;
3785910c303507db5f629684e6dde2cc7f83668eTimo Sirainen unsigned int i, count;
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen uint32_t seq, uid;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen bool expunged, flag_changed;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
68a4946b12583b88fa802e52ebee45cd96056772Timo Sirainen recs = array_get_modifiable(&ctx->sync_recs, &count);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen for (seq = recs[0].uid1; seq <= last_seq; seq++) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen expunged = flag_changed = FALSE;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen for (i = 0; i < count; i++) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen if (recs[i].uid1 > seq)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen break;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen i_assert(recs[i].uid1 == seq);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen switch (recs[i].type) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen case MAIL_INDEX_SYNC_TYPE_EXPUNGE:
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen expunged = TRUE;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen break;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen case MAIL_INDEX_SYNC_TYPE_FLAGS:
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD:
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE:
6ef7e31619edfaa17ed044b45861d106a86191efTimo Sirainen case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET:
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen flag_changed = TRUE;
37ab3cde96bfa4bc5304c0c348fc420aec79572dTimo Sirainen break;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen case MAIL_INDEX_SYNC_TYPE_APPEND:
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen i_unreached();
37ab3cde96bfa4bc5304c0c348fc420aec79572dTimo Sirainen break;
37ab3cde96bfa4bc5304c0c348fc420aec79572dTimo Sirainen }
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
f4bbeadda12fbd7c219063db68f3e78646d83c2cTimo Sirainen
0b47e9f5e0181053b4d9ca7b426b0e5c185e820eTimo Sirainen if (mail_index_lookup_uid(ctx->view, seq, &uid) < 0) {
0b47e9f5e0181053b4d9ca7b426b0e5c185e820eTimo Sirainen mail_storage_set_index_error(&ctx->mbox->ibox);
abe8754852e70763e92f74caabbcc13d0917714cTimo Sirainen return -1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen ctx->seq = seq;
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (expunged) {
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (maildir_file_do(ctx->mbox, uid,
90b8f131849540fa374aede95edd86d47d35c09dTimo Sirainen maildir_expunge, ctx) < 0)
90b8f131849540fa374aede95edd86d47d35c09dTimo Sirainen return -1;
90b8f131849540fa374aede95edd86d47d35c09dTimo Sirainen } else if (flag_changed) {
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (maildir_file_do(ctx->mbox, uid,
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen maildir_sync_flags, ctx) < 0)
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen return -1;
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen }
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen for (i = count; i > 0; i--) {
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen if (++recs[i-1].uid1 > recs[i-1].uid2) {
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen array_delete(&ctx->sync_recs, i-1, 1);
c09f9f95db314e7482c95e502e1c56ed6c555797Timo Sirainen recs = array_get_modifiable(&ctx->sync_recs,
90b8f131849540fa374aede95edd86d47d35c09dTimo Sirainen &count);
c06f4017027263cf3a08becc551f5126409e2a83Timo Sirainen if (count == 0) {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen /* all sync_recs committed */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen return 0;
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen }
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen }
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen }
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen }
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen return 0;
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen}
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainenstatic int maildir_sync_record(struct maildir_index_sync_context *ctx,
904f9d5654b9c39edcdf32883e5e88771faf4d69Timo Sirainen const struct mail_index_sync_rec *sync_rec)
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen{
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen struct mail_index_view *view = ctx->view;
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen struct mail_index_sync_rec sync_copy;
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen
97e62b2b36dda0acb3215667042f5c80cdee8155Timo Sirainen if (sync_rec == NULL) {
97e62b2b36dda0acb3215667042f5c80cdee8155Timo Sirainen /* deinit */
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen while (array_count(&ctx->sync_recs) > 0) {
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen if (maildir_sync_record_commit_until(ctx,
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen (uint32_t)-1) < 0)
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen return -1;
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen }
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen return 0;
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen }
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen
97e62b2b36dda0acb3215667042f5c80cdee8155Timo Sirainen if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_APPEND)
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen return 0; /* ignore */
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen /* convert to sequences to avoid looping through huge holes in
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen UID range */
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen sync_copy = *sync_rec;
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen if (mail_index_lookup_uid_range(view, sync_rec->uid1,
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen sync_rec->uid2,
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen &sync_copy.uid1,
7d5d50dd9a8c2539d7025a69e39d34fca56daeafTimo Sirainen &sync_copy.uid2) < 0) {
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen mail_storage_set_index_error(&ctx->mbox->ibox);
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen return -1;
dd2df6a67f10792ce31a3666197c0b6885893a3aTimo Sirainen }
dd2df6a67f10792ce31a3666197c0b6885893a3aTimo Sirainen
14175321ddb88619015866978c05a27786ca4814Timo Sirainen if (sync_copy.uid1 == 0) {
14175321ddb88619015866978c05a27786ca4814Timo Sirainen /* UIDs were expunged */
14175321ddb88619015866978c05a27786ca4814Timo Sirainen return 0;
14175321ddb88619015866978c05a27786ca4814Timo Sirainen }
14175321ddb88619015866978c05a27786ca4814Timo Sirainen
14175321ddb88619015866978c05a27786ca4814Timo Sirainen while (array_count(&ctx->sync_recs) > 0) {
14175321ddb88619015866978c05a27786ca4814Timo Sirainen const struct mail_index_sync_rec *rec =
14175321ddb88619015866978c05a27786ca4814Timo Sirainen array_idx(&ctx->sync_recs, 0);
14175321ddb88619015866978c05a27786ca4814Timo Sirainen
14175321ddb88619015866978c05a27786ca4814Timo Sirainen i_assert(rec->uid1 <= sync_copy.uid1);
14175321ddb88619015866978c05a27786ca4814Timo Sirainen if (rec->uid1 == sync_copy.uid1)
14175321ddb88619015866978c05a27786ca4814Timo Sirainen break;
14175321ddb88619015866978c05a27786ca4814Timo Sirainen
14175321ddb88619015866978c05a27786ca4814Timo Sirainen if (maildir_sync_record_commit_until(ctx, sync_copy.uid1-1) < 0)
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen return -1;
439dd06aec3301e65d650f6dc1d4a1a00b356b4fTimo Sirainen }
439dd06aec3301e65d650f6dc1d4a1a00b356b4fTimo Sirainen
439dd06aec3301e65d650f6dc1d4a1a00b356b4fTimo Sirainen array_append(&ctx->sync_recs, &sync_copy, 1);
439dd06aec3301e65d650f6dc1d4a1a00b356b4fTimo Sirainen return 0;
14175321ddb88619015866978c05a27786ca4814Timo Sirainen}
14175321ddb88619015866978c05a27786ca4814Timo Sirainen
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainenstatic int maildir_sync_index_records(struct maildir_index_sync_context *ctx)
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen{
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen struct mail_index_sync_rec sync_rec;
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen int ret;
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen ret = mail_index_sync_next(ctx->sync_ctx, &sync_rec);
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen if (ret <= 0) {
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen if (ret < 0)
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen mail_storage_set_index_error(&ctx->mbox->ibox);
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen return ret;
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen }
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen t_array_init(&ctx->sync_recs, 32);
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen do {
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen if (maildir_sync_record(ctx, &sync_rec) < 0)
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen return -1;
c7acd38cd4ef76a0f4652f9ca659ea5e64458b52Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen ret = mail_index_sync_next(ctx->sync_ctx, &sync_rec);
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen } while (ret > 0);
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen if (ret < 0)
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen mail_storage_set_index_error(&ctx->mbox->ibox);
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen
dd2df6a67f10792ce31a3666197c0b6885893a3aTimo Sirainen if (maildir_sync_record(ctx, NULL) < 0)
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen return -1;
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen return ret;
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen}
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainenstatic struct maildir_sync_context *
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainenmaildir_sync_context_new(struct maildir_mailbox *mbox)
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen{
282a436a74d8835edb45cc019b1c916013013fd3Timo Sirainen struct maildir_sync_context *ctx;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen ctx = t_new(struct maildir_sync_context, 1);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen ctx->mbox = mbox;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen ctx->new_dir = t_strconcat(mbox->path, "/new", NULL);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen ctx->cur_dir = t_strconcat(mbox->path, "/cur", NULL);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen return ctx;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen}
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainenstatic void maildir_sync_deinit(struct maildir_sync_context *ctx)
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen{
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen if (ctx->uidlist_sync_ctx != NULL)
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen (void)maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen if (ctx->index_sync_ctx != NULL) {
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen (void)maildir_sync_index_finish(&ctx->index_sync_ctx,
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen TRUE, FALSE);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen }
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen}
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainenstatic int maildir_fix_duplicate(struct maildir_sync_context *ctx,
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen const char *dir, const char *old_fname)
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen{
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen struct maildir_mailbox *mbox = ctx->mbox;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen const char *existing_fname, *existing_path;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen const char *new_fname, *old_path, *new_path;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen struct stat st, st2;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen int ret = 0;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen existing_fname =
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen maildir_uidlist_sync_get_full_filename(ctx->uidlist_sync_ctx,
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen old_fname);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen i_assert(existing_fname != NULL);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen t_push();
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen existing_path = t_strconcat(dir, "/", existing_fname, NULL);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen old_path = t_strconcat(dir, "/", old_fname, NULL);
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen if (stat(existing_path, &st) < 0 ||
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen stat(old_path, &st2) < 0) {
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen /* most likely the files just don't exist anymore.
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen don't really care about other errors much. */
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen t_pop();
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen return 0;
4c096615cb86a826fda377b87df22c579bfe5525Timo Sirainen }
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen if (st.st_ino == st2.st_ino && CMP_DEV_T(st.st_dev, st2.st_dev)) {
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen /* files are the same. this means either a race condition
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen between stat() calls, or someone has started link()ing the
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen files. either way there's no data loss if we just leave it
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen there. */
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen t_pop();
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen return 0;
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen }
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen new_fname = maildir_generate_tmp_filename(&ioloop_timeval);
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen new_path = t_strconcat(mbox->path, "/new/", new_fname, NULL);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen if (rename(old_path, new_path) == 0) {
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen i_warning("Fixed duplicate in %s: %s -> %s",
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen mbox->path, old_fname, new_fname);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen } else if (errno != ENOENT) {
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen mail_storage_set_critical(STORAGE(mbox->storage),
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen "rename(%s, %s) failed: %m", old_path, new_path);
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen ret = -1;
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen }
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen t_pop();
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen return ret;
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen}
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen
4aa7fe81503a20bc972ae625da4dd9e6996fbdbfTimo Sirainenstatic int maildir_scan_dir(struct maildir_sync_context *ctx, bool new_dir)
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen{
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen struct mail_storage *storage = STORAGE(ctx->mbox->storage);
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen const char *dir;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen DIR *dirp;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen string_t *src, *dest;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen struct dirent *dp;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen enum maildir_uidlist_rec_flag flags;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen unsigned int moves = 0;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen int ret = 1;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen bool move_new;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen dir = new_dir ? ctx->new_dir : ctx->cur_dir;
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen dirp = opendir(dir);
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen if (dirp == NULL) {
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen mail_storage_set_critical(storage,
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen "opendir(%s) failed: %m", dir);
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen return -1;
66d84e6f0ae34a3cf5b8fa8e009d6caf025b6a2aTimo Sirainen }
66d84e6f0ae34a3cf5b8fa8e009d6caf025b6a2aTimo Sirainen
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen t_push();
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen src = t_str_new(1024);
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen dest = t_str_new(1024);
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen move_new = new_dir && !mailbox_is_readonly(&ctx->mbox->ibox.box) &&
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen !ctx->mbox->ibox.keep_recent;
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen while ((dp = readdir(dirp)) != NULL) {
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen if (dp->d_name[0] == '.')
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen continue;
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen ret = maildir_uidlist_sync_next_pre(ctx->uidlist_sync_ctx,
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen dp->d_name);
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen if (ret == 0) {
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen /* new file and we couldn't lock uidlist, check this
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen later in next sync. */
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen if (new_dir)
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen ctx->mbox->last_new_mtime = 0;
d9ab8a13b51c9d8f4e13e1bf785eeadce6702b3bTimo Sirainen else
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen ctx->mbox->dirty_cur_time = ioloop_time;
5df8396a7cbad0b38b83a86667fb3d4c223f6f7cTimo Sirainen continue;
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen }
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen if (ret < 0)
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen break;
5df8396a7cbad0b38b83a86667fb3d4c223f6f7cTimo Sirainen
d5b3f66491101aba8667369586c95c615cb26ae6Timo Sirainen flags = 0;
5df8396a7cbad0b38b83a86667fb3d4c223f6f7cTimo Sirainen if (move_new) {
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen str_truncate(src, 0);
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen str_truncate(dest, 0);
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen str_printfa(src, "%s/%s", ctx->new_dir, dp->d_name);
9db5ade1c16c7f67c51004f28c28ea335755d3f0Timo Sirainen str_printfa(dest, "%s/%s", ctx->cur_dir, dp->d_name);
9db5ade1c16c7f67c51004f28c28ea335755d3f0Timo Sirainen if (strchr(dp->d_name, MAILDIR_INFO_SEP) == NULL) {
9db5ade1c16c7f67c51004f28c28ea335755d3f0Timo Sirainen str_append(dest, MAILDIR_FLAGS_FULL_SEP);
9db5ade1c16c7f67c51004f28c28ea335755d3f0Timo Sirainen }
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen if (rename(str_c(src), str_c(dest)) == 0) {
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen /* we moved it - it's \Recent for us */
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen moves++;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen ctx->mbox->dirty_cur_time = ioloop_time;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED |
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen MAILDIR_UIDLIST_REC_FLAG_RECENT;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen } else if (ENOTFOUND(errno)) {
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen /* someone else moved it already */
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen moves++;
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen flags |= MAILDIR_UIDLIST_REC_FLAG_MOVED;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen } else if (ENOSPACE(errno) || errno == EACCES) {
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen /* not enough disk space / read-only maildir,
01f4ee4a0243f3fe9af763e1a540cd5cff0d63f5Timo Sirainen leave here */
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen MAILDIR_UIDLIST_REC_FLAG_RECENT;
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen move_new = FALSE;
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen } else {
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen MAILDIR_UIDLIST_REC_FLAG_RECENT;
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen mail_storage_set_critical(storage,
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen "rename(%s, %s) failed: %m",
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen str_c(src), str_c(dest));
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen }
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen } else if (new_dir) {
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen MAILDIR_UIDLIST_REC_FLAG_RECENT;
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen }
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen dp->d_name, flags);
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen if (ret <= 0) {
fe363b433b8038a69b55169da9dca27892ad7d18Timo Sirainen if (ret < 0)
9511a40d933181045343110c8101b75887062aaeTimo Sirainen break;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen /* possibly duplicate - try fixing it */
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen if (maildir_fix_duplicate(ctx, dir, dp->d_name) < 0) {
5f3151744f3ffa73b57391d4a237884b75423f57Timo Sirainen ret = -1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen break;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen }
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen }
e2ce8d4a6ac5d82a906178148453e7613fab9ba0Timo Sirainen }
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen
01e606cda5192c4254c090624a0b2ca92da6da8eTimo Sirainen if (closedir(dirp) < 0) {
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen mail_storage_set_critical(storage,
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen "closedir(%s) failed: %m", dir);
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen }
032964c7cc6788188b63ae6270fc26cbd4a3ca26Timo Sirainen
12a3540693ab69ec622e04d1b3b66962b8b2a3d9Timo Sirainen t_pop();
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen return ret < 0 ? -1 : (moves <= MAILDIR_RENAME_RESCAN_COUNT ? 0 : 1);
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen}
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainen
4b9f99761df5014c659cd87fddaf6854af428cfcTimo Sirainenstatic void
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainenmaildir_sync_update_from_header(struct maildir_mailbox *mbox,
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen struct mail_index_header *hdr_r)
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen{
a94936bafd127680184da114c6a177b37ff656e5Timo Sirainen struct mail_index_view *view;
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen const struct mail_index_header *hdr;
7662010b03ffe5f2a6ecf4b4eb220d1c65efea76Timo Sirainen
7ef5ca6fb59a318c821a852ae48a2edbb671d7ddTimo Sirainen /* open a new view so we get the latest header */
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen view = mail_index_view_open(mbox->ibox.index);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen hdr = mail_index_get_header(view);
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen /* FIXME: ugly, replace with extension header */
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen mbox->last_new_mtime = hdr->sync_size & 0xffffffff;
07e4875d250e7a7157cd99132aafc773cf3cdf83Timo Sirainen mbox->last_dirty_flags = (hdr->sync_size >> 32) &
252db51b6c0a605163326b3ea5d09e9936ca3b29Timo Sirainen (MAILDIR_DIRTY_NEW | MAILDIR_DIRTY_CUR);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen mbox->last_cur_mtime = hdr->sync_stamp;
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen if ((mbox->last_dirty_flags & MAILDIR_DIRTY_CUR) != 0 &&
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen mbox->dirty_cur_time < mbox->last_cur_mtime)
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen mbox->dirty_cur_time = mbox->last_cur_mtime;
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen *hdr_r = *hdr;
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen mail_index_view_close(&view);
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen}
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainenstatic int
d14e62b7b37dc78fcc940aca25042eceb358b156Timo Sirainenmaildir_sync_quick_check(struct maildir_mailbox *mbox,
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen const char *new_dir, const char *cur_dir,
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen bool *new_changed_r, bool *cur_changed_r)
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen{
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen struct index_mailbox *ibox = &mbox->ibox;
0c1835a90dd1dcedaeaedd1cd91672299cbeb5beTimo Sirainen struct mail_index_header hdr;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen struct stat st;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen time_t new_mtime, cur_mtime;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen *new_changed_r = *cur_changed_r = FALSE;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen if (stat(new_dir, &st) < 0) {
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen mail_storage_set_critical(STORAGE(mbox->storage),
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen "stat(%s) failed: %m", new_dir);
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen return -1;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen }
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen new_mtime = st.st_mtime;
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen
f4735bf7ec2019fdc730e9ebdb39e5a4ea580405Timo Sirainen if (stat(cur_dir, &st) < 0) {
mail_storage_set_critical(STORAGE(mbox->storage),
"stat(%s) failed: %m", cur_dir);
return -1;
}
cur_mtime = st.st_mtime;
/* cur stamp is kept in index, we don't have to sync if
someone else has done it and updated the index.
FIXME: For now we're using sync_size field as the new/ dir's stamp.
Pretty ugly.. */
maildir_sync_update_from_header(mbox, &hdr);
if ((mbox->dirty_cur_time == 0 && cur_mtime != mbox->last_cur_mtime) ||
(new_mtime != mbox->last_new_mtime)) {
/* check if the index has been updated.. */
if (mail_index_refresh(ibox->index) < 0) {
mail_storage_set_index_error(ibox);
return -1;
}
maildir_sync_update_from_header(mbox, &hdr);
}
/* If we're removing recent flags, always sync new/ directory if
it has mails. */
if (new_mtime != mbox->last_new_mtime ||
((mbox->last_dirty_flags & MAILDIR_DIRTY_NEW) != 0 &&
new_mtime < ioloop_time - MAILDIR_SYNC_SECS) ||
(!ibox->keep_recent && hdr.recent_messages_count > 0)) {
*new_changed_r = TRUE;
mbox->last_new_mtime = new_mtime;
if (new_mtime < ioloop_time - MAILDIR_SYNC_SECS)
mbox->last_dirty_flags &= ~MAILDIR_DIRTY_NEW;
else
mbox->last_dirty_flags |= MAILDIR_DIRTY_NEW;
}
if (cur_mtime != mbox->last_cur_mtime ||
(mbox->dirty_cur_time != 0 &&
ioloop_time - mbox->dirty_cur_time > MAILDIR_SYNC_SECS)) {
/* cur/ changed, or delayed cur/ check */
*cur_changed_r = TRUE;
mbox->last_cur_mtime = cur_mtime;
if (cur_mtime < ioloop_time - MAILDIR_SYNC_SECS) {
mbox->last_dirty_flags &= ~MAILDIR_DIRTY_CUR;
mbox->dirty_cur_time = 0;
} else {
mbox->last_dirty_flags |= MAILDIR_DIRTY_CUR;
mbox->dirty_cur_time = cur_mtime;
}
}
return 0;
}
int maildir_sync_index_begin(struct maildir_mailbox *mbox,
struct maildir_index_sync_context **ctx_r)
{
struct maildir_index_sync_context *ctx;
struct mail_index_sync_ctx *sync_ctx;
struct mail_index_view *view;
if (mail_index_sync_begin(mbox->ibox.index, &sync_ctx, &view,
(uint32_t)-1, (uoff_t)-1,
FALSE, FALSE) <= 0) {
mail_storage_set_index_error(&mbox->ibox);
return -1;
}
ctx = i_new(struct maildir_index_sync_context, 1);
ctx->mbox = mbox;
ctx->sync_ctx = sync_ctx;
ctx->view = view;
ctx->keywords_sync_ctx =
maildir_keywords_sync_init(mbox->keywords, mbox->ibox.index);
*ctx_r = ctx;
return 0;
}
int maildir_sync_index_finish(struct maildir_index_sync_context **_sync_ctx,
bool failed, bool cancel)
{
struct maildir_index_sync_context *sync_ctx = *_sync_ctx;
struct maildir_mailbox *mbox = sync_ctx->mbox;
uint32_t seq;
uoff_t offset;
int ret = failed ? -1 : 0;
*_sync_ctx = NULL;
if (sync_ctx->trans != NULL) {
if (ret < 0 || cancel)
mail_index_transaction_rollback(&sync_ctx->trans);
else {
if (mail_index_transaction_commit(&sync_ctx->trans,
&seq, &offset) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
} else if (seq != 0) {
mbox->ibox.commit_log_file_seq = seq;
mbox->ibox.commit_log_file_offset = offset;
}
}
}
if (ret < 0 || cancel)
mail_index_sync_rollback(&sync_ctx->sync_ctx);
else {
/* Set syncing_commit=TRUE so that if any sync callbacks try
to access mails which got lost (eg. expunge callback trying
to open the file which was just unlinked) we don't try to
start a second index sync and crash. */
mbox->syncing_commit = TRUE;
if (mail_index_sync_commit(&sync_ctx->sync_ctx) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
} else {
mbox->ibox.commit_log_file_seq = 0;
mbox->ibox.commit_log_file_offset = 0;
}
mbox->syncing_commit = FALSE;
}
maildir_keywords_sync_deinit(sync_ctx->keywords_sync_ctx);
sync_ctx->keywords_sync_ctx = NULL;
i_free(sync_ctx);
return ret;
}
int maildir_sync_index(struct maildir_index_sync_context *sync_ctx,
bool partial)
{
struct maildir_mailbox *mbox = sync_ctx->mbox;
struct mail_index_view *view = sync_ctx->view;
struct maildir_uidlist_iter_ctx *iter;
struct mail_index_transaction *trans;
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;
ARRAY_TYPE(keyword_indexes) keywords;
ARRAY_TYPE(keyword_indexes) idx_keywords;
uint32_t uid_validity, next_uid;
uint64_t value;
int ret = 0;
bool full_rescan = FALSE;
i_assert(maildir_uidlist_is_locked(sync_ctx->mbox->uidlist));
hdr = mail_index_get_header(view);
uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
if (uid_validity != hdr->uid_validity &&
uid_validity != 0 && hdr->uid_validity != 0) {
/* uidvalidity changed and mailbox isn't being initialized,
reset mailbox so we can add all messages as new */
mail_storage_set_critical(STORAGE(mbox->storage),
"Maildir %s sync: UIDVALIDITY changed (%u -> %u)",
mbox->path, hdr->uid_validity, uid_validity);
mail_index_mark_corrupted(mbox->ibox.index);
return -1;
}
sync_ctx->trans = trans =
mail_index_transaction_begin(sync_ctx->view, FALSE, TRUE);
seq = 0;
t_array_init(&keywords, MAILDIR_MAX_KEYWORDS);
t_array_init(&idx_keywords, MAILDIR_MAX_KEYWORDS);
iter = maildir_uidlist_iter_init(mbox->uidlist);
while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
maildir_filename_get_flags(sync_ctx->keywords_sync_ctx,
filename, &flags, &keywords);
/* the private flags are kept only in indexes. don't use them
at all even for newly seen mails */
flags &= ~mbox->private_flags_mask;
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(
STORAGE(mbox->storage),
"Maildir %s sync: "
"UID < next_uid "
"(%u < %u, file = %s)",
mbox->path, uid, hdr->next_uid,
filename);
mail_index_mark_corrupted(
mbox->ibox.index);
ret = -1;
break;
}
mbox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(mbox->uidlist,
filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
mail_index_append(trans, uid, &seq);
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
flags);
if (array_count(&keywords) > 0) {
struct mail_keywords *kw;
kw = mail_index_keywords_create_from_indexes(
trans, &keywords);
mail_index_update_keywords(trans, seq,
MODIFY_REPLACE, kw);
mail_index_keywords_free(&kw);
}
continue;
}
if (mail_index_lookup(view, seq, &rec) < 0) {
mail_storage_set_index_error(&mbox->ibox);
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(
STORAGE(mbox->storage),
"Maildir %s sync: "
"UID inserted in the middle of mailbox "
"(%u > %u, file = %s)",
mbox->path, rec->uid, uid, filename);
mail_index_mark_corrupted(mbox->ibox.index);
ret = -1;
break;
}
mbox->dirty_cur_time = ioloop_time;
maildir_uidlist_add_flags(mbox->uidlist, filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
seq--;
continue;
}
/* the private flags are stored only in indexes, keep them */
flags |= rec->flags & mbox->private_flags_mask;
if ((rec->flags & MAIL_RECENT) != 0) {
index_mailbox_set_recent(&mbox->ibox, seq);
if (mbox->ibox.keep_recent) {
flags |= MAIL_RECENT;
} else {
mail_index_update_flags(trans, seq,
MODIFY_REMOVE,
MAIL_RECENT);
}
}
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
if ((flags & MAIL_RECENT) != 0) {
/* we last saw this mail in new/, but it's
not there anymore. possibly expunged,
make sure. */
full_rescan = TRUE;
}
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))) {
/* 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);
} else if ((flags & MAIL_RECENT) == 0 &&
(rec->flags & MAIL_RECENT) != 0) {
/* just remove recent flag */
mail_index_update_flags(trans, seq, MODIFY_REMOVE,
MAIL_RECENT);
}
/* update keywords if they have changed */
if (mail_index_lookup_keywords(view, seq, &idx_keywords) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
break;
}
if (!index_keyword_array_cmp(&keywords, &idx_keywords)) {
struct mail_keywords *kw;
kw = mail_index_keywords_create_from_indexes(
trans, &keywords);
mail_index_update_keywords(trans, seq,
MODIFY_REPLACE, kw);
mail_index_keywords_free(&kw);
}
}
maildir_uidlist_iter_deinit(iter);
array_free(&keywords);
if (!partial) {
/* expunge the rest */
for (seq++; seq <= hdr->messages_count; seq++)
mail_index_expunge(trans, seq);
/* next_uid must be updated only in non-partial syncs since
partial syncs don't add the new mails to index. also we'll
have to do it here before syncing index records, since after
that the uidlist's next_uid value may have changed. */
next_uid = maildir_uidlist_get_next_uid(mbox->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), FALSE);
}
}
if (!mbox->syncing_commit) {
/* now, sync the index. NOTE: may recurse back to here with
partial syncs */
mbox->syncing_commit = TRUE;
if (maildir_sync_index_records(sync_ctx) < 0)
ret = -1;
mbox->syncing_commit = FALSE;
}
if (mbox->dirty_cur_time != 0)
mbox->last_dirty_flags |= MAILDIR_DIRTY_CUR;
if (mbox->last_cur_mtime != (time_t)hdr->sync_stamp) {
uint32_t sync_stamp = mbox->last_cur_mtime;
mail_index_update_header(trans,
offsetof(struct mail_index_header, sync_stamp),
&sync_stamp, sizeof(sync_stamp), TRUE);
}
/* FIXME: use a header extension instead of sync_size.. */
value = mbox->last_new_mtime |
((uint64_t)mbox->last_dirty_flags << 32);
if (value != hdr->sync_size) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, sync_size),
&value, sizeof(value), TRUE);
}
if (hdr->uid_validity == 0) {
/* get the initial uidvalidity */
if (maildir_uidlist_update(mbox->uidlist) < 0)
ret = -1;
uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
if (uid_validity == 0) {
uid_validity = ioloop_time;
maildir_uidlist_set_uid_validity(mbox->uidlist,
uid_validity);
}
} else if (uid_validity == 0) {
maildir_uidlist_set_uid_validity(mbox->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), TRUE);
}
return ret < 0 ? -1 : (full_rescan ? 0 : 1);
}
static int maildir_sync_context(struct maildir_sync_context *ctx, bool forced,
bool sync_last_commit)
{
bool new_changed, cur_changed, full_rescan = FALSE;
int ret;
if (sync_last_commit) {
new_changed = cur_changed = FALSE;
} else if (!forced) {
if (maildir_sync_quick_check(ctx->mbox,
ctx->new_dir, ctx->cur_dir,
&new_changed, &cur_changed) < 0)
return -1;
if (!new_changed && !cur_changed)
return 1;
} else {
new_changed = cur_changed = TRUE;
}
/*
Locking, locking, locking.. Wasn't maildir supposed to be lockless?
We can get here either as beginning a real maildir sync, or when
committing changes to maildir but a file was lost (maybe renamed).
So, we're going to need two locks. One for index and one for
uidlist. To avoid deadlocking do the uidlist lock always first.
uidlist is needed only for figuring out UIDs for newly seen files,
so theoretically we wouldn't need to lock it unless there are new
files. It has a few problems though, assuming the index lock didn't
already protect it (eg. in-memory indexes):
1. Just because you see a new file which doesn't exist in uidlist
file, doesn't mean that the file really exists anymore, or that
your readdir() lists all new files. Meaning that this is possible:
A: opendir(), readdir() -> new file ...
-- new files are written to the maildir --
B: opendir(), readdir() -> new file, lock uidlist,
readdir() -> another new file, rewrite uidlist, unlock
A: ... lock uidlist, readdir() -> nothing left, rewrite uidlist,
unlock
The second time running A didn't see the two new files. To handle
this correctly, it must not remove the new unseen files from
uidlist. This is possible to do, but adds extra complexity.
2. If another process is rename()ing files while we are
readdir()ing, it's possible that readdir() never lists some files,
causing Dovecot to assume they were expunged. In next sync they
would show up again, but client could have already been notified of
that and they would show up under new UIDs, so the damage is
already done.
Both of the problems can be avoided if we simply lock the uidlist
before syncing and keep it until sync is finished. Typically this
would happen in any case, as there is the index lock..
The second case is still a problem with external changes though,
because maildir doesn't require any kind of locking. Luckily this
problem rarely happens except under high amount of modifications.
*/
ctx->partial = !cur_changed;
ret = maildir_uidlist_sync_init(ctx->mbox->uidlist, ctx->partial,
&ctx->uidlist_sync_ctx);
if (ret <= 0) {
/* failure / timeout. if forced is TRUE, we could still go
forward and check only for renamed files, but is it worth
the trouble? .. */
return ret;
}
if (!ctx->mbox->syncing_commit) {
if (maildir_sync_index_begin(ctx->mbox,
&ctx->index_sync_ctx) < 0)
return -1;
}
if (new_changed || cur_changed) {
/* if we're going to check cur/ dir our current logic requires
that new/ dir is checked as well. it's a good idea anyway. */
while ((ret = maildir_scan_dir(ctx, TRUE)) > 0) {
/* rename()d at least some files, which might have
caused some other files to be missed. check again
(see MAILDIR_RENAME_RESCAN_COUNT). */
}
if (ret < 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->mbox->syncing_commit) {
/* NOTE: index syncing here might cause a re-sync due to
files getting lost, so this function might be called
re-entrantly. FIXME: and that breaks in
maildir_uidlist_sync_deinit() */
ret = maildir_sync_index(ctx->index_sync_ctx, ctx->partial);
if (maildir_sync_index_finish(&ctx->index_sync_ctx,
ret < 0, FALSE) < 0)
return -1;
if (ret < 0)
return -1;
if (ret == 0)
full_rescan = TRUE;
i_assert(maildir_uidlist_is_locked(ctx->mbox->uidlist));
}
ret = maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx);
return ret < 0 ? -1 : (full_rescan ? 0 : 1);
}
int maildir_storage_sync_force(struct maildir_mailbox *mbox)
{
struct maildir_sync_context *ctx;
int ret;
ctx = maildir_sync_context_new(mbox);
ret = maildir_sync_context(ctx, TRUE, FALSE);
maildir_sync_deinit(ctx);
return ret < 0 ? -1 : 0;
}
int maildir_sync_last_commit(struct maildir_mailbox *mbox)
{
struct maildir_sync_context *ctx;
int ret;
if (mbox->ibox.commit_log_file_seq == 0)
return 0;
ctx = maildir_sync_context_new(mbox);
ret = maildir_sync_context(ctx, FALSE, TRUE);
maildir_sync_deinit(ctx);
return ret < 0 ? -1 : 0;
}
struct mailbox_sync_context *
maildir_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
{
struct maildir_mailbox *mbox = (struct maildir_mailbox *)box;
struct maildir_sync_context *ctx;
int ret = 0;
if (!box->opened)
index_storage_mailbox_open(&mbox->ibox);
if ((flags & MAILBOX_SYNC_FLAG_FAST) == 0 ||
mbox->ibox.sync_last_check + MAILBOX_FULL_SYNC_INTERVAL <=
ioloop_time) {
mbox->ibox.sync_last_check = ioloop_time;
ctx = maildir_sync_context_new(mbox);
ret = maildir_sync_context(ctx, FALSE, FALSE);
maildir_sync_deinit(ctx);
i_assert(!maildir_uidlist_is_locked(mbox->uidlist) ||
mbox->ibox.keep_locked);
if (ret == 0) {
/* lost some files from new/, see if thery're in cur/ */
ret = maildir_storage_sync_force(mbox);
}
}
return index_mailbox_sync_init(box, flags, ret < 0);
}
int maildir_sync_is_synced(struct maildir_mailbox *mbox)
{
const char *new_dir, *cur_dir;
bool new_changed, cur_changed;
int ret;
t_push();
new_dir = t_strconcat(mbox->path, "/new", NULL);
cur_dir = t_strconcat(mbox->path, "/cur", NULL);
ret = maildir_sync_quick_check(mbox, new_dir, cur_dir,
&new_changed, &cur_changed);
t_pop();
return ret < 0 ? -1 : (!new_changed && !cur_changed);
}