diff --git a/src/auth/passdb-pam.c b/src/auth/passdb-pam.c --- a/src/auth/passdb-pam.c +++ b/src/auth/passdb-pam.c @@ -229,8 +229,7 @@ static int pam_auth(struct auth_request } } - /* FIXME: this doesn't actually work since we're in the child - process.. */ + /* FIXME: this works only with blocking=yes */ status = pam_get_item(pamh, PAM_USER, (linux_const void **)&item); if (status != PAM_SUCCESS) { *error = t_strdup_printf("pam_get_item() failed: %s", diff --git a/src/imap/client.c b/src/imap/client.c --- a/src/imap/client.c +++ b/src/imap/client.c @@ -485,13 +485,18 @@ int _client_output(void *context) static void idle_timeout(void *context __attr_unused__) { - time_t idle_time; + time_t idle_time, last_change; if (my_client == NULL) return; - idle_time = ioloop_time - + /* We mostly want to check last_input here, but if there is a very long + running command (like copying thousands of messages), we don't want + to disconnect the client just after the command was finished. + But any output that IDLE has sent should be ignored. */ + last_change = my_client->idling ? my_client->last_input : I_MAX(my_client->last_input, my_client->last_output); + idle_time = ioloop_time - last_change; if (my_client->command_pending && o_stream_get_buffer_used_size(my_client->output) > 0 && diff --git a/src/imap/client.h b/src/imap/client.h --- a/src/imap/client.h +++ b/src/imap/client.h @@ -52,6 +52,7 @@ struct client { unsigned int input_pending:1; unsigned int output_pending:1; unsigned int handling_input:1; + unsigned int idling:1; unsigned int input_skip_line:1; /* skip all the data until we've found a new line */ }; diff --git a/src/imap/cmd-append.c b/src/imap/cmd-append.c --- a/src/imap/cmd-append.c +++ b/src/imap/cmd-append.c @@ -285,8 +285,7 @@ static bool cmd_append_continue_parsing( if (ctx->msg_size == 0) { /* no message data, abort */ client_send_tagline(cmd, "NO Append aborted."); - cmd_append_finish(ctx); - return TRUE; + return cmd_append_cancel(ctx, nonsync); } /* save the mail */ diff --git a/src/imap/cmd-idle.c b/src/imap/cmd-idle.c --- a/src/imap/cmd-idle.c +++ b/src/imap/cmd-idle.c @@ -56,6 +56,7 @@ static void idle_finish(struct cmd_idle_ if (client->mailbox != NULL) mailbox_notify_changes(client->mailbox, 0, NULL, NULL); + client->idling = FALSE; if (done_ok) client_send_tagline(ctx->cmd, "OK Idle completed."); else @@ -263,6 +264,7 @@ bool cmd_idle(struct client_command_cont client->io = io_add(i_stream_get_fd(client->input), IO_READ, idle_client_input, ctx); + client->idling = TRUE; client->command_pending = TRUE; cmd->func = cmd_idle_continue; cmd->context = ctx; diff --git a/src/imap/cmd-list.c b/src/imap/cmd-list.c --- a/src/imap/cmd-list.c +++ b/src/imap/cmd-list.c @@ -135,6 +135,15 @@ list_namespace_mailboxes(struct client * continue; str_append(name_str, ctx->ns->prefix); } + if ((list->flags & MAILBOX_NOCHILDREN) != 0 && + strncmp(ctx->ns->prefix, "INBOX", 5) == 0 && + ctx->ns->prefix[5] == ctx->ns->sep) { + /* FIXME: It's unlikely there's only a single + INBOX mailbox, but it's possible. We should + check that instead of assuming it. */ + list->flags |= MAILBOX_CHILDREN; + list->flags &= ~MAILBOX_NOCHILDREN; + } } else { str_append(name_str, ctx->ns->prefix); } diff --git a/src/imap/imap-fetch.c b/src/imap/imap-fetch.c --- a/src/imap/imap-fetch.c +++ b/src/imap/imap-fetch.c @@ -322,6 +322,7 @@ int imap_fetch(struct imap_fetch_context ctx->line_finished = TRUE; if (o_stream_send(ctx->client->output, ")\r\n", 3) < 0) return -1; + ctx->client->last_output = ioloop_time; ctx->cur_mail = NULL; ctx->cur_handler = 0; @@ -362,12 +363,11 @@ int imap_fetch_deinit(struct imap_fetch_ mailbox_header_lookup_deinit(&ctx->all_headers_ctx); if (ctx->trans != NULL) { - if (ctx->failed) - mailbox_transaction_rollback(&ctx->trans); - else { - if (mailbox_transaction_commit(&ctx->trans, 0) < 0) - ctx->failed = TRUE; - } + /* even if something failed, we want to commit changes to + cache, as well as possible \Seen flag changes for FETCH + replies we returned so far. */ + if (mailbox_transaction_commit(&ctx->trans, 0) < 0) + ctx->failed = TRUE; } return ctx->failed ? -1 : 0; } diff --git a/src/lib-index/mail-transaction-log-view.c b/src/lib-index/mail-transaction-log-view.c --- a/src/lib-index/mail-transaction-log-view.c +++ b/src/lib-index/mail-transaction-log-view.c @@ -97,9 +97,10 @@ mail_transaction_log_view_set(struct mai uint32_t max_file_seq, uoff_t max_file_offset, enum mail_transaction_type type_mask) { - struct mail_transaction_log_file *file, *first; + struct mail_transaction_log_file *file, *const *files; + unsigned int i; uint32_t seq; - uoff_t end_offset; + uoff_t start_offset, end_offset; int ret; i_assert(view->log != NULL); @@ -159,12 +160,8 @@ mail_transaction_log_view_set(struct mai return -1; } - end_offset = min_file_seq == max_file_seq ? - max_file_offset : (uoff_t)-1; - ret = mail_transaction_log_file_map(file, min_file_offset, end_offset); - if (ret <= 0) - return ret; - first = file; + view->tail = file; + view->head = file; for (seq = min_file_seq+1; seq <= max_file_seq; seq++) { file = file->next; @@ -192,40 +189,57 @@ mail_transaction_log_view_set(struct mai /* missing files in the middle */ return 0; } - + view->head = file; + } + + /* we have all of them. update refcounts. */ + mail_transaction_log_view_unref_all(view); + + /* reference all used files */ + for (file = view->tail;; file = file->next) { + array_append(&view->file_refs, &file, 1); + file->refcount++; + + if (file == view->head) + break; + } + + /* Map the files only after we've found them all. Otherwise if we map + one file and then another file just happens to get rotated, we could + include both files in the view but skip the last transactions from + the first file. + + We're mapping the files in reverse order so that _log_file_map() + can verify that prev_file_offset matches how far it actually managed + to sync the file. */ + files = array_idx(&view->file_refs, 0); + for (i = array_count(&view->file_refs); i > 0; i--) { + file = files[i-1]; + start_offset = file->hdr.file_seq == min_file_seq ? + min_file_offset : file->hdr.hdr_size; end_offset = file->hdr.file_seq == max_file_seq ? max_file_offset : (uoff_t)-1; - ret = mail_transaction_log_file_map(file, file->hdr.hdr_size, + ret = mail_transaction_log_file_map(file, start_offset, end_offset); if (ret <= 0) return ret; } + i_assert(max_file_seq == (uint32_t)-1 || + max_file_seq == view->head->hdr.file_seq); i_assert(max_file_offset == (uoff_t)-1 || - max_file_offset <= file->sync_offset); - - /* we have all of them. update refcounts. */ - mail_transaction_log_view_unref_all(view); - - view->tail = first; - view->head = view->log->head; - - /* reference all used files */ - for (file = view->tail; file != NULL; file = file->next) { - array_append(&view->file_refs, &file, 1); - file->refcount++; - } + max_file_offset <= view->head->sync_offset); view->prev_file_seq = 0; view->prev_file_offset = 0; - view->cur = first; + view->cur = view->tail; view->cur_offset = min_file_offset; view->min_file_seq = min_file_seq; view->min_file_offset = min_file_offset; view->max_file_seq = max_file_seq; - view->max_file_offset = max_file_offset; + view->max_file_offset = I_MIN(max_file_offset, view->head->sync_offset); view->type_mask = type_mask; view->broken = FALSE; diff --git a/src/lib-index/mail-transaction-log.c b/src/lib-index/mail-transaction-log.c --- a/src/lib-index/mail-transaction-log.c +++ b/src/lib-index/mail-transaction-log.c @@ -1145,15 +1145,20 @@ mail_transaction_log_file_sync(struct ma if (file->sync_offset - file->buffer_offset + hdr_size > size) break; file->sync_offset += hdr_size; + hdr_size = 0; } avail = file->sync_offset - file->buffer_offset; - if (avail != size && avail >= sizeof(*hdr)) { - /* record goes outside the file we've seen. or if - we're accessing the log file via unlocked mmaped - memory, it may be just that the memory was updated - after we checked the file size. */ - if (file->locked || file->mmap_base == NULL) { + if (avail != size) { + /* There's more data than we could sync at the moment. If the + last record's size wasn't valid, we can't know if it will + be updated unless we've locked the log. + + Without locking we can be sure only if we're not using + mmaping, because with mmaping the data and the file size + can get updated at any time. */ + if (file->locked || + (hdr_size != 0 && file->mmap_base == NULL)) { if (hdr_size != 0) { mail_transaction_log_file_set_corrupted(file, "hdr.size too large (%u)", hdr_size); diff --git a/src/lib-storage/mail-copy.c b/src/lib-storage/mail-copy.c --- a/src/lib-storage/mail-copy.c +++ b/src/lib-storage/mail-copy.c @@ -2,7 +2,7 @@ #include "lib.h" #include "istream.h" -#include "mail-storage.h" +#include "mail-storage-private.h" #include "mail-copy.h" int mail_storage_copy(struct mailbox_transaction_context *t, struct mail *mail, @@ -31,6 +31,8 @@ int mail_storage_copy(struct mailbox_tra } if (input->stream_errno != 0) { + mail_storage_set_critical(t->box->storage, + "copy: i_stream_read() failed: %m"); mailbox_save_cancel(&ctx); return -1; } diff --git a/src/lib/ioloop-notify-inotify.c b/src/lib/ioloop-notify-inotify.c --- a/src/lib/ioloop-notify-inotify.c +++ b/src/lib/ioloop-notify-inotify.c @@ -155,7 +155,12 @@ void io_loop_notify_handler_init(struct ctx->inotify_fd = inotify_init(); if (ctx->inotify_fd == -1) { - i_error("inotify_init() failed: %m"); + if (errno != EMFILE) + i_error("inotify_init() failed: %m"); + else { + i_warning("Inotify instance limit for user exceeded, " + "disabling."); + } ctx->disabled = TRUE; return; } diff --git a/src/lib/lib-signals.c b/src/lib/lib-signals.c --- a/src/lib/lib-signals.c +++ b/src/lib/lib-signals.c @@ -21,10 +21,11 @@ struct signal_handler { /* Remember that these are accessed inside signal handler which may be called even while we're initializing/deinitializing. Try hard to keep everything in consistent state. */ -static struct signal_handler *signal_handlers[MAX_SIGNAL_VALUE+1]; -static int sig_pipe_fd[2]; - -static struct io *io_sig; +static struct signal_handler *signal_handlers[MAX_SIGNAL_VALUE+1] = { NULL, }; +static int sig_pipe_fd[2] = { -1, -1 }; + +static bool signals_initialized = FALSE; +static struct io *io_sig = NULL; static void sig_handler(int signo) { @@ -99,6 +100,18 @@ static void signal_read(void *context __ } } +static void lib_signals_set(int signo, bool ignore) +{ + struct sigaction act; + + if (sigemptyset(&act.sa_mask) < 0) + i_fatal("sigemptyset(): %m"); + act.sa_flags = 0; + act.sa_handler = ignore ? sig_ignore : sig_handler; + if (sigaction(signo, &act, NULL) < 0) + i_fatal("sigaction(%d): %m", signo); +} + void lib_signals_set_handler(int signo, bool delayed, signal_handler_t *handler, void *context) { @@ -109,17 +122,10 @@ void lib_signals_set_handler(int signo, signo, MAX_SIGNAL_VALUE); } - if (signal_handlers[signo] == NULL) { + if (signal_handlers[signo] == NULL && + (handler == NULL || signals_initialized)) { /* first handler for this signal */ - struct sigaction act; - - if (sigemptyset(&act.sa_mask) < 0) - i_fatal("sigemptyset(): %m"); - act.sa_flags = 0; - act.sa_handler = handler != NULL ? sig_handler : sig_ignore; - if (sigaction(signo, &act, NULL) < 0) - i_fatal("sigaction(%d): %m", signo); - + lib_signals_set(signo, handler == NULL); if (handler == NULL) { /* we're ignoring the handler, just return */ return; @@ -133,7 +139,10 @@ void lib_signals_set_handler(int signo, i_fatal("pipe() failed: %m"); fd_close_on_exec(sig_pipe_fd[0], TRUE); fd_close_on_exec(sig_pipe_fd[1], TRUE); - io_sig = io_add(sig_pipe_fd[0], IO_READ, signal_read, NULL); + if (signals_initialized) { + io_sig = io_add(sig_pipe_fd[0], IO_READ, + signal_read, NULL); + } } h = i_new(struct signal_handler, 1); @@ -186,10 +195,18 @@ void lib_signals_unset_handler(int signo void lib_signals_init(void) { - sig_pipe_fd[0] = sig_pipe_fd[1] = -1; - io_sig = NULL; - - memset(signal_handlers, 0, sizeof(signal_handlers)); + int i; + + signals_initialized = TRUE; + + /* add signals that were already registered */ + for (i = 0; i < MAX_SIGNAL_VALUE; i++) { + if (signal_handlers[i] != NULL) + lib_signals_set(i, FALSE); + } + + if (sig_pipe_fd[0] != -1) + io_sig = io_add(sig_pipe_fd[0], IO_READ, signal_read, NULL); } void lib_signals_deinit(void) diff --git a/src/plugins/quota/quota-maildir.c b/src/plugins/quota/quota-maildir.c --- a/src/plugins/quota/quota-maildir.c +++ b/src/plugins/quota/quota-maildir.c @@ -234,7 +234,7 @@ static int maildirsize_write(struct mail if (fd == -1) { if (errno == EAGAIN) { /* someone's just in the middle of updating it */ - return -1; + return 1; } mail_storage_set_critical(storage, diff --git a/src/plugins/quota/quota.c b/src/plugins/quota/quota.c --- a/src/plugins/quota/quota.c +++ b/src/plugins/quota/quota.c @@ -373,7 +373,8 @@ void quota_free(struct quota_transaction const char *quota_last_error(struct quota *quota) { - return quota->last_error != NULL ? quota->last_error : "Unknown error"; + return quota->last_error != NULL ? quota->last_error : + "Unknown quota error"; } void quota_set_error(struct quota *quota, const char *errormsg) diff --git a/src/pop3/client.c b/src/pop3/client.c --- a/src/pop3/client.c +++ b/src/pop3/client.c @@ -102,7 +102,7 @@ static int init_mailbox(struct client *c mail_free(&mail); if (mailbox_search_deinit(&ctx) < 0) { client_send_storage_error(client); - mailbox_transaction_rollback(&t); + (void)mailbox_transaction_commit(&t, 0); break; } @@ -113,8 +113,9 @@ static int init_mailbox(struct client *c return TRUE; } - /* well, sync and try again */ - mailbox_transaction_rollback(&t); + /* well, sync and try again. we might have cached virtual + sizes, make sure they get committed. */ + (void)mailbox_transaction_commit(&t, 0); } if (i == 2)