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