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).
This commit is contained in:
Oswald Buddenhagen 2020-08-05 19:48:58 +02:00
parent f099141e42
commit 42f165ecf7

View File

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