Some future Dovecot version will have "imap-idle" processes where
IDLEing IMAP connections get moved, so the system wouldn't waste so much
memory for all the IDLEing imap processes. A week ago I thought I'd see
how easy it would be to implement this. I got a basic proof of concept
working as a "X-STATE" command.
Save the state:
a x-state
* STATE AQDLW45QdwAAAAMAAABuAQAAAAAAAFAcffYAPHnpFctbjlDbYQAAcEmzCwAA
a OK State exported.
Restore the state:
b x-state AQDLW45QdwAAAAMAAABuAQAAAAAAAFAcffYAPHnpFctbjlDbYQAAcEmzCwAA
b OK State imported.
This could also be used to implement quick session state restoring for
webmails (as suggested by Michael Slusarz).
For getting the imap-idle process there would have to be code that:
* triggers the session saving when process is IDLEing
* figures out what filesystem paths the imap-idle should be looking at
(i.e. paths to selected mailbox's dovecot.index.log file and maybe for
e.g. maildir new/)
* send the session state string, paths and imap connection fd to
imap-idle process via UNIX socket
* implement the actual imap-idle process
* implement a way for imap-idle process to send back the state and
connection fd to restore the imap process
The patch is ugly and still missing many things. Anyway I thought I'd
include it here just in case someone was really eager to continue
implementing it. :) I'm not sure when I'll have time for it.
A full patch would probably have to have some
session_save()/session_restore() functions in lib-storage API. But a
quick and dirty way is possible to implement for v2.1 as well, as long
as some IMAP extensions aren't used (most importantly rfc5267).
diff -r 67e9cb0b06ec src/imap/Makefile.am
--- a/src/imap/Makefile.am Mon Oct 29 16:36:59 2012 +0200
+++ b/src/imap/Makefile.am Mon Oct 29 18:40:43 2012 +0200
@@ -60,7 +60,8 @@
cmd-uid.c \
cmd-unselect.c \
cmd-unsubscribe.c \
- cmd-x-cancel.c
+ cmd-x-cancel.c \
+ cmd-x-state.c
imap_SOURCES = \
$(cmds) \
@@ -74,6 +75,7 @@
imap-search-args.c \
imap-settings.c \
imap-status.c \
+ imap-state.c \
imap-sync.c \
mail-storage-callbacks.c \
main.c
@@ -90,6 +92,7 @@
imap-search-args.h \
imap-settings.h \
imap-status.h \
+ imap-state.h \
imap-sync.h
pkginc_libdir=$(pkgincludedir)
diff -r 67e9cb0b06ec src/imap/imap-commands.c
--- a/src/imap/imap-commands.c Mon Oct 29 16:36:59 2012 +0200
+++ b/src/imap/imap-commands.c Mon Oct 29 18:40:43 2012 +0200
@@ -60,6 +60,7 @@
{ "UID THREAD", cmd_thread, COMMAND_FLAG_BREAKS_SEQS },
{ "UNSELECT", cmd_unselect, COMMAND_FLAG_BREAKS_MAILBOX },
{ "X-CANCEL", cmd_x_cancel, 0 },
+ { "X-STATE", cmd_x_state, COMMAND_FLAG_REQUIRES_SYNC },
{ "XLIST", cmd_list, 0 }
};
#define IMAP_EXT_COMMANDS_COUNT N_ELEMENTS(imap_ext_commands)
diff -r 67e9cb0b06ec src/imap/imap-commands.h
--- a/src/imap/imap-commands.h Mon Oct 29 16:36:59 2012 +0200
+++ b/src/imap/imap-commands.h Mon Oct 29 18:40:43 2012 +0200
@@ -112,6 +112,7 @@
bool cmd_uid_expunge(struct client_command_context *cmd);
bool cmd_unselect(struct client_command_context *cmd);
bool cmd_x_cancel(struct client_command_context *cmd);
+bool cmd_x_state(struct client_command_context *cmd);
/* private: */
bool cmd_list_full(struct client_command_context *cmd, bool lsub);
diff -r 67e9cb0b06ec src/imap/imap-state.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-state.c Mon Oct 29 18:40:43 2012 +0200
@@ -0,0 +1,319 @@
+/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */
+
+#include "imap-common.h"
+#include "crc32.h"
+#include "str.h"
+#include "mail-search-build.h"
+#include "mail-storage-private.h"
+#include "imap-client.h"
+#include "imap-state.h"
+
+enum imap_state_type {
+ IMAP_STATE_TYPE_SESSION_ID,
+ IMAP_STATE_TYPE_MAILBOX
+};
+
+#define BUF_ADD(buf, data) \
+ buffer_append(buf, &(data), sizeof(data))
+
+static bool
+imap_state_export_mailbox(struct client *client, buffer_t *output)
+{
+ struct mailbox_status status;
+ struct mailbox_metadata metadata;
+ struct mail_namespace *ns = mailbox_get_namespace(client->mailbox);
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ uint32_t crc = 0;
+ bool ret = TRUE;
+
+ if (strcmp(client->mailbox->storage->name, "virtual") == 0 ||
+ strcmp(client->mailbox->storage->name, "imapc") == 0 ||
+ strcmp(client->mailbox->storage->name, "pop3c") == 0)
+ return FALSE;
+
+ if (mailbox_get_metadata(client->mailbox, MAILBOX_METADATA_GUID,
+ &metadata) < 0)
+ return FALSE;
+
+ mailbox_get_open_status(client->mailbox,
+ STATUS_UIDVALIDITY | STATUS_MESSAGES |
+ STATUS_HIGHESTMODSEQ, &status);
+
+ if (status.highest_modseq != client->sync_last_full_modseq)
+ return FALSE;
+
+ buffer_append_c(output, IMAP_STATE_TYPE_MAILBOX);
+ buffer_append_c(output, client->mailbox_examined ? 1 : 0);
+ BUF_ADD(output, status.uidvalidity);
+ BUF_ADD(output, status.uidnext);
+ BUF_ADD(output, status.messages);
+ BUF_ADD(output, client->sync_last_full_modseq);
+
+ /* we're now basically done, but just in case there's a bug add a
+ checksum of the currently existing UIDs and verify it when
+ importing */
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(client->mailbox, 0);
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ while (mailbox_search_next(search_ctx, &mail))
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ if (mailbox_search_deinit(&search_ctx) < 0)
+ ret = FALSE;
+ (void)mailbox_transaction_commit(&trans);
+
+ BUF_ADD(output, crc);
+ buffer_append(output, ns->prefix, strlen(ns->prefix)+1);
+ BUF_ADD(output, metadata.guid);
+ return ret;
+}
+
+bool imap_state_export(struct client *client, buffer_t *output)
+{
+ struct client_command_context *cmd;
+
+ for (cmd = client->command_queue; cmd != NULL; cmd = cmd->next) {
+ if (strcasecmp(cmd->name, "X-STATE") != 0 &&
+ strcasecmp(cmd->name, "IDLE") != 0)
+ return FALSE;
+ }
+
+ if (client->enabled_features != 0 ||
+ client->tls_compression)
+ return FALSE;
+ if (array_is_created(&client->search_saved_uidset) ||
+ array_is_created(&client->search_updates))
+ return FALSE;
+
+ if (client->mailbox != NULL) {
+ if (!imap_state_export_mailbox(client, output))
+ return FALSE;
+ }
+
+ buffer_append_c(output, IMAP_STATE_TYPE_SESSION_ID);
+ if (client->session_id == NULL)
+ buffer_append_c(output, '\0');
+ else {
+ buffer_append(output, client->session_id,
+ strlen(client->session_id)+1);
+ }
+
+ // FIXME: keywords, recent flags, id_logged
+ return TRUE;
+}
+
+static int
+import_string(const unsigned char **data, size_t *size, const char **str_r)
+{
+ const unsigned char *p;
+ unsigned int len;
+
+ p = memchr(*data, '\0', *size);
+ if (p == NULL)
+ return -1;
+ len = p - *data + 1;
+
+ *str_r = (const void *)*data;
+ *data += len;
+ *size -= len;
+ return 0;
+}
+
+static int
+import_uint8(const unsigned char **data, size_t *size, uint8_t *num_r)
+{
+ if (*size == 0)
+ return -1;
+ *num_r = **data;
+ *data += 1;
+ *size -= 1;
+ return 0;
+}
+
+static int
+import_uint32(const unsigned char **data, size_t *size, uint32_t *num_r)
+{
+ if (*size < sizeof(*num_r))
+ return -1;
+ memcpy(num_r, *data, sizeof(*num_r));
+ *data += sizeof(*num_r);
+ *size -= sizeof(*num_r);
+ return 0;
+}
+
+static int
+import_uint64(const unsigned char **data, size_t *size, uint64_t *num_r)
+{
+ if (*size < sizeof(*num_r))
+ return -1;
+ memcpy(num_r, *data, sizeof(*num_r));
+ *data += sizeof(*num_r);
+ *size -= sizeof(*num_r);
+ return 0;
+}
+
+static int
+send_expunges(struct client *client, uint32_t uidnext,
+ uint64_t modseq, uint32_t orig_crc)
+{
+ struct mailbox_status status;
+ struct mailbox_transaction_context *trans;
+ struct mail_search_args *search_args;
+ struct mail_search_context *search_ctx;
+ struct mail *mail;
+ uint32_t crc = 0, seq, expunged_uid;
+ ARRAY_TYPE(seq_range) uids_filter, expunged_uids;
+ ARRAY_TYPE(uint32_t) expunged_seqs;
+ struct seq_range_iter iter;
+ const uint32_t *seqs;
+ unsigned int i, count, n = 0;
+ string_t *str;
+ int ret = 0;
+
+ mailbox_get_open_status(client->mailbox,
+ STATUS_UIDVALIDITY | STATUS_UIDNEXT |
+ STATUS_MESSAGES | STATUS_KEYWORDS |
+ STATUS_HIGHESTMODSEQ, &status);
+ if (status.uidvalidity != client->uidvalidity)
+ return -1;
+
+ t_array_init(&uids_filter, 1);
+ t_array_init(&expunged_uids, 128);
+ seq_range_array_add_range(&uids_filter, 1, (uint32_t)-1);
+ if (!mailbox_get_expunged_uids(client->mailbox, modseq, &uids_filter,
+ &expunged_uids))
+ return -1;
+ seq_range_array_iter_init(&iter, &expunged_uids);
+
+ search_args = mail_search_build_init();
+ mail_search_build_add_all(search_args);
+
+ trans = mailbox_transaction_begin(client->mailbox, 0);
+ search_ctx = mailbox_search_init(trans, search_args, NULL, 0, NULL);
+ mail_search_args_unref(&search_args);
+
+ t_array_init(&expunged_seqs, array_count(&expunged_uids)+1);
+ while (mailbox_search_next(search_ctx, &mail)) {
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid) &&
+ expunged_uid < mail->uid) {
+ seq = mail->seq+n;
+ array_append(&expunged_seqs, &seq, 1);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ n++;
+ }
+ crc = crc32_data_more(crc, &mail->uid, sizeof(mail->uid));
+ }
+ while (seq_range_array_iter_nth(&iter, n, &expunged_uid)) {
+ seq = status.messages+n;
+ array_append(&expunged_seqs, &seq, 1);
+ crc = crc32_data_more(crc, &expunged_uid,
+ sizeof(expunged_uid));
+ n++;
+ }
+
+ if (mailbox_search_deinit(&search_ctx) < 0)
+ ret = -1;
+ (void)mailbox_transaction_commit(&trans);
+ if (ret < 0)
+ return -1;
+
+ seqs = array_get(&expunged_seqs, &count);
+ // FIXME: broken if new messages have been added
+ if (client->messages_count != status.messages + count)
+ return -1;
+ if (crc != orig_crc)
+ return -1;
+
+ str = t_str_new(32);
+ for (i = count; i > 0; i--) {
+ str_truncate(str, 0);
+ str_printfa(str, "* %u EXPUNGE", seqs[i-1]);
+ client_send_line(client, str_c(str));
+ }
+ client->messages_count -= count;
+ return 0;
+}
+
+static int
+import_mailbox(struct client *client,
+ const unsigned char **data, size_t *size)
+{
+ struct mail_namespace *ns;
+ uint32_t uidvalidity, uidnext, messages_count, crc;
+ uint64_t modseq;
+ uint8_t examined;
+ const char *ns_prefix;
+ guid_128_t guid;
+ enum mailbox_flags flags = 0;
+
+ if (import_uint8(data, size, &examined) < 0 ||
+ import_uint32(data, size, &uidvalidity) < 0 ||
+ import_uint32(data, size, &uidnext) < 0 ||
+ import_uint32(data, size, &messages_count) < 0 ||
+ import_uint64(data, size, &modseq) < 0 ||
+ import_uint32(data, size, &crc) < 0 ||
+ import_string(data, size, &ns_prefix) < 0)
+ return 0;
+ if (*size < sizeof(guid))
+ return 0;
+ memcpy(guid, *data, sizeof(guid));
+ *data += sizeof(guid);
+ *size -= sizeof(guid);
+
+ ns = mail_namespace_find_prefix(client->user->namespaces, ns_prefix);
+ if (ns == NULL)
+ return 0;
+
+ if (examined)
+ flags |= MAILBOX_FLAG_READONLY;
+ else
+ flags |= MAILBOX_FLAG_DROP_RECENT;
+ client->mailbox = mailbox_alloc_guid(ns->list, guid, flags);
+ if (mailbox_sync(client->mailbox, 0) < 0)
+ return -1;
+ client->select_counter++;
+ client->mailbox_examined = examined;
+ client->messages_count = messages_count;
+ //FIXME:client->recent_count = recent_count;
+ client->uidvalidity = uidvalidity;
+
+ if (send_expunges(client, uidnext, modseq, crc) < 0)
+ return -1;
+ return 1;
+}
+
+int imap_state_import(struct client *client,
+ const unsigned char *data, size_t size)
+{
+ enum imap_state_type type;
+ const char *str;
+ int ret;
+
+ i_assert(client->mailbox == NULL);
+
+ while (size > 0) {
+ type = data[0]; data++; size--;
+ switch (type) {
+ case IMAP_STATE_TYPE_SESSION_ID:
+ // FIXME: untrusted commands shouldn't be able to set session ID
+ if (import_string(&data, &size, &str) < 0)
+ return 0;
+ client->session_id = p_strdup_empty(client->pool, str);
+ break;
+ case IMAP_STATE_TYPE_MAILBOX:
+ if ((ret = import_mailbox(client, &data, &size)) <= 0)
+ return ret;
+ break;
+ default:
+ return 0;
+ }
+ }
+ return 1;
+}
diff -r 67e9cb0b06ec src/imap/imap-state.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/imap/imap-state.h Mon Oct 29 18:40:43 2012 +0200
@@ -0,0 +1,11 @@
+#ifndef IMAP_STATE_H
+#define IMAP_STATE_H
+
+/* Export the IMAP client state to the given buffer. Returns TRUE if ok,
+ FALSE if state couldn't be exported. */
+bool imap_state_export(struct client *client, buffer_t *output);
+/* Returns 1 if ok, 0 if state was corrupted, -1 if other error. */
+int imap_state_import(struct client *client,
+ const unsigned char *data, size_t size);
+
+#endif