From 0de9f2f1e46ba55b1521e0a282d1dfb09d4e7bcd Mon Sep 17 00:00:00 2001 From: Brendan Cully Date: Sat, 10 Dec 2005 07:06:44 +0000 Subject: [PATCH] Set up a command pipeline. Currently everything still runs a single command from start to finish before moving on, but this creates the infrastructure to pipeline mailbox polling calls. Bitter experience at the end of a flaky wireless link teaches me that this can be useful. --- imap/browse.c | 21 ++++---- imap/command.c | 117 +++++++++++++++++++++++++++----------------- imap/imap.c | 20 ++++---- imap/imap_private.h | 14 ++++-- imap/util.c | 12 +++-- 5 files changed, 114 insertions(+), 70 deletions(-) diff --git a/imap/browse.c b/imap/browse.c index 4a914f19..fb5d8b3b 100644 --- a/imap/browse.c +++ b/imap/browse.c @@ -98,6 +98,7 @@ int imap_browse (char* path, struct browser_state* state) /* skip check for parents when at the root */ if (mx.mbox && mx.mbox[0] != '\0') { + int rc; imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox)); imap_munge_mbox_name (buf, sizeof (buf), mbox); imap_unquote_string(buf); /* As kludgy as it gets */ @@ -115,8 +116,8 @@ int imap_browse (char* path, struct browser_state* state) imap_cmd_start (idata, buf); do { - if (imap_parse_list_response (idata, &cur_folder, &noselect, - &noinferiors, &idata->delim) != 0) + if ((rc = imap_parse_list_response (idata, &cur_folder, &noselect, + &noinferiors, &idata->delim)) == IMAP_CMD_BAD) goto fail; if (cur_folder) @@ -131,7 +132,7 @@ int imap_browse (char* path, struct browser_state* state) } } } - while (ascii_strncmp (idata->buf, idata->cmd.seq, SEQLEN)); + while (rc == IMAP_CMD_CONTINUE); } /* if we're descending a folder, mark it as current in browser_state */ @@ -365,6 +366,7 @@ static int browse_add_list_result (IMAP_DATA* idata, const char* cmd, int noselect; int noinferiors; IMAP_MBOX mx; + int rc; if (imap_parse_path (state->folder, &mx)) { @@ -377,8 +379,8 @@ static int browse_add_list_result (IMAP_DATA* idata, const char* cmd, do { - if (imap_parse_list_response(idata, &name, &noselect, &noinferiors, - &idata->delim) != 0) + if ((rc = imap_parse_list_response(idata, &name, &noselect, &noinferiors, + &idata->delim)) == IMAP_CMD_BAD) { FREE (&mx.mbox); return -1; @@ -395,7 +397,7 @@ static int browse_add_list_result (IMAP_DATA* idata, const char* cmd, isparent); } } - while ((ascii_strncmp (idata->buf, idata->cmd.seq, SEQLEN) != 0)); + while (rc == IMAP_CMD_CONTINUE); FREE (&mx.mbox); return 0; @@ -582,6 +584,7 @@ static int browse_verify_namespace (IMAP_DATA* idata, int i = 0; char *name; char delim; + int rc; for (i = 0; i < nns; i++, nsi++) { @@ -602,12 +605,12 @@ static int browse_verify_namespace (IMAP_DATA* idata, nsi->home_namespace = 0; do { - if (imap_parse_list_response(idata, &name, &nsi->noselect, - &nsi->noinferiors, &delim) != 0) + if ((rc = imap_parse_list_response(idata, &name, &nsi->noselect, + &nsi->noinferiors, &delim)) == IMAP_CMD_BAD) return -1; nsi->listable |= (name != NULL); } - while ((ascii_strncmp (idata->buf, idata->cmd.seq, SEQLEN) != 0)); + while (rc == IMAP_CMD_CONTINUE); } return 0; diff --git a/imap/command.c b/imap/command.c index a4741cb9..f2986d2f 100644 --- a/imap/command.c +++ b/imap/command.c @@ -36,15 +36,16 @@ #define IMAP_CMD_BUFSIZE 512 /* forward declarations */ +static IMAP_COMMAND* cmd_new (IMAP_DATA* idata); +static int cmd_status (const char *s); static void cmd_handle_fatal (IMAP_DATA* idata); static int cmd_handle_untagged (IMAP_DATA* idata); -static void cmd_make_sequence (IMAP_DATA* idata); static void cmd_parse_capabilities (IMAP_DATA* idata, char* s); static void cmd_parse_expunge (IMAP_DATA* idata, const char* s); static void cmd_parse_lsub (IMAP_DATA* idata, char* s); static void cmd_parse_fetch (IMAP_DATA* idata, char* s); -static void cmd_parse_myrights (IMAP_DATA* idata, char* s); -static void cmd_parse_search (IMAP_DATA* idata, char* s); +static void cmd_parse_myrights (IMAP_DATA* idata, const char* s); +static void cmd_parse_search (IMAP_DATA* idata, const char* s); static char *Capabilities[] = { "IMAP4", @@ -64,8 +65,9 @@ static char *Capabilities[] = { /* imap_cmd_start: Given an IMAP command, send it to the server. * Currently a minor convenience, but helps to route all IMAP commands * through a single interface. */ -int imap_cmd_start (IMAP_DATA* idata, const char* cmd) +int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr) { + IMAP_COMMAND* cmd; char* out; int outlen; int rc; @@ -76,11 +78,13 @@ int imap_cmd_start (IMAP_DATA* idata, const char* cmd) return IMAP_CMD_BAD; } - cmd_make_sequence (idata); + if (!(cmd = cmd_new (idata))) + return IMAP_CMD_BAD; + /* seq, space, cmd, \r\n\0 */ - outlen = strlen (idata->cmd.seq) + strlen (cmd) + 4; + outlen = strlen (cmd->seq) + strlen (cmdstr) + 4; out = (char*) safe_malloc (outlen); - snprintf (out, outlen, "%s %s\r\n", idata->cmd.seq, cmd); + snprintf (out, outlen, "%s %s\r\n", cmd->seq, cmdstr); rc = mutt_socket_write (idata->conn, out); @@ -95,9 +99,9 @@ int imap_cmd_start (IMAP_DATA* idata, const char* cmd) * large!). */ int imap_cmd_step (IMAP_DATA* idata) { - IMAP_COMMAND* cmd = &idata->cmd; size_t len = 0; int c; + int rc; if (idata->status == IMAP_FATAL) { @@ -143,7 +147,7 @@ int imap_cmd_step (IMAP_DATA* idata) dprint (3, (debugfile, "imap_cmd_step: shrank buffer to %u bytes\n", idata->blen)); } - idata->lastread = time(NULL); + idata->lastread = time (NULL); /* handle untagged messages. The caller still gets its shot afterwards. */ if (!ascii_strncmp (idata->buf, "* ", 2) && @@ -154,22 +158,25 @@ int imap_cmd_step (IMAP_DATA* idata) if (idata->buf[0] == '+') return IMAP_CMD_RESPOND; - /* tagged completion code */ - if (!ascii_strncmp (idata->buf, cmd->seq, SEQLEN)) + /* tagged completion code. TODO: I believe commands will always be completed + * in order, but will have to double-check when I have net again */ + rc = IMAP_CMD_CONTINUE; + if (!ascii_strncmp (idata->buf, idata->cmds[idata->lastcmd].seq, SEQLEN)) { + idata->cmds[idata->lastcmd].state = cmd_status (idata->buf); + idata->lastcmd = (idata->lastcmd + 1) % IMAP_PIPELINE_DEPTH; + if (idata->lastcmd == idata->nextcmd) + rc = cmd_status (idata->buf); imap_cmd_finish (idata); - return imap_code (idata->buf) ? IMAP_CMD_OK : IMAP_CMD_NO; } - return IMAP_CMD_CONTINUE; + return rc; } /* imap_code: returns 1 if the command result was OK, or 0 if NO or BAD */ int imap_code (const char *s) { - s += SEQLEN; - SKIPWS (s); - return (ascii_strncasecmp ("OK", s, 2) == 0); + return cmd_status (s) == IMAP_CMD_OK; } /* imap_exec: execute a command, and wait for the response from the server. @@ -180,8 +187,9 @@ int imap_code (const char *s) * IMAP_CMD_PASS: command contains a password. Suppress logging. * Return 0 on success, -1 on Failure, -2 on OK Failure */ -int imap_exec (IMAP_DATA* idata, const char* cmd, int flags) +int imap_exec (IMAP_DATA* idata, const char* cmdstr, int flags) { + IMAP_COMMAND* cmd; char* out; int outlen; int rc; @@ -192,12 +200,13 @@ int imap_exec (IMAP_DATA* idata, const char* cmd, int flags) return -1; } - /* create sequence for command */ - cmd_make_sequence (idata); + if (!(cmd = cmd_new (idata))) + return -1; + /* seq, space, cmd, \r\n\0 */ - outlen = strlen (idata->cmd.seq) + strlen (cmd) + 4; + outlen = strlen (cmd->seq) + strlen (cmdstr) + 4; out = (char*) safe_malloc (outlen); - snprintf (out, outlen, "%s %s\r\n", idata->cmd.seq, cmd); + snprintf (out, outlen, "%s %s\r\n", cmd->seq, cmdstr); rc = mutt_socket_write_d (idata->conn, out, flags & IMAP_CMD_PASS ? IMAP_LOG_PASS : IMAP_LOG_CMD); @@ -228,16 +237,6 @@ int imap_exec (IMAP_DATA* idata, const char* cmd, int flags) return 0; } -/* imap_cmd_running: Returns whether an IMAP command is in progress. */ -int imap_cmd_running (IMAP_DATA* idata) -{ - if (idata->cmd.state == IMAP_CMD_CONTINUE || - idata->cmd.state == IMAP_CMD_RESPOND) - return 1; - - return 0; -} - /* imap_cmd_finish: Attempts to perform cleanup (eg fetch new mail if * detected, do expunge). Called automatically by imap_cmd_step, but * may be called at any time. Called by imap_check_mailbox just before @@ -284,6 +283,43 @@ void imap_cmd_finish (IMAP_DATA* idata) idata->status = 0; } +/* sets up a new command control block and adds it to the queue. + * Returns NULL if the pipeline is full. */ +static IMAP_COMMAND* cmd_new (IMAP_DATA* idata) +{ + IMAP_COMMAND* cmd; + + if ((idata->nextcmd + 1) % IMAP_PIPELINE_DEPTH == idata->lastcmd) + { + dprint (2, (debugfile, "cmd_new: IMAP command queue full\n")); + return NULL; + } + + cmd = idata->cmds + idata->nextcmd; + idata->nextcmd = (idata->nextcmd + 1) % IMAP_PIPELINE_DEPTH; + + snprintf (cmd->seq, sizeof (cmd->seq), "a%04u", idata->seqno++); + if (idata->seqno > 9999) + idata->seqno = 0; + + cmd->state = IMAP_CMD_NEW; + + return cmd; +} + +/* parse response line for tagged OK/NO/BAD */ +static int cmd_status (const char *s) +{ + s = imap_next_word((char*)s); + + if (!ascii_strncasecmp("OK", s, 2)) + return IMAP_CMD_OK; + if (!ascii_strncasecmp("NO", s, 3)) + return IMAP_CMD_NO; + + return IMAP_CMD_BAD; +} + /* cmd_handle_fatal: when IMAP_DATA is in fatal state, do what we can */ static void cmd_handle_fatal (IMAP_DATA* idata) { @@ -400,15 +436,6 @@ static int cmd_handle_untagged (IMAP_DATA* idata) return 0; } -/* cmd_make_sequence: make a tag suitable for starting an IMAP command */ -static void cmd_make_sequence (IMAP_DATA* idata) -{ - snprintf (idata->cmd.seq, sizeof (idata->cmd.seq), "a%04u", idata->seqno++); - - if (idata->seqno > 9999) - idata->seqno = 0; -} - /* cmd_parse_capabilities: set capability bits according to CAPABILITY * response */ static void cmd_parse_capabilities (IMAP_DATA* idata, char* s) @@ -583,12 +610,12 @@ static void cmd_parse_lsub (IMAP_DATA* idata, char* s) } /* cmd_parse_myrights: set rights bits according to MYRIGHTS response */ -static void cmd_parse_myrights (IMAP_DATA* idata, char* s) +static void cmd_parse_myrights (IMAP_DATA* idata, const char* s) { dprint (2, (debugfile, "Handling MYRIGHTS\n")); - s = imap_next_word (s); - s = imap_next_word (s); + s = imap_next_word ((char*)s); + s = imap_next_word ((char*)s); /* zero out current rights set */ memset (idata->rights, 0, sizeof (idata->rights)); @@ -645,14 +672,14 @@ static int uid2msgno (IMAP_DATA* idata, unsigned int uid) } /* cmd_parse_search: store SEARCH response for later use */ -static void cmd_parse_search (IMAP_DATA* idata, char* s) +static void cmd_parse_search (IMAP_DATA* idata, const char* s) { unsigned int uid; int msgno; dprint (2, (debugfile, "Handling SEARCH\n")); - while ((s = imap_next_word (s)) && *s != '\0') + while ((s = imap_next_word ((char*)s)) && *s != '\0') { uid = atoi (s); msgno = uid2msgno (idata, uid); diff --git a/imap/imap.c b/imap/imap.c index ebd69d5f..89b2af2c 100644 --- a/imap/imap.c +++ b/imap/imap.c @@ -436,7 +436,8 @@ int imap_open_connection (IMAP_DATA* idata) idata->state = IMAP_CONNECTED; - if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) { + if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) + { mutt_socket_close (idata->conn); idata->state = IMAP_DISCONNECTED; return -1; @@ -1460,9 +1461,9 @@ int imap_parse_list_response(IMAP_DATA* idata, char **name, int *noselect, rc = imap_cmd_step (idata); if (rc == IMAP_CMD_OK) - return 0; + return rc; if (rc != IMAP_CMD_CONTINUE) - return -1; + return IMAP_CMD_BAD; s = imap_next_word (idata->buf); if ((ascii_strncasecmp ("LIST", s, 4) == 0) || @@ -1508,16 +1509,16 @@ int imap_parse_list_response(IMAP_DATA* idata, char **name, int *noselect, if (s && *s == '{') /* Literal */ { if (imap_get_literal_count(idata->buf, &bytes) < 0) - return -1; + return IMAP_CMD_BAD; if (imap_cmd_step (idata) != IMAP_CMD_CONTINUE) - return -1; + return IMAP_CMD_BAD; *name = idata->buf; } else *name = s; } - return 0; + return IMAP_CMD_CONTINUE; } int imap_subscribe (char *path, int subscribe) @@ -1654,6 +1655,7 @@ int imap_complete(char* dest, size_t dlen, char* path) { int clen, matchlen = 0; int completions = 0; IMAP_MBOX mx; + int rc; if (imap_parse_path (path, &mx) || !mx.mbox) { @@ -1688,8 +1690,8 @@ int imap_complete(char* dest, size_t dlen, char* path) { strfcpy (completion, NONULL(mx.mbox), sizeof(completion)); do { - if (imap_parse_list_response(idata, &list_word, &noselect, &noinferiors, - &delim)) + if ((rc = imap_parse_list_response(idata, &list_word, &noselect, &noinferiors, + &delim)) == IMAP_CMD_BAD) break; if (list_word) @@ -1718,7 +1720,7 @@ int imap_complete(char* dest, size_t dlen, char* path) { completions++; } } - while (ascii_strncmp(idata->cmd.seq, idata->buf, SEQLEN)); + while (rc == IMAP_CMD_CONTINUE); if (completions) { diff --git a/imap/imap_private.h b/imap/imap_private.h index d4369852..8dfc2acf 100644 --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -44,10 +44,15 @@ #define IMAP_CMD_CONTINUE (1) /* + */ #define IMAP_CMD_RESPOND (2) +/* IMAP_COMMAND.state additions */ +#define IMAP_CMD_NEW (3) /* number of entries in the hash table */ #define IMAP_CACHE_LEN 10 +/* number of commands that can be batched into a single request */ +#define IMAP_PIPELINE_DEPTH 10 + #define SEQLEN 5 #define IMAP_REOPEN_ALLOW (1<<0) @@ -165,10 +170,13 @@ typedef struct unsigned char capabilities[(CAPMAX + 7)/8]; unsigned int seqno; time_t lastread; /* last time we read a command for the server */ - /* who knows, one day we may run multiple commands in parallel */ - IMAP_COMMAND cmd; char* buf; - size_t blen; + unsigned int blen; + + /* command queue */ + IMAP_COMMAND cmds[IMAP_PIPELINE_DEPTH]; + int nextcmd; + int lastcmd; /* The following data is all specific to the currently SELECTED mbox */ char delim; diff --git a/imap/util.c b/imap/util.c index 5af39731..8d5da196 100644 --- a/imap/util.c +++ b/imap/util.c @@ -245,12 +245,16 @@ void imap_error (const char *where, const char *msg) /* imap_new_idata: Allocate and initialise a new IMAP_DATA structure. * Returns NULL on failure (no mem) */ -IMAP_DATA* imap_new_idata (void) { - return safe_calloc (1, sizeof (IMAP_DATA)); +IMAP_DATA* imap_new_idata (void) +{ + IMAP_DATA* idata = safe_calloc (1, sizeof (IMAP_DATA)); + + return idata; } /* imap_free_idata: Release and clear storage in an IMAP_DATA structure. */ -void imap_free_idata (IMAP_DATA** idata) { +void imap_free_idata (IMAP_DATA** idata) +{ if (!idata) return; @@ -321,7 +325,7 @@ int imap_get_literal_count(const char *buf, long *bytes) * the qualifier message. Used by imap_copy_message for TRYCREATE */ char* imap_get_qualifier (char* buf) { - char *s = buf; + const char *s = buf; /* skip tag */ s = imap_next_word (s); -- 2.40.0