diff src/pop3/commands.c @ 2421:d141e1bfdd63 HEAD

We never do blocking reads/writes to network anymore. Changed imap and pop3 processes to use a single I/O loop. Not much tested yet, and currently LIST/LSUB may eat too much memory and APPEND eats all CPU.
author Timo Sirainen <tss@iki.fi>
date Sun, 15 Aug 2004 06:40:30 +0300
parents ec1dac19cb06
children a2e2c76021b9
line wrap: on
line diff
--- a/src/pop3/commands.c	Sun Aug 15 05:54:47 2004 +0300
+++ b/src/pop3/commands.c	Sun Aug 15 06:40:30 2004 +0300
@@ -111,23 +111,48 @@
 	return TRUE;
 }
 
+struct cmd_list_context {
+	unsigned int msgnum;
+};
+
+static void cmd_list_callback(struct client *client)
+{
+	struct cmd_list_context *ctx = client->cmd_context;
+	int ret;
+
+	for (; ctx->msgnum != client->messages_count; ctx->msgnum++) {
+		if (client->deleted) {
+			if (client->deleted_bitmask[ctx->msgnum / CHAR_BIT] &
+			    (1 << (ctx->msgnum % CHAR_BIT)))
+				continue;
+		}
+		ret = client_send_line(client, "%u %"PRIuUOFF_T,
+				       ctx->msgnum+1,
+				       client->message_sizes[ctx->msgnum]);
+		if (ret < 0)
+			break;
+		if (ret == 0)
+			return;
+	}
+
+	client_send_line(client, ".");
+
+	i_free(ctx);
+	client->cmd = NULL;
+}
+
 static int cmd_list(struct client *client, const char *args)
 {
-	unsigned int i;
+        struct cmd_list_context *ctx;
 
 	if (*args == '\0') {
+		ctx = i_new(struct cmd_list_context, 1);
 		client_send_line(client, "+OK %u messages:",
 				 client->messages_count - client->deleted_count);
-		for (i = 0; i < client->messages_count; i++) {
-			if (client->deleted) {
-				if (client->deleted_bitmask[i / CHAR_BIT] &
-				    (1 << (i % CHAR_BIT)))
-					continue;
-			}
-			client_send_line(client, "%u %"PRIuUOFF_T,
-					 i+1, client->message_sizes[i]);
-		}
-		client_send_line(client, ".");
+
+		client->cmd = cmd_list_callback;
+		client->cmd_context = ctx;
+		cmd_list_callback(client);
 	} else {
 		unsigned int msgnum;
 
@@ -198,127 +223,149 @@
 	return TRUE;
 }
 
-static void stream_send_escaped(struct ostream *output, struct istream *input,
-				uoff_t body_lines)
+struct fetch_context {
+        struct mailbox_transaction_context *t;
+	struct mail_search_context *search_ctx;
+	struct istream *stream;
+	uoff_t body_lines;
+
+	struct mail_search_arg search_arg;
+        struct mail_search_seqset seqset;
+
+	unsigned char last;
+	int cr_skipped, in_body;
+};
+
+static void fetch_deinit(struct fetch_context *ctx)
 {
+	(void)mailbox_search_deinit(ctx->search_ctx);
+	(void)mailbox_transaction_commit(ctx->t);
+	i_free(ctx);
+}
+
+static void fetch_callback(struct client *client)
+{
+	struct fetch_context *ctx = client->cmd_context;
 	const unsigned char *data;
-	unsigned char last, add;
+	unsigned char add;
 	size_t i, size;
-	int cr_skipped, in_header;
+	ssize_t ret;
+
+	o_stream_set_max_buffer_size(client->output, 0);
 
-	if (body_lines != (uoff_t)-1)
-		body_lines++; /* internally we count the empty line too */
+	while ((ctx->body_lines > 0 || !ctx->in_body) &&
+	       i_stream_read_data(ctx->stream, &data, &size, 0) > 0) {
+		if (size > 4096)
+			size = 4096;
 
-	cr_skipped = FALSE; in_header = TRUE; last = '\0';
-	while ((body_lines > 0 || in_header) &&
-	       i_stream_read_data(input, &data, &size, 0) > 0) {
 		add = '\0';
 		for (i = 0; i < size; i++) {
-			if (in_header && (data[i] == '\r' || data[i] == '\n')) {
-				if (i == 0 && (last == '\0' || last == '\n'))
-					in_header = FALSE;
+			if ((data[i] == '\r' || data[i] == '\n') &&
+			    !ctx->in_body) {
+				if (i == 0 && (ctx->last == '\0' ||
+					       ctx->last == '\n'))
+					ctx->in_body = TRUE;
 				else if (i > 0 && data[i-1] == '\n')
-					in_header = FALSE;
+					ctx->in_body = TRUE;
 			}
 
 			if (data[i] == '\n') {
-				if ((i == 0 && last != '\r') ||
+				if ((i == 0 && ctx->last != '\r') ||
 				    (i > 0 && data[i-1] != '\r')) {
 					/* missing CR */
 					add = '\r';
 					break;
 				}
 
-				if (!in_header) {
-					if (--body_lines == 0) {
+				if (ctx->in_body) {
+					if (--ctx->body_lines == 0) {
 						i++;
 						break;
 					}
 				}
 			} else if (data[i] == '.' &&
-				   ((i == 0 && last == '\n') ||
+				   ((i == 0 && ctx->last == '\n') ||
 				    (i > 0 && data[i-1] == '\n'))) {
 				/* escape the dot */
 				add = '.';
-				i++;
 				break;
 			} else if (data[i] == '\0' &&
 				   (client_workarounds &
 				    WORKAROUND_OUTLOOK_NO_NULS) != 0) {
-				add = '\x80';
+				add = 0x80;
 				break;
 			}
 		}
 
-		if (o_stream_send(output, data, i) < 0)
+		if ((ret = o_stream_send(client->output, data, i)) < 0)
+			break;
+		if (ret > 0)
+			ctx->last = data[ret-1];
+		i_stream_skip(ctx->stream, ret);
+
+		if ((size_t)ret != i) {
+			/* continue later */
 			return;
+		}
 
 		if (add != '\0') {
-			if (o_stream_send(output, &add, 1) < 0)
+			if ((ret = o_stream_send(client->output, &add, 1)) < 0)
+				break;
+			if (ret == 0)
 				return;
-			last = add;
-			if (client_workarounds & WORKAROUND_OUTLOOK_NO_NULS) {
-				if (i < size && data[i] == '\0')
-					i++;
-			}
-		} else {
-			last = data[i-1];
+
+			ctx->last = add;
+			if (add == 0x80)
+				i_stream_skip(ctx->stream, 1);
 		}
+	}
+	o_stream_set_max_buffer_size(client->output, (size_t)-1);
 
-		i_stream_skip(input, i);
+	if (ctx->last != '\n') {
+		/* didn't end with CRLF */
+		(void)o_stream_send(client->output, "\r\n", 2);
 	}
 
-	if (last != '\n') {
-		/* didn't end with CRLF */
-		(void)o_stream_send(output, "\r\n", 2);
-	}
+	client_send_line(client, ".");
+	fetch_deinit(ctx);
+	client->cmd = NULL;
 }
 
-static void fetch(struct client *client, unsigned int msgnum,
-		  uoff_t body_lines)
+static void fetch(struct client *client, unsigned int msgnum, uoff_t body_lines)
 {
-	struct mail_search_arg search_arg;
-        struct mail_search_seqset seqset;
-        struct mailbox_transaction_context *t;
-	struct mail_search_context *ctx;
+        struct fetch_context *ctx;
 	struct mail *mail;
-	struct istream *stream;
+
+	ctx = i_new(struct fetch_context, 1);
 
-	memset(&seqset, 0, sizeof(seqset));
-	seqset.seq1 = seqset.seq2 = msgnum+1;
-
-	memset(&search_arg, 0, sizeof(search_arg));
-	search_arg.type = SEARCH_SEQSET;
-	search_arg.value.seqset = &seqset;
+	ctx->seqset.seq1 = ctx->seqset.seq2 = msgnum+1;
+	ctx->search_arg.type = SEARCH_SEQSET;
+	ctx->search_arg.value.seqset = &ctx->seqset;
 
-	t = mailbox_transaction_begin(client->mailbox, FALSE);
-	ctx = mailbox_search_init(t, NULL, &search_arg, NULL,
-				  MAIL_FETCH_STREAM_HEADER |
-				  MAIL_FETCH_STREAM_BODY, NULL);
-	if (ctx == NULL) {
-		mailbox_transaction_rollback(t);
-		client_send_storage_error(client);
+	ctx->t = mailbox_transaction_begin(client->mailbox, FALSE);
+	ctx->search_ctx = mailbox_search_init(ctx->t, NULL, &ctx->search_arg,
+					      NULL, MAIL_FETCH_STREAM_HEADER |
+					      MAIL_FETCH_STREAM_BODY, NULL);
+	mail = mailbox_search_next(ctx->search_ctx);
+	ctx->stream = mail == NULL ? NULL : mail->get_stream(mail, NULL, NULL);
+	if (ctx->stream == NULL) {
+		client_send_line(client, "-ERR Message not found.");
+		fetch_deinit(ctx);
 		return;
 	}
 
-	mail = mailbox_search_next(ctx);
-	stream = mail == NULL ? NULL : mail->get_stream(mail, NULL, NULL);
-	if (stream == NULL)
-		client_send_line(client, "-ERR Message not found.");
-	else {
-		if (body_lines == (uoff_t)-1) {
-			client_send_line(client, "+OK %"PRIuUOFF_T" octets",
-					 client->message_sizes[msgnum]);
-		} else {
-			client_send_line(client, "+OK");
-		}
-
-		stream_send_escaped(client->output, stream, body_lines);
-		client_send_line(client, ".");
+	ctx->body_lines = body_lines;
+	if (body_lines == (uoff_t)-1) {
+		client_send_line(client, "+OK %"PRIuUOFF_T" octets",
+				 client->message_sizes[msgnum]);
+	} else {
+		client_send_line(client, "+OK");
+		ctx->body_lines++; /* internally we count the empty line too */
 	}
 
-	(void)mailbox_search_deinit(ctx);
-	(void)mailbox_transaction_commit(t);
+	client->cmd = fetch_callback;
+	client->cmd_context = ctx;
+	fetch_callback(client);
 }
 
 static int cmd_retr(struct client *client, const char *args)
@@ -368,37 +415,22 @@
 	return TRUE;
 }
 
-static void list_uids(struct client *client, unsigned int message)
-{
+struct cmd_uidl_context {
+        struct mailbox_transaction_context *t;
+	struct mail_search_context *search_ctx;
+	unsigned int message;
+
 	struct mail_search_arg search_arg;
 	struct mail_search_seqset seqset;
-        struct mailbox_transaction_context *t;
-	struct mail_search_context *ctx;
+};
+
+static int list_uids_iter(struct client *client, struct cmd_uidl_context *ctx)
+{
 	struct mail *mail;
 	const char *uid_str;
-	int found = FALSE;
-
-	if (client->messages_count == 0 && message == 0)
-		return;
+	int ret, found = FALSE;
 
-	memset(&search_arg, 0, sizeof(search_arg));
-	if (message == 0)
-		search_arg.type = SEARCH_ALL;
-	else {
-		seqset.seq1 = seqset.seq2 = message;
-		search_arg.type = SEARCH_SEQSET;
-		search_arg.value.seqset = &seqset;
-	}
-
-	t = mailbox_transaction_begin(client->mailbox, FALSE);
-	ctx = mailbox_search_init(t, NULL, &search_arg, NULL, 0, NULL);
-	if (ctx == NULL) {
-		mailbox_transaction_rollback(t);
-		client_send_storage_error(client);
-		return;
-	}
-
-	while ((mail = mailbox_search_next(ctx)) != NULL) {
+	while ((mail = mailbox_search_next(ctx->search_ctx)) != NULL) {
 		if (client->deleted) {
 			uint32_t idx = mail->seq - 1;
 			if (client->deleted_bitmask[idx / CHAR_BIT] &
@@ -407,31 +439,81 @@
 		}
 
 		uid_str = mail->get_special(mail, MAIL_FETCH_UID_STRING);
-		client_send_line(client, message == 0 ? "%u %s" : "+OK %u %s",
-				 mail->seq, uid_str);
 		found = TRUE;
+
+		ret = client_send_line(client, ctx->message == 0 ?
+				       "%u %s" : "+OK %u %s",
+				       mail->seq, uid_str);
+		if (ret < 0)
+			break;
+		if (ret == 0 && ctx->message == 0) {
+			/* output is being buffered, continue when there's
+			   more space */
+			return 0;
+		}
 	}
 
-	(void)mailbox_search_deinit(ctx);
-	(void)mailbox_transaction_commit(t);
+	/* finished */
+	(void)mailbox_search_deinit(ctx->search_ctx);
+	(void)mailbox_transaction_commit(ctx->t);
+
+	client->cmd = NULL;
+
+	if (ctx->message == 0)
+		client_send_line(client, ".");
+	i_free(ctx);
+	return found;
+}
+
+static void cmd_uidl_callback(struct client *client)
+{
+	struct cmd_uidl_context *ctx = client->cmd_context;
+
+        (void)list_uids_iter(client, ctx);
+}
 
-	if (!found && message != 0)
-		client_send_line(client, "-ERR Message not found.");
+static struct cmd_uidl_context *
+cmd_uidl_init(struct client *client, unsigned int message)
+{
+        struct cmd_uidl_context *ctx;
+
+	ctx = i_new(struct cmd_uidl_context, 1);
+
+	if (message == 0)
+		ctx->search_arg.type = SEARCH_ALL;
+	else {
+		ctx->seqset.seq1 = ctx->seqset.seq2 = message;
+		ctx->search_arg.type = SEARCH_SEQSET;
+		ctx->search_arg.value.seqset = &ctx->seqset;
+	}
+
+	ctx->t = mailbox_transaction_begin(client->mailbox, FALSE);
+	ctx->search_ctx = mailbox_search_init(ctx->t, NULL, &ctx->search_arg,
+					      NULL, 0, NULL);
+	if (message == 0) {
+		client->cmd = cmd_uidl_callback;
+		client->cmd_context = ctx;
+	}
+	return ctx;
 }
 
 static int cmd_uidl(struct client *client, const char *args)
 {
+        struct cmd_uidl_context *ctx;
+
 	if (*args == '\0') {
 		client_send_line(client, "+OK");
-		list_uids(client, 0);
-		client_send_line(client, ".");
+		ctx = cmd_uidl_init(client, 0);
+		list_uids_iter(client, ctx);
 	} else {
 		unsigned int msgnum;
 
 		if (get_msgnum(client, args, &msgnum) == NULL)
 			return FALSE;
 
-		list_uids(client, msgnum+1);
+		ctx = cmd_uidl_init(client, msgnum+1);
+		if (list_uids_iter(client, ctx))
+			client_send_line(client, "-ERR Message not found.");
 	}
 
 	return TRUE;