Mercurial > dovecot > original-hg > dovecot-1.2
changeset 1015:40a327d356de HEAD
Support for MULTIAPPEND extension. COPY now behaves like spec says - if it
fails, none of the messages are copied. maildir_copy_with_hardlinks didn't
actually work.
line wrap: on
line diff
--- a/configure.in Wed Jan 22 20:41:29 2003 +0200 +++ b/configure.in Wed Jan 22 21:23:28 2003 +0200 @@ -796,7 +796,7 @@ dnl ** capabilities dnl ** -capability="IMAP4rev1 SORT THREAD=REFERENCES" +capability="IMAP4rev1 SORT THREAD=REFERENCES MULTIAPPEND" AC_DEFINE_UNQUOTED(CAPABILITY_STRING, "$capability", IMAP capabilities) dnl **
--- a/doc/multiaccess.txt Wed Jan 22 20:41:29 2003 +0200 +++ b/doc/multiaccess.txt Wed Jan 22 21:23:28 2003 +0200 @@ -13,6 +13,4 @@ SEARCH ignores expunged messages. -COPY fails if any of the given messages were expunged. Messages copied -until the failure is noticed are currently left to destination mailbox - -this may change later. +COPY fails if any of the given messages were expunged.
--- a/src/imap/client.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/imap/client.c Wed Jan 22 21:23:28 2003 +0200 @@ -12,10 +12,6 @@ /* max. size of one parameter in line */ #define MAX_INBUF_SIZE 8192 -/* max. number of IMAP argument elements to accept. The maximum memory usage - for command from user is around MAX_INBUF_SIZE * MAX_IMAP_ARG_ELEMENTS */ -#define MAX_IMAP_ARG_ELEMENTS 128 - /* If we can't send a buffer in a minute, disconnect the client */ #define CLIENT_OUTPUT_TIMEOUT (60*1000) @@ -164,6 +160,8 @@ { int ret; + i_assert(count <= INT_MAX); + ret = imap_parser_read_args(client->parser, count, flags, args); if (ret >= (int)count) { /* all parameters read successfully */ @@ -193,11 +191,15 @@ for (i = 0; i < count; i++) { const char **ret = va_arg(va, const char **); + if (imap_args[i].type == IMAP_ARG_EOL) { + client_send_command_error(client, "Missing arguments."); + break; + } + str = imap_arg_string(&imap_args[i]); if (str == NULL) { - client_send_command_error(client, "Missing arguments."); - va_end(va); - return FALSE; + client_send_command_error(client, "Invalid arguments."); + break; } if (ret != NULL) @@ -205,7 +207,7 @@ } va_end(va); - return TRUE; + return i == count; } static void client_reset_command(struct client *client) @@ -219,12 +221,6 @@ imap_parser_reset(client->parser); } -static void client_command_finished(struct client *client) -{ - client->input_skip_line = TRUE; - client_reset_command(client); -} - /* Skip incoming data until newline is found, returns TRUE if newline was found. */ static int client_skip_line(struct client *client) @@ -249,9 +245,10 @@ { if (client->cmd_func != NULL) { /* command is being executed - continue it */ + client->input_skip_line = TRUE; if (client->cmd_func(client) || client->cmd_error) { /* command execution was finished */ - client_command_finished(client); + client_reset_command(client); client->bad_counter = 0; return TRUE; } @@ -292,11 +289,13 @@ /* unknown command */ client_send_command_error(client, t_strconcat( "Unknown command '", client->cmd_name, "'", NULL)); - client_command_finished(client); + client->input_skip_line = TRUE; + client_reset_command(client); } else { + client->input_skip_line = TRUE; if (client->cmd_func(client) || client->cmd_error) { /* command execution was finished */ - client_command_finished(client); + client_reset_command(client); client->bad_counter = 0; } } @@ -323,7 +322,7 @@ client->input_skip_line = TRUE; client_send_command_error(client, "Too long argument."); - client_command_finished(client); + client_reset_command(client); break; }
--- a/src/imap/cmd-append.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/imap/cmd-append.c Wed Jan 22 21:23:28 2003 +0200 @@ -2,6 +2,7 @@ #include "common.h" #include "ioloop.h" +#include "istream.h" #include "ostream.h" #include "commands.h" #include "imap-parser.h" @@ -12,107 +13,50 @@ /* Returns -1 = error, 0 = need more data, 1 = successful. flags and internal_date may be NULL as a result, but mailbox and msg_size are always set when successful. */ -static int validate_args(struct client *client, const char **mailbox, - struct imap_arg_list **flags, - const char **internal_date, - uoff_t *msg_size, unsigned int count) +static int validate_args(struct imap_arg *args, struct imap_arg_list **flags, + const char **internal_date, uoff_t *msg_size) { - struct imap_arg *args; - - i_assert(count >= 2 && count <= 4); - - *flags = NULL; - *internal_date = NULL; - - if (!client_read_args(client, count, IMAP_PARSE_FLAG_LITERAL_SIZE, - &args)) - return 0; - - switch (count) { - case 2: - /* do we have flags or internal date parameter? */ - if (args[1].type == IMAP_ARG_LIST || - args[1].type == IMAP_ARG_STRING) - return validate_args(client, mailbox, flags, - internal_date, msg_size, 3); - - break; - case 3: - /* do we have both flags and internal date? */ - if (args[1].type == IMAP_ARG_LIST && - args[2].type == IMAP_ARG_STRING) - return validate_args(client, mailbox, flags, - internal_date, msg_size, 4); - - if (args[1].type == IMAP_ARG_LIST) - *flags = IMAP_ARG_LIST(&args[1]); - else if (args[1].type == IMAP_ARG_STRING) - *internal_date = IMAP_ARG_STR(&args[1]); - else - return -1; - break; - case 4: - /* we have all parameters */ - *flags = IMAP_ARG_LIST(&args[1]); - *internal_date = IMAP_ARG_STR(&args[2]); - break; - default: - i_unreached(); + /* [<flags>] */ + if (args->type != IMAP_ARG_LIST) + *flags = NULL; + else { + *flags = IMAP_ARG_LIST(args); + args++; } - /* check that mailbox and message arguments are ok */ - *mailbox = imap_arg_string(&args[0]); - if (*mailbox == NULL) - return -1; + /* [<internal date>] */ + if (args->type != IMAP_ARG_STRING) + *internal_date = NULL; + else { + *internal_date = IMAP_ARG_STR(args); + args++; + } - if (args[count-1].type != IMAP_ARG_LITERAL_SIZE) - return -1; + if (args->type != IMAP_ARG_LITERAL_SIZE) + return FALSE; - *msg_size = IMAP_ARG_LITERAL_SIZE(&args[count-1]); - return 1; + *msg_size = IMAP_ARG_LITERAL_SIZE(args); + return TRUE; } int cmd_append(struct client *client) { + struct mailbox *box; + struct mail_save_context *ctx; + struct imap_parser *save_parser; + struct imap_arg *args; struct imap_arg_list *flags_list; - struct mailbox *box; struct mail_full_flags flags; time_t internal_date; const char *mailbox, *internal_date_str; uoff_t msg_size; - int failed, timezone_offset; - - /* <mailbox> [<flags>] [<internal date>] <message literal> */ - switch (validate_args(client, &mailbox, &flags_list, - &internal_date_str, &msg_size, 2)) { - case -1: - /* error */ - client_send_command_error(client, NULL); - return TRUE; - case 0: - /* need more data */ - return FALSE; - } + unsigned int count; + int ret, failed, timezone_offset; - if (flags_list != NULL) { - if (!client_parse_mail_flags(client, flags_list->args, - &flags)) - return TRUE; - } else { - memset(&flags, 0, sizeof(flags)); - } + /* <mailbox> */ + if (!client_read_string_args(client, 1, &mailbox)) + return FALSE; - if (internal_date_str == NULL) { - /* no time given, default to now. */ - internal_date = ioloop_time; - timezone_offset = ioloop_timezone.tz_minuteswest; - } else if (!imap_parse_datetime(internal_date_str, &internal_date, - &timezone_offset)) { - client_send_tagline(client, "BAD Invalid internal date."); - return TRUE; - } - - /* open the mailbox */ if (!client_verify_mailbox_name(client, mailbox, TRUE, FALSE)) return TRUE; @@ -123,17 +67,120 @@ return TRUE; } - o_stream_send(client->output, "+ OK\r\n", 6); - o_stream_flush(client->output); + ctx = box->save_init(box, TRUE); + if (ctx == NULL) { + client_send_storage_error(client); + return TRUE; + } + + /* if error occurs, the CRLF is already read. */ + client->input_skip_line = FALSE; + + count = 0; + failed = TRUE; + save_parser = imap_parser_create(client->input, client->output, + 0, MAX_IMAP_ARG_ELEMENTS); + + for (;;) { + /* [<flags>] [<internal date>] <message literal> */ + imap_parser_reset(save_parser); + for (;;) { + ret = imap_parser_read_args(save_parser, 0, + IMAP_PARSE_FLAG_LITERAL_SIZE, + &args); + if (ret >= 0) + break; + if (ret == -1) { + client_send_command_error(client, + imap_parser_get_error(save_parser)); + break; + } + + /* need more data */ + ret = i_stream_read(client->input); + if (ret == -2) { + client_send_command_error(client, + "Too long argument."); + break; + } + if (ret < 0) { + /* disconnected */ + client->cmd_error = TRUE; + break; + } + } + + if (client->cmd_error) + break; + + if (args->type == IMAP_ARG_EOL) { + /* last one */ + if (count > 0) + failed = FALSE; + client->input_skip_line = TRUE; + break; + } - /* save the mail */ - failed = !box->save(box, &flags, internal_date, timezone_offset, - client->input, msg_size); + if (!validate_args(args, &flags_list, &internal_date_str, + &msg_size)) { + /* error */ + client_send_command_error(client, "Invalid arguments."); + break; + } + + if (flags_list != NULL) { + if (!client_parse_mail_flags(client, flags_list->args, + &flags)) + break; + } else { + memset(&flags, 0, sizeof(flags)); + } + + if (internal_date_str == NULL) { + /* no time given, default to now. */ + internal_date = ioloop_time; + timezone_offset = ioloop_timezone.tz_minuteswest; + } else if (!imap_parse_datetime(internal_date_str, + &internal_date, + &timezone_offset)) { + client_send_tagline(client, + "BAD Invalid internal date."); + break; + } + + if (msg_size == 0) { + /* no message data, abort */ + client_send_tagline(client, "NO Append aborted."); + break; + } + + o_stream_send(client->output, "+ OK\r\n", 6); + o_stream_flush(client->output); + + /* save the mail */ + i_stream_set_read_limit(client->input, + client->input->v_offset + msg_size); + if (!box->save_next(ctx, &flags, internal_date, + timezone_offset, client->input)) { + client_send_storage_error(client); + break; + } + i_stream_set_read_limit(client->input, 0); + + if (client->input->closed) + break; + + count++; + } + + if (!box->save_deinit(ctx, failed)) { + failed = TRUE; + client_send_storage_error(client); + } + box->close(box); - if (failed) { - client_send_storage_error(client); - } else { + if (!failed) { client_sync_full(client); client_send_tagline(client, "OK Append completed."); }
--- a/src/imap/cmd-copy.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/imap/cmd-copy.c Wed Jan 22 21:23:28 2003 +0200 @@ -7,6 +7,7 @@ { struct mailbox *destbox; const char *messageset, *mailbox; + int ret; /* <message set> <mailbox> */ if (!client_read_string_args(client, 2, &messageset, &mailbox)) @@ -27,11 +28,16 @@ } /* copy the mail */ - if (client->mailbox->copy(client->mailbox, destbox, - messageset, client->cmd_uid)) { - client_sync_full(client); + ret = client->mailbox->copy(client->mailbox, destbox, + messageset, client->cmd_uid); + + /* sync always - if COPY fails because of expunges they'll get + synced here */ + client_sync_full(client); + + if (ret) client_send_tagline(client, "OK Copy completed."); - } else + else client_send_storage_error(client); destbox->close(destbox);
--- a/src/imap/common.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/imap/common.h Wed Jan 22 21:23:28 2003 +0200 @@ -4,6 +4,10 @@ #include "lib.h" #include "client.h" +/* max. number of IMAP argument elements to accept. The maximum memory usage + for command from user is around MAX_INBUF_SIZE * MAX_IMAP_ARG_ELEMENTS */ +#define MAX_IMAP_ARG_ELEMENTS 128 + extern struct ioloop *ioloop; #endif
--- a/src/lib-storage/index/index-copy.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-copy.c Wed Jan 22 21:23:28 2003 +0200 @@ -8,13 +8,15 @@ #include <unistd.h> -static int copy_messageset(struct messageset_context *ctx, - struct index_mailbox *src, struct mailbox *dest) +static int copy_messageset(struct messageset_context *msgset_ctx, + struct mail_save_context *save_ctx, + struct index_mailbox *src, + struct mailbox *dest) { const struct messageset_mail *mail; struct mail_full_flags flags; struct istream *input; - time_t internal_date; + time_t received_date; int failed, deleted; memset(&flags, 0, sizeof(flags)); @@ -22,19 +24,15 @@ mail_custom_flags_list_get(src->index->custom_flags); flags.custom_flags_count = MAIL_CUSTOM_FLAGS_COUNT; - while ((mail = index_messageset_next(ctx)) != NULL) { + while ((mail = index_messageset_next(msgset_ctx)) != NULL) { input = src->index->open_mail(src->index, mail->rec, - &internal_date, &deleted); - if (input == NULL) { - if (deleted) - continue; + &received_date, &deleted); + if (input == NULL) return FALSE; - } - /* save it in destination mailbox */ flags.flags = mail->rec->msg_flags; - failed = !dest->save(dest, &flags, internal_date, 0, - input, input->v_limit); + failed = !dest->save_next(save_ctx, &flags, received_date, + 0, input); i_stream_unref(input); if (failed) @@ -48,9 +46,9 @@ const char *messageset, int uidset) { struct index_mailbox *ibox = (struct index_mailbox *) box; - struct messageset_context *ctx; - enum mail_lock_type lock_type; - int ret, copy_inside_mailbox; + struct messageset_context *msgset_ctx; + struct mail_save_context *save_ctx; + int ret, ret2, copy_inside_mailbox; if (destbox->readonly) { mail_storage_set_error(box->storage, @@ -63,31 +61,36 @@ strcmp(destbox->name, box->name) == 0; if (copy_inside_mailbox) { - /* copying inside same mailbox */ if (!index_storage_lock(ibox, MAIL_LOCK_EXCLUSIVE)) return FALSE; - - /* kludgy.. */ - ((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE; - - lock_type = MAIL_LOCK_EXCLUSIVE; } else { - lock_type = MAIL_LOCK_SHARED; + if (!index_storage_sync_and_lock(ibox, TRUE, MAIL_LOCK_SHARED)) + return FALSE; } - if (!index_storage_sync_and_lock(ibox, TRUE, lock_type)) - return FALSE; + save_ctx = destbox->save_init(destbox, TRUE); + if (save_ctx == NULL) + ret = FALSE; + else { + /* abort if any of the messages are expunged */ + msgset_ctx = index_messageset_init(ibox, messageset, uidset, + FALSE); + ret = copy_messageset(msgset_ctx, save_ctx, ibox, destbox); + ret2 = index_messageset_deinit(msgset_ctx); + if (ret2 < 0) + ret = FALSE; + else if (ret2 == 0) { + mail_storage_set_error(ibox->box.storage, + "Some of the requested messages no longer exist."); + ret = FALSE; + } - ctx = index_messageset_init(ibox, messageset, uidset); - ret = copy_messageset(ctx, ibox, destbox); - if (index_messageset_deinit(ctx) < 0) - ret = FALSE; - - if (copy_inside_mailbox) - ((struct index_mailbox *) destbox)->delay_save_unlocking = TRUE; + if (!destbox->save_deinit(save_ctx, !ret)) + ret = FALSE; + } if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) - return FALSE; + ret = FALSE; return ret; }
--- a/src/lib-storage/index/index-fetch.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-fetch.c Wed Jan 22 21:23:28 2003 +0200 @@ -58,7 +58,7 @@ ctx->update_seen = *update_seen; index_mail_init(ibox, &ctx->mail, wanted_fields, NULL); - ctx->msgset_ctx = index_messageset_init(ibox, messageset, uidset); + ctx->msgset_ctx = index_messageset_init(ibox, messageset, uidset, TRUE); return ctx; }
--- a/src/lib-storage/index/index-messageset.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-messageset.c Wed Jan 22 21:23:28 2003 +0200 @@ -19,7 +19,7 @@ unsigned int num1, num2; const char *messageset, *p; - int uidset; + int uidset, skip_expunged; int first, ret; const char *error; @@ -30,7 +30,7 @@ struct messageset_context * index_messageset_init(struct index_mailbox *ibox, - const char *messageset, int uidset) + const char *messageset, int uidset, int skip_expunged) { struct messageset_context *ctx; @@ -42,6 +42,7 @@ ctx->messages_count = ibox->synced_messages_count; ctx->p = ctx->messageset = messageset; ctx->uidset = uidset; + ctx->skip_expunged = skip_expunged; /* Reset index errors, we rely on it to check for failures */ index_reset_error(ctx->index); @@ -55,7 +56,7 @@ { struct messageset_context *ctx; - ctx = index_messageset_init(ibox, NULL, uidset); + ctx = index_messageset_init(ibox, NULL, uidset, TRUE); if (num1 <= num2) { ctx->num1 = num1; ctx->num2 = num2; @@ -283,6 +284,13 @@ ctx->ret = uidset_init(ctx); else ctx->ret = seqset_init(ctx); + + if (ctx->expunges_found && !ctx->skip_expunged) { + /* we wish to abort if there's any + expunged messages */ + ctx->ret = 1; + return NULL; + } } while (ctx->ret == 1); if (ctx->ret != 0)
--- a/src/lib-storage/index/index-messageset.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-messageset.h Wed Jan 22 21:23:28 2003 +0200 @@ -13,13 +13,13 @@ struct messageset_context * index_messageset_init(struct index_mailbox *ibox, - const char *messageset, int uidset); + const char *messageset, int uidset, int skip_expunged); struct messageset_context * index_messageset_init_range(struct index_mailbox *ibox, unsigned int num1, unsigned int num2, int uidset); -/* Returns 1 if all were found, 0 if some messages were deleted, +/* Returns 1 if all were found, 0 if some messages were expunged, -1 if internal error occured or -2 if messageset was invalid. */ int index_messageset_deinit(struct messageset_context *ctx);
--- a/src/lib-storage/index/index-save.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-save.c Wed Jan 22 21:23:28 2003 +0200 @@ -71,8 +71,7 @@ } int index_storage_save(struct mail_storage *storage, const char *path, - struct istream *input, struct ostream *output, - uoff_t data_size) + struct istream *input, struct ostream *output) { int (*write_func)(struct ostream *, const unsigned char *, size_t); const unsigned char *data; @@ -83,13 +82,13 @@ write_func = getenv("MAIL_SAVE_CRLF") ? write_with_crlf : write_with_lf; failed = FALSE; - while (data_size > 0) { + for (;;) { ret = i_stream_read(input); if (ret < 0) { errno = input->stream_errno; if (errno == 0) { - mail_storage_set_error(storage, - "Client disconnected"); + /* EOF */ + break; } else if (errno == EAGAIN) { mail_storage_set_error(storage, "Timeout while waiting for input"); @@ -97,13 +96,11 @@ mail_storage_set_critical(storage, "Error reading mail from client: %m"); } - return FALSE; + failed = TRUE; + break; } data = i_stream_get_data(input, &size); - if (size > data_size) - size = (size_t)data_size; - if (!failed) { ret = write_func(output, data, size); if (ret < 0) { @@ -122,7 +119,6 @@ } } - data_size -= size; i_stream_skip(input, size); }
--- a/src/lib-storage/index/index-storage.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-storage.h Wed Jan 22 21:23:28 2003 +0200 @@ -25,7 +25,6 @@ time_t next_lock_notify; /* temporary */ unsigned int sent_diskspace_warning:1; - unsigned int delay_save_unlocking:1; /* For COPYing inside mailbox */ }; int mail_storage_set_index_error(struct index_mailbox *ibox); @@ -61,8 +60,7 @@ unsigned int seq, int notify); int index_storage_save(struct mail_storage *storage, const char *path, - struct istream *input, struct ostream *output, - uoff_t data_size); + struct istream *input, struct ostream *output); void index_mailbox_check_add(struct index_mailbox *ibox, const char *path); void index_mailbox_check_remove(struct index_mailbox *ibox);
--- a/src/lib-storage/index/index-update-flags.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/index-update-flags.c Wed Jan 22 21:23:28 2003 +0200 @@ -85,7 +85,7 @@ mail_flags &= ~MAIL_RECENT; /* \Recent can't be changed */ - ctx = index_messageset_init(ibox, messageset, uidset); + ctx = index_messageset_init(ibox, messageset, uidset, TRUE); ret = update_messageset(ctx, ibox, mail_flags, modify_type, notify); ret2 = index_messageset_deinit(ctx);
--- a/src/lib-storage/index/maildir/maildir-copy.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-copy.c Wed Jan 22 21:23:28 2003 +0200 @@ -10,24 +10,38 @@ #include <stdlib.h> #include <unistd.h> +struct rollback { + struct rollback *next; + const char *fname; +}; + static int hardlink_messageset(struct messageset_context *ctx, struct index_mailbox *src, struct index_mailbox *dest) { - struct mail_index *index = src->index; + struct mail_index *index = src->index; + pool_t pool; + struct rollback *rollbacks, *rb; const struct messageset_mail *mail; enum mail_flags flags; const char **custom_flags; - const char *fname, *src_fname, *dest_fname; + const char *fname, *src_path, *dest_fname, *dest_path; + int ret; + + pool = pool_alloconly_create("hard copy rollbacks", 2048); + rollbacks = NULL; custom_flags = mail_custom_flags_list_get(index->custom_flags); + ret = 1; while ((mail = index_messageset_next(ctx)) != NULL) { flags = mail->rec->msg_flags; if (!index_mailbox_fix_custom_flags(dest, &flags, custom_flags, - MAIL_CUSTOM_FLAGS_COUNT)) - return -1; + MAIL_CUSTOM_FLAGS_COUNT)) { + ret = -1; + break; + } /* link the file */ fname = index->lookup_field(index, mail->rec, @@ -36,31 +50,50 @@ index_set_corrupted(index, "Missing location field for record %u", mail->rec->uid); - return -1; + ret = -1; + break; } t_push(); - src_fname = t_strconcat(index->mailbox_path, "cur/", - fname, NULL); - dest_fname = t_strconcat(dest->index->mailbox_path, "new/", - maildir_filename_set_flags( + src_path = t_strconcat(index->mailbox_path, "/cur/", + fname, NULL); + dest_fname = t_strconcat(maildir_filename_set_flags( maildir_generate_tmp_filename(), flags), NULL); + dest_path = t_strconcat(dest->index->mailbox_path, "/new/", + dest_fname, NULL); - if (link(src_fname, dest_fname) < 0) { + if (link(src_path, dest_path) == 0) { + rb = p_new(pool, struct rollback, 1); + rb->fname = p_strdup(pool, dest_fname); + rb->next = rollbacks; + rollbacks = rb; + } else { if (errno != EXDEV) { mail_storage_set_critical(src->box.storage, "link(%s, %s) failed: %m", - src_fname, dest_fname); + src_path, dest_path); t_pop(); - return -1; + ret = -1; + break; } t_pop(); - return 0; + ret = 0; + break; } t_pop(); } - return 1; + if (ret <= 0) { + for (rb = rollbacks; rb != NULL; rb = rb->next) { + t_push(); + (void)unlink(t_strconcat(dest->index->mailbox_path, + "new/", rb->fname, NULL)); + t_pop(); + } + } + + pool_unref(pool); + return ret; } static int copy_with_hardlinks(struct index_mailbox *src, @@ -68,15 +101,21 @@ const char *messageset, int uidset) { struct messageset_context *ctx; - int ret; + int ret, ret2; if (!index_storage_sync_and_lock(src, TRUE, MAIL_LOCK_SHARED)) return -1; - ctx = index_messageset_init(src, messageset, uidset); + ctx = index_messageset_init(src, messageset, uidset, FALSE); ret = hardlink_messageset(ctx, src, dest); - if (index_messageset_deinit(ctx) < 0) + ret2 = index_messageset_deinit(ctx); + if (ret2 < 0) ret = -1; + else { + mail_storage_set_error(src->box.storage, + "Some of the requested messages no longer exist."); + ret = -1; + } (void)index_storage_lock(src, MAIL_LOCK_UNLOCK);
--- a/src/lib-storage/index/maildir/maildir-save.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-save.c Wed Jan 22 21:23:28 2003 +0200 @@ -12,6 +12,21 @@ #include <fcntl.h> #include <utime.h> +struct mail_filename { + struct mail_filename *next; + const char *src, *dest; +}; + +struct mail_save_context { + pool_t pool; + + struct index_mailbox *ibox; + int transaction; + + const char *tmpdir, *newdir; + struct mail_filename *files; +}; + const char *maildir_generate_tmp_filename(void) { static unsigned int create_count = 0; @@ -49,7 +64,7 @@ static const char * maildir_read_into_tmp(struct mail_storage *storage, const char *dir, - struct istream *input, uoff_t data_size) + struct istream *input) { const char *fname, *path; struct ostream *output; @@ -65,7 +80,7 @@ o_stream_set_blocking(output, 60000, NULL, NULL); path = t_strconcat(dir, "/", fname, NULL); - if (!index_storage_save(storage, path, input, output, data_size)) + if (!index_storage_save(storage, path, input, output)) fname = NULL; o_stream_unref(output); @@ -78,25 +93,48 @@ return fname; } -int maildir_storage_save(struct mailbox *box, - const struct mail_full_flags *flags, - time_t internal_date, - int timezone_offset __attr_unused__, - struct istream *data, uoff_t data_size) +static int maildir_copy(struct mail_save_context *ctx, + const char *src, const char *dest) { - struct index_mailbox *ibox = (struct index_mailbox *) box; + const char *tmp_path, *new_path; + + t_push(); + + tmp_path = t_strconcat(ctx->tmpdir, "/", src, NULL); + new_path = t_strconcat(ctx->newdir, "/", dest, NULL); + + if (rename(tmp_path, new_path) == 0) { + t_pop(); + return TRUE; + } + + if (errno == ENOSPC) { + mail_storage_set_error(ctx->ibox->box.storage, + "Not enough disk space"); + } else { + mail_storage_set_critical(ctx->ibox->box.storage, + "rename(%s, %s) failed: %m", + tmp_path, new_path); + } + + (void)unlink(tmp_path); + t_pop(); + return FALSE; +} + +int maildir_storage_save_next(struct mail_save_context *ctx, + const struct mail_full_flags *flags, + time_t received_date, + int timezone_offset __attr_unused__, + struct istream *data) +{ enum mail_flags mail_flags; struct utimbuf buf; - const char *tmpdir, *fname, *tmp_path, *new_path; + const char *fname, *dest_fname, *tmp_path; int failed; - if (box->readonly) { - mail_storage_set_error(box->storage, "Mailbox is read-only"); - return FALSE; - } - mail_flags = flags->flags; - if (!index_mailbox_fix_custom_flags(ibox, &mail_flags, + if (!index_mailbox_fix_custom_flags(ctx->ibox, &mail_flags, flags->custom_flags, flags->custom_flags_count)) return FALSE; @@ -104,44 +142,100 @@ t_push(); /* create the file into tmp/ directory */ - tmpdir = t_strconcat(ibox->index->mailbox_path, "/tmp", NULL); - fname = maildir_read_into_tmp(box->storage, tmpdir, data, data_size); + fname = maildir_read_into_tmp(ctx->ibox->box.storage, + ctx->tmpdir, data); if (fname == NULL) { t_pop(); return FALSE; } - tmp_path = t_strconcat(tmpdir, "/", fname, NULL); - fname = maildir_filename_set_flags(fname, mail_flags); - new_path = t_strconcat(ibox->index->mailbox_path, "/new/", fname, NULL); + tmp_path = t_strconcat(ctx->tmpdir, "/", fname, NULL); - /* set the internal_date by modifying mtime */ + /* set the received_date by modifying mtime */ buf.actime = ioloop_time; - buf.modtime = internal_date; + buf.modtime = received_date; if (utime(tmp_path, &buf) < 0) { - /* just warn, don't bother actually failing */ - mail_storage_set_critical(box->storage, "utime() failed for " - "%s: %m", tmp_path); + mail_storage_set_critical(ctx->ibox->box.storage, + "utime() failed for %s: %m", + tmp_path); + t_pop(); + return FALSE; } - /* move the file into new/ directory - syncing will pick it - up from there */ - if (rename(tmp_path, new_path) == 0) + /* now, if we want to be able to rollback the whole append session, + we'll just store the name of this temp file and move it later + into new/ */ + dest_fname = maildir_filename_set_flags(fname, mail_flags); + if (ctx->transaction) { + struct mail_filename *mf; + + mf = p_new(ctx->pool, struct mail_filename, 1); + mf->next = ctx->files; + mf->src = p_strdup(ctx->pool, fname); + mf->dest = p_strdup(ctx->pool, dest_fname); + ctx->files = mf; + failed = FALSE; - else { - if (errno == ENOSPC) { - mail_storage_set_error(box->storage, - "Not enough disk space"); - } else { - mail_storage_set_critical(box->storage, - "rename(%s, %s) failed: %m", - tmp_path, new_path); - } - - (void)unlink(tmp_path); - failed = TRUE; + } else { + failed = !maildir_copy(ctx, fname, dest_fname); } t_pop(); return !failed; } + +struct mail_save_context * +maildir_storage_save_init(struct mailbox *box, int transaction) +{ + struct index_mailbox *ibox = (struct index_mailbox *) box; + struct mail_save_context *ctx; + pool_t pool; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return NULL; + } + + pool = pool_alloconly_create("mail_save_context", 2048); + ctx = p_new(pool, struct mail_save_context, 1); + ctx->pool = pool; + ctx->ibox = ibox; + ctx->transaction = transaction; + + ctx->tmpdir = p_strconcat(pool, ibox->index->mailbox_path, + "/tmp", NULL); + ctx->newdir = p_strconcat(pool, ibox->index->mailbox_path, + "/new", NULL); + + return ctx; +} + +int maildir_storage_save_deinit(struct mail_save_context *ctx, int rollback) +{ + struct mail_filename *mf, *mf2; + const char *new_path; + int failed = FALSE; + + if (!rollback) { + for (mf = ctx->files; mf != NULL; mf = mf->next) { + if (!maildir_copy(ctx, mf->src, mf->dest)) { + failed = TRUE; + break; + } + } + + if (failed) { + /* failed, try to unlink the mails already moved */ + for (mf2 = ctx->files; mf2 != mf; mf2 = mf2->next) { + t_push(); + new_path = t_strconcat(ctx->newdir, "/", + mf2->dest, NULL); + (void)unlink(new_path); + t_pop(); + } + } + } + + pool_unref(ctx->pool); + return !failed; +}
--- a/src/lib-storage/index/maildir/maildir-storage.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.c Wed Jan 22 21:23:28 2003 +0200 @@ -569,7 +569,9 @@ index_storage_search_init, index_storage_search_deinit, index_storage_search_next, - maildir_storage_save, + maildir_storage_save_init, + maildir_storage_save_deinit, + maildir_storage_save_next, mail_storage_is_inconsistency_error, FALSE,
--- a/src/lib-storage/index/maildir/maildir-storage.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.h Wed Jan 22 21:23:28 2003 +0200 @@ -5,10 +5,14 @@ int maildir_storage_copy(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset); -int maildir_storage_save(struct mailbox *box, - const struct mail_full_flags *flags, - time_t internal_date, int timezone_offset, - struct istream *data, uoff_t data_size); + +struct mail_save_context * +maildir_storage_save_init(struct mailbox *box, int transaction); +int maildir_storage_save_deinit(struct mail_save_context *ctx, int rollback); +int maildir_storage_save_next(struct mail_save_context *ctx, + const struct mail_full_flags *flags, + time_t received_date, int timezone_offset, + struct istream *data); int maildir_find_mailboxes(struct mail_storage *storage, const char *mask, mailbox_list_callback_t callback, void *context);
--- a/src/lib-storage/index/mbox/mbox-save.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-save.c Wed Jan 22 21:23:28 2003 +0200 @@ -14,68 +14,82 @@ #include <sys/stat.h> #include <netdb.h> +struct mail_save_context { + pool_t pool; + + struct index_mailbox *ibox; + int transaction; + + struct ostream *output; + uoff_t sync_offset; +}; + static char my_hostdomain[256] = ""; -static int write_error(struct mail_storage *storage, const char *mbox_path) +static int write_error(struct mail_save_context *ctx) { - if (errno == ENOSPC) - mail_storage_set_error(storage, "Not enough disk space"); - else { - mail_storage_set_critical(storage, - "Error writing to mbox file %s: %m", mbox_path); + if (errno == ENOSPC) { + mail_storage_set_error(ctx->ibox->box.storage, + "Not enough disk space"); + } else { + mail_storage_set_critical(ctx->ibox->box.storage, + "Error writing to mbox file %s: %m", + ctx->ibox->index->mailbox_path); } return FALSE; } -static int mbox_seek_to_end(struct mail_storage *storage, int fd, - const char *mbox_path, off_t *pos) +static int mbox_seek_to_end(struct mail_save_context *ctx, off_t *offset) { struct stat st; char ch; + int fd; + fd = ctx->ibox->index->mbox_fd; if (fstat(fd, &st) < 0) { - mail_storage_set_critical(storage, - "fstat() failed for mbox file %s: %m", mbox_path); + mail_storage_set_critical(ctx->ibox->box.storage, + "fstat() failed for mbox file %s: %m", + ctx->ibox->index->mailbox_path); return FALSE; } - *pos = st.st_size; + *offset = st.st_size; if (st.st_size == 0) return TRUE; if (lseek(fd, st.st_size-1, SEEK_SET) < 0) { - mail_storage_set_critical(storage, - "lseek() failed for mbox file %s: %m", mbox_path); + mail_storage_set_critical(ctx->ibox->box.storage, + "lseek() failed for mbox file %s: %m", + ctx->ibox->index->mailbox_path); return FALSE; } if (read(fd, &ch, 1) != 1) { - mail_storage_set_critical(storage, - "read() failed for mbox file %s: %m", mbox_path); + mail_storage_set_critical(ctx->ibox->box.storage, + "read() failed for mbox file %s: %m", + ctx->ibox->index->mailbox_path); return FALSE; } if (ch != '\n') { if (write_full(fd, "\n", 1) < 0) - return write_error(storage, mbox_path); - *pos += 1; + return write_error(ctx); + *offset += 1; } return TRUE; } -static int mbox_append_lf(struct mail_storage *storage, struct ostream *output, - const char *mbox_path) +static int mbox_append_lf(struct mail_save_context *ctx) { - if (o_stream_send(output, "\n", 1) < 0) - return write_error(storage, mbox_path); + if (o_stream_send(ctx->output, "\n", 1) < 0) + return write_error(ctx); return TRUE; } -static int write_from_line(struct mail_storage *storage, struct ostream *output, - const char *mbox_path, time_t internal_date) +static int write_from_line(struct mail_save_context *ctx, time_t received_date) { const char *sender, *line, *name; @@ -94,19 +108,19 @@ strocpy(my_hostdomain, name, sizeof(my_hostdomain)); } - sender = t_strconcat(storage->user, "@", my_hostdomain, NULL); + sender = t_strconcat(ctx->ibox->box.storage->user, "@", + my_hostdomain, NULL); /* save in local timezone, no matter what it was given with */ - line = mbox_from_create(sender, internal_date); + line = mbox_from_create(sender, received_date); - if (o_stream_send_str(output, line) < 0) - return write_error(storage, mbox_path); + if (o_stream_send_str(ctx->output, line) < 0) + return write_error(ctx); return TRUE; } -static int write_flags(struct mail_storage *storage, struct ostream *output, - const char *mbox_path, +static int write_flags(struct mail_save_context *ctx, const struct mail_full_flags *full_flags) { enum mail_flags flags = full_flags->flags; @@ -118,8 +132,8 @@ return TRUE; if (flags & MAIL_SEEN) { - if (o_stream_send_str(output, "Status: R\n") < 0) - return write_error(storage, mbox_path); + if (o_stream_send_str(ctx->output, "Status: R\n") < 0) + return write_error(ctx); } if (flags & (MAIL_ANSWERED|MAIL_DRAFT|MAIL_FLAGGED|MAIL_DELETED)) { @@ -130,100 +144,134 @@ (flags & MAIL_DELETED) ? "T" : "", "\n", NULL); - if (o_stream_send_str(output, str) < 0) - return write_error(storage, mbox_path); + if (o_stream_send_str(ctx->output, str) < 0) + return write_error(ctx); } if (flags & MAIL_CUSTOM_FLAGS_MASK) { - if (o_stream_send_str(output, "X-Keywords:") < 0) - return write_error(storage, mbox_path); + if (o_stream_send_str(ctx->output, "X-Keywords:") < 0) + return write_error(ctx); field = 1 << MAIL_CUSTOM_FLAG_1_BIT; for (i = 0; i < full_flags->custom_flags_count; i++) { const char *custom_flag = full_flags->custom_flags[i]; if ((flags & field) && custom_flag != NULL) { - if (o_stream_send(output, " ", 1) < 0) - return write_error(storage, mbox_path); + if (o_stream_send(ctx->output, " ", 1) < 0) + return write_error(ctx); - if (o_stream_send_str(output, custom_flag) < 0) - return write_error(storage, mbox_path); + if (o_stream_send_str(ctx->output, + custom_flag) < 0) + return write_error(ctx); } field <<= 1; } - if (o_stream_send(output, "\n", 1) < 0) - return write_error(storage, mbox_path); + if (o_stream_send(ctx->output, "\n", 1) < 0) + return write_error(ctx); } return TRUE; } -int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags, - time_t internal_date, - int timezone_offset __attr_unused__, - struct istream *data, uoff_t data_size) +int mbox_storage_save_next(struct mail_save_context *ctx, + const struct mail_full_flags *flags, + time_t received_date, + int timezone_offset __attr_unused__, + struct istream *data) { - struct index_mailbox *ibox = (struct index_mailbox *) box; - struct mail_index *index; enum mail_flags real_flags; - const char *mbox_path; - struct ostream *output; int failed; - off_t pos; - - if (box->readonly) { - mail_storage_set_error(box->storage, "Mailbox is read-only"); - return FALSE; - } /* we don't need the real flag positions, easier to keep using our own. they need to be checked/added though. */ real_flags = flags->flags; - if (!index_mailbox_fix_custom_flags(ibox, &real_flags, + if (!index_mailbox_fix_custom_flags(ctx->ibox, &real_flags, flags->custom_flags, flags->custom_flags_count)) return FALSE; - if (!index_storage_sync_and_lock(ibox, FALSE, MAIL_LOCK_EXCLUSIVE)) - return FALSE; - - index = ibox->index; - mbox_path = index->mailbox_path; - if (!mbox_seek_to_end(box->storage, index->mbox_fd, mbox_path, &pos)) + t_push(); + if (!write_from_line(ctx, received_date) || + !write_flags(ctx, flags) || + !index_storage_save(ctx->ibox->box.storage, + ctx->ibox->index->mailbox_path, + data, ctx->output) || + !mbox_append_lf(ctx)) { + /* failed, truncate file back to original size. + output stream needs to be flushed before truncating + so unref() won't write anything. */ + o_stream_flush(ctx->output); + if (ctx->sync_offset != (uoff_t)-1) { + (void)ftruncate(ctx->ibox->index->mbox_fd, + ctx->sync_offset); + ctx->sync_offset = (uoff_t)-1; + } failed = TRUE; - else { + } else { + if (!ctx->transaction) + ctx->sync_offset = ctx->output->offset; failed = FALSE; - - t_push(); - output = o_stream_create_file(index->mbox_fd, - data_stack_pool, 4096, - 0, FALSE); - o_stream_set_blocking(output, 60000, NULL, NULL); - - if (!write_from_line(box->storage, output, mbox_path, - internal_date) || - !write_flags(box->storage, output, mbox_path, flags) || - !index_storage_save(box->storage, mbox_path, - data, output, data_size) || - !mbox_append_lf(box->storage, output, mbox_path)) { - /* failed, truncate file back to original size. - output stream needs to be flushed before truncating - so unref() won't write anything. */ - o_stream_flush(output); - (void)ftruncate(index->mbox_fd, pos); - failed = TRUE; - } - o_stream_unref(output); - t_pop(); } - - /* kludgy.. for copying inside same mailbox. */ - if (!ibox->delay_save_unlocking) { - if (!index_storage_lock(ibox, MAIL_LOCK_UNLOCK)) - return FALSE; - } + t_pop(); return !failed; } + +struct mail_save_context * +mbox_storage_save_init(struct mailbox *box, int transaction) +{ + struct index_mailbox *ibox = (struct index_mailbox *) box; + struct mail_save_context *ctx; + pool_t pool; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return NULL; + } + + if (!index_storage_sync_and_lock(ibox, FALSE, MAIL_LOCK_EXCLUSIVE)) + return NULL; + + pool = pool_alloconly_create("mail_save_context", 2048); + ctx = p_new(pool, struct mail_save_context, 1); + ctx->pool = pool; + ctx->ibox = ibox; + ctx->transaction = transaction; + + if (!mbox_seek_to_end(ctx, &ctx->sync_offset)) { + pool_unref(pool); + return NULL; + } + + ctx->output = o_stream_create_file(ibox->index->mbox_fd, + ctx->pool, 4096, 0, FALSE); + o_stream_set_blocking(ctx->output, 60000, NULL, NULL); + return ctx; +} + +int mbox_storage_save_deinit(struct mail_save_context *ctx, int rollback) +{ + int failed = FALSE; + + if (!index_storage_lock(ctx->ibox, MAIL_LOCK_UNLOCK)) + failed = TRUE; + + if (o_stream_flush(ctx->output) < 0) + failed = TRUE; + o_stream_unref(ctx->output); + + if (rollback && ctx->sync_offset != (uoff_t)-1) { + if (ftruncate(ctx->ibox->index->mbox_fd, + ctx->sync_offset) < 0) { + mail_storage_set_critical(ctx->ibox->box.storage, + "ftruncate(%s) failed: %m", + ctx->ibox->index->mailbox_path); + failed = TRUE; + } + } + + pool_unref(ctx->pool); + return !failed; +}
--- a/src/lib-storage/index/mbox/mbox-storage.c Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.c Wed Jan 22 21:23:28 2003 +0200 @@ -630,7 +630,9 @@ index_storage_search_init, index_storage_search_deinit, index_storage_search_next, - mbox_storage_save, + mbox_storage_save_init, + mbox_storage_save_deinit, + mbox_storage_save_next, mail_storage_is_inconsistency_error, FALSE,
--- a/src/lib-storage/index/mbox/mbox-storage.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/index/mbox/mbox-storage.h Wed Jan 22 21:23:28 2003 +0200 @@ -5,9 +5,14 @@ int mbox_storage_copy(struct mailbox *box, struct mailbox *destbox, const char *messageset, int uidset); -int mbox_storage_save(struct mailbox *box, const struct mail_full_flags *flags, - time_t internal_date, int timezone_offset, - struct istream *data, uoff_t data_size); + +struct mail_save_context * +mbox_storage_save_init(struct mailbox *box, int transaction); +int mbox_storage_save_deinit(struct mail_save_context *ctx, int rollback); +int mbox_storage_save_next(struct mail_save_context *ctx, + const struct mail_full_flags *flags, + time_t received_date, int timezone_offset, + struct istream *data); int mbox_find_mailboxes(struct mail_storage *storage, const char *mask, mailbox_list_callback_t callback, void *context);
--- a/src/lib-storage/mail-storage.h Wed Jan 22 20:41:29 2003 +0200 +++ b/src/lib-storage/mail-storage.h Wed Jan 22 21:23:28 2003 +0200 @@ -268,12 +268,22 @@ the next call to search_next() or search_deinit(). */ struct mail *(*search_next)(struct mail_search_context *ctx); - /* Save a new mail into mailbox. timezone_offset specifies the - timezone in minutes which received_date was originally given - with. */ - int (*save)(struct mailbox *box, const struct mail_full_flags *flags, - time_t received_date, int timezone_offset, - struct istream *data, uoff_t data_size); + /* Initialize saving one or more mails. If transaction is TRUE, all + the saved mails are deleted if an error occurs or save_deinit() + is called with rollback TRUE. */ + struct mail_save_context *(*save_init)(struct mailbox *box, + int transaction); + /* Deinitialize saving. rollback has effect only if save_init() was + called with transaction being TRUE. If rollback is FALSE but + committing the changes fails, all the commits are rollbacked if + possible. */ + int (*save_deinit)(struct mail_save_context *ctx, int rollback); + /* Save a mail into mailbox. timezone_offset specifies the timezone in + minutes in which received_date was originally given with. */ + int (*save_next)(struct mail_save_context *ctx, + const struct mail_full_flags *flags, + time_t received_date, int timezone_offset, + struct istream *data); /* Returns TRUE if mailbox is now in inconsistent state, meaning that the message IDs etc. may have changed - only way to recover this