src/imap/imap-sync.c
author Timo Sirainen <tss@iki.fi>
Thu Mar 20 16:55:44 2008 +0200 (2008-03-20)
branchHEAD
changeset 7434 4407b7265afd
parent 7432 9b8590b3749b
child 7444 9d694f41a699
permissions -rw-r--r--
Try to send tagged replies in the same order as the commands were received
(fixes Apple Mail bug).
tss@7086
     1
/* Copyright (c) 2002-2008 Dovecot authors, see the included COPYING file */
tss@2322
     2
tss@2322
     3
#include "common.h"
tss@2322
     4
#include "str.h"
tss@7195
     5
#include "ostream.h"
tss@3016
     6
#include "mail-storage.h"
tss@2322
     7
#include "imap-util.h"
tss@2322
     8
#include "imap-sync.h"
tss@2427
     9
#include "commands.h"
tss@2322
    10
tss@7353
    11
struct client_sync_context {
tss@7353
    12
	/* if multiple commands are in progress, we may need to wait for them
tss@7353
    13
	   to finish before syncing mailbox. */
tss@7353
    14
	unsigned int counter;
tss@7353
    15
	enum mailbox_sync_flags flags;
tss@7353
    16
	enum imap_sync_flags imap_flags;
tss@7353
    17
	const char *tagline;
tss@7353
    18
	imap_sync_callback_t *callback;
tss@7353
    19
};
tss@7353
    20
tss@2427
    21
struct imap_sync_context {
tss@2427
    22
	struct client *client;
tss@2427
    23
	struct mailbox *box;
tss@3765
    24
        enum imap_sync_flags imap_flags;
tss@2427
    25
tss@2427
    26
	struct mailbox_transaction_context *t;
tss@2427
    27
	struct mailbox_sync_context *sync_ctx;
tss@3209
    28
	struct mail *mail;
tss@2427
    29
tss@2427
    30
	struct mailbox_sync_rec sync_rec;
tss@7057
    31
	ARRAY_TYPE(keywords) tmp_keywords;
tss@2427
    32
	uint32_t seq;
tss@2427
    33
tss@2427
    34
	unsigned int messages_count;
tss@2427
    35
tss@5762
    36
	unsigned int failed:1;
tss@5762
    37
	unsigned int no_newmail:1;
tss@2427
    38
};
tss@2427
    39
tss@2427
    40
struct imap_sync_context *
tss@2427
    41
imap_sync_init(struct client *client, struct mailbox *box,
tss@3765
    42
	       enum imap_sync_flags imap_flags, enum mailbox_sync_flags flags)
tss@2322
    43
{
tss@2427
    44
	struct imap_sync_context *ctx;
tss@2427
    45
tss@2427
    46
	i_assert(client->mailbox == box);
tss@2427
    47
tss@2427
    48
	ctx = i_new(struct imap_sync_context, 1);
tss@2427
    49
	ctx->client = client;
tss@2427
    50
	ctx->box = box;
tss@3765
    51
	ctx->imap_flags = imap_flags;
tss@2427
    52
tss@2884
    53
	ctx->sync_ctx = mailbox_sync_init(box, flags);
tss@3209
    54
	ctx->t = mailbox_transaction_begin(box, 0);
tss@3209
    55
	ctx->mail = mail_alloc(ctx->t, MAIL_FETCH_FLAGS, 0);
tss@2427
    56
	ctx->messages_count = client->messages_count;
tss@7057
    57
	i_array_init(&ctx->tmp_keywords, client->keywords.announce_count + 8);
tss@3515
    58
tss@7057
    59
	client_send_mailbox_flags(client, FALSE);
tss@2427
    60
	return ctx;
tss@2427
    61
}
tss@2427
    62
tss@2427
    63
int imap_sync_deinit(struct imap_sync_context *ctx)
tss@2427
    64
{
tss@2322
    65
	struct mailbox_status status;
tss@5089
    66
	int ret;
tss@2427
    67
tss@3879
    68
	mail_free(&ctx->mail);
tss@3209
    69
tss@5848
    70
	if (mailbox_sync_deinit(&ctx->sync_ctx, STATUS_UIDVALIDITY |
tss@4848
    71
				STATUS_MESSAGES | STATUS_RECENT, &status) < 0 ||
tss@4848
    72
	    ctx->failed) {
tss@3879
    73
		mailbox_transaction_rollback(&ctx->t);
tss@2427
    74
		i_free(ctx);
tss@2427
    75
		return -1;
tss@2427
    76
	}
tss@2427
    77
tss@6512
    78
	ret = mailbox_transaction_commit(&ctx->t);
tss@2427
    79
tss@5848
    80
	if (status.uidvalidity != ctx->client->uidvalidity) {
tss@5848
    81
		/* most clients would get confused by this. disconnect them. */
tss@5848
    82
		client_disconnect_with_error(ctx->client,
tss@5848
    83
					     "Mailbox UIDVALIDITY changed");
tss@5848
    84
	}
tss@5765
    85
	if (!ctx->no_newmail) {
tss@5916
    86
		if (status.messages < ctx->messages_count)
tss@5916
    87
			i_panic("Message count decreased");
tss@5762
    88
		ctx->client->messages_count = status.messages;
tss@5765
    89
		if (status.messages != ctx->messages_count) {
tss@5765
    90
			client_send_line(ctx->client,
tss@5765
    91
				t_strdup_printf("* %u EXISTS", status.messages));
tss@5765
    92
		}
tss@7055
    93
		if (status.recent != ctx->client->recent_count &&
tss@7055
    94
		    !ctx->no_newmail) {
tss@5765
    95
			ctx->client->recent_count = status.recent;
tss@5765
    96
			client_send_line(ctx->client,
tss@5765
    97
				t_strdup_printf("* %u RECENT", status.recent));
tss@5765
    98
		}
tss@2427
    99
	}
tss@2427
   100
tss@7057
   101
	array_free(&ctx->tmp_keywords);
tss@2427
   102
	i_free(ctx);
tss@5089
   103
	return ret;
tss@2427
   104
}
tss@2427
   105
tss@7057
   106
static int imap_sync_send_flags(struct imap_sync_context *ctx, string_t *str)
tss@6986
   107
{
tss@6986
   108
	enum mail_flags flags;
tss@7057
   109
	const char *const *keywords;
tss@6986
   110
tss@6986
   111
	mail_set_seq(ctx->mail, ctx->seq);
tss@6986
   112
	flags = mail_get_flags(ctx->mail);
tss@7057
   113
	keywords = client_get_keyword_names(ctx->client, &ctx->tmp_keywords,
tss@7057
   114
			mail_get_keyword_indexes(ctx->mail));
tss@6986
   115
tss@7353
   116
	if ((flags & MAIL_DELETED) != 0)
tss@7353
   117
		ctx->client->sync_seen_deletes = TRUE;
tss@7353
   118
tss@6986
   119
	str_truncate(str, 0);
tss@6986
   120
	str_printfa(str, "* %u FETCH (", ctx->seq);
tss@6986
   121
	if (ctx->imap_flags & IMAP_SYNC_FLAG_SEND_UID)
tss@6986
   122
		str_printfa(str, "UID %u ", ctx->mail->uid);
tss@6986
   123
tss@6986
   124
	str_append(str, "FLAGS (");
tss@7057
   125
	imap_write_flags(str, flags, keywords);
tss@6986
   126
	str_append(str, "))");
tss@6986
   127
	return client_send_line(ctx->client, str_c(str));
tss@6986
   128
}
tss@6986
   129
tss@2427
   130
int imap_sync_more(struct imap_sync_context *ctx)
tss@2427
   131
{
tss@2322
   132
	string_t *str;
tss@3107
   133
	int ret = 1;
tss@2322
   134
tss@2322
   135
	str = t_str_new(256);
tss@2427
   136
	for (;;) {
tss@2427
   137
		if (ctx->seq == 0) {
tss@2427
   138
			/* get next one */
tss@6279
   139
			if (!mailbox_sync_next(ctx->sync_ctx, &ctx->sync_rec)) {
tss@6279
   140
				/* finished */
tss@6279
   141
				ret = 1;
tss@2427
   142
				break;
tss@3107
   143
			}
tss@2427
   144
		}
tss@2427
   145
tss@4008
   146
		if (ctx->sync_rec.seq2 > ctx->messages_count) {
tss@4008
   147
			/* don't send change notifications of messages we
tss@4008
   148
			   haven't even announced to client yet */
tss@6355
   149
			if (ctx->sync_rec.seq1 > ctx->messages_count) {
tss@6355
   150
				ctx->seq = 0;
tss@4008
   151
				continue;
tss@6355
   152
			}
tss@4008
   153
			ctx->sync_rec.seq2 = ctx->messages_count;
tss@4008
   154
		}
tss@4008
   155
tss@2427
   156
		switch (ctx->sync_rec.type) {
tss@2322
   157
		case MAILBOX_SYNC_TYPE_FLAGS:
tss@2427
   158
			if (ctx->seq == 0)
tss@2427
   159
				ctx->seq = ctx->sync_rec.seq1;
tss@2427
   160
tss@3652
   161
			ret = 1;
tss@2427
   162
			for (; ctx->seq <= ctx->sync_rec.seq2; ctx->seq++) {
tss@3652
   163
				if (ret <= 0)
tss@3652
   164
					break;
tss@3652
   165
tss@7057
   166
				ret = imap_sync_send_flags(ctx, str);
tss@2322
   167
			}
tss@2322
   168
			break;
tss@2322
   169
		case MAILBOX_SYNC_TYPE_EXPUNGE:
tss@4038
   170
			if (ctx->seq == 0)
tss@2427
   171
				ctx->seq = ctx->sync_rec.seq2;
tss@3652
   172
			ret = 1;
tss@2427
   173
			for (; ctx->seq >= ctx->sync_rec.seq1; ctx->seq--) {
tss@3652
   174
				if (ret <= 0)
tss@3652
   175
					break;
tss@3652
   176
tss@2322
   177
				str_truncate(str, 0);
tss@2427
   178
				str_printfa(str, "* %u EXPUNGE", ctx->seq);
tss@3107
   179
				ret = client_send_line(ctx->client, str_c(str));
tss@2322
   180
			}
tss@4038
   181
			if (ctx->seq < ctx->sync_rec.seq1) {
tss@4038
   182
				/* update only after we're finished, so that
tss@4038
   183
				   the seq2 > messages_count check above
tss@4038
   184
				   doesn't break */
tss@4038
   185
				ctx->messages_count -=
tss@4038
   186
					ctx->sync_rec.seq2 -
tss@4038
   187
					ctx->sync_rec.seq1 + 1;
tss@4038
   188
			}
tss@2322
   189
			break;
tss@2322
   190
		}
tss@3652
   191
		if (ret <= 0) {
tss@3652
   192
			/* failure / buffer full */
tss@3652
   193
			break;
tss@3652
   194
		}
tss@3652
   195
tss@2427
   196
		ctx->seq = 0;
tss@2427
   197
	}
tss@3107
   198
	return ret;
tss@2427
   199
}
tss@2427
   200
tss@7353
   201
static bool cmd_finish_sync(struct client_command_context *cmd)
tss@7353
   202
{
tss@7353
   203
	if (cmd->sync->callback != NULL)
tss@7353
   204
		return cmd->sync->callback(cmd);
tss@7353
   205
	else {
tss@7353
   206
		client_send_tagline(cmd, cmd->sync->tagline);
tss@7353
   207
		return TRUE;
tss@7353
   208
	}
tss@7353
   209
}
tss@7353
   210
tss@7195
   211
static bool cmd_sync_continue(struct client_command_context *sync_cmd)
tss@2427
   212
{
tss@7434
   213
	struct client_command_context *cmd, *prev;
tss@7195
   214
	struct client *client = sync_cmd->client;
tss@7195
   215
	struct imap_sync_context *ctx = sync_cmd->context;
tss@3652
   216
	int ret;
tss@2427
   217
tss@7195
   218
	i_assert(ctx->client == client);
tss@7195
   219
tss@7195
   220
	if ((ret = imap_sync_more(ctx)) == 0)
tss@7195
   221
		return FALSE;
tss@7195
   222
	if (ret < 0)
tss@7195
   223
		ctx->failed = TRUE;
tss@7195
   224
tss@7195
   225
	client->syncing = FALSE;
tss@7195
   226
	if (imap_sync_deinit(ctx) < 0) {
tss@7195
   227
		client_send_untagged_storage_error(client,
tss@7195
   228
			mailbox_get_storage(client->mailbox));
tss@4939
   229
	}
tss@7195
   230
	sync_cmd->context = NULL;
tss@2427
   231
tss@7434
   232
	/* Finish all commands that waited for this sync. Go through the queue
tss@7434
   233
	   backwards, so that tagged replies are sent in the same order as
tss@7434
   234
	   they were received. This fixes problems with clients that rely on
tss@7434
   235
	   this (Apple Mail 3.2) */
tss@7434
   236
	for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
tss@7434
   237
	for (; cmd != NULL; cmd = prev) {
tss@7434
   238
		prev = cmd->prev;
tss@7432
   239
tss@7195
   240
		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
tss@7195
   241
		    cmd != sync_cmd &&
tss@7353
   242
		    cmd->sync->counter+1 == client->sync_counter) {
tss@7353
   243
			if (cmd_finish_sync(cmd))
tss@7431
   244
				client_command_free(&cmd);
tss@7195
   245
		}
tss@2322
   246
	}
tss@7353
   247
	return cmd_finish_sync(sync_cmd);
tss@2427
   248
}
tss@2427
   249
tss@7195
   250
static void get_common_sync_flags(struct client *client,
tss@7195
   251
				  enum mailbox_sync_flags *flags_r,
tss@7195
   252
				  enum imap_sync_flags *imap_flags_r)
tss@2427
   253
{
tss@7195
   254
	struct client_command_context *cmd;
tss@7195
   255
	unsigned int count = 0, fast_count = 0, noexpunges_count = 0;
tss@7195
   256
tss@7195
   257
	*flags_r = 0;
tss@7195
   258
	*imap_flags_r = 0;
tss@7195
   259
tss@7195
   260
	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
tss@7353
   261
		if (cmd->sync != NULL &&
tss@7353
   262
		    cmd->sync->counter == client->sync_counter) {
tss@7353
   263
			if ((cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0)
tss@7195
   264
				fast_count++;
tss@7353
   265
			if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES)
tss@7195
   266
				noexpunges_count++;
tss@7353
   267
			*flags_r |= cmd->sync->flags;
tss@7353
   268
			*imap_flags_r |= cmd->sync->imap_flags;
tss@7195
   269
			count++;
tss@7195
   270
		}
tss@7195
   271
	}
tss@7421
   272
	i_assert(noexpunges_count == 0 || noexpunges_count == count);
tss@7195
   273
	if (fast_count != count)
tss@7195
   274
		*flags_r &= ~MAILBOX_SYNC_FLAG_FAST;
tss@7195
   275
tss@7195
   276
	i_assert((*flags_r & (MAILBOX_SYNC_AUTO_STOP |
tss@7195
   277
			      MAILBOX_SYNC_FLAG_FIX_INCONSISTENT)) == 0);
tss@7195
   278
}
tss@7195
   279
tss@7195
   280
static bool cmd_sync_client(struct client_command_context *sync_cmd)
tss@7195
   281
{
tss@7195
   282
	struct client *client = sync_cmd->client;
tss@7195
   283
	struct imap_sync_context *ctx;
tss@7195
   284
	enum mailbox_sync_flags flags;
tss@7195
   285
	enum imap_sync_flags imap_flags;
tss@5762
   286
	bool no_newmail;
tss@2427
   287
tss@7195
   288
	/* there may be multiple commands waiting. use their combined flags */
tss@7195
   289
	get_common_sync_flags(client, &flags, &imap_flags);
tss@7195
   290
	client->sync_counter++;
tss@2322
   291
tss@5762
   292
	no_newmail = (client_workarounds & WORKAROUND_DELAY_NEWMAIL) != 0 &&
tss@6454
   293
		(imap_flags & IMAP_SYNC_FLAG_SAFE) == 0;
tss@5762
   294
	if (no_newmail) {
tss@5762
   295
		/* expunges might break the client just as badly as new mail
tss@5762
   296
		   notifications. */
tss@5762
   297
		flags |= MAILBOX_SYNC_FLAG_NO_EXPUNGES;
tss@3358
   298
	}
tss@3204
   299
tss@7195
   300
	client->syncing = TRUE;
tss@2322
   301
tss@7195
   302
	ctx = imap_sync_init(client, client->mailbox, imap_flags, flags);
tss@7195
   303
	ctx->no_newmail = no_newmail;
tss@7195
   304
tss@7195
   305
	/* handle the syncing using sync_cmd. it doesn't actually matter which
tss@7195
   306
	   one of the pending commands it is. */
tss@7195
   307
	sync_cmd->func = cmd_sync_continue;
tss@7195
   308
	sync_cmd->context = ctx;
tss@7195
   309
	sync_cmd->state = CLIENT_COMMAND_STATE_WAIT_OUTPUT;
tss@7195
   310
	if (!cmd_sync_continue(sync_cmd)) {
tss@7195
   311
		o_stream_set_flush_pending(client->output, TRUE);
tss@7195
   312
		return FALSE;
tss@7195
   313
	}
tss@7195
   314
tss@7431
   315
	client_command_free(&sync_cmd);
tss@7195
   316
	(void)cmd_sync_delayed(client);
tss@7195
   317
	return TRUE;
tss@7195
   318
}
tss@7195
   319
tss@7353
   320
static bool
tss@7353
   321
cmd_sync_full(struct client_command_context *cmd, enum mailbox_sync_flags flags,
tss@7353
   322
	      enum imap_sync_flags imap_flags, const char *tagline,
tss@7353
   323
	      imap_sync_callback_t *callback)
tss@7195
   324
{
tss@7195
   325
	struct client *client = cmd->client;
tss@7195
   326
tss@7195
   327
	i_assert(client->output_lock == cmd || client->output_lock == NULL);
tss@7195
   328
tss@7195
   329
	if (cmd->cancel)
tss@7195
   330
		return TRUE;
tss@7195
   331
tss@7195
   332
	if (client->mailbox == NULL) {
tss@7195
   333
		/* no mailbox selected, no point in delaying the sync */
tss@7353
   334
		i_assert(callback == NULL);
tss@7195
   335
		client_send_tagline(cmd, tagline);
tss@7195
   336
		return TRUE;
tss@7195
   337
	}
tss@7195
   338
tss@7353
   339
	cmd->sync = p_new(cmd->pool, struct client_sync_context, 1);
tss@7353
   340
	cmd->sync->counter = client->sync_counter;
tss@7353
   341
	cmd->sync->flags = flags;
tss@7353
   342
	cmd->sync->imap_flags = imap_flags;
tss@7353
   343
	cmd->sync->tagline = p_strdup(cmd->pool, tagline);
tss@7353
   344
	cmd->sync->callback = callback;
tss@7195
   345
	cmd->state = CLIENT_COMMAND_STATE_WAIT_SYNC;
tss@7195
   346
tss@7195
   347
	cmd->func = NULL;
tss@7195
   348
	cmd->context = NULL;
tss@7195
   349
tss@7195
   350
	client->output_lock = NULL;
tss@4939
   351
	if (client->input_lock == cmd)
tss@4939
   352
		client->input_lock = NULL;
tss@7195
   353
	return FALSE;
tss@2322
   354
}
tss@7195
   355
tss@7353
   356
bool cmd_sync(struct client_command_context *cmd, enum mailbox_sync_flags flags,
tss@7353
   357
	      enum imap_sync_flags imap_flags, const char *tagline)
tss@7353
   358
{
tss@7353
   359
	return cmd_sync_full(cmd, flags, imap_flags, tagline, NULL);
tss@7353
   360
}
tss@7353
   361
tss@7353
   362
bool cmd_sync_callback(struct client_command_context *cmd,
tss@7353
   363
		       enum mailbox_sync_flags flags,
tss@7353
   364
		       enum imap_sync_flags imap_flags,
tss@7353
   365
		       imap_sync_callback_t *callback)
tss@7353
   366
{
tss@7353
   367
	return cmd_sync_full(cmd, flags, imap_flags, NULL, callback);
tss@7353
   368
}
tss@7353
   369
tss@7195
   370
static bool cmd_sync_drop_fast(struct client *client)
tss@7195
   371
{
tss@7434
   372
	struct client_command_context *cmd, *prev;
tss@7195
   373
	bool ret = FALSE;
tss@7195
   374
tss@7434
   375
	if (client->command_queue == NULL)
tss@7434
   376
		return FALSE;
tss@7434
   377
tss@7434
   378
	for (cmd = client->command_queue; cmd->next != NULL; cmd = cmd->next) ;
tss@7434
   379
	for (; cmd != NULL; cmd = prev) {
tss@7434
   380
		prev = cmd->next;
tss@7195
   381
tss@7195
   382
		if (cmd->state == CLIENT_COMMAND_STATE_WAIT_SYNC &&
tss@7353
   383
		    (cmd->sync->flags & MAILBOX_SYNC_FLAG_FAST) != 0) {
tss@7353
   384
			if (cmd_finish_sync(cmd)) {
tss@7431
   385
				client_command_free(&cmd);
tss@7353
   386
				ret = TRUE;
tss@7353
   387
			}
tss@7195
   388
		}
tss@7195
   389
	}
tss@7195
   390
	return ret;
tss@7195
   391
}
tss@7195
   392
tss@7195
   393
bool cmd_sync_delayed(struct client *client)
tss@7195
   394
{
tss@7421
   395
	struct client_command_context *cmd, *first_expunge, *first_nonexpunge;
tss@7195
   396
tss@7195
   397
	if (client->output_lock != NULL) {
tss@7195
   398
		/* wait until we can send output to client */
tss@7195
   399
		return FALSE;
tss@7195
   400
	}
tss@7195
   401
tss@7195
   402
	if (client->syncing ||
tss@7195
   403
	    (client->mailbox != NULL &&
tss@7195
   404
	     mailbox_transaction_get_count(client->mailbox) > 0)) {
tss@7195
   405
		/* wait until mailbox can be synced */
tss@7195
   406
		return cmd_sync_drop_fast(client);
tss@7195
   407
	}
tss@7195
   408
tss@7421
   409
	/* separate syncs that can send expunges from those that can't */
tss@7421
   410
	first_expunge = first_nonexpunge = NULL;
tss@7195
   411
	for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
tss@7421
   412
		if (cmd->sync != NULL &&
tss@7421
   413
		    cmd->sync->counter == client->sync_counter) {
tss@7421
   414
			if (cmd->sync->flags & MAILBOX_SYNC_FLAG_NO_EXPUNGES) {
tss@7421
   415
				if (first_nonexpunge == NULL)
tss@7421
   416
					first_nonexpunge = cmd;
tss@7421
   417
			} else {
tss@7421
   418
				if (first_expunge == NULL)
tss@7421
   419
					first_expunge = cmd;
tss@7421
   420
			}
tss@7195
   421
		}
tss@7195
   422
	}
tss@7421
   423
	if (first_expunge != NULL && first_nonexpunge != NULL) {
tss@7421
   424
		/* sync expunges after nonexpunges */
tss@7421
   425
		for (cmd = first_expunge; cmd != NULL; cmd = cmd->next) {
tss@7421
   426
			if (cmd->sync != NULL &&
tss@7421
   427
			    cmd->sync->counter == client->sync_counter &&
tss@7421
   428
			    (cmd->sync->flags &
tss@7421
   429
			     MAILBOX_SYNC_FLAG_NO_EXPUNGES) == 0)
tss@7421
   430
				cmd->sync->counter++;
tss@7421
   431
		}
tss@7421
   432
		first_expunge = NULL;
tss@7421
   433
	}
tss@7421
   434
	cmd = first_nonexpunge != NULL ? first_nonexpunge : first_expunge;
tss@7195
   435
tss@7195
   436
	if (cmd == NULL)
tss@7195
   437
		return cmd_sync_drop_fast(client);
tss@7195
   438
	i_assert(client->mailbox != NULL);
tss@7195
   439
	return cmd_sync_client(cmd);
tss@7195
   440
}