isync

mailbox synchronization program
git clone https://git.code.sf.net/p/isync/isync
Log | Files | Refs | README | LICENSE

commit 42f165ecf72028bd5b41211537d2a64e117be82f
parent f099141e4285173f79725635f31c8b0e0ce710b6
Author: Oswald Buddenhagen <ossi@users.sf.net>
Date:   Wed,  5 Aug 2020 19:48:58 +0200

fix UIDNEXT query vs. concurrent imap_fetch_msg()

the uidnext query following message stores can be interleaved with
message fetches. that means that we cannot rely on the 1st command in
flight being that query. but instead of iterating over all commands in
flight, move the uidnext query flag to imap_store (and make sure to
check for the presence of a message body before testing it) - this
avoids the loop and an extra byte in every command.

this also makes it clear that the query is mutually exclusive with
loading messages (the untagged responses are not distinguishable).

Diffstat:
Msrc/drv_imap.c | 30+++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/src/drv_imap.c b/src/drv_imap.c @@ -113,6 +113,8 @@ struct imap_store { enum { SST_BAD, SST_HALF, SST_GOOD } state; /* trash folder's existence is not confirmed yet */ enum { TrashUnknown, TrashChecking, TrashKnown } trashnc; + // What kind of BODY-less FETCH response we're expecting. + enum { FetchNone, FetchMsgs, FetchUidNext } fetch_sts; uint got_namespace:1; uint has_forwarded:1; char delimiter[2]; /* hierarchy delimiter */ @@ -174,7 +176,6 @@ struct imap_cmd { char to_trash; /* we are storing to trash, not current. */ char create; /* create the mailbox if we get an error which suggests so. */ char failok; /* Don't complain about NO response. */ - char lastuid; /* querying the last UID in the mailbox. */ } param; }; @@ -1144,15 +1145,11 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED ) if (!uid) { // Ignore async flag updates for now. status &= ~(M_FLAGS | M_RECENT); - } else if ((cmdp = ctx->in_progress) && cmdp->param.lastuid) { - // Workaround for server not sending UIDNEXT and/or APPENDUID. - ctx->uidnext = uid + 1; } else if (status & M_BODY) { for (cmdp = ctx->in_progress; cmdp; cmdp = cmdp->next) if (cmdp->param.uid == uid) goto gotuid; - error( "IMAP error: unexpected FETCH response (UID %u)\n", uid ); - return LIST_BAD; + goto badrsp; gotuid: msgdata = ((imap_cmd_fetch_msg_t *)cmdp)->msg_data; msgdata->data = body->val; @@ -1162,7 +1159,10 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED ) if (status & M_FLAGS) msgdata->flags = mask; status &= ~(M_FLAGS | M_RECENT | M_BODY | M_DATE); - } else { + } else if (ctx->fetch_sts == FetchUidNext) { + // Workaround for server not sending UIDNEXT and/or APPENDUID. + ctx->uidnext = uid + 1; + } else if (ctx->fetch_sts == FetchMsgs) { cur = nfcalloc( sizeof(*cur) ); *ctx->msgapp = &cur->gen; ctx->msgapp = &cur->gen.next; @@ -1175,6 +1175,10 @@ parse_fetch_rsp( imap_store_t *ctx, list_t *list, char *s ATTR_UNUSED ) if (tuid) memcpy( cur->gen.tuid, tuid, TUIDL ); status &= ~(M_FLAGS | M_RECENT | M_SIZE | M_HEADER); + } else { + badrsp: + error( "IMAP error: unexpected FETCH response (UID %u)\n", uid ); + return LIST_BAD; } if (status) { @@ -2536,8 +2540,9 @@ imap_open_box_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response ) return; } + assert( ctx->fetch_sts == FetchNone ); + ctx->fetch_sts = FetchUidNext; INIT_IMAP_CMD(imap_cmd_open_box_t, cmd, cmdp->callback, cmdp->callback_aux) - cmd->gen.param.lastuid = 1; imap_exec( ctx, &cmd->gen, imap_open_box_p3, "UID FETCH * (UID)" ); } @@ -2547,6 +2552,7 @@ imap_open_box_p3( imap_store_t *ctx, imap_cmd_t *gcmd, int response ) { imap_cmd_open_box_t *cmdp = (imap_cmd_open_box_t *)gcmd; + ctx->fetch_sts = FetchNone; if (!ctx->uidnext) { if (ctx->total_msgs) { error( "IMAP error: querying server for highest UID failed\n" ); @@ -2714,6 +2720,9 @@ imap_load_box( store_t *gctx, uint minuid, uint maxuid, uint finduid, uint pairu free( excs.data ); cb( DRV_OK, NULL, 0, 0, aux ); } else { + assert( ctx->fetch_sts == FetchNone ); + ctx->fetch_sts = FetchMsgs; + INIT_REFCOUNTED_STATE(imap_load_box_state_t, sts, cb, aux) for (uint i = 0; i < excs.size; ) { for (int bl = 0; i < excs.size && bl < 960; i++) { @@ -2821,6 +2830,7 @@ static void imap_submit_load_p3( imap_store_t *ctx, imap_load_box_state_t *sts ) { DONE_REFCOUNTED_STATE_ARGS(sts, { + ctx->fetch_sts = FetchNone; if (sts->gen.ret_val == DRV_OK) imap_sort_msgs( ctx ); }, ctx->msgs, ctx->total_msgs, ctx->recent_msgs) @@ -3126,11 +3136,12 @@ imap_find_new_msgs_p2( imap_store_t *ctx, imap_cmd_t *gcmd, int response ) // We appended messages, so we need to re-query UIDNEXT. ctx->uidnext = 0; + assert( ctx->fetch_sts == FetchNone ); + ctx->fetch_sts = FetchUidNext; INIT_IMAP_CMD(imap_cmd_find_new_t, cmd, cmdp->callback, cmdp->callback_aux) cmd->out_msgs = cmdp->out_msgs; cmd->uid = cmdp->uid; - cmd->gen.param.lastuid = 1; imap_exec( ctx, &cmd->gen, imap_find_new_msgs_p3, "UID FETCH * (UID)" ); } @@ -3141,6 +3152,7 @@ imap_find_new_msgs_p3( imap_store_t *ctx, imap_cmd_t *gcmd, int response ) imap_cmd_find_new_t *cmdp = (imap_cmd_find_new_t *)gcmd; imap_cmd_find_new_t *cmd; + ctx->fetch_sts = FetchNone; if (response != RESP_OK) { imap_find_new_msgs_p4( ctx, gcmd, response ); return;