Mercurial > dovecot > original-hg > dovecot-1.2
changeset 6340:7b71ba1250e3 HEAD
Initial commit for dbox redesign/rewrite. Currently supports only one
message per file mode.
line wrap: on
line diff
--- a/src/lib-storage/index/dbox/Makefile.am Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/Makefile.am Sat Sep 01 03:04:02 2007 +0300 @@ -10,23 +10,20 @@ libstorage_dbox_a_SOURCES = \ dbox-file.c \ - dbox-keywords.c \ + dbox-index.c \ dbox-mail.c \ dbox-save.c \ dbox-sync.c \ - dbox-sync-expunge.c \ - dbox-sync-full.c \ + dbox-sync-file.c \ + dbox-sync-rebuild.c \ dbox-storage.c \ - dbox-transaction.c \ - dbox-uidlist.c + dbox-transaction.c headers = \ dbox-file.h \ - dbox-format.h \ - dbox-keywords.h \ + dbox-index.h \ dbox-storage.h \ - dbox-sync.h \ - dbox-uidlist.h + dbox-sync.h if INSTALL_HEADERS pkginc_libdir=$(pkgincludedir)/src/lib-storage/index/dbox
--- a/src/lib-storage/index/dbox/dbox-file.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-file.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,311 +1,1053 @@ -/* Copyright (C) 2005-2006 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" +#include "ioloop.h" #include "array.h" -#include "bsearch-insert-pos.h" #include "hex-dec.h" +#include "hostpid.h" #include "istream.h" #include "ostream.h" -#include "read-full.h" +#include "write-full.h" +#include "str.h" #include "dbox-storage.h" +#include "dbox-index.h" #include "dbox-file.h" -int dbox_file_lookup_offset(struct dbox_mailbox *mbox, - struct mail_index_view *view, uint32_t seq, - uint32_t *file_seq_r, uoff_t *offset_r) -{ - const void *data1, *data2; - bool expunged; +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +enum mail_flags dbox_mail_flags_map[DBOX_METADATA_FLAGS_COUNT] = { + MAIL_ANSWERED, + MAIL_FLAGGED, + MAIL_DELETED, + MAIL_SEEN, + MAIL_DRAFT +}; + +char dbox_mail_flag_chars[DBOX_METADATA_FLAGS_COUNT] = { + 'A', 'F', 'D', 'S', 'T' +}; - mail_index_lookup_ext(view, seq, mbox->dbox_file_ext_idx, - &data1, &expunged); - if (expunged) - return 0; - mail_index_lookup_ext(view, seq, mbox->dbox_offset_ext_idx, - &data2, &expunged); - if (expunged) - return 0; +static int dbox_file_metadata_skip_header(struct dbox_file *file); + +static char *dbox_generate_tmp_filename(const char *path) +{ + static unsigned int create_count = 0; - if (data1 == NULL || data2 == NULL) { - *file_seq_r = 0; - return 1; - } + return i_strdup_printf("%s/temp.%s.P%sQ%uM%s.%s", + path, dec2str(ioloop_timeval.tv_sec), my_pid, + create_count++, + dec2str(ioloop_timeval.tv_usec), my_hostname); +} - /* success */ - *file_seq_r = *((const uint32_t *)data1); - *offset_r = *((const uint64_t *)data2); - return 1; +void dbox_file_set_syscall_error(struct dbox_file *file, const char *function) +{ + mail_storage_set_critical(file->mbox->ibox.box.storage, + "%s(%s) failed: %m", function, file->path); } -void dbox_file_close(struct dbox_file *file) +static void +dbox_file_set_corrupted(struct dbox_file *file, const char *reason) +{ + mail_storage_set_critical(file->mbox->ibox.box.storage, + "%s corrupted: %s", file->path, reason); +} + + +static struct dbox_file * +dbox_find_and_move_open_file(struct dbox_mailbox *mbox, unsigned int file_id) { - if (array_is_created(&file->file_idx_keywords)) { - array_free(&file->idx_file_keywords); - array_free(&file->file_idx_keywords); + struct dbox_file *const *files, *file; + unsigned int i, count; + + files = array_get(&mbox->open_files, &count); + for (i = 0; i < count; i++) { + if (files[i]->file_id == file_id) { + /* move to last in the array */ + file = files[i]; + array_delete(&mbox->open_files, i, 1); + array_append(&mbox->open_files, &file, 1); + return file; + } } + return NULL; +} +static void dbox_file_free(struct dbox_file *file) +{ + i_assert(file->refcount == 0); + + if (file->metadata_pool != NULL) + pool_unref(file->metadata_pool); if (file->input != NULL) - i_stream_destroy(&file->input); + i_stream_unref(&file->input); + if (file->output != NULL) + o_stream_unref(&file->output); if (file->fd != -1) { if (close(file->fd) < 0) - i_error("close(dbox) failed: %m"); + dbox_file_set_syscall_error(file, "close"); + file->fd = -1; } - i_free(file->seeked_keywords); i_free(file->path); i_free(file); } -static int -dbox_file_read_mail_header(struct dbox_mailbox *mbox, struct dbox_file *file, - uoff_t offset) +void dbox_files_free(struct dbox_mailbox *mbox) +{ + struct dbox_file *const *files; + unsigned int i, count; + + files = array_get(&mbox->open_files, &count); + for (i = 0; i < count; i++) + dbox_file_free(files[i]); + array_clear(&mbox->open_files); +} + +static void +dbox_close_open_files(struct dbox_mailbox *mbox, unsigned int close_count) +{ + struct dbox_file *const *files; + unsigned int i, count; + + files = array_get(&mbox->open_files, &count); + for (i = 0; i < count;) { + if (files[i]->refcount == 0) { + dbox_file_free(files[i]); + array_delete(&mbox->open_files, i, 1); + + if (--close_count == 0) + break; + + files = array_get(&mbox->open_files, &count); + } else { + i++; + } + } +} + +static char * +dbox_file_id_get_path(struct dbox_mailbox *mbox, unsigned int file_id) +{ + if ((file_id & DBOX_FILE_ID_FLAG_UID) != 0) { + file_id &= ~DBOX_FILE_ID_FLAG_UID; + return i_strdup_printf("%s/"DBOX_MAIL_FILE_UID_FORMAT, + mbox->path, file_id); + } else { + return i_strdup_printf("%s/"DBOX_MAIL_FILE_MULTI_FORMAT, + mbox->path, file_id); + } +} + +struct dbox_file * +dbox_file_init(struct dbox_mailbox *mbox, unsigned int file_id) +{ + struct dbox_file *file; + unsigned int count; + + file = file_id == 0 ? NULL : + dbox_find_and_move_open_file(mbox, file_id); + if (file != NULL) { + file->refcount++; + return file; + } + + count = array_count(&mbox->open_files); + if (count > mbox->max_open_files) + dbox_close_open_files(mbox, count - mbox->max_open_files); + + file = i_new(struct dbox_file, 1); + file->refcount = 1; + file->mbox = mbox; + if (file_id != 0) { + file->file_id = file_id; + file->path = dbox_file_id_get_path(mbox, file_id); + } else { + file->path = dbox_generate_tmp_filename(mbox->path); + } + file->fd = -1; + + if (file_id != 0) + array_append(&file->mbox->open_files, &file, 1); + return file; +} + +int dbox_file_assign_id(struct dbox_file *file, unsigned int file_id) +{ + char *new_path; + + i_assert(file->file_id == 0); + i_assert(file_id != 0); + + new_path = dbox_file_id_get_path(file->mbox, file_id); + if (rename(file->path, new_path) < 0) { + mail_storage_set_critical(file->mbox->ibox.box.storage, + "rename(%s, %s) failed: %m", file->path, new_path); + i_free(new_path); + return -1; + } + + i_free(file->path); + file->path = new_path; + + file->file_id = file_id; + array_append(&file->mbox->open_files, &file, 1); + return 0; +} + +void dbox_file_unref(struct dbox_file **_file) { - const struct dbox_mail_header *hdr; - const unsigned char *data; - size_t size; + struct dbox_file *file = *_file; + struct dbox_file *const *files; + unsigned int i, count; + + *_file = NULL; + + i_assert(file->refcount > 0); + if (--file->refcount > 0) + return; + + /* don't cache metadata seeks while file isn't being referenced */ + file->metadata_read_offset = 0; + + if (file->file_id != 0) { + files = array_get(&file->mbox->open_files, &count); + if (!file->deleted && count <= file->mbox->max_open_files) { + /* we can leave this file open for now */ + return; + } + + for (i = 0; i < count; i++) { + if (files[i] == file) + break; + } + i_assert(i != count); + array_delete(&file->mbox->open_files, i, 1); + } + + dbox_file_free(file); +} + +static time_t day_begin_stamp(unsigned int days) +{ + struct tm tm; + time_t stamp; + + if (days == 0) + return 0; + + /* get beginning of today */ + tm = *localtime(&ioloop_time); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + stamp = mktime(&tm); + if (stamp == (time_t)-1) + i_panic("mktime(today) failed"); + + return stamp - (3600*24 * (days-1)); +} + +bool dbox_file_can_append(struct dbox_file *file, uoff_t mail_size) +{ + if (file->nonappendable) + return 0; + + if (file->append_offset == 0) { + /* messages have been expunged */ + return FALSE; + } - /* read the header */ - i_stream_seek(file->input, offset); - (void)i_stream_read_data(file->input, &data, &size, - file->mail_header_size-1); - if (size < file->mail_header_size) { + if (file->append_offset < file->mbox->rotate_min_size || + file->append_offset == file->file_header_size) + return TRUE; + if (file->append_offset + mail_size >= file->mbox->rotate_size) + return FALSE; + return file->create_time >= day_begin_stamp(file->mbox->rotate_days); +} + +static int dbox_file_parse_header(struct dbox_file *file, const char *line) +{ + const char *const *tmp, *value; + unsigned int pos; + enum dbox_header_key key; + + if (*line - '0' != DBOX_VERSION || line[1] != ' ') { + dbox_file_set_corrupted(file, "Invalid dbox version"); + return -1; + } + line += 2; + pos = 2; + + file->append_offset = 0; + file->msg_header_size = 0; + + t_push(); + for (tmp = t_strsplit(line, " "); *tmp != NULL; tmp++) { + key = **tmp; + value = *tmp + 1; + + switch (key) { + case DBOX_HEADER_APPEND_OFFSET: + file->append_offset_header_pos = pos + 1; + file->append_offset = *value == 'X' ? 0 : + strtoull(value, NULL, 16); + break; + case DBOX_HEADER_MSG_HEADER_SIZE: + file->msg_header_size = strtoul(value, NULL, 16); + break; + case DBOX_HEADER_CREATE_STAMP: + file->create_time = strtoul(value, NULL, 16); + break; + } + pos += strlen(value) + 2; + } + t_pop(); + + if (file->msg_header_size == 0) { + dbox_file_set_corrupted(file, "Missing message header size"); + return -1; + } + + if (!file->nonappendable) + file->nonappendable = !dbox_file_can_append(file, 0); + return 0; +} + +static int dbox_file_read_header(struct dbox_file *file) +{ + const char *line; + + i_stream_seek(file->input, 0); + line = i_stream_read_next_line(file->input); + if (line == NULL) { if (file->input->stream_errno == 0) return 0; - errno = file->input->stream_errno; - mail_storage_set_critical(&mbox->storage->storage, - "read(%s) failed: %m", file->path); + dbox_file_set_syscall_error(file, "read"); + return -1; + } + file->file_header_size = file->input->v_offset; + return dbox_file_parse_header(file, line) < 0 ? 0 : 1; +} + +static int dbox_file_open(struct dbox_file *file, bool read_header, + bool *deleted_r) +{ + i_assert(file->input == NULL); + + *deleted_r = FALSE; + + if (file->fd == -1) + file->fd = open(file->path, O_RDWR); + if (file->fd == -1) { + if (errno == ENOENT) { + *deleted_r = TRUE; + return 1; + } + + dbox_file_set_syscall_error(file, "open"); return -1; } - memcpy(&file->seeked_mail_header, data, - sizeof(file->seeked_mail_header)); - /* @UNSAFE */ - memcpy(file->seeked_keywords, data + sizeof(file->seeked_mail_header), - file->keyword_count); - file->seeked_offset = offset; + file->input = i_stream_create_fd(file->fd, MAIL_READ_BLOCK_SIZE, FALSE); + return !read_header ? 1 : dbox_file_read_header(file); +} + +static int dbox_file_create(struct dbox_file *file) +{ + string_t *hdr; + const char *hdrsize; + + i_assert(file->fd == -1); + + file->fd = open(file->path, O_RDWR | O_CREAT | O_TRUNC, 0600); + if (file->fd == -1) { + mail_storage_set_critical(file->mbox->ibox.box.storage, + "open(%s, O_CREAT) failed: %m", file->path); + return -1; + } + file->output = o_stream_create_fd_file(file->fd, 0, FALSE); - /* parse the header */ - hdr = &file->seeked_mail_header; - file->seeked_mail_size = - hex2dec(hdr->mail_size_hex, sizeof(hdr->mail_size_hex)); - file->seeked_uid = hex2dec(hdr->uid_hex, sizeof(hdr->uid_hex)); + hdr = t_str_new(128); + str_printfa(hdr, "%u %c%x %c%x %c", DBOX_VERSION, + DBOX_HEADER_MSG_HEADER_SIZE, + (unsigned int)sizeof(struct dbox_message_header), + DBOX_HEADER_CREATE_STAMP, (unsigned int)ioloop_time, + DBOX_HEADER_APPEND_OFFSET); + file->append_offset_header_pos = str_len(hdr); + str_printfa(hdr, "%08x\n", 0); - if (memcmp(hdr->magic, DBOX_MAIL_HEADER_MAGIC, - sizeof(hdr->magic)) != 0) { - mail_storage_set_critical(&mbox->storage->storage, - "Corrupted mail header at %"PRIuUOFF_T - " in dbox file %s", offset, file->path); + file->file_header_size = str_len(hdr); + file->msg_header_size = sizeof(struct dbox_message_header); + file->append_offset = str_len(hdr); + + hdrsize = t_strdup_printf("%08x", (unsigned int)file->append_offset); + buffer_write(hdr, file->append_offset_header_pos, hdrsize, 8); + + if (o_stream_send(file->output, str_data(hdr), str_len(hdr)) < 0) { + dbox_file_set_syscall_error(file, "write"); return -1; } + return 0; +} + +int dbox_file_open_or_create(struct dbox_file *file, bool read_header, + bool *deleted_r) +{ + *deleted_r = FALSE; + + if (file->file_id == 0) + return dbox_file_create(file) < 0 ? -1 : 1; + else if (file->input != NULL) + return 1; + else + return dbox_file_open(file, read_header, deleted_r); +} + +static int dbox_file_read_mail_header(struct dbox_file *file, uint32_t *uid_r, + uoff_t *physical_size_r) +{ + struct dbox_message_header hdr; + const unsigned char *data; + size_t size; + int ret; + + ret = i_stream_read_data(file->input, &data, &size, + file->msg_header_size - 1); + if (ret <= 0) { + if (file->input->stream_errno == 0) { + /* EOF, broken offset */ + return 0; + } + dbox_file_set_syscall_error(file, "read"); + return -1; + } + if (data[file->msg_header_size-1] != '\n') + return 0; + + memcpy(&hdr, data, I_MIN(sizeof(hdr), file->msg_header_size)); + if (memcmp(hdr.magic_pre, DBOX_MAGIC_PRE, sizeof(hdr.magic_pre)) != 0) { + /* probably broken offset */ + return 0; + } + + *uid_r = hex2dec(hdr.uid_hex, sizeof(hdr.uid_hex)); + *physical_size_r = hex2dec(hdr.message_size_hex, + sizeof(hdr.message_size_hex)); return 1; } -int dbox_file_seek(struct dbox_mailbox *mbox, uint32_t file_seq, uoff_t offset, - bool ignore_zero_uid) +int dbox_file_get_mail_stream(struct dbox_file *file, uoff_t offset, + uint32_t *uid_r, uoff_t *physical_size_r, + struct istream **stream_r, bool *expunged_r) { int ret; - if (mbox->file != NULL && mbox->file->file_seq != file_seq) { - dbox_file_close(mbox->file); - mbox->file = NULL; - } - - if (mbox->file == NULL) { - mbox->file = i_new(struct dbox_file, 1); - mbox->file->file_seq = file_seq; - mbox->file->fd = -1; - - mbox->file->path = - i_strdup_printf("%s/"DBOX_MAIL_FILE_FORMAT, - mbox->path, file_seq); - } + *expunged_r = FALSE; - if (mbox->file->fd == -1) { - mbox->file->fd = open(mbox->file->path, O_RDWR); - if (mbox->file->fd == -1) { - if (errno == ENOENT) - return 0; - mail_storage_set_critical(&mbox->storage->storage, - "open(%s) failed: %m", mbox->file->path); - return -1; - } - - mbox->file->input = - i_stream_create_fd(mbox->file->fd, 65536, FALSE); - - if (dbox_file_read_header(mbox, mbox->file) < 0) - return -1; - } else { - /* make sure we're not caching outdated data */ - i_stream_sync(mbox->file->input); + if (file->input == NULL) { + if ((ret = dbox_file_open(file, TRUE, expunged_r)) <= 0 || + *expunged_r) + return ret; } if (offset == 0) - offset = mbox->file->header_size; + offset = file->file_header_size; - if ((ret = dbox_file_read_mail_header(mbox, mbox->file, offset)) <= 0) + i_stream_seek(file->input, offset); + ret = dbox_file_read_mail_header(file, uid_r, physical_size_r); + if (ret <= 0) return ret; - if (mbox->file->seeked_mail_size == 0 || - (mbox->file->seeked_uid == 0 && !ignore_zero_uid)) { - /* could be legitimately just not written yet. we're at EOF. */ - return 0; + i_stream_skip(file->input, file->msg_header_size); + if (stream_r != NULL) { + *stream_r = i_stream_create_limit(file->input, + file->input->v_offset, + *physical_size_r); } return 1; } -int dbox_file_seek_next_nonexpunged(struct dbox_mailbox *mbox) +static int +dbox_file_seek_next_at_metadata(struct dbox_file *file, uoff_t *offset, + uint32_t *uid_r, uoff_t *physical_size_r) { - const struct dbox_mail_header *hdr; - uoff_t offset; + const char *line; int ret; - for (;;) { - offset = mbox->file->seeked_offset + - mbox->file->mail_header_size + - mbox->file->seeked_mail_size; + if ((ret = dbox_file_metadata_skip_header(file)) <= 0) + return ret; - ret = dbox_file_seek(mbox, mbox->file->file_seq, offset, FALSE); - if (ret <= 0) - return ret; - - hdr = &mbox->file->seeked_mail_header; - if (hdr->expunged != '1') { - /* non-expunged mail found */ + /* skip over the actual metadata */ + while ((line = i_stream_read_next_line(file->input)) != NULL) { + if (*line == DBOX_METADATA_SPACE) { + /* end of metadata */ break; } } + *offset = file->input->v_offset; + (void)i_stream_read(file->input); + if (!i_stream_have_bytes_left(file->input)) { + *uid_r = 0; + *physical_size_r = 0; + return 1; + } + + return dbox_file_read_mail_header(file, uid_r, physical_size_r); +} + +int dbox_file_seek_next(struct dbox_file *file, uoff_t *offset, + uint32_t *uid_r, uoff_t *physical_size_r) +{ + uint32_t uid; + uoff_t size; + bool first = *offset == 0; + bool deleted; + int ret; + + ret = dbox_file_get_mail_stream(file, *offset, &uid, &size, NULL, + &deleted); + if (ret <= 0) + return ret; + + if (deleted) { + *uid_r = 0; + *physical_size_r = 0; + return 1; + } + if (first) { + *uid_r = uid; + *physical_size_r = size; + return 1; + } + + i_stream_skip(file->input, size); + return dbox_file_seek_next_at_metadata(file, offset, uid_r, + physical_size_r); +} + +static int dbox_file_seek_append_pos(struct dbox_file *file, uoff_t mail_size) +{ + int ret; + + if ((ret = dbox_file_read_header(file)) <= 0) + return ret; + + if (file->append_offset == 0 || + file->msg_header_size != sizeof(struct dbox_message_header) || + !dbox_file_can_append(file, mail_size)) { + /* can't append */ + return 0; + } + + file->output = o_stream_create_fd_file(file->fd, (uoff_t)-2, FALSE); + o_stream_seek(file->output, file->append_offset); + return 1; +} + +static int +dbox_file_get_append_stream_int(struct dbox_file *file, uoff_t mail_size, + struct ostream **stream_r) +{ + bool deleted; + int ret; + + if (file->fd == -1) { + i_assert(file->output == NULL); + if ((ret = dbox_file_open_or_create(file, FALSE, + &deleted)) <= 0 || deleted) + return ret; + } + + if (file->output == NULL) { + ret = dbox_file_seek_append_pos(file, mail_size); + if (ret <= 0) + return ret; + } else { + if (!dbox_file_can_append(file, mail_size)) + return 0; + } + + if (file->output->offset > (uint32_t)-1) { + /* we use 32bit offsets to messages */ + return 0; + } + + o_stream_ref(file->output); + *stream_r = file->output; + return 1; +} + +int dbox_file_get_append_stream(struct dbox_file *file, uoff_t mail_size, + struct ostream **stream_r) +{ + int ret; + + if (file->nonappendable) + return 0; + + ret = dbox_file_get_append_stream_int(file, mail_size, stream_r); + if (ret == 0) + file->nonappendable = TRUE; + return ret; +} + +uoff_t dbox_file_get_next_append_offset(struct dbox_file *file) +{ + i_assert(file->output_stream_offset != 0); + i_assert(file->output == NULL || + file->output_stream_offset == file->output->offset); + + return file->output_stream_offset; +} + +void dbox_file_cancel_append(struct dbox_file *file, uoff_t append_offset) +{ + if (ftruncate(file->fd, append_offset) < 0) { + dbox_file_set_syscall_error(file, "ftruncate"); + file->append_offset = 0; + file->nonappendable = TRUE; + } + + o_stream_seek(file->output, append_offset); + file->output_stream_offset = append_offset; +} + +void dbox_file_finish_append(struct dbox_file *file) +{ + file->output_stream_offset = file->output->offset; + file->append_offset = file->output->offset; + file->append_count++; +} + +uoff_t dbox_file_get_metadata_offset(struct dbox_file *file, uoff_t offset, + uoff_t physical_size) +{ + if (offset == 0) { + i_assert(file->file_header_size != 0); + offset = file->file_header_size; + } + return offset + sizeof(struct dbox_message_header) + physical_size; +} + +static int dbox_file_metadata_skip_header(struct dbox_file *file) +{ + struct dbox_metadata_header metadata_hdr; + const unsigned char *data; + size_t size; + int ret; + + ret = i_stream_read_data(file->input, &data, &size, + sizeof(metadata_hdr) - 1); + if (ret <= 0) { + if (file->input->stream_errno == 0) { + /* EOF, broken offset */ + return 0; + } + dbox_file_set_syscall_error(file, "read"); + return -1; + } + memcpy(&metadata_hdr, data, sizeof(metadata_hdr)); + if (memcmp(metadata_hdr.magic_post, DBOX_MAGIC_POST, + sizeof(metadata_hdr.magic_post)) != 0) { + /* probably broken offset */ + return 0; + } + i_stream_skip(file->input, sizeof(metadata_hdr)); + return 1; +} + +int dbox_file_metadata_seek(struct dbox_file *file, uoff_t metadata_offset, + bool *expunged_r) +{ + const char *line; + uoff_t metadata_data_offset, prev_offset; + bool deleted; + int ret; + + *expunged_r = FALSE; + + if (file->metadata_read_offset == metadata_offset) + return 1; + + if (file->metadata_pool != NULL) { + if (array_is_created(&file->metadata_changes)) + array_free(&file->metadata_changes); + p_clear(file->metadata_pool); + } else { + file->metadata_pool = + pool_alloconly_create("dbox metadata", 512); + } + file->metadata_read_offset = 0; + + if (file->input == NULL) { + if ((ret = dbox_file_open(file, TRUE, &deleted)) <= 0) + return ret; + if (deleted) { + *expunged_r = TRUE; + return 1; + } + } + + i_stream_seek(file->input, metadata_offset); + if ((ret = dbox_file_metadata_skip_header(file)) <= 0) + return ret; + metadata_data_offset = file->input->v_offset; + + *expunged_r = TRUE; + p_array_init(&file->metadata, file->metadata_pool, 16); + for (;;) { + prev_offset = file->input->v_offset; + if ((line = i_stream_read_next_line(file->input)) == NULL) + break; + + if (*line == DBOX_METADATA_SPACE || *line == '\0') { + /* end of metadata */ + file->metadata_space_pos = + prev_offset - metadata_data_offset; + *expunged_r = FALSE; + break; + } + line = p_strdup(file->metadata_pool, line); + array_append(&file->metadata, &line, 1); + } + file->metadata_read_offset = metadata_offset; + file->metadata_len = file->input->v_offset - metadata_data_offset; + if (*expunged_r) + file->metadata_space_pos = file->metadata_len; + return 1; +} + +int dbox_file_metadata_seek_mail_offset(struct dbox_file *file, uoff_t offset, + bool *expunged_r) +{ + uoff_t physical_size, metadata_offset; + uint32_t uid; + bool expunged1, expunged2; + int ret; + + ret = dbox_file_get_mail_stream(file, offset, &uid, &physical_size, + NULL, &expunged1); + if (ret <= 0) + return ret; + + metadata_offset = + dbox_file_get_metadata_offset(file, offset, physical_size); + ret = dbox_file_metadata_seek(file, metadata_offset, &expunged2); + if (ret <= 0) + return ret; + *expunged_r = expunged1 || expunged2; return 1; } -void dbox_file_header_init(struct dbox_file_header *hdr) +const char *dbox_file_metadata_get(struct dbox_file *file, + enum dbox_metadata_key key) { - uint16_t base_header_size = sizeof(*hdr); - uint32_t header_size = - base_header_size + DBOX_KEYWORD_NAMES_RESERVED_SPACE; - uint32_t append_offset = header_size; - uint16_t keyword_count = DBOX_KEYWORD_COUNT; - uint16_t mail_header_size = - sizeof(struct dbox_mail_header) + keyword_count; - uint32_t create_time = ioloop_time; - - memset(hdr, '0', sizeof(*hdr)); - DEC2HEX(hdr->base_header_size_hex, base_header_size); - DEC2HEX(hdr->header_size_hex, header_size); - DEC2HEX(hdr->append_offset_hex, append_offset); - DEC2HEX(hdr->create_time_hex, create_time); - DEC2HEX(hdr->mail_header_size_hex, mail_header_size); - DEC2HEX(hdr->keyword_list_offset_hex, base_header_size); - DEC2HEX(hdr->keyword_count_hex, keyword_count); -} - -int dbox_file_header_parse(struct dbox_mailbox *mbox, struct dbox_file *file, - const struct dbox_file_header *hdr) -{ - file->hdr = *hdr; + const char *const *metadata; + unsigned int i, count; - file->base_header_size = hex2dec(hdr->base_header_size_hex, - sizeof(hdr->base_header_size_hex)); - file->header_size = hex2dec(hdr->header_size_hex, - sizeof(hdr->header_size_hex)); - file->append_offset = hex2dec(hdr->append_offset_hex, - sizeof(hdr->append_offset_hex)); - file->create_time = hex2dec(hdr->create_time_hex, - sizeof(hdr->create_time_hex)); - file->mail_header_size = hex2dec(hdr->mail_header_size_hex, - sizeof(hdr->mail_header_size_hex)); - file->mail_header_align = - hex2dec(hdr->mail_header_align_hex, - sizeof(hdr->mail_header_align_hex)); - file->keyword_count = hex2dec(hdr->keyword_count_hex, - sizeof(hdr->keyword_count_hex)); - file->keyword_list_offset = - hex2dec(hdr->keyword_list_offset_hex, - sizeof(hdr->keyword_list_offset_hex)); - - if (file->base_header_size == 0 || - file->header_size < file->base_header_size || - file->append_offset < file->header_size || - file->keyword_list_offset < file->base_header_size || - file->mail_header_size < sizeof(struct dbox_mail_header) || - file->keyword_count > file->mail_header_size - - sizeof(struct dbox_mail_header)) { - mail_storage_set_critical(&mbox->storage->storage, - "dbox %s: broken file header", file->path); - return -1; + metadata = array_get(&file->metadata, &count); + for (i = 0; i < count; i++) { + if (*metadata[i] == (char)key) + return metadata[i] + 1; } - - i_free(file->seeked_keywords); - file->seeked_keywords = file->keyword_count == 0 ? NULL : - i_malloc(file->keyword_count); - return 0; + return NULL; } -int dbox_file_read_header(struct dbox_mailbox *mbox, struct dbox_file *file) +void dbox_file_metadata_set(struct dbox_file *file, enum dbox_metadata_key key, + const char *value) { - struct dbox_file_header hdr; - const unsigned char *data; - size_t size; + const char **changes, *data; + unsigned int i, count; - /* read the file header */ - i_stream_seek(file->input, 0); - (void)i_stream_read_data(file->input, &data, &size, sizeof(hdr)-1); - if (size < sizeof(hdr)) { - if (file->input->stream_errno != 0) { - errno = file->input->stream_errno; - mail_storage_set_critical(&mbox->storage->storage, - "read(%s) failed: %m", file->path); - return -1; - } + data = dbox_file_metadata_get(file, key); + if (data != NULL && strcmp(data, value) == 0) { + /* value didn't change */ + return; + } - mail_storage_set_critical(&mbox->storage->storage, - "dbox %s: unexpected end of file", file->path); - return -1; + if (file->metadata_pool == NULL) { + file->metadata_pool = + pool_alloconly_create("dbox metadata", 512); } - memcpy(&hdr, data, sizeof(hdr)); + data = p_strdup_printf(file->metadata_pool, "%c%s", (char)key, value); - /* parse the header */ - if (dbox_file_header_parse(mbox, file, &hdr) < 0) - return -1; + if (!array_is_created(&file->metadata_changes)) + p_array_init(&file->metadata_changes, file->metadata_pool, 16); + else { + /* see if we have already changed this metadata */ + changes = array_get_modifiable(&file->metadata_changes, &count); + for (i = 0; i < count; i++) { + if (*changes[i] == (char)key) { + changes[i] = data; + return; + } + } + } - /* keywords may not be up to date anymore */ - if (array_is_created(&file->idx_file_keywords)) { - array_free(&file->idx_file_keywords); - array_free(&file->file_idx_keywords); - } - return 0; + array_append(&file->metadata_changes, &data, 1); } -int dbox_file_write_header(struct dbox_mailbox *mbox, struct dbox_file *file) +static int dbox_file_metadata_is_at_eof(struct dbox_file *file) { - struct dbox_file_header hdr; - char buf[1024]; + uoff_t size; + uint32_t uid; + uoff_t offset; int ret; - dbox_file_header_init(&hdr); - ret = dbox_file_header_parse(mbox, file, &hdr); - i_assert(ret == 0); + if ((file->file_id & DBOX_FILE_ID_FLAG_UID) != 0) + return 1; + + offset = file->metadata_read_offset; + ret = dbox_file_seek_next_at_metadata(file, &offset, &uid, &size); + return ret <= 0 ? ret : uid == 0; +} - /* write header + LF to mark end-of-keywords list */ - if (o_stream_send(file->output, &hdr, sizeof(hdr)) < 0 || - o_stream_send_str(file->output, "\n") < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "write(%s) failed: %m", file->path); - return -1; - } +static int dbox_file_write_empty_block(struct dbox_file *file, uoff_t offset, + unsigned int len) +{ + char space[256]; - /* fill the rest of the header with spaces */ - memset(buf, ' ', sizeof(buf)); - while (file->output->offset < file->header_size) { - unsigned int size = I_MIN(sizeof(buf), file->header_size - - file->output->offset); + i_assert(len > 0); - if (o_stream_send(file->output, buf, size) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "write(%s) failed: %m", file->path); + len--; + memset(space, DBOX_METADATA_SPACE, I_MIN(sizeof(space), len)); + while (len >= sizeof(space)) { + if (pwrite_full(file->fd, space, sizeof(space), offset) < 0) { + dbox_file_set_syscall_error(file, "pwrite"); return -1; } } - return 0; + /* @UNSAFE: last block ends with LF */ + space[len++] = '\n'; + if (pwrite_full(file->fd, space, len, offset) < 0) { + dbox_file_set_syscall_error(file, "pwrite"); + return -1; + } + file->metadata_len += len; + return 1; +} + +static int dbox_file_grow_metadata(struct dbox_file *file, unsigned int len) +{ + enum dbox_index_file_lock_status lock_status; + uoff_t offset; + int ret; + + ret = dbox_index_try_lock_file(file->mbox->dbox_index, file->file_id, + &lock_status); + if (ret <= 0 || (ret = dbox_file_metadata_is_at_eof(file)) <= 0) + return ret; + + offset = file->metadata_read_offset + + sizeof(struct dbox_metadata_header) + file->metadata_len; + i_stream_seek(file->input, offset); + (void)i_stream_read(file->input); + if (!i_stream_have_bytes_left(file->input)) { + len = len - file->metadata_len + DBOX_EXTRA_SPACE; + ret = dbox_file_write_empty_block(file, offset, len); + } else { + i_error("%s: Metadata changed unexpectedly", file->path); + ret = 0; + } + + dbox_index_unlock_file(file->mbox->dbox_index, file->file_id); + return ret; } + +int dbox_file_metadata_write(struct dbox_file *file) +{ + const char *const *metadata, *const *changes; + unsigned int i, j, count, changes_count, space_needed, skip_pos; + char space[DBOX_EXTRA_SPACE]; + string_t *str; + uoff_t offset; + size_t last_change_len, orig_len; + int ret; + + if (!array_is_created(&file->metadata_changes)) { + /* nothing to write */ + return 1; + } + + offset = file->metadata_read_offset + + sizeof(struct dbox_metadata_header); + metadata = array_get(&file->metadata, &count); + changes = array_get(&file->metadata_changes, &changes_count); + + /* skip as many metadata fields from beginning as we can */ + for (i = skip_pos = 0; i < count; i++) { + for (j = 0; j < changes_count; j++) { + if (*changes[j] == *metadata[i]) + break; + } + if (j != changes_count) + break; + skip_pos += strlen(metadata[i]) + 1; + } + + t_push(); + str = t_str_new(512); + last_change_len = orig_len = 0; + /* overwrite existing metadata fields */ + for (; i < count; i++) { + for (j = 0; j < changes_count; j++) { + if (*changes[j] == *metadata[i]) + break; + } + if (j != changes_count) { + str_append(str, changes[j]); + str_append_c(str, '\n'); + last_change_len = str_len(str); + } else { + str_append(str, metadata[i]); + str_append_c(str, '\n'); + if (orig_len != str_len(str)) + last_change_len = str_len(str); + } + orig_len += strlen(metadata[i]) + 1; + } + /* add new metadata */ + for (j = 0; j < changes_count; j++) { + for (i = 0; i < count; i++) { + if (*changes[j] == *metadata[i]) + break; + } + if (i == count) { + str_append(str, changes[j]); + str_append_c(str, '\n'); + last_change_len = str_len(str); + } + } + str_truncate(str, last_change_len); + if (skip_pos + str_len(str) >= file->metadata_len) { + if ((ret = dbox_file_grow_metadata(file, skip_pos + + str_len(str))) <= 0) { + t_pop(); + return ret; + } + } + + memset(space, DBOX_METADATA_SPACE, sizeof(space)); + while (skip_pos + str_len(str) < file->metadata_space_pos) { + space_needed = file->metadata_space_pos - + (skip_pos + str_len(str)); + str_append_n(str, space, I_MIN(sizeof(space), space_needed)); + } + i_assert(skip_pos + str_len(str) <= file->metadata_len); + + ret = pwrite_full(file->fd, str_data(str), str_len(str), + offset + skip_pos); + if (ret < 0) + dbox_file_set_syscall_error(file, "pwrite"); + t_pop(); + return ret < 0 ? -1 : 1; +} + +void dbox_file_metadata_write_to(struct dbox_file *file, struct ostream *output) +{ + struct dbox_metadata_header metadata_hdr; + char space[DBOX_EXTRA_SPACE]; + const char *const *metadata, *const *changes; + unsigned int i, j, count, changes_count; + + memset(&metadata_hdr, 0, sizeof(metadata_hdr)); + memcpy(metadata_hdr.magic_post, DBOX_MAGIC_POST, + sizeof(metadata_hdr.magic_post)); + o_stream_send(output, &metadata_hdr, sizeof(metadata_hdr)); + + metadata = array_get(&file->metadata, &count); + if (!array_is_created(&file->metadata_changes)) { + for (i = 0; i < count; i++) { + o_stream_send_str(output, metadata[i]); + o_stream_send(output, "\n", 1); + } + } else { + changes = array_get(&file->metadata_changes, &changes_count); + /* write unmodified metadata */ + for (i = 0; i < count; i++) { + for (j = 0; j < changes_count; j++) { + if (*changes[j] == *metadata[i]) + break; + } + if (j == changes_count) { + o_stream_send_str(output, metadata[i]); + o_stream_send(output, "\n", 1); + } + } + /* write modified metadata */ + for (i = 0; i < changes_count; i++) { + o_stream_send_str(output, metadata[i]); + o_stream_send(output, "\n", 1); + } + } + + memset(space, ' ', sizeof(space)); + o_stream_send(output, space, sizeof(space)); + o_stream_send(output, "\n", 1); +} + +bool dbox_file_lookup(struct dbox_mailbox *mbox, struct mail_index_view *view, + uint32_t seq, uint32_t *file_id_r, uoff_t *offset_r) +{ + const struct dbox_mail_index_record *dbox_rec; + const void *data; + uint32_t uid; + bool expunged; + + mail_index_lookup_ext(view, seq, mbox->dbox_ext_id, &data, &expunged); + if (expunged) + return FALSE; + if (data == NULL) { + mail_index_lookup_uid(view, seq, &uid); + if ((uid & DBOX_FILE_ID_FLAG_UID) != 0) { + /* something's broken, we can't handle this high UIDs */ + return FALSE; + } + *file_id_r = DBOX_FILE_ID_FLAG_UID | uid; + *offset_r = 0; + } else { + dbox_rec = data; + *file_id_r = dbox_rec->file_id; + *offset_r = dbox_rec->offset; + } + return TRUE; +} + +void dbox_mail_metadata_flags_append(string_t *str, enum mail_flags flags) +{ + unsigned int i; + + for (i = 0; i < DBOX_METADATA_FLAGS_COUNT; i++) { + if ((flags & dbox_mail_flags_map[i]) != 0) + str_append_c(str, dbox_mail_flag_chars[i]); + else + str_append_c(str, '0'); + } +} + +void dbox_mail_metadata_keywords_append(struct dbox_mailbox *mbox, + string_t *str, + const struct mail_keywords *keywords) +{ + const ARRAY_TYPE(keywords) *keyword_names_list; + const char *const *keyword_names; + unsigned int i, keyword_names_count; + + if (keywords == NULL || keywords->count == 0) + return; + + keyword_names_list = mail_index_get_keywords(mbox->ibox.index); + keyword_names = array_get(keyword_names_list, &keyword_names_count); + + for (i = 0; i < keywords->count; i++) { + i_assert(keywords->idx[i] < keyword_names_count); + + str_append(str, keyword_names[keywords->idx[i]]); + str_append_c(str, ' '); + } + str_truncate(str, str_len(str)-1); +}
--- a/src/lib-storage/index/dbox/dbox-file.h Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-file.h Sat Sep 01 03:04:02 2007 +0300 @@ -1,26 +1,216 @@ #ifndef __DBOX_FILE_H #define __DBOX_FILE_H -struct mail_index_view; -struct dbox_mailbox; -struct dbox_file; -struct dbox_file_header; +/* The file begins with a header followed by zero or more messages: + + <dbox message header> + <LF> + <message body> + <metadata> + + Metadata block begins with DBOX_MAGIC_POST, followed by zero or more lines + in format <key character><value><LF>. The block ends with a line containing + zero or more spaces. The spaces can be used for writing more headers. + Unknown metadata should be ignored, but preserved when copying. + + There should be no duplicates for the current metadata, but future + extensions may need them so they should be preserved. +*/ +#define DBOX_VERSION 1 +#define DBOX_MAGIC_PRE "\001\002" +#define DBOX_MAGIC_POST "\n\001\003\n" + +#define DBOX_EXTRA_SPACE 64 +/* If file_id has this flag set, the file is a single file with file_id=UID. */ +#define DBOX_FILE_ID_FLAG_UID 0x80000000 + +enum dbox_header_key { + /* Offset for appending next message. In %08x format so it can be + updated without moving data in header. If messages have been + expunged and file must not be appended anymore, the value is filled + with 'X'. */ + DBOX_HEADER_APPEND_OFFSET = 'A', + /* Must be sizeof(struct dbox_message_header) when appending (hex) */ + DBOX_HEADER_MSG_HEADER_SIZE = 'M', + /* Creation UNIX timestamp (hex) */ + DBOX_HEADER_CREATE_STAMP = 'C' +}; + +enum dbox_metadata_flags { + DBOX_METADATA_FLAGS_ANSWERED = 0, + DBOX_METADATA_FLAGS_FLAGGED, + DBOX_METADATA_FLAGS_DELETED, + DBOX_METADATA_FLAGS_SEEN, + DBOX_METADATA_FLAGS_DRAFT, + + DBOX_METADATA_FLAGS_COUNT +}; -/* Returns -1 = error, 0 = expunged, 1 = ok */ -int dbox_file_lookup_offset(struct dbox_mailbox *mbox, - struct mail_index_view *view, uint32_t seq, - uint32_t *file_seq_r, uoff_t *offset_r); +enum dbox_metadata_key { + /* Message is marked as expunged. '0' = no, '1' = yes */ + DBOX_METADATA_EXPUNGED = 'E', + /* Message flags in dbox_metadata_flags order. '0' = not set, anything + else = set. Unknown flags should be preserved. */ + DBOX_METADATA_FLAGS = 'F', + /* Space separated list of keywords */ + DBOX_METADATA_KEYWORDS = 'K', + /* Pointer to external message data. Format is: + 1*(<start offset> <byte count> <ref>) */ + DBOX_METADATA_EXT_REF = 'P', + /* Received UNIX timestamp in hex */ + DBOX_METADATA_RECEIVED_TIME = 'R', + /* Saved UNIX timestamp in hex */ + DBOX_METADATA_SAVE_TIME = 'S', + /* Virtual message size in hex (line feeds counted as CRLF) */ + DBOX_METADATA_VIRTUAL_SIZE = 'V', + + /* End of metadata block. The spaces can be used for writing more + metadata. */ + DBOX_METADATA_SPACE = ' ' +}; + +enum dbox_message_type { + /* Normal message */ + DBOX_MESSAGE_TYPE_NORMAL = 'N', + /* Parts of the message exists outside the following data. + See the metadata for how to find them. */ + DBOX_MESSAGE_TYPE_EXT_REFS = 'E' +}; + +struct dbox_message_header { + unsigned char magic_pre[2]; + unsigned char type; + unsigned char space1; + unsigned char uid_hex[8]; + unsigned char space2; + unsigned char message_size_hex[16]; + /* <space reserved for future extensions, LF is always last> */ + unsigned char save_lf; +}; + +struct dbox_metadata_header { + unsigned char magic_post[sizeof(DBOX_MAGIC_POST)-1]; +}; + +struct dbox_file { + struct dbox_mailbox *mbox; + int refcount; + unsigned int file_id; + + unsigned int file_header_size; + unsigned int msg_header_size; + unsigned int append_offset_header_pos; + + unsigned int append_count; + uint32_t last_append_uid; -void dbox_file_close(struct dbox_file *file); -/* Returns -1 = error, 0 = EOF (mail was just moved / file broken), 1 = ok */ -int dbox_file_seek(struct dbox_mailbox *mbox, uint32_t file_seq, uoff_t offset, - bool ignore_zero_uid); -int dbox_file_seek_next_nonexpunged(struct dbox_mailbox *mbox); + uoff_t append_offset; + time_t create_time; + uoff_t output_stream_offset; + + char *path; + int fd; + struct istream *input; + struct ostream *output; + + /* Metadata for the currently seeked metadata block. */ + pool_t metadata_pool; + ARRAY_DEFINE(metadata, const char *); + ARRAY_DEFINE(metadata_changes, const char *); + uoff_t metadata_read_offset; + unsigned int metadata_space_pos; + /* Includes the trailing LF that shouldn't be used */ + unsigned int metadata_len; + + unsigned int nonappendable:1; + unsigned int deleted:1; +}; + +extern enum mail_flags dbox_mail_flags_map[DBOX_METADATA_FLAGS_COUNT]; +extern char dbox_mail_flag_chars[DBOX_METADATA_FLAGS_COUNT]; + +struct dbox_file * +dbox_file_init(struct dbox_mailbox *mbox, unsigned int file_id); +void dbox_file_unref(struct dbox_file **file); + +/* Free all currently opened files. */ +void dbox_files_free(struct dbox_mailbox *mbox); + +/* Assign a newly created file (file_id=0) a new id. */ +int dbox_file_assign_id(struct dbox_file *file, unsigned int file_id); + +/* If file_id is 0, open the file, otherwise create it. Returns 1 if ok, + 0 if read_header=TRUE and opened file was broken, -1 if error. If file is + deleted, deleted_r=TRUE and 1 is returned. */ +int dbox_file_open_or_create(struct dbox_file *file, bool read_header, + bool *deleted_r); + +/* Seek to given offset in file and return the message's input stream, UID + and physical size. Returns 1 if ok, 0 if file/offset is corrupted, + -1 if I/O error. */ +int dbox_file_get_mail_stream(struct dbox_file *file, uoff_t offset, + uint32_t *uid_r, uoff_t *physical_size_r, + struct istream **stream_r, bool *expunged_r); +/* Seek to next message after given offset, or to first message if offset=0. + If there are no more messages, uid_r is set to 0. Returns 1 if ok, 0 if + file/offset is corrupted, -1 if I/O error. */ +int dbox_file_seek_next(struct dbox_file *file, uoff_t *offset, + uint32_t *uid_r, uoff_t *physical_size_r); -void dbox_file_header_init(struct dbox_file_header *hdr); -int dbox_file_header_parse(struct dbox_mailbox *mbox, struct dbox_file *file, - const struct dbox_file_header *hdr); -int dbox_file_read_header(struct dbox_mailbox *mbox, struct dbox_file *file); -int dbox_file_write_header(struct dbox_mailbox *mbox, struct dbox_file *file); +/* Returns TRUE if mail_size bytes can be appended to the file. */ +bool dbox_file_can_append(struct dbox_file *file, uoff_t mail_size); +/* Get output stream for appending a new message. Returns 1 if ok, 0 if + file can't be appended to (limits reached, expunges, corrupted) or + -1 if error. If 0 is returned, index is also updated. */ +int dbox_file_get_append_stream(struct dbox_file *file, uoff_t mail_size, + struct ostream **stream_r); +/* Returns the next offset for append a message. dbox_file_get_append_stream() + must have been called for this file already at least once. */ +uoff_t dbox_file_get_next_append_offset(struct dbox_file *file); +/* Truncate file to append_offset */ +void dbox_file_cancel_append(struct dbox_file *file, uoff_t append_offset); +/* Finish appending the current mail. */ +void dbox_file_finish_append(struct dbox_file *file); + +/* Calculate offset to message's metadata. */ +uoff_t dbox_file_get_metadata_offset(struct dbox_file *file, uoff_t offset, + uoff_t physical_size); +/* Seek to given metadata block. Returns 1 if ok, 0 if file/offset is + corrupted, -1 if I/O error. If message has already been expunged, + expunged_r=TRUE and 1 is returned. */ +int dbox_file_metadata_seek(struct dbox_file *file, uoff_t metadata_offset, + bool *expunged_r); +/* Like dbox_file_metadata_seek(), but the offset points to beginning of the + message. The function internally reads the message header to find the + metadata offset. */ +int dbox_file_metadata_seek_mail_offset(struct dbox_file *file, uoff_t offset, + bool *expunged_r); + +/* Return wanted metadata value, or NULL if not found. */ +const char *dbox_file_metadata_get(struct dbox_file *file, + enum dbox_metadata_key key); +/* Add key=value metadata update (not written yet, not visible to _get()). + The changes are reset by dbox_file_metadata_seek() call. */ +void dbox_file_metadata_set(struct dbox_file *file, enum dbox_metadata_key key, + const char *value); +/* Write all metadata updates to disk. Returns 1 if ok, 0 if metadata doesn't + fit to its reserved space and message isn't last in file, -1 if I/O error. */ +int dbox_file_metadata_write(struct dbox_file *file); +/* Write all metadata to output stream. Returns 0 if ok, -1 if I/O error. */ +void dbox_file_metadata_write_to(struct dbox_file *file, + struct ostream *output); + +/* Get file/offset for wanted message. Returns TRUE if found. */ +bool dbox_file_lookup(struct dbox_mailbox *mbox, struct mail_index_view *view, + uint32_t seq, uint32_t *file_id_r, uoff_t *offset_r); + +/* Append flags as metadata value to given string */ +void dbox_mail_metadata_flags_append(string_t *str, enum mail_flags flags); +/* Append keywords as metadata value to given string */ +void dbox_mail_metadata_keywords_append(struct dbox_mailbox *mbox, + string_t *str, + const struct mail_keywords *keywords); + +void dbox_file_set_syscall_error(struct dbox_file *file, const char *function); #endif
--- a/src/lib-storage/index/dbox/dbox-format.h Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -#ifndef __DBOX_FORMAT_H -#define __DBOX_FORMAT_H - -#define DBOX_SUBSCRIPTION_FILE_NAME "subscriptions" -#define DBOX_INDEX_PREFIX "dovecot.index" -#define DBOX_MAILDIR_NAME "dbox-Mails" -#define DBOX_MAIL_FILE_PREFIX "msg." -#define DBOX_MAIL_FILE_FORMAT DBOX_MAIL_FILE_PREFIX"%u" - -#define DBOX_KEYWORD_COUNT 64 -#define DBOX_KEYWORD_NAMES_RESERVED_SPACE (2048-sizeof(struct dbox_file_header)) - -/* Default rotation settings */ -#define DBOX_DEFAULT_ROTATE_SIZE (2*1024*1024) -#define DBOX_DEFAULT_ROTATE_MIN_SIZE (1024*16) -#define DBOX_DEFAULT_ROTATE_DAYS 0 - -struct dbox_file_header { - /* Size of the base header. sizeof(struct dbox_file_header) */ - unsigned char base_header_size_hex[4]; - /* Size of the full header, including keywords list and padding */ - unsigned char header_size_hex[8]; - /* Offset where to store the next mail. note that a mail may already - have been fully written here and added to uidlist, but this offset - just wasn't updated. In that case the append_offset should be - updated instead of overwriting the mail. */ - unsigned char append_offset_hex[16]; - /* Initial file creation time as UNIX timestamp. */ - unsigned char create_time_hex[8]; - /* Size of each message's header. */ - unsigned char mail_header_size_hex[4]; - /* If set, mail headers start always at given alignmentation. - Currently not supported. */ - unsigned char mail_header_align_hex[4]; - /* Number of keywords allocated for each mail (not necessarily used) */ - unsigned char keyword_count_hex[4]; - /* Offset for the keyword list inside the file header. */ - unsigned char keyword_list_offset_hex[8]; - - /* Non-zero if some mails have been marked as expunged in the file. */ - unsigned char have_expunged_mails; - - /* space reserved for keyword list and possible other future - extensions. */ - /* unsigned char [header_size - header_base_size]; */ -}; - -#define DBOX_MAIL_HEADER_MAGIC "\001\003" -struct dbox_mail_header { - /* This field acts as kind of a verification marker to make sure that - seeked offset is valid. So the magic value should be something that - normally doesn't occur in mails. */ - unsigned char magic[2]; - unsigned char uid_hex[8]; - unsigned char mail_size_hex[16]; - unsigned char received_time_hex[8]; - unsigned char save_time_hex[8]; - unsigned char answered; - unsigned char flagged; - unsigned char deleted; - unsigned char seen; - unsigned char draft; - unsigned char expunged; - /* unsigned char keywords[keywords_count]; */ -}; - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/dbox/dbox-index.c Sat Sep 01 03:04:02 2007 +0300 @@ -0,0 +1,890 @@ +/* Copyright (C) 2007 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "hex-dec.h" +#include "str.h" +#include "istream.h" +#include "ostream.h" +#include "write-full.h" +#include "safe-mkstemp.h" +#include "dbox-storage.h" +#include "dbox-file.h" +#include "dbox-index.h" + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#define DBOX_INDEX_LOCK_RETRY_COUNT 10 + +struct dbox_index { + struct dbox_mailbox *mbox; + + struct istream *input; + char *path; + int fd; + + uint32_t uid_validity, next_uid; + unsigned int next_file_id; + + ARRAY_DEFINE(records, struct dbox_index_record); +}; + +struct dbox_index_append_context { + struct dbox_index *index; + ARRAY_DEFINE(files, struct dbox_file *); + + uoff_t output_offset; + unsigned int new_record_idx; + unsigned int first_new_file_id; + + unsigned int locked_header:1; +}; + +static int dbox_index_recreate(struct dbox_index *index, bool locked); + +struct dbox_index *dbox_index_init(struct dbox_mailbox *mbox) +{ + struct dbox_index *index; + + index = i_new(struct dbox_index, 1); + index->mbox = mbox; + index->path = i_strdup_printf("%s/"DBOX_INDEX_NAME, mbox->path); + index->fd = -1; + index->next_uid = 1; + index->next_file_id = 1; + i_array_init(&index->records, 128); + return index; +} + +static void dbox_index_close(struct dbox_index *index) +{ + if (index->input != NULL) + i_stream_unref(&index->input); + if (index->fd != -1) { + if (close(index->fd) < 0) + i_error("close(%s) failed: %m", index->path); + index->fd = -1; + } +} + +void dbox_index_deinit(struct dbox_index **_index) +{ + struct dbox_index *index = *_index; + + *_index = NULL; + + dbox_index_close(index); + array_free(&index->records); + i_free(index->path); + i_free(index); +} + +static int dbox_index_parse_line(struct dbox_index *index, const char *line, + unsigned int offset) +{ + struct dbox_index_record rec; + + memset(&rec, 0, sizeof(rec)); + rec.file_offset = offset; + + /* <file id> <status><expunges><dirty> [<status-specific data>] */ + while (*line >= '0' && *line <= '9') { + rec.file_id = rec.file_id*10 + *line - '0'; + line++; + } + if (*line++ != ' ') + return -1; + + if ((rec.file_id & DBOX_FILE_ID_FLAG_UID) != 0) { + /* UID files shouldn't be listed in dbox.index */ + return -1; + } + + if (line[0] == '\0' || line[1] == '\0' || line[2] == '\0') + return -1; + rec.status = line[0]; + rec.expunges = line[1] != '0'; + rec.dirty = line[2] != '0'; + + line += 3; + array_append(&index->records, &rec, 1); + return 0; +} + +static int +dbox_index_set_corrupted(struct dbox_index *index, const char *reason) +{ + mail_storage_set_critical(index->mbox->ibox.box.storage, + "dbox index %s corrupted: %s", + index->path, reason); + + if (unlink(index->path) < 0 && errno != ENOENT) + i_error("unlink(%s) failed: %m", index->path); + return -1; +} + +static void dbox_index_header_init(struct dbox_index *index, + struct dbox_index_file_header *hdr) +{ + if (index->uid_validity == 0) { + const struct mail_index_header *hdr; + + hdr = mail_index_get_header(index->mbox->ibox.view); + index->uid_validity = hdr->uid_validity != 0 ? + hdr->uid_validity : (uint32_t)ioloop_time; + } + + memset(hdr, ' ', sizeof(*hdr)); + hdr->version = DBOX_INDEX_VERSION; + dec2hex(hdr->uid_validity_hex, index->uid_validity, + sizeof(hdr->uid_validity_hex)); + dec2hex(hdr->next_uid_hex, index->next_uid, sizeof(hdr->next_uid_hex)); + dec2hex(hdr->next_file_id_hex, index->next_file_id, + sizeof(hdr->next_file_id_hex)); +} + +static int dbox_index_parse_header(struct dbox_index *index, const char *line) +{ + struct dbox_index_file_header hdr; + + if (strlen(line) < sizeof(hdr)) + return dbox_index_set_corrupted(index, "Header too short"); + + memcpy(&hdr, line, sizeof(hdr)); + if (hdr.version != DBOX_INDEX_VERSION) + return dbox_index_set_corrupted(index, "Invalid version"); + + index->uid_validity = + hex2dec(hdr.uid_validity_hex, sizeof(hdr.uid_validity_hex)); + if (index->uid_validity == 0) + return dbox_index_set_corrupted(index, "uid_validity = 0"); + + index->next_uid = hex2dec(hdr.next_uid_hex, sizeof(hdr.next_uid_hex)); + if (index->next_uid == 0) + return dbox_index_set_corrupted(index, "next_uid = 0"); + index->next_file_id = + hex2dec(hdr.next_file_id_hex, sizeof(hdr.next_file_id_hex)); + return 0; +} + +static int dbox_index_read_header(struct dbox_index *index) +{ + const char *line; + + i_stream_sync(index->input); + i_stream_seek(index->input, 0); + + line = i_stream_read_next_line(index->input); + if (line == NULL) + return dbox_index_set_corrupted(index, "Missing header"); + return dbox_index_parse_header(index, line); +} + +static int dbox_index_read(struct dbox_index *index) +{ + struct istream *input; + const char *line; + uoff_t start_offset; + int ret; + + if (index->fd != -1) + dbox_index_close(index); + + index->fd = open(index->path, O_RDWR); + if (index->fd == -1) { + if (errno == ENOENT) + return 0; + mail_storage_set_critical(index->mbox->ibox.box.storage, + "open(%s) failed: %m", index->path); + return -1; + } + + array_clear(&index->records); + input = index->input = i_stream_create_fd(index->fd, 1024, FALSE); + + ret = dbox_index_read_header(index); + start_offset = input->v_offset; + while ((line = i_stream_read_next_line(input)) != NULL) { + if (dbox_index_parse_line(index, line, start_offset) < 0) { + dbox_index_set_corrupted(index, "Corrupted record"); + ret = -1; + break; + } + start_offset = input->v_offset; + } + return ret == 0 ? 1 : + (input->stream_errno == 0 ? 0 : -1); +} + +static int dbox_index_read_or_create(struct dbox_index *index) +{ + unsigned int i; + int ret; + + for (i = 0;; i++) { + if ((ret = dbox_index_read(index)) != 0) + return ret; + + /* doesn't exist / corrupted */ + if (i == DBOX_INDEX_LOCK_RETRY_COUNT) + break; + + if (index->fd != -1) + dbox_index_close(index); + + if (dbox_index_recreate(index, FALSE) < 0) + return -1; + } + + mail_storage_set_critical(index->mbox->ibox.box.storage, + "dbox index recreation keeps failing: %s", index->path); + return -1; +} + +static int dbox_index_refresh(struct dbox_index *index) +{ + struct stat st1, st2; + + if (index->fd == -1) { + if (dbox_index_read_or_create(index) < 0) + return -1; + i_assert(index->fd != -1); + return 1; + } + + if (fstat(index->fd, &st1) < 0) { + mail_storage_set_critical(index->mbox->ibox.box.storage, + "fstat(%s) failed: %m", index->path); + return -1; + } + if (stat(index->path, &st2) < 0) { + mail_storage_set_critical(index->mbox->ibox.box.storage, + "stat(%s) failed: %m", index->path); + return -1; + } + + if (st1.st_ino != st2.st_ino || !CMP_DEV_T(st1.st_dev, st2.st_dev)) { + if (dbox_index_read(index) < 0) + return -1; + return 1; + } + return 0; +} + +int dbox_index_get_uid_validity(struct dbox_index *index, + uint32_t *uid_validity_r) +{ + if (index->fd == -1) { + if (dbox_index_refresh(index) < 0) + return -1; + } + *uid_validity_r = index->uid_validity; + return 0; +} + +static int dbox_index_record_cmp(const void *key, const void *data) +{ + const unsigned int *file_id = key; + const struct dbox_index_record *rec = data; + + return *file_id - rec->file_id; +} + +struct dbox_index_record * +dbox_index_record_lookup(struct dbox_index *index, unsigned int file_id) +{ + struct dbox_index_record *records; + unsigned int count; + + if ((file_id & DBOX_FILE_ID_FLAG_UID) != 0) + return NULL; + + records = array_get_modifiable(&index->records, &count); + return bsearch(&file_id, records, count, sizeof(*records), + dbox_index_record_cmp); +} + +static int +dbox_index_lock_range(struct dbox_index *index, int cmd, int lock_type, + off_t start, off_t len) +{ + struct flock fl; + + fl.l_type = lock_type; + fl.l_whence = SEEK_SET; + fl.l_start = start; + fl.l_len = len; + if (fcntl(index->fd, cmd, &fl) < 0) { + if ((errno == EACCES || errno == EAGAIN || errno == EINTR) && + cmd == F_SETLK) + return 0; + mail_storage_set_critical(index->mbox->ibox.box.storage, + "fcntl(%s, %s) failed: %m", index->path, + lock_type == F_UNLCK ? "F_UNLCK" : "F_WRLCK"); + return -1; + } + return 1; +} + +static void dbox_index_unlock_range(struct dbox_index *index, + off_t start, off_t len) +{ + (void)dbox_index_lock_range(index, F_SETLK, F_UNLCK, start, len); +} + +static int +dbox_index_try_lock_once(struct dbox_index *index, unsigned int file_id, + enum dbox_index_file_lock_status *lock_status_r) +{ + struct dbox_index_record *rec; + int ret; + + i_assert((file_id & DBOX_FILE_ID_FLAG_UID) == 0); + + rec = dbox_index_record_lookup(index, file_id); + if (rec == NULL || rec->status == DBOX_INDEX_FILE_STATUS_UNLINKED) { + *lock_status_r = DBOX_INDEX_FILE_LOCK_UNLINKED; + return 0; + } + + if (rec->status != DBOX_INDEX_FILE_STATUS_APPENDABLE) { + *lock_status_r = DBOX_INDEX_FILE_LOCK_NOT_NEEDED; + return 1; + } + + /* we'll need to try to lock this record */ + ret = dbox_index_lock_range(index, F_SETLK, F_WRLCK, + rec->file_offset, 1); + if (ret > 0) { + *lock_status_r = DBOX_INDEX_FILE_LOCKED; + rec->locked = TRUE; + } else if (ret == 0) + *lock_status_r = DBOX_INDEX_FILE_LOCK_TRY_AGAIN; + return ret; +} + +int dbox_index_try_lock_file(struct dbox_index *index, unsigned int file_id, + enum dbox_index_file_lock_status *lock_status_r) +{ + int i, ret; + + if ((file_id & DBOX_FILE_ID_FLAG_UID) != 0) { + *lock_status_r = DBOX_INDEX_FILE_LOCK_NOT_NEEDED; + return 1; + } + + if (index->fd == -1) { + if (dbox_index_refresh(index) < 0) + return 1; + } + + for (i = 0; i < DBOX_INDEX_LOCK_RETRY_COUNT; i++) { + ret = dbox_index_try_lock_once(index, file_id, lock_status_r); + if (ret <= 0 || *lock_status_r != DBOX_INDEX_FILE_LOCKED) + return ret; + + /* if file was recreated, reopen it and try again */ + if ((ret = dbox_index_refresh(index)) <= 0) + return ret < 0 ? -1 : 1; + } + + i_warning("dbox index keeps getting recreated: %s", index->path); + return 0; +} + +void dbox_index_unlock_file(struct dbox_index *index, unsigned int file_id) +{ + struct dbox_index_record *rec; + + rec = dbox_index_record_lookup(index, file_id); + if (rec == NULL || !rec->locked) + return; + + dbox_index_unlock_range(index, rec->file_offset, 1); + rec->locked = FALSE; +} + +int dbox_index_try_lock_recreate(struct dbox_index *index) +{ + int i, ret; + + if (index->fd == -1) { + if (dbox_index_refresh(index) < 0) + return 1; + } + + for (i = 0; i < DBOX_INDEX_LOCK_RETRY_COUNT; i++) { + /* lock the whole file */ + ret = dbox_index_lock_range(index, F_SETLK, F_WRLCK, 0, 0); + if (ret <= 0) + return ret; + if ((ret = dbox_index_refresh(index)) <= 0) + return ret < 0 ? -1 : 1; + } + + i_warning("dbox index keeps getting recreated: %s", index->path); + return 0; +} + +static int dbox_index_lock_header(struct dbox_index *index) +{ + int i, ret; + + if (index->fd == -1) { + if (dbox_index_refresh(index) < 0) + return 1; + } + + for (i = 0; i < DBOX_INDEX_LOCK_RETRY_COUNT; i++) { + ret = dbox_index_lock_range(index, F_SETLKW, F_WRLCK, 0, + sizeof(struct dbox_index_file_header)); + if (ret <= 0) + return -1; + + /* if file was recreated, reopen it and try again */ + if ((ret = dbox_index_refresh(index)) <= 0) + return ret < 0; + } + + mail_storage_set_critical(index->mbox->ibox.box.storage, + "dbox index keeps getting recreated: %s", index->path); + return -1; +} + +static void dbox_index_unlock_header(struct dbox_index *index) +{ + dbox_index_unlock_range(index, 0, + sizeof(struct dbox_index_file_header)); +} + +static void +dbox_index_append_record(const struct dbox_index_record *rec, string_t *str) +{ + str_printfa(str, "%u %c%c%c", + rec->file_id, rec->status, + rec->expunges ? 'E' : '0', + rec->dirty ? 'D' : '0'); + + switch (rec->status) { + case DBOX_INDEX_FILE_STATUS_APPENDABLE: + str_append(str, " 00000000"); + break; + case DBOX_INDEX_FILE_STATUS_APPENDING: + case DBOX_INDEX_FILE_STATUS_UNLINKED: + i_unreached(); + break; + case DBOX_INDEX_FILE_STATUS_NONAPPENDABLE: + case DBOX_INDEX_FILE_STATUS_SINGLE_MESSAGE: + break; + case DBOX_INDEX_FILE_STATUS_MAILDIR: + /* FIXME */ + break; + } + str_append_c(str, '\n'); +} + +static int dbox_index_recreate(struct dbox_index *index, bool locked) +{ + struct mail_storage *storage = &index->mbox->storage->storage; + struct dbox_index_record *records; + struct ostream *output; + struct dbox_index_file_header hdr; + string_t *temp_path, *str; + unsigned int i, count; + int fd, ret = 0; + + t_push(); + temp_path = t_str_new(256); + str_append(temp_path, index->path); + if (locked) { + str_append(temp_path, ".tmp"); + fd = open(str_c(temp_path), O_RDWR | O_CREAT | O_TRUNC, 0600); + if (fd == -1) { + mail_storage_set_critical(storage, + "open(%s, O_CREAT) failed: %m", + str_c(temp_path)); + t_pop(); + return -1; + } + } else { + str_append_c(temp_path, '.'); + fd = safe_mkstemp_hostpid(temp_path, 0600, + (uid_t)-1, (gid_t)-1); + if (fd == -1) { + mail_storage_set_critical(storage, + "safe_mkstemp_hostpid(%s) failed: %m", + str_c(temp_path)); + t_pop(); + return -1; + } + } + + str = t_str_new(256); + output = o_stream_create_fd_file(fd, 0, FALSE); + o_stream_cork(output); + + dbox_index_header_init(index, &hdr); + o_stream_send(output, &hdr, sizeof(hdr)); + o_stream_send(output, "\n", 1); + + records = array_get_modifiable(&index->records, &count); + for (i = 0; i < count; ) { + if (records[i].status == DBOX_INDEX_FILE_STATUS_UNLINKED) { + array_delete(&index->records, i, 1); + records = array_get_modifiable(&index->records, &count); + } else { + records[i].file_offset = output->offset; + str_truncate(str, 0); + dbox_index_append_record(&records[i], str); + o_stream_send(output, str_data(str), str_len(str)); + i++; + } + } + + if (o_stream_flush(output) < 0) { + mail_storage_set_critical(storage, + "write(%s) failed: %m", str_c(temp_path)); + ret = -1; + } + + o_stream_destroy(&output); + if (ret == 0 && index->mbox->ibox.fsync_disable) { + if (fdatasync(fd) < 0) { + mail_storage_set_critical(storage, + "fdatasync(%s) failed: %m", str_c(temp_path)); + ret = -1; + } + } + if (close(fd) < 0) { + mail_storage_set_critical(storage, + "close(%s) failed: %m", str_c(temp_path)); + ret = -1; + } + if (ret == 0) { + if (locked) { + if (rename(str_c(temp_path), index->path) < 0) { + mail_storage_set_critical(storage, + "rename(%s, %s) failed: %m", + str_c(temp_path), index->path); + ret = -1; + } + } else { + if (link(str_c(temp_path), index->path) < 0 && + errno != EEXIST) { + mail_storage_set_critical(storage, + "link(%s, %s) failed: %m", + str_c(temp_path), index->path); + ret = -1; + } + } + } + if (ret < 0 || !locked) { + if (unlink(str_c(temp_path)) < 0) + i_error("unlink(%s) failed: %m", str_c(temp_path)); + } + t_pop(); + return ret; +} + +struct dbox_index_append_context * +dbox_index_append_begin(struct dbox_index *index) +{ + struct dbox_index_append_context *ctx; + const void *data; + bool expunged; + + ctx = i_new(struct dbox_index_append_context, 1); + ctx->index = index; + ctx->first_new_file_id = (unsigned int)-1; + i_array_init(&ctx->files, 64); + + /* refresh the index now if there's a possibility of some appendable + files existing */ + if (mail_index_view_get_messages_count(index->mbox->ibox.view) > 0) { + mail_index_lookup_ext(index->mbox->ibox.view, 1, + index->mbox->dbox_ext_id, + &data, &expunged); + if (data != NULL) + (void)dbox_index_refresh(index); + } + return ctx; +} + +static bool +dbox_index_append_file_record(struct dbox_index_append_context *ctx, + struct dbox_index_record *record, + uoff_t mail_size, struct dbox_file **file_r, + struct ostream **output_r) +{ + struct dbox_file *const *files, *file; + enum dbox_index_file_lock_status lock_status; + unsigned int i, count; + + if (record->status != DBOX_INDEX_FILE_STATUS_APPENDABLE) + return FALSE; + + if (record->expunges) + return FALSE; + + /* if we already have it in our files list, we already checked that + we can't append to it. */ + files = array_get(&ctx->files, &count); + for (i = 0; i < count; i++) { + if (files[i]->file_id == record->file_id) + return FALSE; + } + i_assert(!record->locked); + + if (dbox_index_try_lock_file(ctx->index, record->file_id, + &lock_status) <= 0) + return FALSE; + + /* open the file to see if we can append */ + file = dbox_file_init(ctx->index->mbox, record->file_id); + if (dbox_file_get_append_stream(file, mail_size, output_r) <= 0) { + dbox_index_unlock_file(ctx->index, record->file_id); + dbox_file_unref(&file); + return FALSE; + } + *file_r = file; + return TRUE; +} + +int dbox_index_append_next(struct dbox_index_append_context *ctx, + uoff_t mail_size, + struct dbox_file **file_r, + struct ostream **output_r) +{ + struct dbox_file *const *files, *file = NULL; + struct dbox_index_record *records; + unsigned int i, count; + int ret; + + /* first try to use files already used in this append */ + files = array_get(&ctx->files, &count); + for (i = 0; i < count; i++) { + if (dbox_file_get_append_stream(files[i], mail_size, + output_r) > 0) { + *file_r = files[i]; + return 0; + } + } + + /* try to find an existing appendable file */ + records = array_get_modifiable(&ctx->index->records, &count); + for (i = 0; i < count; i++) { + if (dbox_index_append_file_record(ctx, &records[i], mail_size, + &file, output_r)) + break; + } + + if (file == NULL) { + /* create a new file */ + file = dbox_file_init(ctx->index->mbox, 0); + if ((ret = dbox_file_get_append_stream(file, mail_size, + output_r)) <= 0) { + i_assert(ret < 0); + (void)unlink(file->path); + dbox_file_unref(&file); + return -1; + } + } + + *file_r = file; + array_append(&ctx->files, &file, 1); + return 0; +} + +static int dbox_index_append_commit_new(struct dbox_index_append_context *ctx, + struct dbox_file *file, string_t *str) +{ + struct mail_storage *storage = &ctx->index->mbox->storage->storage; + struct dbox_index_record rec; + struct stat st; + unsigned int file_id; + + i_assert(file->append_count > 0); + + if (file->append_count == 1 && !dbox_file_can_append(file, 0)) { + /* single UID message file */ + i_assert(file->last_append_uid != 0); + file_id = file->last_append_uid | DBOX_FILE_ID_FLAG_UID; + return dbox_file_assign_id(file, file_id); + } + + if (!ctx->locked_header) { + if (dbox_index_lock_header(ctx->index) < 0) + return -1; + if (dbox_index_read_header(ctx->index) < 0) { + dbox_index_unlock_header(ctx->index); + return -1; + } + if (fstat(ctx->index->fd, &st) < 0) { + mail_storage_set_critical(storage, + "fstat(%s) failed: %m", ctx->index->path); + dbox_index_unlock_header(ctx->index); + return -1; + } + ctx->output_offset = st.st_size; + ctx->new_record_idx = array_count(&ctx->index->records); + ctx->first_new_file_id = ctx->index->next_file_id; + ctx->locked_header = TRUE; + } + + file_id = ctx->index->next_file_id++; + if (dbox_file_assign_id(file, file_id) < 0) + return -1; + + memset(&rec, 0, sizeof(rec)); + rec.file_id = file_id; + rec.file_offset = ctx->output_offset + str_len(str); + rec.status = dbox_file_can_append(file, 0) ? + DBOX_INDEX_FILE_STATUS_APPENDABLE : + DBOX_INDEX_FILE_STATUS_NONAPPENDABLE; + + array_append(&ctx->index->records, &rec, 1); + dbox_index_append_record(&rec, str); + return 0; +} + +static void +dbox_index_append_rollback_commit(struct dbox_index_append_context *ctx) +{ + struct dbox_file *const *files; + unsigned int i, count; + + files = array_get(&ctx->files, &count); + for (i = 0; i < count; i++) { + if (files[i]->file_id >= ctx->first_new_file_id) { + if (unlink(files[i]->path) < 0) { + i_error("unlink(%s) failed: %m", + files[i]->path); + } + files[i]->deleted = TRUE; + } else { + /* FIXME: we should delete the appended mails.. */ + } + } + array_delete(&ctx->index->records, ctx->new_record_idx, + array_count(&ctx->index->records) - ctx->new_record_idx); +} + +static int +dbox_index_append_write_records(struct dbox_index_append_context *ctx, + string_t *str) +{ + int ret; + + ret = dbox_index_lock_range(ctx->index, F_SETLKW, F_WRLCK, + ctx->output_offset, str_len(str)); + if (ret <= 0) + return -1; + + if (pwrite_full(ctx->index->fd, str_data(str), str_len(str), + ctx->output_offset) < 0) { + mail_storage_set_critical(&ctx->index->mbox->storage->storage, + "pwrite(%s) failed: %m", ctx->index->path); + if (ftruncate(ctx->index->fd, ctx->output_offset) < 0) + i_error("ftruncate(%s) failed: %m", ctx->index->path); + ret = -1; + } + dbox_index_unlock_range(ctx->index, ctx->output_offset, str_len(str)); + return ret; +} + +static int dbox_index_write_header(struct dbox_index *index) +{ + struct dbox_index_file_header hdr; + + dbox_index_header_init(index, &hdr); + if (pwrite_full(index->fd, &hdr, sizeof(hdr), 0) < 0) { + mail_storage_set_critical(&index->mbox->storage->storage, + "pwrite(%s) failed: %m", index->path); + return -1; + } + return 0; +} + +int dbox_index_append_assign_file_ids(struct dbox_index_append_context *ctx) +{ + struct dbox_file *const *files, *file; + string_t *str; + unsigned int i, count; + int ret = 0; + + str = t_str_new(1024); + files = array_get(&ctx->files, &count); + for (i = 0; i < count; i++) { + file = files[i]; + + if (file->file_id == 0) { + if (dbox_index_append_commit_new(ctx, file, str) < 0) + ret = -1; + } + } + + if (ret == 0 && str_len(str) > 0) { + /* write the new records to index */ + ret = dbox_index_append_write_records(ctx, str); + } + if (ret < 0 && str_len(str) > 0) { + /* we have to rollback changes we made */ + dbox_index_append_rollback_commit(ctx); + } + return ret; +} + +int dbox_index_append_commit(struct dbox_index_append_context **_ctx) +{ + struct dbox_index_append_context *ctx = *_ctx; + struct dbox_file **files; + unsigned int i, count; + int ret = 0; + + *_ctx = NULL; + + files = array_get_modifiable(&ctx->files, &count); + for (i = 0; i < count; i++) { + if (files[i]->file_id < ctx->first_new_file_id) { + // FIXME: update status + dbox_index_unlock_file(ctx->index, files[i]->file_id); + } + dbox_file_unref(&files[i]); + } + + if (ctx->locked_header) { + if (dbox_index_write_header(ctx->index) < 0) + ret = -1; + dbox_index_unlock_header(ctx->index); + } + + array_free(&ctx->files); + i_free(ctx); + return 0; +} + +void dbox_index_append_rollback(struct dbox_index_append_context **_ctx) +{ + struct dbox_index_append_context *ctx = *_ctx; + struct dbox_file *const *files, *file; + unsigned int i, count; + + *_ctx = NULL; + + files = array_get(&ctx->files, &count); + for (i = 0; i < count; i++) { + file = files[i]; + + if (file->file_id != 0) + dbox_index_unlock_file(ctx->index, file->file_id); + else { + if (unlink(file->path) < 0) + i_error("unlink(%s) failed: %m", file->path); + } + dbox_file_unref(&file); + } + array_free(&ctx->files); + i_free(ctx); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/dbox/dbox-index.h Sat Sep 01 03:04:02 2007 +0300 @@ -0,0 +1,130 @@ +#ifndef __DBOX_INDEX_H +#define __DBOX_INDEX_H + +/* The file begins with a header followed by zero or more records: + + <file id> <status><expunges><dirty> [<status-specific data>]<LF> + + <expunges> contains either '0' = no or 'E' = file contains messages marked + as expunged, which should be removed when possible. + + <dirty> contains either '0' = no or 'D' = file contains messages that don't + have up-to-date metadata. When expunge copies message data to a new file, + the dirty state should be flushed for the copied messages (or the dirty + state should be copied). + + <expunges> and <dirty> can be written without locking the record, so syncing + can update them even while messages are being appended to the file. + + If status-specific data isn't specified for the given status, it should be + ignored. Especially 'U' status may contain different kinds of data. +*/ + +struct dbox_file; +struct dbox_index_append_context; + +#define DBOX_INDEX_VERSION '1' + +enum dbox_index_file_status { + /* File can be appended to as long as <expunges> is zero. It must be + locked when expunging. status-specific data contains a %08x lock + timestamp. */ + DBOX_INDEX_FILE_STATUS_APPENDABLE = '0', + /* File is currently being appended to. If this record can be locked, + the append crashed and this file should be opened for fixing + (truncate non-committed appends from the file). */ + DBOX_INDEX_FILE_STATUS_APPENDING = 'A', + /* File can't be appended to. */ + DBOX_INDEX_FILE_STATUS_NONAPPENDABLE = 'N', + /* File contains only a single message. It can't be appended to + and it can be expunged by unlinking the file. */ + DBOX_INDEX_FILE_STATUS_SINGLE_MESSAGE = '1', + /* The file has already been unlinked, this record should be removed. */ + DBOX_INDEX_FILE_STATUS_UNLINKED = 'U', + + /* File is a maildir file. Status-specific data contains + <uid> <filename>. */ + DBOX_INDEX_FILE_STATUS_MAILDIR = 'M' +}; + +enum dbox_index_file_lock_status { + /* File was locked (ret=1) */ + DBOX_INDEX_FILE_LOCKED, + /* File didn't have appendable status (ret=1) */ + DBOX_INDEX_FILE_LOCK_NOT_NEEDED, + /* File was already locked by someone else (ret=0) */ + DBOX_INDEX_FILE_LOCK_TRY_AGAIN, + /* File is already unlinked (ret=0) */ + DBOX_INDEX_FILE_LOCK_UNLINKED +}; + +struct dbox_index_file_header { + /* DBOX_INDEX_VERSION */ + unsigned char version; + unsigned char space_1; + + /* Current UIDVALIDITY */ + unsigned char uid_validity_hex[8]; + unsigned char space_2; + + /* Next available message UID */ + unsigned char next_uid_hex[8]; + unsigned char space_3; + + /* Next available <file id> */ + unsigned char next_file_id_hex[8]; +}; + +struct dbox_index_record { + unsigned int file_id; + unsigned int file_offset; + + enum dbox_index_file_status status; + unsigned int expunges:1; + unsigned int dirty:1; + unsigned int locked:1; +}; + +struct dbox_index *dbox_index_init(struct dbox_mailbox *mbox); +void dbox_index_deinit(struct dbox_index **index); + +/* Get the current UIDVALIDITY. Returns 0 if ok, -1 if I/O error. */ +int dbox_index_get_uid_validity(struct dbox_index *index, + uint32_t *uid_validity_r); + +struct dbox_index_record * +dbox_index_record_lookup(struct dbox_index *index, unsigned int file_id); + +/* Try to lock a file record. Only appendable files are actually locked. + Returns 1 if lock acquired or not needed, 0 if we failed to get a lock or + file is unlinked, -1 if error. lock_status_r is set if 0 or 1 is returned. */ +int dbox_index_try_lock_file(struct dbox_index *index, unsigned int file_id, + enum dbox_index_file_lock_status *lock_status_r); +void dbox_index_unlock_file(struct dbox_index *index, unsigned int file_id); + +/* Try to lock index file for recreating. Returns 1 if ok, 0 if file already + contains locks, -1 if error. */ +int dbox_index_try_lock_recreate(struct dbox_index *index); +/* Lock index file for syncing. Returns 0 if ok, -1 if error. */ +int dbox_index_lock_sync(struct dbox_index *index); + +struct dbox_index_append_context * +dbox_index_append_begin(struct dbox_index *index); +/* Request file for saving a new message with given size. If an existing file + can be used, the record is locked and updated in index. Returns 0 if ok, + -1 if error. */ +int dbox_index_append_next(struct dbox_index_append_context *ctx, + uoff_t mail_size, + struct dbox_file **file_r, + struct ostream **output_r); +/* Assign file_ids to all appended files. */ +int dbox_index_append_assign_file_ids(struct dbox_index_append_context *ctx); +/* Returns 0 if ok, -1 if error. */ +int dbox_index_append_commit(struct dbox_index_append_context **ctx); +void dbox_index_append_rollback(struct dbox_index_append_context **ctx); + +/* Mark */ +void dbox_index_mark_expunges(struct dbox_index *index, unsigned int file_id); +void dbox_index_mark_dirty(struct dbox_index *index, unsigned int file_id); + +#endif
--- a/src/lib-storage/index/dbox/dbox-keywords.c Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,214 +0,0 @@ -/* Copyright (C) 2006 Timo Sirainen */ - -#include "lib.h" -#include "array.h" -#include "str.h" -#include "istream.h" -#include "write-full.h" -#include "seq-range-array.h" -#include "bsearch-insert-pos.h" -#include "dbox-file.h" -#include "dbox-storage.h" -#include "dbox-keywords.h" - -#include <stdlib.h> - -static int dbox_keyword_map_compare(const void *p1, const void *p2) -{ - const struct keyword_map *map1 = p1, *map2 = p2; - - return map1->index_idx < map2->index_idx ? -1 : - map1->index_idx > map2->index_idx ? 1 : 0; -} - -int dbox_file_read_keywords(struct dbox_mailbox *mbox, struct dbox_file *file) -{ - struct keyword_map *map, kw; - const char *line; - unsigned int idx, count, insert_idx; - uoff_t last_offset; - - if (array_is_created(&file->idx_file_keywords)) { - array_clear(&file->idx_file_keywords); - array_clear(&file->file_idx_keywords); - } else { - i_array_init(&file->idx_file_keywords, file->keyword_count); - i_array_init(&file->file_idx_keywords, file->keyword_count); - } - - /* currently we assume that all extra space at the end of header - belongs to keyword list. */ - file->keyword_list_size_alloc = - file->header_size - file->keyword_list_offset; - - i_stream_seek(file->input, file->keyword_list_offset); - idx = 0; - last_offset = file->input->v_offset; - while ((line = i_stream_read_next_line(file->input)) != NULL) { - if (*line == '\0') { - /* end of list */ - break; - } - last_offset = file->input->v_offset; - - /* set up map record for the keyword */ - mail_index_keyword_lookup_or_create(mbox->ibox.index, line, - &kw.index_idx); - kw.file_idx = idx; - - /* look up the position where to insert it */ - map = array_get_modifiable(&file->idx_file_keywords, &count); - if (idx == 0) - insert_idx = 0; - else { - bsearch_insert_pos(&kw, map, count, sizeof(*map), - dbox_keyword_map_compare, - &insert_idx); - } - array_insert(&file->idx_file_keywords, insert_idx, &kw, 1); - array_append(&file->file_idx_keywords, &kw.index_idx, 1); - - if (++idx == file->keyword_count) - break; - } - - if (line == NULL || file->input->v_offset > file->header_size) { - /* unexpected end of list, or list continues outside its - allocated area */ - mail_storage_set_critical(&mbox->storage->storage, - "Corrupted keyword list offset in dbox file %s", - file->path); - array_clear(&file->idx_file_keywords); - return 0; - } - - file->keyword_list_size_used = - last_offset - file->keyword_list_offset; - return 1; -} - -static int keyword_lookup_cmp(const void *key, const void *obj) -{ - const unsigned int *index_idx = key; - const struct keyword_map *map = obj; - - return *index_idx < map->index_idx ? -1 : - *index_idx > map->index_idx ? 1 : 0; -} - -bool dbox_file_lookup_keyword(struct dbox_mailbox *mbox, struct dbox_file *file, - unsigned int index_idx, unsigned int *idx_r) -{ - const struct keyword_map *map, *pos; - unsigned int count; - - if (!array_is_created(&file->idx_file_keywords)) { - /* Read the keywords, if there are any */ - if (dbox_file_read_keywords(mbox, file) <= 0) - return FALSE; - } - - map = array_get(&file->idx_file_keywords, &count); - pos = bsearch(&index_idx, map, count, sizeof(*map), - keyword_lookup_cmp); - if (pos != NULL && idx_r != NULL) - *idx_r = pos->file_idx; - return pos != NULL; -} - -int dbox_file_append_keywords(struct dbox_mailbox *mbox, struct dbox_file *file, - const struct seq_range *idx_range, - unsigned int count) -{ - const ARRAY_TYPE(keywords) *idx_keywords; - string_t *keyword_str; - const char *const *idx_keyword_names; - unsigned int i, idx_keyword_count, new_pos; - int ret; - - t_push(); - keyword_str = t_str_new(2048); - idx_keywords = mail_index_get_keywords(mbox->ibox.index); - idx_keyword_names = array_get(idx_keywords, &idx_keyword_count); - - /* make sure we've read the existing keywords */ - if (!array_is_created(&file->idx_file_keywords)) { - ret = dbox_file_read_keywords(mbox, file); - if (ret < 0) - return -1; - - if (ret == 0) { - /* broken keywords list. */ - file->keyword_list_size_used = 0; - } - } - - /* append existing keywords */ - if (array_count(&file->idx_file_keywords) > 0) { - const unsigned int *file_idx; - unsigned int file_count; - - file_idx = array_get(&file->file_idx_keywords, &file_count); - for (i = 0; i < file_count; i++) { - i_assert(file_idx[i] < idx_keyword_count); - - str_append(keyword_str, idx_keyword_names[file_idx[i]]); - str_append_c(keyword_str, '\n'); - } - } - - /* append new keywords */ - if (file->keyword_list_size_used == 0) - new_pos = 0; - else { - new_pos = str_len(keyword_str); - i_assert(new_pos == file->keyword_list_size_used); - } - for (i = 0; i < count; i++) { - unsigned int idx; - - for (idx = idx_range[i].seq1; idx <= idx_range[i].seq2; idx++) { - size_t prev_len; - - i_assert(idx < idx_keyword_count); - i_assert(!dbox_file_lookup_keyword(mbox, file, - idx, NULL)); - - prev_len = str_len(keyword_str); - str_append(keyword_str, idx_keyword_names[idx]); - str_append_c(keyword_str, '\n'); - - if (str_len(keyword_str) >= - file->keyword_list_size_alloc) { - /* FIXME: keyword list doesn't fit to the - space allocated for it. create a new file - where there's more space for keywords and - move the mails there. - - for now we'll just ignore the problem. */ - str_truncate(keyword_str, prev_len); - break; - } - } - } - - str_append_c(keyword_str, '\n'); - i_assert(str_len(keyword_str) <= file->keyword_list_size_alloc); - i_assert(new_pos < str_len(keyword_str)); - - /* we can reuse the existing keyword list position */ - if (pwrite_full(file->fd, str_data(keyword_str) + new_pos, - str_len(keyword_str) - new_pos, - file->keyword_list_offset + new_pos) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "pwrite_full(%s) failed: %m", file->path); - } - - /* FIXME: we could do this faster than by reading them.. */ - ret = 0; - if (dbox_file_read_keywords(mbox, file) <= 0) - ret = -1; - - t_pop(); - return ret; -}
--- a/src/lib-storage/index/dbox/dbox-keywords.h Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -#ifndef __DBOX_KEYWORDS_H -#define __DBOX_KEYWORDS_H - -struct seq_range; - -/* Read keywords from file into memory. Returns 1 if ok, 0 if the list is - broken or -1 if I/O error. */ -int dbox_file_read_keywords(struct dbox_mailbox *mbox, struct dbox_file *file); -/* Index file -> dbox file keyword index lookup. Returns TRUE if found. */ -bool dbox_file_lookup_keyword(struct dbox_mailbox *mbox, struct dbox_file *file, - unsigned int index_idx, unsigned int *idx_r); -/* Save keywords to dbox file. Returns -1 if error, 0 if ok. */ -int dbox_file_append_keywords(struct dbox_mailbox *mbox, struct dbox_file *file, - const struct seq_range *idx_range, - unsigned int count); - -#endif
--- a/src/lib-storage/index/dbox/dbox-mail.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-mail.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,195 +1,162 @@ -/* Copyright (C) 2005 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" -#include "ioloop.h" -#include "hex-dec.h" -#include "read-full.h" #include "istream.h" #include "index-mail.h" +#include "dbox-storage.h" #include "dbox-file.h" -#include "dbox-sync.h" -#include "dbox-storage.h" + +#include <stdlib.h> + +struct dbox_mail { + struct index_mail imail; -#include <fcntl.h> -#include <unistd.h> -#include <sys/stat.h> + struct dbox_file *open_file; + uoff_t offset; +}; -static int dbox_mail_parse_mail_header(struct index_mail *mail, - struct dbox_file *file) +struct mail * +dbox_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers) { - struct dbox_mailbox *mbox = - (struct dbox_mailbox *)mail->mail.mail.box; - const struct dbox_mail_header *hdr = &file->seeked_mail_header; - uint32_t hdr_uid = hex2dec(hdr->uid_hex, sizeof(hdr->uid_hex)); + struct dbox_mail *mail; + pool_t pool; + + pool = pool_alloconly_create("mail", 1024); + mail = p_new(pool, struct dbox_mail, 1); + mail->imail.mail.pool = pool; + + index_mail_init(&mail->imail, t, wanted_fields, wanted_headers); + return &mail->imail.mail.mail; +} + +static void dbox_mail_close(struct mail *_mail) +{ + struct dbox_mail *mail = (struct dbox_mail *)_mail; - if (hdr_uid != mail->mail.mail.uid) { - mail_storage_set_critical(&mbox->storage->storage, - "dbox %s: Cached file offset broken", - mbox->file->path); + if (mail->open_file != NULL) + dbox_file_unref(&mail->open_file); + index_mail_close(_mail); +} - /* make sure we get it fixed */ - (void)dbox_sync(mbox, TRUE); - return -1; +static int dbox_mail_lookup(struct dbox_mail *mail, + uoff_t *offset_r, struct dbox_file **file_r) +{ + struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->imail.ibox; + unsigned int file_id; + + if (mail->open_file == NULL) { + if (!dbox_file_lookup(mbox, mbox->ibox.view, + mail->imail.mail.mail.seq, + &file_id, &mail->offset)) { + mail_set_expunged(&mail->imail.mail.mail); + return -1; + } + mail->open_file = dbox_file_init(mbox, file_id); } - /* Note that the mail may already have an expunge flag, but we don't - care since we can still read it */ - mail->data.physical_size = mail->data.virtual_size = - hex2dec(hdr->mail_size_hex, sizeof(hdr->mail_size_hex)); - mail->data.received_date = - hex2dec(hdr->received_time_hex, sizeof(hdr->received_time_hex)); - return 1; + *file_r = mail->open_file; + *offset_r = mail->offset; + return 0; } -int dbox_mail_lookup_offset(struct index_transaction_context *trans, - uint32_t seq, uint32_t *file_seq_r, - uoff_t *offset_r) +static int +dbox_mail_metadata_seek(struct dbox_mail *mail, struct dbox_file **file_r) { - struct dbox_mailbox *mbox = - (struct dbox_mailbox *)trans->ibox; - uint32_t uid; - bool synced = FALSE; + struct mail *_mail = &mail->imail.mail.mail; + uoff_t offset, metadata_offset, physical_size; + bool expunged; int ret; - for (;;) { - ret = dbox_file_lookup_offset(mbox, trans->trans_view, seq, - file_seq_r, offset_r); - if (ret <= 0) - return ret; - if (*file_seq_r != 0) - return 1; - - /* lost file sequence/offset */ - if (synced) - return -1; - - mail_index_lookup_uid(trans->trans_view, seq, &uid); - mail_storage_set_critical(&mbox->storage->storage, - "Cached message offset lost for uid %u in " - "dbox %s", uid, mbox->path); + if (dbox_mail_lookup(mail, &offset, file_r) < 0) + return -1; - /* resync and try again */ - if (dbox_sync(mbox, TRUE) < 0) - return -1; - synced = TRUE; - } -} - -static bool dbox_mail_try_open(struct index_mail *mail, - uint32_t *file_seq_r, uoff_t *offset_r, - int *ret_r) -{ - struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->ibox; - struct dbox_transaction_context *t = - (struct dbox_transaction_context *)mail->trans; - uint32_t seq = mail->mail.mail.seq; - - *ret_r = dbox_mail_lookup_offset(mail->trans, seq, - file_seq_r, offset_r); - if (*ret_r <= 0) { - if (*ret_r == 0) - mail_set_expunged(&mail->mail.mail); - return TRUE; - } + if (mail_get_physical_size(_mail, &physical_size) < 0) + return -1; - if ((*ret_r = dbox_file_seek(mbox, *file_seq_r, *offset_r, - seq >= t->first_saved_mail_seq)) < 0) - return TRUE; - if (*ret_r > 0) { - /* ok */ - *ret_r = dbox_mail_parse_mail_header(mail, mbox->file); - return TRUE; + metadata_offset = + dbox_file_get_metadata_offset(*file_r, offset, physical_size); + ret = dbox_file_metadata_seek(*file_r, metadata_offset, &expunged); + if (ret <= 0) { + if (ret < 0) + return -1; + /* FIXME */ + return -1; } - return FALSE; -} - -static int dbox_mail_open(struct index_mail *mail, uoff_t *offset_r) -{ - struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->ibox; - uint32_t file_seq, prev_file_seq = 0; - uoff_t prev_offset = 0; - int i, ret; - - if (mail->mail.mail.expunged || mbox->syncing) - return 0; - - for (i = 0; i < 3; i++) { - if (dbox_mail_try_open(mail, &file_seq, offset_r, &ret)) - return ret; - - if (prev_file_seq == file_seq && prev_offset == *offset_r) { - /* broken offset */ - break; - } else { - /* mail was moved. resync dbox to find out the new - offset and try again. */ - if (dbox_sync(mbox, FALSE) < 0) - return -1; - } - - prev_file_seq = file_seq; - prev_offset = *offset_r; + if (expunged) { + mail_set_expunged(&mail->imail.mail.mail); + return -1; } - - mail_storage_set_critical(&mbox->storage->storage, - "Cached message offset (%u, %"PRIuUOFF_T") " - "broken for uid %u in dbox %s", - file_seq, *offset_r, mail->mail.mail.uid, - mbox->path); - - if (dbox_sync(mbox, TRUE) < 0) - return -1; - if (dbox_mail_try_open(mail, &file_seq, offset_r, &ret)) - return ret; - return -1; + return 0; } static int dbox_mail_get_received_date(struct mail *_mail, time_t *date_r) { - struct index_mail *mail = (struct index_mail *)_mail; - struct index_mail_data *data = &mail->data; - uoff_t offset; + struct dbox_mail *mail = (struct dbox_mail *)_mail; + struct index_mail_data *data = &mail->imail.data; + struct dbox_file *file; + const char *value; uint32_t t; (void)index_mail_get_received_date(_mail, date_r); if (*date_r != (time_t)-1) return 0; - if (dbox_mail_open(mail, &offset) <= 0) - return (time_t)-1; - if (data->received_date == (time_t)-1) { - /* it's broken and conflicts with our "not found" - return value. change it. */ - data->received_date = 0; - } + if (dbox_mail_metadata_seek(mail, &file) < 0) + return -1; - t = data->received_date; - index_mail_cache_add(mail, MAIL_CACHE_RECEIVED_DATE, &t, sizeof(t)); + value = dbox_file_metadata_get(file, DBOX_METADATA_RECEIVED_TIME); + data->received_date = t = value == NULL ? 0 : strtoul(value, NULL, 16); + index_mail_cache_add(&mail->imail, MAIL_CACHE_RECEIVED_DATE, + &t, sizeof(t)); *date_r = data->received_date; return 0; } static int dbox_mail_get_save_date(struct mail *_mail, time_t *date_r) { - struct index_mail *mail = (struct index_mail *)_mail; - struct index_mail_data *data = &mail->data; - uoff_t offset; + struct dbox_mail *mail = (struct dbox_mail *)_mail; + struct index_mail_data *data = &mail->imail.data; + struct dbox_file *file; + const char *value; + uint32_t t; (void)index_mail_get_save_date(_mail, date_r); if (*date_r != (time_t)-1) return 0; - if (dbox_mail_open(mail, &offset) <= 0) + if (dbox_mail_metadata_seek(mail, &file) < 0) return -1; - if (data->save_date == (time_t)-1) { - /* it's broken and conflicts with our "not found" - return value. change it. */ - data->save_date = ioloop_time; - } + + value = dbox_file_metadata_get(file, DBOX_METADATA_SAVE_TIME); + data->save_date = t = value == NULL ? 0 : strtoul(value, NULL, 16); + index_mail_cache_add(&mail->imail, MAIL_CACHE_SAVE_DATE, &t, sizeof(t)); + *date_r = data->save_date; + return 0; +} + +static int dbox_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r) +{ + struct dbox_mail *mail = (struct dbox_mail *)_mail; + struct index_mail_data *data = &mail->imail.data; + struct dbox_file *file; + const char *value; - index_mail_cache_add(mail, MAIL_CACHE_SAVE_DATE, - &data->save_date, sizeof(data->save_date)); - *date_r = data->save_date; + if (index_mail_get_cached_virtual_size(&mail->imail, size_r)) + return 0; + + if (dbox_mail_metadata_seek(mail, &file) < 0) + return -1; + + value = dbox_file_metadata_get(file, DBOX_METADATA_VIRTUAL_SIZE); + if (value == NULL) + return index_mail_get_virtual_size(_mail, size_r); + + data->virtual_size = strtoul(value, NULL, 16); + index_mail_cache_add(&mail->imail, MAIL_CACHE_VIRTUAL_FULL_SIZE, + &data->virtual_size, sizeof(data->virtual_size)); + *size_r = data->virtual_size; return 0; } @@ -197,46 +164,62 @@ { struct index_mail *mail = (struct index_mail *)_mail; struct index_mail_data *data = &mail->data; - uoff_t offset; + struct istream *input; (void)index_mail_get_physical_size(_mail, size_r); if (*size_r != (uoff_t)-1) return 0; - if (dbox_mail_open(mail, &offset) <= 0) + if (mail_get_stream(_mail, NULL, NULL, &input) < 0) return -1; + i_assert(data->physical_size != (uoff_t)-1); index_mail_cache_add(mail, MAIL_CACHE_PHYSICAL_FULL_SIZE, &data->physical_size, sizeof(data->physical_size)); *size_r = data->physical_size; return 0; - } -static int dbox_mail_get_stream(struct mail *_mail, - struct message_size *hdr_size, - struct message_size *body_size, - struct istream **stream_r) +static int +dbox_mail_get_stream(struct mail *_mail, struct message_size *hdr_size, + struct message_size *body_size, struct istream **stream_r) { - struct index_mail *mail = (struct index_mail *)_mail; - struct dbox_mailbox *mbox = (struct dbox_mailbox *)mail->ibox; + struct dbox_mail *mail = (struct dbox_mail *)_mail; + struct index_mail_data *data = &mail->imail.data; + struct istream *input; uoff_t offset; + uint32_t uid; + bool expunged; + int ret; - if (mail->data.stream == NULL) { - if (dbox_mail_open(mail, &offset) <= 0) + if (data->stream == NULL) { + if (dbox_mail_lookup(mail, &offset, &mail->open_file) < 0) return -1; - offset += mbox->file->mail_header_size; - mail->data.stream = - i_stream_create_limit(mbox->file->input, offset, - mbox->file->seeked_mail_size); + ret = dbox_file_get_mail_stream(mail->open_file, offset, &uid, + &data->physical_size, &input, + &expunged); + if (ret < 0) + return -1; + if (ret > 0 && expunged) { + mail_set_expunged(_mail); + return -1; + } + if (ret == 0 || uid != _mail->uid) { + /* FIXME: broken file/offset */ + if (ret > 0) + i_stream_unref(&input); + return -1; + } + data->stream = input; } - return index_mail_init_stream(mail, hdr_size, body_size, stream_r); + return index_mail_init_stream(&mail->imail, hdr_size, body_size, + stream_r); } struct mail_vfuncs dbox_mail_vfuncs = { - index_mail_close, + dbox_mail_close, index_mail_free, index_mail_set_seq, index_mail_set_uid, @@ -247,7 +230,7 @@ index_mail_get_date, dbox_mail_get_received_date, dbox_mail_get_save_date, - dbox_mail_get_physical_size, /* physical = virtual in our case */ + dbox_mail_get_virtual_size, dbox_mail_get_physical_size, index_mail_get_first_header, index_mail_get_headers,
--- a/src/lib-storage/index/dbox/dbox-save.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-save.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,98 +1,63 @@ -/* Copyright (C) 2005 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" #include "array.h" -#include "ioloop.h" +#include "hex-dec.h" +#include "str.h" #include "istream.h" -#include "hex-dec.h" +#include "ostream.h" +#include "ostream-crlf.h" #include "write-full.h" -#include "ostream.h" -#include "seq-range-array.h" #include "index-mail.h" -#include "dbox-uidlist.h" -#include "dbox-keywords.h" +#include "dbox-storage.h" +#include "dbox-index.h" +#include "dbox-file.h" #include "dbox-sync.h" -#include "dbox-storage.h" + +#include <stdlib.h> -#include <stddef.h> +struct dbox_save_mail { + struct dbox_file *file; + uint32_t seq; + uint32_t append_offset; + uoff_t message_size; +}; struct dbox_save_context { struct mail_save_context ctx; struct dbox_mailbox *mbox; struct mail_index_transaction *trans; - struct dbox_uidlist_append_ctx *append_ctx; - struct mail_index_sync_ctx *index_sync_ctx; + struct dbox_index_append_context *append_ctx; + struct dbox_sync_context *sync_ctx; /* updated for each appended mail: */ uint32_t seq; struct istream *input; - struct ostream *output; - struct dbox_file *file; - struct mail *mail; - uint64_t hdr_offset; - uint64_t mail_offset; + struct mail *mail, *cur_dest_mail; + time_t cur_received_date; + enum mail_flags cur_flags; + string_t *cur_keywords; + + struct dbox_file *cur_file; + struct ostream *cur_output; + + ARRAY_DEFINE(mails, struct dbox_save_mail); unsigned int failed:1; unsigned int finished:1; }; -static int -dbox_save_add_keywords(struct dbox_save_context *ctx, - const struct mail_keywords *keywords, - buffer_t *file_keywords) +static void dbox_save_keywords(struct dbox_save_context *ctx, + struct mail_keywords *keywords) { - ARRAY_TYPE(seq_range) new_keywords; - const struct seq_range *range; - unsigned int i, count, file_idx; - int ret = 0; - - /* Get a list of all new keywords. Using seq_range is the easiest - way to do this and should be pretty fast too. */ - t_push(); - t_array_init(&new_keywords, 16); - for (i = 0; i < keywords->count; i++) { - /* check if it's already in the file */ - if (dbox_file_lookup_keyword(ctx->mbox, ctx->file, - keywords->idx[i], &file_idx)) { - buffer_write(file_keywords, file_idx, "1", 1); - continue; - } - - /* add it. if it already exists, it's handled internally. */ - seq_range_array_add(&new_keywords, 0, keywords->idx[i]); - } - - /* now, write them to file */ - range = array_get(&new_keywords, &count); - if (count > 0) { - if (dbox_file_append_keywords(ctx->mbox, ctx->file, - range, count) < 0) { - ret = -1; - count = 0; - } - - /* write the new keywords to file_keywords */ - for (i = 0; i < count; i++) { - unsigned int kw; - - for (kw = range[i].seq1; kw <= range[i].seq2; kw++) { - if (!dbox_file_lookup_keyword(ctx->mbox, - ctx->file, kw, - &file_idx)) { - /* it should have been found */ - i_unreached(); - continue; - } - - buffer_write(file_keywords, file_idx, "1", 1); - } - } - } - - t_pop(); - return ret; + if (ctx->cur_keywords == NULL) + ctx->cur_keywords = str_new(default_pool, 128); + else + str_truncate(ctx->cur_keywords, 0); + dbox_mail_metadata_keywords_append(ctx->mbox, ctx->cur_keywords, + keywords); } int dbox_save_init(struct mailbox_transaction_context *_t, @@ -106,117 +71,33 @@ (struct dbox_transaction_context *)_t; struct dbox_mailbox *mbox = (struct dbox_mailbox *)t->ictx.ibox; struct dbox_save_context *ctx = t->save_ctx; - struct dbox_mail_header hdr; - const struct stat *st; - buffer_t *file_keywords = NULL; + struct ostream *output; + struct dbox_message_header dbox_msg_hdr; + struct dbox_save_mail *save_mail; enum mail_flags save_flags; - unsigned int i, pos, left; - char buf[128]; - int ret; + const struct stat *st; + uoff_t mail_size; i_assert((t->ictx.flags & MAILBOX_TRANSACTION_FLAG_EXTERNAL) != 0); - if (received_date == (time_t)-1) - received_date = ioloop_time; - if (ctx == NULL) { ctx = t->save_ctx = i_new(struct dbox_save_context, 1); ctx->ctx.transaction = &t->ictx.mailbox_ctx; ctx->mbox = mbox; ctx->trans = t->ictx.trans; - ctx->append_ctx = dbox_uidlist_append_init(mbox->uidlist); - - if ((ret = dbox_sync_is_changed(mbox)) < 0) { - ctx->failed = TRUE; - return -1; - } - if (ret > 0) { - if (dbox_sync(mbox, FALSE) < 0) { - ctx->failed = TRUE; - return -1; - } - } + ctx->append_ctx = dbox_index_append_begin(mbox->dbox_index); + i_array_init(&ctx->mails, 32); } - ctx->input = input; /* get the size of the mail to be saved, if possible */ st = i_stream_stat(input, TRUE); - if (st != NULL && st->st_size == -1) - st = NULL; + mail_size = st == NULL || st->st_size == -1 ? 0 : st->st_size; - if (dbox_uidlist_append_locked(ctx->append_ctx, &ctx->file, - st != NULL ? st->st_size : 0) < 0) { + if (dbox_index_append_next(ctx->append_ctx, mail_size, + &ctx->cur_file, &ctx->cur_output) < 0) { ctx->failed = TRUE; return -1; } - ctx->hdr_offset = ctx->file->output->offset; - - t_push(); - if (keywords != NULL && keywords->count > 0) { - uint32_t uid; - time_t mtime; - - /* uidlist must be locked while we're reading or modifying - file's header */ - if (dbox_uidlist_append_get_first_uid(ctx->append_ctx, - &uid, &mtime) < 0) { - ctx->failed = TRUE; - t_pop(); - return -1; - } - - /* write keywords to the file */ - file_keywords = buffer_create_dynamic(pool_datastack_create(), - DBOX_KEYWORD_COUNT); - if (dbox_save_add_keywords(ctx, keywords, file_keywords) < 0) { - ctx->failed = TRUE; - t_pop(); - return -1; - } - o_stream_seek(ctx->file->output, ctx->hdr_offset); - } - - /* append mail header. UID and mail size are written later. */ - memset(&hdr, '0', sizeof(hdr)); - memcpy(hdr.magic, DBOX_MAIL_HEADER_MAGIC, sizeof(hdr.magic)); - DEC2HEX(hdr.received_time_hex, received_date); - DEC2HEX(hdr.save_time_hex, ioloop_time); - hdr.answered = (flags & MAIL_ANSWERED) != 0 ? '1' : '0'; - hdr.flagged = (flags & MAIL_FLAGGED) != 0 ? '1' : '0'; - hdr.deleted = (flags & MAIL_DELETED) != 0 ? '1' : '0'; - hdr.seen = (flags & MAIL_SEEN) != 0 ? '1' : '0'; - hdr.draft = (flags & MAIL_DRAFT) != 0 ? '1' : '0'; - hdr.expunged = '0'; - o_stream_send(ctx->file->output, &hdr, sizeof(hdr)); - - /* write keywords */ - if (file_keywords != NULL) { - unsigned char *keyword_string; - size_t size; - - keyword_string = - buffer_get_modifiable_data(file_keywords, &size); - - /* string should be filled with NULs and '1' now. - Change NULs to '0'. */ - for (i = 0; i < size; i++) { - if (keyword_string[i] == '\0') - keyword_string[i] = '0'; - } - o_stream_send(ctx->file->output, keyword_string, size); - } - - /* fill rest of the header with '0' characters */ - pos = ctx->file->output->offset - ctx->hdr_offset; - i_assert(pos <= ctx->file->mail_header_size); - left = ctx->file->mail_header_size - pos; - memset(buf, '0', I_MIN(sizeof(buf), left)); - while (left > sizeof(buf)) { - o_stream_send(ctx->file->output, buf, sizeof(buf)); - left -= sizeof(buf); - } - o_stream_send(ctx->file->output, buf, left); - ctx->mail_offset = ctx->file->output->offset; /* add to index */ save_flags = flags & ~MAIL_RECENT; @@ -227,22 +108,36 @@ mail_index_update_keywords(ctx->trans, ctx->seq, MODIFY_REPLACE, keywords); } - mail_index_update_ext(ctx->trans, ctx->seq, mbox->dbox_file_ext_idx, - &ctx->file->file_seq, NULL); - mail_index_update_ext(ctx->trans, ctx->seq, - mbox->dbox_offset_ext_idx, &ctx->hdr_offset, - NULL); if (dest_mail == NULL) { if (ctx->mail == NULL) - ctx->mail = index_mail_alloc(_t, 0, NULL); + ctx->mail = mail_alloc(_t, 0, NULL); dest_mail = ctx->mail; } mail_set_seq(dest_mail, ctx->seq); - if (t->first_saved_mail_seq == 0) - t->first_saved_mail_seq = ctx->seq; - t_pop(); + ctx->cur_dest_mail = dest_mail; + ctx->input = index_mail_cache_parse_init(dest_mail, input); + + save_mail = array_append_space(&ctx->mails); + save_mail->file = ctx->cur_file; + save_mail->seq = ctx->seq; + i_assert(ctx->cur_output->offset <= (uint32_t)-1); + save_mail->append_offset = ctx->cur_output->offset; + + /* write a dummy header. it'll get rewritten when we're finished */ + memset(&dbox_msg_hdr, 0, sizeof(dbox_msg_hdr)); + o_stream_cork(ctx->cur_output); + o_stream_send(ctx->cur_output, &dbox_msg_hdr, sizeof(dbox_msg_hdr)); + + output = o_stream_create_lf(ctx->cur_output); + o_stream_unref(&ctx->cur_output); + ctx->cur_output = output; + + ctx->cur_received_date = received_date != (time_t)-1 ? + received_date : ioloop_time; + ctx->cur_flags = flags; + dbox_save_keywords(ctx, keywords); *ctx_r = &ctx->ctx; return ctx->failed ? -1 : 0; @@ -256,59 +151,109 @@ if (ctx->failed) return -1; - if (o_stream_send_istream(ctx->file->output, ctx->input) < 0) { - if (!mail_storage_set_error_from_errno(storage)) { - mail_storage_set_critical(storage, - "o_stream_send_istream(%s) failed: %m", - ctx->file->path); + do { + if (o_stream_send_istream(ctx->cur_output, ctx->input) < 0) { + if (!mail_storage_set_error_from_errno(storage)) { + mail_storage_set_critical(storage, + "o_stream_send_istream(%s) failed: %m", + ctx->cur_file->path); + } + ctx->failed = TRUE; + return -1; } - ctx->failed = TRUE; - return -1; + index_mail_cache_parse_continue(ctx->cur_dest_mail); + + /* both tee input readers may consume data from our primary + input stream. we'll have to make sure we don't return with + one of the streams still having data in them. */ + } while (i_stream_read(ctx->input) > 0); + return 0; +} + +static void dbox_save_write_metadata(struct dbox_save_context *ctx) +{ + struct dbox_metadata_header metadata_hdr; + char space[DBOX_EXTRA_SPACE]; + string_t *str; + uoff_t vsize; + + memset(&metadata_hdr, 0, sizeof(metadata_hdr)); + memcpy(metadata_hdr.magic_post, DBOX_MAGIC_POST, + sizeof(metadata_hdr.magic_post)); + o_stream_send(ctx->cur_output, &metadata_hdr, sizeof(metadata_hdr)); + + str = t_str_new(256); + /* write first fields that don't change */ + str_printfa(str, "%c%lx\n", DBOX_METADATA_RECEIVED_TIME, + (unsigned long)ctx->cur_received_date); + str_printfa(str, "%c%lx\n", DBOX_METADATA_SAVE_TIME, + (unsigned long)ioloop_time); + if (mail_get_virtual_size(ctx->cur_dest_mail, &vsize) < 0) + i_unreached(); + str_printfa(str, "%c%llx\n", DBOX_METADATA_VIRTUAL_SIZE, + (unsigned long long)vsize); + + /* flags */ + str_append_c(str, DBOX_METADATA_FLAGS); + dbox_mail_metadata_flags_append(str, ctx->cur_flags); + str_append_c(str, '\n'); + + /* keywords */ + if (ctx->cur_keywords != NULL && str_len(ctx->cur_keywords) > 0) { + str_append_c(str, DBOX_METADATA_KEYWORDS); + str_append_str(str, ctx->cur_keywords); + str_append_c(str, '\n'); } - return 0; + + o_stream_send(ctx->cur_output, str_data(str), str_len(str)); + memset(space, ' ', sizeof(space)); + o_stream_send(ctx->cur_output, space, sizeof(space)); + o_stream_send(ctx->cur_output, "\n", 1); } int dbox_save_finish(struct mail_save_context *_ctx) { struct dbox_save_context *ctx = (struct dbox_save_context *)_ctx; - struct dbox_mail_header hdr; + struct mail_storage *storage = &ctx->mbox->storage->storage; + struct dbox_save_mail *save_mail; + uoff_t offset = 0; + unsigned int count; ctx->finished = TRUE; + if (ctx->cur_output == NULL) + return -1; - if (ctx->file != NULL) { - /* Make sure the file ends here (we could have been overwriting - some existing aborted mail). In case we failed, truncate the - file to the size before writing. */ - if (ftruncate(ctx->file->fd, ctx->failed ? ctx->hdr_offset : - ctx->file->output->offset) < 0) { - mail_storage_set_critical(&ctx->mbox->storage->storage, - "ftruncate(%s) failed: %m", - ctx->file->path); + index_mail_cache_parse_deinit(ctx->cur_dest_mail, + ctx->cur_received_date); + + if (!ctx->failed) { + offset = ctx->cur_output->offset; + dbox_save_write_metadata(ctx); + if (o_stream_flush(ctx->cur_output) < 0) { + mail_storage_set_critical(storage, + "o_stream_flush(%s) failed: %m", + ctx->cur_file->path); ctx->failed = TRUE; } } - if (!ctx->failed) { - /* write mail size to header */ - DEC2HEX(hdr.mail_size_hex, - ctx->file->output->offset - ctx->mail_offset); + o_stream_destroy(&ctx->cur_output); + i_stream_unref(&ctx->input); - if (pwrite_full(ctx->file->fd, hdr.mail_size_hex, - sizeof(hdr.mail_size_hex), ctx->hdr_offset + - offsetof(struct dbox_mail_header, - mail_size_hex)) < 0) { - mail_storage_set_critical(&ctx->mbox->storage->storage, - "pwrite_full(%s) failed: %m", - ctx->file->path); - ctx->failed = TRUE; - } + count = array_count(&ctx->mails); + save_mail = array_idx_modifiable(&ctx->mails, count - 1); + if (ctx->failed) { + dbox_file_cancel_append(save_mail->file, + save_mail->append_offset); + dbox_file_unref(&save_mail->file); + array_delete(&ctx->mails, count - 1, 1); + return -1; + } else { + dbox_file_finish_append(save_mail->file); + save_mail->message_size = offset - save_mail->append_offset - + save_mail->file->msg_header_size; + return 0; } - - if (ctx->failed) - return -1; - - dbox_uidlist_append_finish_mail(ctx->append_ctx, ctx->file); - return 0; } void dbox_save_cancel(struct mail_save_context *_ctx) @@ -319,102 +264,190 @@ (void)dbox_save_finish(_ctx); } +static int +dbox_save_mail_write_header(struct dbox_save_mail *mail, uint32_t uid) +{ + struct dbox_message_header dbox_msg_hdr; + struct ostream *output = mail->file->output; + uoff_t orig_offset; + int ret = 0; + + i_assert(mail->file->msg_header_size == sizeof(dbox_msg_hdr)); + + mail->file->last_append_uid = uid; + + memset(&dbox_msg_hdr, ' ', sizeof(dbox_msg_hdr)); + memcpy(dbox_msg_hdr.magic_pre, DBOX_MAGIC_PRE, + sizeof(dbox_msg_hdr.magic_pre)); + dbox_msg_hdr.type = DBOX_MESSAGE_TYPE_NORMAL; + dec2hex(dbox_msg_hdr.uid_hex, uid, sizeof(dbox_msg_hdr.uid_hex)); + dec2hex(dbox_msg_hdr.message_size_hex, mail->message_size, + sizeof(dbox_msg_hdr.message_size_hex)); + dbox_msg_hdr.save_lf = '\n'; + + orig_offset = output->offset; + o_stream_seek(output, mail->append_offset); + if (o_stream_send(output, &dbox_msg_hdr, sizeof(dbox_msg_hdr)) < 0 || + o_stream_flush(output) < 0) { + dbox_file_set_syscall_error(mail->file, "write"); + ret = -1; + } + o_stream_seek(output, orig_offset); + return ret; +} + +static int +dbox_save_file_write_append_offset(struct dbox_file *file, uoff_t append_offset) +{ + char buf[8+1]; + + i_assert(append_offset <= (uint32_t)-1); + + i_snprintf(buf, sizeof(buf), "%08x", (unsigned int)append_offset); + if (pwrite_full(file->fd, buf, sizeof(buf)-1, + file->append_offset_header_pos) < 0) { + dbox_file_set_syscall_error(file, "pwrite"); + return -1; + } + return 0; +} + +static int dbox_save_file_commit_header(struct dbox_save_mail *mail) +{ + uoff_t append_offset; + + append_offset = dbox_file_get_next_append_offset(mail->file); + return dbox_save_file_write_append_offset(mail->file, append_offset); +} + +static void dbox_save_file_uncommit_header(struct dbox_save_mail *mail) +{ + if (mail->file->file_id == 0) { + /* temporary file, we'll just unlink it later */ + return; + } + (void)dbox_save_file_write_append_offset(mail->file, + mail->append_offset); +} + +static int dbox_save_mail_file_cmp(const void *p1, const void *p2) +{ + const struct dbox_save_mail *m1 = p1, *m2 = p2; + int ret; + + ret = strcmp(m1->file->path, m2->file->path); + if (ret == 0) { + /* the oldest sequence is first. this is needed for uncommit + to work right. */ + ret = (int)m1->seq - (int)m2->seq; + } + return ret; +} + +static int dbox_save_commit(struct dbox_save_context *ctx, uint32_t first_uid) +{ + struct dbox_mail_index_record rec; + struct dbox_save_mail *mails; + unsigned int i, count; + + /* first write updated mail headers and collect all files we wrote to */ + mails = array_get_modifiable(&ctx->mails, &count); + for (i = 0; i < count; i++) { + if (dbox_save_mail_write_header(&mails[i], first_uid++) < 0) + return -1; + } + + /* update append offsets in file headers */ + qsort(mails, count, sizeof(*mails), dbox_save_mail_file_cmp); + for (i = 0; i < count; i++) { + if (i > 0 && mails[i].file == mails[i-1].file) { + /* already written */ + continue; + } + + if (dbox_save_file_commit_header(&mails[i]) < 0) { + /* have to uncommit all changes so far */ + for (; i > 0; i--) { + if (i > 1 && + mails[i-2].file == mails[i-1].file) + continue; + dbox_save_file_uncommit_header(&mails[i-1]); + } + return -1; + } + } + + /* set file_id / offsets to records */ + if (dbox_index_append_assign_file_ids(ctx->append_ctx) < 0) + return -1; + + memset(&rec, 0, sizeof(rec)); + for (i = 0; i < count; i++) { + rec.file_id = mails[i].file->file_id; + rec.offset = mails[i].append_offset; + + if ((rec.file_id & DBOX_FILE_ID_FLAG_UID) == 0) { + mail_index_update_ext(ctx->trans, mails[i].seq, + ctx->mbox->dbox_ext_id, + &rec, NULL); + } + } + return 0; +} + int dbox_transaction_save_commit_pre(struct dbox_save_context *ctx) { struct dbox_transaction_context *t = (struct dbox_transaction_context *)ctx->ctx.transaction; - struct dbox_mail_header hdr; - struct dbox_file *file; - struct mail_index_view *view; - const struct mail_index_header *idx_hdr; - uint32_t seq, uid, next_uid, file_seq; - time_t old_mtime, new_mtime; - uoff_t offset; - int ret; + const struct mail_index_header *hdr; + uint32_t uid, next_uid; i_assert(ctx->finished); - /* uidlist locking is done before index locking. */ - if (dbox_uidlist_append_get_first_uid(ctx->append_ctx, - &uid, &old_mtime) < 0) { - ctx->failed = TRUE; - dbox_transaction_save_rollback(ctx); - return -1; - } - mail_index_append_assign_uids(ctx->trans, uid, &next_uid); - - *t->ictx.first_saved_uid = uid; - *t->ictx.last_saved_uid = next_uid - 1; - - /* update UIDs */ - for (seq = t->first_saved_mail_seq; seq <= ctx->seq; seq++, uid++) { - ret = dbox_mail_lookup_offset(&t->ictx, seq, - &file_seq, &offset); - i_assert(ret > 0); /* it's in memory, shouldn't fail! */ - - DEC2HEX(hdr.uid_hex, uid); - - file = dbox_uidlist_append_lookup_file(ctx->append_ctx, - file_seq); - if (pwrite_full(file->fd, hdr.uid_hex, - sizeof(hdr.uid_hex), offset + - offsetof(struct dbox_mail_header, - uid_hex)) < 0) { - mail_storage_set_critical(&ctx->mbox->storage->storage, - "pwrite_full(%s) failed: %m", - file->path); - ctx->failed = TRUE; - dbox_transaction_save_rollback(ctx); - return -1; - } - } - - /* lock index lock before dropping uidlist lock in _append_commit() */ - if (mail_index_sync_begin(ctx->mbox->ibox.index, &ctx->index_sync_ctx, - &view, &ctx->trans, 0) < 0) { + if (dbox_sync_begin(ctx->mbox, &ctx->sync_ctx, FALSE) < 0) { ctx->failed = TRUE; dbox_transaction_save_rollback(ctx); return -1; } - if (dbox_uidlist_append_commit(ctx->append_ctx, &new_mtime) < 0) { - mail_index_sync_rollback(&ctx->index_sync_ctx); - i_free(ctx); + hdr = mail_index_get_header(ctx->sync_ctx->sync_view); + uid = hdr->next_uid; + mail_index_append_assign_uids(ctx->trans, uid, &next_uid); + + if (dbox_save_commit(ctx, uid) < 0) { + ctx->failed = TRUE; + dbox_transaction_save_rollback(ctx); return -1; } - idx_hdr = mail_index_get_header(view); - if ((uint32_t)old_mtime == idx_hdr->sync_stamp && - old_mtime != new_mtime) { - /* index was fully synced. keep it that way. */ - uint32_t sync_stamp = new_mtime; + *t->ictx.saved_uid_validity = hdr->uid_validity; + *t->ictx.first_saved_uid = uid; + *t->ictx.last_saved_uid = next_uid - 1; - mail_index_update_header(ctx->trans, - offsetof(struct mail_index_header, sync_stamp), - &sync_stamp, sizeof(sync_stamp), TRUE); - } - + dbox_index_append_commit(&ctx->append_ctx); return 0; } void dbox_transaction_save_commit_post(struct dbox_save_context *ctx) { - mail_index_sync_rollback(&ctx->index_sync_ctx); - if (ctx->mail != NULL) - index_mail_free(ctx->mail); - i_free(ctx); + (void)dbox_sync_finish(&ctx->sync_ctx, TRUE); + dbox_transaction_save_rollback(ctx); } void dbox_transaction_save_rollback(struct dbox_save_context *ctx) { if (!ctx->finished) dbox_save_cancel(&ctx->ctx); + if (ctx->append_ctx != NULL) + dbox_index_append_rollback(&ctx->append_ctx); - if (ctx->index_sync_ctx != NULL) - mail_index_sync_rollback(&ctx->index_sync_ctx); + if (ctx->sync_ctx != NULL) + (void)dbox_sync_finish(&ctx->sync_ctx, FALSE); - dbox_uidlist_append_rollback(ctx->append_ctx); if (ctx->mail != NULL) - index_mail_free(ctx->mail); + mail_free(&ctx->mail); + if (ctx->cur_keywords != NULL) + str_free(&ctx->cur_keywords); + array_free(&ctx->mails); i_free(ctx); }
--- a/src/lib-storage/index/dbox/dbox-storage.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-storage.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,69 +1,29 @@ -/* Copyright (C) 2005-2007 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" #include "array.h" #include "ioloop.h" +#include "str.h" #include "mkdir-parents.h" #include "unlink-directory.h" #include "index-mail.h" #include "mail-copy.h" -#include "dbox-uidlist.h" #include "dbox-sync.h" +#include "dbox-index.h" #include "dbox-file.h" #include "dbox-storage.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <dirent.h> #include <sys/stat.h> #define CREATE_MODE 0770 /* umask() should limit it more */ -/* How often to touch the uidlist lock file when using KEEP_LOCKED flag */ -#define DBOX_LOCK_TOUCH_MSECS (10*1000) - #define DBOX_LIST_CONTEXT(obj) \ MODULE_CONTEXT(obj, dbox_mailbox_list_module) -const struct dotlock_settings default_uidlist_dotlock_set = { - MEMBER(temp_prefix) NULL, - MEMBER(lock_suffix) NULL, - - MEMBER(timeout) 120, - MEMBER(stale_timeout) 60, - - MEMBER(callback) NULL, - MEMBER(context) NULL, - - MEMBER(use_excl_lock) FALSE -}; - -const struct dotlock_settings default_file_dotlock_set = { - MEMBER(temp_prefix) NULL, - MEMBER(lock_suffix) NULL, - - MEMBER(timeout) 120, - MEMBER(stale_timeout) 60, - - MEMBER(callback) NULL, - MEMBER(context) NULL, - - MEMBER(use_excl_lock) FALSE -}; - -static const struct dotlock_settings default_new_file_dotlock_set = { - MEMBER(temp_prefix) NULL, - MEMBER(lock_suffix) NULL, - - MEMBER(timeout) 60, - MEMBER(stale_timeout) 30, - - MEMBER(callback) NULL, - MEMBER(context) NULL, - - MEMBER(use_excl_lock) FALSE -}; - extern struct mail_storage dbox_storage; extern struct mailbox dbox_mailbox; @@ -77,44 +37,6 @@ enum mailbox_list_file_type type, enum mailbox_info_flags *flags); -static bool -dbox_storage_is_valid_existing_name(struct mailbox_list *list, const char *name) -{ - struct dbox_storage *storage = DBOX_LIST_CONTEXT(list); - const char *p; - - if (!storage->list_module_ctx.super.is_valid_existing_name(list, name)) - return FALSE; - - /* Don't allow the mailbox name to end in dbox-Mails */ - p = strrchr(name, '/'); - if (p != NULL) - name = p + 1; - return strcmp(name, DBOX_MAILDIR_NAME) != 0; -} - -static bool -dbox_storage_is_valid_create_name(struct mailbox_list *list, const char *name) -{ - struct dbox_storage *storage = DBOX_LIST_CONTEXT(list); - const char *const *tmp; - bool ret = TRUE; - - if (!storage->list_module_ctx.super.is_valid_create_name(list, name)) - return FALSE; - - /* Don't allow creating mailboxes under dbox-Mails */ - t_push(); - for (tmp = t_strsplit(name, "/"); *tmp != NULL; tmp++) { - if (strcmp(*tmp, DBOX_MAILDIR_NAME) == 0) { - ret = FALSE; - break; - } - } - t_pop(); - return ret; -} - static int dbox_get_list_settings(struct mailbox_list_settings *list_set, const char *data, enum mail_storage_flags flags, @@ -126,7 +48,7 @@ memset(list_set, 0, sizeof(*list_set)); list_set->subscription_fname = DBOX_SUBSCRIPTION_FILE_NAME; - list_set->maildir_name = DBOX_MAILDIR_NAME; + list_set->maildir_name = ""; if (data == NULL || *data == '\0' || *data == ':') { /* we won't do any guessing for this format. */ @@ -155,7 +77,7 @@ /* strip trailing '/' */ len = strlen(list_set->root_dir); - if (list_set->root_dir[len-1] == '/') + if (len > 1 && list_set->root_dir[len-1] == '/') list_set->root_dir = t_strndup(list_set->root_dir, len-1); if (list_set->index_dir != NULL && @@ -182,7 +104,6 @@ { struct dbox_storage *storage = (struct dbox_storage *)_storage; struct mailbox_list_settings list_set; - struct mailbox_list *list; struct stat st; if (dbox_get_list_settings(&list_set, data, _storage->flags, @@ -213,57 +134,21 @@ } } - if (mailbox_list_alloc("fs", &list, error_r) < 0) + if (mailbox_list_alloc("fs", &_storage->list, error_r) < 0) return -1; + storage->list_module_ctx.super = _storage->list->v; + _storage->list->v.iter_is_mailbox = dbox_list_iter_is_mailbox; + _storage->list->v.delete_mailbox = dbox_list_delete_mailbox; - _storage->list = list; - storage->list_module_ctx.super = list->v; - list->v.is_valid_existing_name = dbox_storage_is_valid_existing_name; - list->v.is_valid_create_name = dbox_storage_is_valid_create_name; - list->v.iter_is_mailbox = dbox_list_iter_is_mailbox; - list->v.delete_mailbox = dbox_list_delete_mailbox; - - MODULE_CONTEXT_SET_FULL(list, dbox_mailbox_list_module, + MODULE_CONTEXT_SET_FULL(_storage->list, dbox_mailbox_list_module, storage, &storage->list_module_ctx); /* finish list init after we've overridden vfuncs */ - mailbox_list_init(list, _storage->ns, &list_set, + mailbox_list_init(_storage->list, _storage->ns, &list_set, mail_storage_get_list_flags(_storage->flags)); - - storage->uidlist_dotlock_set = default_uidlist_dotlock_set; - storage->file_dotlock_set = default_file_dotlock_set; - storage->new_file_dotlock_set = default_new_file_dotlock_set; - if ((_storage->flags & MAIL_STORAGE_FLAG_DOTLOCK_USE_EXCL) != 0) { - storage->uidlist_dotlock_set.use_excl_lock = TRUE; - storage->file_dotlock_set.use_excl_lock = TRUE; - storage->new_file_dotlock_set.use_excl_lock = TRUE; - } return 0; } -static bool dbox_autodetect(const char *data, enum mail_storage_flags flags) -{ - bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0; - struct stat st; - const char *path; - - data = t_strcut(data, ':'); - - path = t_strconcat(data, "/INBOX/"DBOX_MAILDIR_NAME, NULL); - if (stat(path, &st) < 0) { - if (debug) - i_info("dbox autodetect: stat(%s) failed: %m", path); - return FALSE; - } - - if (!S_ISDIR(st.st_mode)) { - if (debug) - i_info("dbox autodetect: %s not a directory", path); - return FALSE; - } - return TRUE; -} - static int create_dbox(struct mail_storage *storage, const char *path) { if (mkdir_parents(path, CREATE_MODE) < 0 && errno != EEXIST) { @@ -276,12 +161,6 @@ return 0; } - -static void dbox_lock_touch_timeout(struct dbox_mailbox *mbox) -{ - (void)dbox_uidlist_lock_touch(mbox->uidlist); -} - static struct mailbox * dbox_open(struct dbox_storage *storage, const char *name, enum mailbox_open_flags flags) @@ -306,44 +185,44 @@ mbox->ibox.storage = &storage->storage; mbox->ibox.mail_vfuncs = &dbox_mail_vfuncs; mbox->ibox.index = index; + mbox->path = p_strdup(pool, path); + mbox->storage = storage; + mbox->last_interactive_change = ioloop_time; value = getenv("DBOX_ROTATE_SIZE"); if (value != NULL) mbox->rotate_size = (uoff_t)strtoul(value, NULL, 10) * 1024; else mbox->rotate_size = DBOX_DEFAULT_ROTATE_SIZE; + mbox->rotate_size = 0; /* FIXME: currently anything else doesn't work */ value = getenv("DBOX_ROTATE_MIN_SIZE"); if (value != NULL) mbox->rotate_min_size = (uoff_t)strtoul(value, NULL, 10) * 1024; else mbox->rotate_min_size = DBOX_DEFAULT_ROTATE_MIN_SIZE; + if (mbox->rotate_min_size > mbox->rotate_size) + mbox->rotate_min_size = mbox->rotate_size; value = getenv("DBOX_ROTATE_DAYS"); if (value != NULL) mbox->rotate_days = (unsigned int)strtoul(value, NULL, 10); else mbox->rotate_days = DBOX_DEFAULT_ROTATE_DAYS; - mbox->storage = storage; - mbox->path = p_strdup(pool, path); - mbox->dbox_file_ext_idx = - mail_index_ext_register(index, "dbox-seq", 0, - sizeof(uint32_t), sizeof(uint32_t)); - mbox->dbox_offset_ext_idx = - mail_index_ext_register(index, "dbox-off", 0, - sizeof(uint64_t), sizeof(uint64_t)); + value = getenv("DBOX_MAX_OPEN_FILES"); + if (value != NULL) + mbox->max_open_files = (unsigned int)strtoul(value, NULL, 10); + else + mbox->max_open_files = DBOX_DEFAULT_MAX_OPEN_FILES; + i_array_init(&mbox->open_files, I_MIN(mbox->max_open_files, 128)); - mbox->uidlist = dbox_uidlist_init(mbox); - if ((flags & MAILBOX_OPEN_KEEP_LOCKED) != 0) { - if (dbox_uidlist_lock(mbox->uidlist) < 0) { - struct mailbox *box = &mbox->ibox.box; - - mailbox_close(&box); - return NULL; - } - mbox->keep_lock_to = timeout_add(DBOX_LOCK_TOUCH_MSECS, - dbox_lock_touch_timeout, - mbox); - } + mbox->dbox_ext_id = + mail_index_ext_register(index, "dbox", 0, + sizeof(struct dbox_mail_index_record), + sizeof(uint32_t)); + mbox->dbox_hdr_ext_id = + mail_index_ext_register(index, "dbox-hdr", + sizeof(struct dbox_index_header), 0, 0); + mbox->dbox_index = dbox_index_init(mbox); index_storage_mailbox_init(&mbox->ibox, name, flags, FALSE); return &mbox->ibox.box; @@ -381,9 +260,22 @@ } } +static int dbox_storage_mailbox_close(struct mailbox *box) +{ + struct dbox_mailbox *mbox = (struct dbox_mailbox *)box; + int ret; + + ret = dbox_sync(mbox, TRUE); + + dbox_index_deinit(&mbox->dbox_index); + dbox_files_free(mbox); + array_free(&mbox->open_files); + + return index_storage_mailbox_close(box) < 0 ? -1 : ret; +} + static int dbox_mailbox_create(struct mail_storage *_storage, - const char *name, - bool directory __attr_unused__) + const char *name, bool directory __attr_unused__) { const char *path; struct stat st; @@ -400,85 +292,101 @@ } static int +dbox_delete_nonrecursive(struct mailbox_list *list, const char *path, + const char *name) +{ + DIR *dir; + struct dirent *d; + string_t *full_path; + unsigned int dir_len; + bool unlinked_something = FALSE; + + dir = opendir(path); + if (dir == NULL) { + if (!mailbox_list_set_error_from_errno(list)) { + mailbox_list_set_critical(list, + "opendir(%s) failed: %m", path); + } + return -1; + } + + full_path = t_str_new(256); + str_append(full_path, path); + str_append_c(full_path, '/'); + dir_len = str_len(full_path); + + errno = 0; + while ((d = readdir(dir)) != NULL) { + if (d->d_name[0] == '.') { + /* skip . and .. */ + if (d->d_name[1] == '\0') + continue; + if (d->d_name[1] == '.' && d->d_name[2] == '\0') + continue; + } + + str_truncate(full_path, dir_len); + str_append(full_path, d->d_name); + + /* trying to unlink() a directory gives either EPERM or EISDIR + (non-POSIX). it doesn't really work anywhere in practise, + so don't bother stat()ing the file first */ + if (unlink(str_c(full_path)) == 0) + unlinked_something = TRUE; + else if (errno != ENOENT && errno != EISDIR && errno != EPERM) { + mailbox_list_set_critical(list, + "unlink_directory(%s) failed: %m", + str_c(full_path)); + } + } + + if (closedir(dir) < 0) { + mailbox_list_set_critical(list, "closedir(%s) failed: %m", + path); + } + + if (rmdir(path) == 0) + unlinked_something = TRUE; + else if (errno != ENOENT && errno != ENOTEMPTY) { + mailbox_list_set_critical(list, "rmdir(%s) failed: %m", path); + return -1; + } + + if (!unlinked_something) { + mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, + t_strdup_printf("Directory %s isn't empty, " + "can't delete it.", name)); + return -1; + } + return 0; +} + +static int dbox_list_delete_mailbox(struct mailbox_list *list, const char *name) { struct dbox_storage *storage = DBOX_LIST_CONTEXT(list); struct stat st; - const char *path, *mail_path; + const char *src; - /* make sure the indexes are closed before trying to delete the - directory that contains them */ + /* Make sure the indexes are closed before trying to delete the + directory that contains them. It can still fail with some NFS + implementations if indexes are opened by another session, but + that can't really be helped. */ index_storage_destroy_unrefed(); /* delete the index and control directories */ if (storage->list_module_ctx.super.delete_mailbox(list, name) < 0) return -1; - path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR); - mail_path = mailbox_list_get_path(list, name, - MAILBOX_LIST_PATH_TYPE_MAILBOX); - - if (stat(mail_path, &st) < 0 && ENOTFOUND(errno)) { - if (stat(path, &st) < 0) { - /* doesn't exist at all */ - mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, - T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); - return -1; - } - - /* exists as a \NoSelect mailbox */ - if (rmdir(path) == 0) - return 0; - - if (errno == ENOTEMPTY) { - mailbox_list_set_error(list, MAIL_ERROR_NOTPOSSIBLE, - t_strdup_printf("Directory %s isn't empty, " - "can't delete it.", name)); - } else { - mailbox_list_set_critical(list, - "rmdir() failed for %s: %m", path); - } - + /* check if the mailbox actually exists */ + src = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); + if (stat(src, &st) != 0 && errno == ENOENT) { + mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND, + T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); return -1; } - - if (unlink_directory(mail_path, TRUE) < 0) { - if (!mailbox_list_set_error_from_errno(list)) { - mailbox_list_set_critical(list, - "unlink_directory() failed for %s: %m", - mail_path); - } - return -1; - } - /* try also removing the root directory. it can fail if the deleted - mailbox had submailboxes. do it as long as we can. */ - while (rmdir(path) == 0 || errno == ENOENT) { - const char *p = strrchr(name, '/'); - - if (p == NULL) - break; - - name = t_strdup_until(name, p); - path = mailbox_list_get_path(list, name, - MAILBOX_LIST_PATH_TYPE_DIR); - } - return 0; -} - -static int dbox_storage_close(struct mailbox *box) -{ - struct dbox_mailbox *mbox = (struct dbox_mailbox *)box; - - if (mbox->keep_lock_to != NULL) { - dbox_uidlist_unlock(mbox->uidlist); - timeout_remove(&mbox->keep_lock_to); - } - - dbox_uidlist_deinit(mbox->uidlist); - if (mbox->file != NULL) - dbox_file_close(mbox->file); - return index_storage_mailbox_close(box); + return dbox_delete_nonrecursive(list, src, name); } static void dbox_notify_changes(struct mailbox *box) @@ -491,7 +399,8 @@ index_mailbox_check_add(&mbox->ibox, mbox->path); } -static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx, +static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx + __attr_unused__, const char *dir, const char *fname, enum mailbox_list_file_type type, enum mailbox_info_flags *flags) @@ -500,16 +409,10 @@ struct stat st; int ret = 1; - if (strcmp(fname, DBOX_MAILDIR_NAME) == 0) { - *flags = MAILBOX_NOSELECT; - return 0; - } - /* try to avoid stat() with these checks */ if (type != MAILBOX_LIST_FILE_TYPE_DIR && type != MAILBOX_LIST_FILE_TYPE_SYMLINK && - type != MAILBOX_LIST_FILE_TYPE_UNKNOWN && - (ctx->flags & MAILBOX_LIST_ITER_RETURN_NO_FLAGS) != 0) { + type != MAILBOX_LIST_FILE_TYPE_UNKNOWN) { /* it's a file */ *flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS; return 0; @@ -517,7 +420,7 @@ /* need to stat() then */ t_push(); - mail_path = t_strconcat(dir, "/", fname, "/"DBOX_MAILDIR_NAME, NULL); + mail_path = t_strconcat(dir, "/", fname, NULL); if (stat(mail_path, &st) == 0) { if (!S_ISDIR(st.st_mode)) { @@ -526,9 +429,8 @@ ret = 0; } } else { - /* non-selectable, but may contain subdirs */ - if (errno != ENOTDIR) - *flags |= MAILBOX_CHILDREN; + /* non-selectable. probably either access denied, or symlink + destination not found. don't bother logging errors. */ *flags |= MAILBOX_NOSELECT; } t_pop(); @@ -556,7 +458,7 @@ dbox_alloc, dbox_create, NULL, - dbox_autodetect, + NULL, dbox_mailbox_open, dbox_mailbox_create } @@ -569,7 +471,7 @@ { index_storage_is_readonly, index_storage_allow_new_keywords, - dbox_storage_close, + dbox_storage_mailbox_close, index_storage_get_status, NULL, NULL, @@ -584,7 +486,7 @@ index_keywords_create, index_keywords_free, index_storage_get_uids, - index_mail_alloc, + dbox_mail_alloc, index_header_lookup_init, index_header_lookup_deinit, index_storage_search_init,
--- a/src/lib-storage/index/dbox/dbox-storage.h Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-storage.h Sat Sep 01 03:04:02 2007 +0300 @@ -3,74 +3,54 @@ #include "index-storage.h" #include "mailbox-list-private.h" -#include "dbox-format.h" #define DBOX_STORAGE_NAME "dbox" +#define DBOX_SUBSCRIPTION_FILE_NAME ".dbox-subscriptions" +#define DBOX_INDEX_PREFIX "dovecot.index" -struct dbox_uidlist; +#define DBOX_MAILDIR_NAME "dbox-Mails" +#define DBOX_INDEX_NAME "dbox.index" +#define DBOX_MAIL_FILE_MULTI_PREFIX "m." +#define DBOX_MAIL_FILE_UID_PREFIX "u." +#define DBOX_MAIL_FILE_MULTI_FORMAT DBOX_MAIL_FILE_MULTI_PREFIX"%u" +#define DBOX_MAIL_FILE_UID_FORMAT DBOX_MAIL_FILE_UID_PREFIX"%u" + +/* Default rotation settings */ +#define DBOX_DEFAULT_ROTATE_SIZE (2*1024*1024) +#define DBOX_DEFAULT_ROTATE_MIN_SIZE (1024*16) +#define DBOX_DEFAULT_ROTATE_DAYS 0 +#define DBOX_DEFAULT_MAX_OPEN_FILES 64 + +struct dbox_index_header { + uint32_t last_dirty_flush_stamp; +}; struct dbox_storage { struct mail_storage storage; union mailbox_list_module_context list_module_ctx; - - struct dotlock_settings uidlist_dotlock_set; - struct dotlock_settings file_dotlock_set; - struct dotlock_settings new_file_dotlock_set; -}; - -struct keyword_map { - unsigned int index_idx; - unsigned int file_idx; }; -struct dbox_file { - uint32_t file_seq; - char *path; - - int fd; - struct istream *input; - struct ostream *output; /* while appending mails */ - - uint16_t base_header_size; - uint32_t header_size; - time_t create_time; - uint64_t append_offset; - uint16_t mail_header_size; - uint16_t mail_header_align; - uint16_t keyword_count; - uint64_t keyword_list_offset; - uint32_t keyword_list_size_alloc; - uint32_t keyword_list_size_used; - struct dbox_file_header hdr; - - uoff_t seeked_offset; - uoff_t seeked_mail_size; - uint32_t seeked_uid; - struct dbox_mail_header seeked_mail_header; - unsigned char *seeked_keywords; - - /* Keywords list, sorted by index_idx. */ - ARRAY_DEFINE(idx_file_keywords, struct keyword_map); - /* idx -> index_idx array */ - ARRAY_DEFINE(file_idx_keywords, unsigned int); +struct dbox_mail_index_record { + uint32_t file_id; + uint32_t offset; }; struct dbox_mailbox { struct index_mailbox ibox; struct dbox_storage *storage; - struct dbox_uidlist *uidlist; - const char *path; - struct timeout *keep_lock_to; - - struct dbox_file *file; - uint32_t dbox_file_ext_idx; - uint32_t dbox_offset_ext_idx; + struct dbox_index *dbox_index; + uint32_t dbox_ext_id, dbox_hdr_ext_id; + /* timestamp when the mailbox was last modified interactively */ + time_t last_interactive_change; uoff_t rotate_size, rotate_min_size; unsigned int rotate_days; - unsigned int syncing:1; + ARRAY_DEFINE(open_files, struct dbox_file *); + unsigned int max_open_files; + + const char *path; }; struct dbox_transaction_context { @@ -86,6 +66,11 @@ void dbox_transaction_class_init(void); void dbox_transaction_class_deinit(void); +struct mail * +dbox_mail_alloc(struct mailbox_transaction_context *t, + enum mail_fetch_field wanted_fields, + struct mailbox_header_lookup_ctx *wanted_headers); + int dbox_save_init(struct mailbox_transaction_context *_t, enum mail_flags flags, struct mail_keywords *keywords, time_t received_date, int timezone_offset, @@ -99,8 +84,4 @@ void dbox_transaction_save_commit_post(struct dbox_save_context *ctx); void dbox_transaction_save_rollback(struct dbox_save_context *ctx); -int dbox_mail_lookup_offset(struct index_transaction_context *trans, - uint32_t seq, uint32_t *file_seq_r, - uoff_t *offset_r); - #endif
--- a/src/lib-storage/index/dbox/dbox-sync-expunge.c Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,503 +0,0 @@ -/* Copyright (C) 2005 Timo Sirainen */ - -#include "lib.h" -#include "array.h" -#include "istream.h" -#include "ostream.h" -#include "write-full.h" -#include "hex-dec.h" -#include "seq-range-array.h" -#include "dbox-storage.h" -#include "dbox-uidlist.h" -#include "dbox-file.h" -#include "dbox-sync.h" - -#include <stddef.h> - -static void -dbox_sync_rec_get_uids(struct dbox_sync_context *ctx, - const struct dbox_sync_rec *sync_rec, - uint32_t *uid1_r, uint32_t *uid2_r) -{ - mail_index_lookup_uid(ctx->sync_view, sync_rec->seq1, uid1_r); - mail_index_lookup_uid(ctx->sync_view, sync_rec->seq2, uid2_r); -} - -static int -dbox_next_expunge(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *sync_entry, - unsigned int *sync_idx, uint32_t *uid1_r, uint32_t *uid2_r) -{ - struct mailbox *box = &ctx->mbox->ibox.box; - const struct dbox_sync_rec *sync_recs, *sync_rec; - unsigned int count; - uint32_t uid, seq; - - sync_recs = array_get(&sync_entry->sync_recs, &count); - - while (*sync_idx < count) { - *sync_idx += 1; - sync_rec = &sync_recs[*sync_idx]; - - if (sync_rec->type != MAIL_INDEX_SYNC_TYPE_EXPUNGE) - continue; - - dbox_sync_rec_get_uids(ctx, sync_rec, uid1_r, uid2_r); - if (box->v.sync_notify != NULL) { - /* all of the UIDs uid1..uid2 should exist */ - for (uid = *uid1_r; uid <= *uid2_r; uid++) { - box->v.sync_notify(box, uid, - MAILBOX_SYNC_TYPE_EXPUNGE); - } - } - for (seq = sync_rec->seq1; seq != sync_rec->seq2; seq++) - mail_index_expunge(ctx->trans, seq); - - return 1; - } - - *uid1_r = *uid2_r = 0; - return 0; -} - -static int dbox_sync_expunge_copy(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *sync_entry, - unsigned int sync_idx, - uint32_t first_nonexpunged_uid, - const struct dbox_uidlist_entry *orig_entry, - uoff_t orig_offset) -{ - struct dbox_mailbox *mbox = ctx->mbox; - struct mail_storage *storage = &mbox->storage->storage; - struct dotlock *dotlock; - struct istream *input; - struct ostream *output; - struct dbox_file *file; - struct dbox_uidlist_entry dest_entry; - const struct dbox_sync_rec *sync_recs; - const char *path, *lock_path; - uint32_t file_seq, seq, uid1, uid2; - unsigned int sync_count; - int ret, fd; - uoff_t full_size; - off_t bytes; - - ret = dbox_file_seek(mbox, orig_entry->file_seq, orig_offset, FALSE); - - if (ret >= 0 && mbox->file->hdr.have_expunged_mails != '0') { - /* there are some expunged mails in the file, go through all - of the mails. */ - ret = dbox_file_seek(mbox, orig_entry->file_seq, - mbox->file->header_size, FALSE); - } - - /* skip mails until we find the first we don't want expunged */ - while (ret > 0) { - ret = dbox_file_seek_next_nonexpunged(mbox); - if (mbox->file->seeked_uid >= first_nonexpunged_uid) - break; - } - - if (ret <= 0) { - if (ret == 0) { - mail_storage_set_critical(storage, - "%s: Expunging lost UID %u from file %u", - mbox->path, first_nonexpunged_uid, - orig_entry->file_seq); - } - return ret; - } - - sync_recs = array_get(&sync_entry->sync_recs, &sync_count); - if (sync_idx == sync_count) - uid1 = uid2 = 0; - else - dbox_sync_rec_get_uids(ctx, &sync_recs[sync_idx], &uid1, &uid2); - - file_seq = dbox_uidlist_get_new_file_seq(mbox->uidlist); - - for (;; file_seq++) { - path = t_strdup_printf("%s/"DBOX_MAIL_FILE_FORMAT, - mbox->path, file_seq); - fd = file_dotlock_open(&mbox->storage->new_file_dotlock_set, - path, DOTLOCK_CREATE_FLAG_NONBLOCK, - &dotlock); - if (fd >= 0) - break; - - if (errno != EAGAIN) { - mail_storage_set_critical(storage, - "file_dotlock_open(%s) failed: %m", path); - return -1; - } - - /* try again with another file name */ - } - output = o_stream_create_fd_file(fd, 0, FALSE); - lock_path = file_dotlock_get_lock_path(dotlock); - - memset(&dest_entry, 0, sizeof(dest_entry)); - t_array_init(&dest_entry.uid_list, array_count(&orig_entry->uid_list)); - dest_entry.file_seq = file_seq; - - /* write file header */ - file = i_new(struct dbox_file, 1); - file->fd = -1; - file->output = output; - if (dbox_file_write_header(mbox, file) < 0) - ret = -1; - dbox_file_close(file); - - while (ret > 0) { - /* update mail's location in index */ - uint32_t uid = mbox->file->seeked_uid; - uint64_t hdr_offset = output->offset; - - mail_index_lookup_uid_range(ctx->sync_view, uid, uid, - &seq, &seq); - if (seq == 0) { - mail_storage_set_critical(storage, - "Expunged UID %u reappeared in file %s", - uid, path); - mail_index_mark_corrupted(mbox->ibox.index); - ret = -1; - break; - } - - mail_index_update_ext(ctx->trans, seq, mbox->dbox_file_ext_idx, - &file_seq, NULL); - mail_index_update_ext(ctx->trans, seq, - mbox->dbox_offset_ext_idx, - &hdr_offset, NULL); - - /* copy the mail */ - full_size = mbox->file->mail_header_size + - mbox->file->seeked_mail_size; - input = i_stream_create_limit(mbox->file->input, - mbox->file->seeked_offset, - full_size); - bytes = o_stream_send_istream(output, input); - i_stream_destroy(&input); - - if (bytes < 0) { - mail_storage_set_critical(storage, - "o_stream_send_istream(%s) failed: %m", - lock_path); - ret = -1; - break; - } - if ((uoff_t)bytes != full_size) { - mail_storage_set_critical(storage, - "o_stream_send_istream(%s) wrote only %" - PRIuUOFF_T" of %"PRIuUOFF_T" bytes", lock_path, - (uoff_t)bytes, full_size); - ret = -1; - break; - } - - seq_range_array_add(&dest_entry.uid_list, 0, - mbox->file->seeked_uid); - - /* seek to next non-expunged mail */ - for (;;) { - ret = dbox_file_seek_next_nonexpunged(mbox); - if (ret <= 0) - break; - - while (mbox->file->seeked_uid > uid2 && uid2 != 0) { - ret = dbox_next_expunge(ctx, sync_entry, - &sync_idx, - &uid1, &uid2); - if (ret <= 0) - break; - } - if (ret <= 0) { - if (ret == 0) { - /* we want to keep copying */ - ret = 1; - } - break; - } - - if (mbox->file->seeked_uid < uid1 || uid1 == 0) - break; - } - } - - if (ret == 0) { - struct dbox_file_header hdr; - - /* update append_offset in header */ - DEC2HEX(hdr.append_offset_hex, output->offset); - - o_stream_flush(output); - if (pwrite_full(fd, hdr.append_offset_hex, - sizeof(hdr.append_offset_hex), - offsetof(struct dbox_file_header, - append_offset_hex)) < 0) { - mail_storage_set_critical(storage, - "pwrite_full(%s) failed: %m", lock_path); - ret = -1; - } - } - o_stream_destroy(&output); - - if (ret < 0) { - file_dotlock_delete(&dotlock); - return -1; - } else { - if (file_dotlock_replace(&dotlock, 0) < 0) - return -1; - - /* new file created successfully. append it to uidlist. */ - dbox_uidlist_sync_append(ctx->uidlist_sync_ctx, &dest_entry); - return 0; - } -} - -static int dbox_sync_expunge_file(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *sync_entry, - unsigned int sync_idx) -{ - struct dbox_mailbox *mbox = ctx->mbox; - const struct dbox_sync_rec *sync_recs; - struct dbox_uidlist_entry *entry; - struct seq_range *range; - const char *path; - unsigned int i, count, sync_count; - uint32_t file_seq, uid, exp_uid1, exp_uid2, first_expunged_uid; - uoff_t offset; - int ret; - bool seen_expunges, skipped_expunges; - - sync_recs = array_get(&sync_entry->sync_recs, &sync_count); - if (dbox_sync_get_file_offset(ctx, sync_recs[sync_idx].seq1, - &file_seq, &offset) < 0) - return -1; - i_assert(file_seq == sync_entry->file_seq); - - entry = dbox_uidlist_entry_lookup(mbox->uidlist, sync_entry->file_seq); - if (entry == NULL) { - /* file is already unlinked. just remove from index. */ - return 0; - } - - dbox_sync_rec_get_uids(ctx, &sync_recs[sync_idx], &exp_uid1, &exp_uid2); - - /* find the first non-expunged mail */ - first_expunged_uid = exp_uid1; - seen_expunges = FALSE; skipped_expunges = FALSE; uid = 0; - range = array_get_modifiable(&entry->uid_list, &count); - for (i = 0; i < count; i++) { - uid = range[i].seq1; - - if (!seen_expunges) { - if (uid < first_expunged_uid) { - /* range begins with non-expunged messages */ - uid = first_expunged_uid; - skipped_expunges = TRUE; - } - } - - while (uid <= range[i].seq2) { - if (uid < exp_uid1 || exp_uid1 == 0) { - /* non-expunged mails exist in this file */ - break; - } - seen_expunges = TRUE; - - if (range[i].seq2 < exp_uid2) { - /* fully used up this uid range */ - uid = range[i].seq2 + 1; - break; - } - - /* this sync_rec was fully used. look up the next. - range[] doesn't contain non-existing UIDs, so - exp_uid2+1 should exist in it. */ - if (uid <= exp_uid2) - uid = exp_uid2 + 1; - - ret = dbox_next_expunge(ctx, sync_entry, &sync_idx, - &exp_uid1, &exp_uid2); - if (ret <= 0) { - if (ret < 0) - return -1; - /* end of sync records */ - break; - } - } - if (uid <= range[i].seq2) { - /* non-expunged mails exist in this file */ - break; - } - } - - if (i != count) { - /* mails expunged from the middle. have to copy everything - after the first expunged mail to new file. after copying - we'll truncate/unlink the old file. */ - if (dbox_sync_expunge_copy(ctx, sync_entry, sync_idx, - uid, entry, offset) < 0) - return -1; - i++; - } - - if (!skipped_expunges) { - /* all mails expunged from file, unlink it. */ - path = t_strdup_printf("%s/"DBOX_MAIL_FILE_FORMAT, - mbox->path, entry->file_seq); - if (unlink(path) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "unlink(%s) failed: %m", path); - return -1; - } - - dbox_uidlist_sync_unlink(ctx->uidlist_sync_ctx, - entry->file_seq); - return 0; - } - - /* mails expunged from the end of file, ftruncate() it */ - ret = dbox_file_seek(mbox, entry->file_seq, offset, FALSE); - if (ret <= 0) { - if (ret < 0) - return -1; - - /* unexpected EOF -> already truncated */ - } else { - /* file can no longer be appended to */ - if (pwrite_full(mbox->file->fd, "00000000EFFFFFFF", 16, - offsetof(struct dbox_file_header, - append_offset_hex)) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "pwrite_full(%s) failed: %m", mbox->path); - return -1; - } - - if (ftruncate(mbox->file->fd, offset) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "ftruncate(%s) failed: %m", mbox->file->path); - return -1; - } - - if (mbox->file->hdr.have_expunged_mails != '0') { - /* all mails in the file are expunged now */ - if (pwrite_full(mbox->file->fd, "0", 1, - offsetof(struct dbox_file_header, - have_expunged_mails)) < 0) { - mail_storage_set_critical( - &mbox->storage->storage, - "pwrite_full(%s) failed: %m", - mbox->path); - return -1; - } - } - } - - /* remove from uidlist entry */ - for (; i > 0; i--) { - if (range[i-1].seq1 < first_expunged_uid) - break; - } - array_delete(&entry->uid_list, i, count-i); - if (i > 0 && range[i-1].seq2 >= first_expunged_uid) - range[i-1].seq2 = first_expunged_uid-1; - - /* file can no longer be written to */ - entry->file_size = INT_MAX; - - dbox_uidlist_sync_set_modified(ctx->uidlist_sync_ctx); - return 0; -} - -static void -uidlist_entry_remove_uids(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *sync_entry) -{ - struct dbox_uidlist_entry *entry; - const struct dbox_sync_rec *recs; - uint32_t uid; - unsigned int i, count, seq; - - entry = dbox_uidlist_entry_lookup(ctx->mbox->uidlist, - sync_entry->file_seq); - if (entry == NULL) - return; - - recs = array_get(&sync_entry->sync_recs, &count); - for (i = 0; i < count; i++) { - if (recs[i].type != MAIL_INDEX_SYNC_TYPE_EXPUNGE) - continue; - - for (seq = recs[i].seq1; seq <= recs[i].seq2; seq++) { - mail_index_lookup_uid(ctx->sync_view, seq, &uid); - seq_range_array_remove(&entry->uid_list, uid); - } - } - - if (array_count(&entry->uid_list) == 0) { - dbox_uidlist_sync_unlink(ctx->uidlist_sync_ctx, - entry->file_seq); - } - dbox_uidlist_sync_set_modified(ctx->uidlist_sync_ctx); -} - -int dbox_sync_expunge(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *sync_entry, - unsigned int sync_idx) -{ - struct dbox_mailbox *mbox = ctx->mbox; - const struct dbox_sync_rec *sync_rec; - struct dotlock *dotlock; - const char *path; - int ret; - - if (ctx->dotlock_failed_file_seq != sync_entry->file_seq) { - /* we need to have the file locked in case another process is - appending there already. */ - path = t_strdup_printf("%s/"DBOX_MAIL_FILE_FORMAT, - mbox->path, sync_entry->file_seq); - ret = file_dotlock_create(&mbox->storage->new_file_dotlock_set, - path, DOTLOCK_CREATE_FLAG_NONBLOCK, - &dotlock); - if (ret < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "file_dotlock_create(%s) failed: %m", path); - return -1; - } - - if (ret > 0) { - /* locked - copy the non-expunged mails after the - expunged mail to new file */ - ret = dbox_sync_expunge_file(ctx, sync_entry, sync_idx); - file_dotlock_delete(&dotlock); - return ret < 0 ? -1 : 1; - } - - /* remember that we failed, so we don't waste time trying to - lock the file multiple times within same sync. */ - ctx->dotlock_failed_file_seq = sync_entry->file_seq; - } - - /* couldn't lock it, someone's appending. we have no other - choice but to just mark the mail expunged. otherwise we'd - deadlock (appending process waits for uidlist lock which - we have, we wait for file lock which append process has) */ - sync_rec = array_idx(&sync_entry->sync_recs, sync_idx); - if (dbox_sync_update_flags(ctx, sync_rec) < 0) - return -1; - - /* mark in the header that the file contains expunged messages */ - if (pwrite_full(mbox->file->fd, "1", 1, - offsetof(struct dbox_file_header, - have_expunged_mails)) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "pwrite(%s) failed: %m", mbox->file->path); - return -1; - } - - /* remove UIDs from the uidlist entry */ - uidlist_entry_remove_uids(ctx, sync_entry); - return 0; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/dbox/dbox-sync-file.c Sat Sep 01 03:04:02 2007 +0300 @@ -0,0 +1,291 @@ +/* Copyright (C) 2007 Timo Sirainen */ + +#include "lib.h" +#include "array.h" +#include "istream.h" +#include "ostream.h" +#include "str.h" +#include "dbox-storage.h" +#include "dbox-index.h" +#include "dbox-file.h" +#include "dbox-sync.h" + +static int dbox_sync_file_unlink(struct dbox_file *file) +{ + if (unlink(file->path) < 0) { + dbox_file_set_syscall_error(file, "unlink"); + if (errno != ENOENT) + return -1; + } + return 0; +} + +static void +dbox_sync_update_metadata(struct dbox_sync_context *ctx, struct dbox_file *file, + const struct dbox_sync_file_entry *entry, + uint32_t seq) +{ + const struct mail_index_record *rec; + ARRAY_TYPE(keyword_indexes) keyword_indexes; + struct mail_keywords *keywords; + string_t *value; + const char *old_value; + + t_push(); + value = t_str_new(256); + + /* flags */ + rec = mail_index_lookup(ctx->sync_view, seq); + dbox_mail_metadata_flags_append(value, rec->flags); + dbox_file_metadata_set(file, DBOX_METADATA_FLAGS, str_c(value)); + + /* keywords */ + t_array_init(&keyword_indexes, 32); + mail_index_lookup_keywords(ctx->sync_view, seq, &keyword_indexes); + old_value = dbox_file_metadata_get(file, DBOX_METADATA_KEYWORDS); + if (array_count(&keyword_indexes) > 0 || + (old_value != NULL && *old_value != '\0' && + array_count(&keyword_indexes) == 0)) { + str_truncate(value, 0); + keywords = mail_index_keywords_create_from_indexes(ctx->trans, + &keyword_indexes); + dbox_mail_metadata_keywords_append(ctx->mbox, value, keywords); + mail_index_keywords_free(&keywords); + + dbox_file_metadata_set(file, DBOX_METADATA_KEYWORDS, + str_c(value)); + } + + /* expunge state */ + if (array_is_created(&entry->expunges) && + seq_range_exists(&entry->expunges, seq)) { + dbox_file_metadata_set(file, DBOX_METADATA_EXPUNGED, "1"); + mail_index_expunge(ctx->trans, seq); + } + t_pop(); +} + +static int +dbox_sync_file_expunge(struct dbox_sync_context *ctx, struct dbox_file *file, + const struct dbox_sync_file_entry *entry) +{ + const struct seq_range *expunges; + struct dbox_file *out_file = NULL; + struct istream *input; + struct ostream *output; + uint32_t file_id, seq, uid; + uoff_t first_offset, offset, physical_size, metadata_offset; + unsigned int i, count; + bool expunged; + int ret; + + expunges = array_get(&entry->expunges, &count); + if (!dbox_file_lookup(ctx->mbox, ctx->sync_view, expunges[0].seq1, + &file_id, &first_offset)) + return 0; + i_assert(file_id == file->file_id); + mail_index_expunge(ctx->trans, expunges[0].seq1); + + offset = first_offset; + for (;;) { + if ((ret = dbox_file_seek_next(file, &offset, &uid, + &physical_size)) <= 0) + break; + if (uid == 0) { + /* EOF */ + break; + } + + if (i < count) { + mail_index_lookup_uid_range(ctx->sync_view, uid, uid, + &seq, &seq); + while (seq > expunges[i].seq2) { + if (++i == count) + break; + } + } + if (seq == 0 || (i < count && seq >= expunges[i].seq1 && + seq <= expunges[i].seq2)) { + /* this message gets expunged */ + if (seq != 0) + mail_index_expunge(ctx->trans, seq); + continue; + } + + /* non-expunged message. write it to output file. */ + if (out_file == NULL) { + out_file = dbox_file_init(ctx->mbox, 0); + ret = dbox_file_get_append_stream(out_file, + physical_size, + &output); + if (ret <= 0) + break; + } + + input = i_stream_create_limit(file->input, offset, + file->msg_header_size + + physical_size); + o_stream_send_istream(output, input); + i_stream_unref(&input); + + /* write metadata */ + metadata_offset = dbox_file_get_metadata_offset(file, offset, + physical_size); + (void)dbox_file_metadata_seek(file, metadata_offset, &expunged); + dbox_sync_update_metadata(ctx, file, entry, seq); + dbox_file_metadata_write_to(file, output); + mail_index_update_flags(ctx->trans, seq, MODIFY_REMOVE, + (enum mail_flags)MAIL_INDEX_MAIL_FLAG_DIRTY); + } + + if (ret <= 0) { + if (out_file != NULL) { + if (unlink(out_file->path) < 0) { + i_error("unlink(%s) failed: %m", + out_file->path); + } + o_stream_unref(&output); + } + } else if (out_file != NULL) { + /* FIXME: rename out_file and add to index */ + o_stream_unref(&output); + } + + if (ret <= 0) + ; + else if (first_offset == file->file_header_size) { + /* nothing exists in this file anymore */ + ret = dbox_sync_file_unlink(file); + } else { + if (ftruncate(file->fd, first_offset) < 0) { + dbox_file_set_syscall_error(file, "ftruncate"); + ret = -1; + } + } + + if (out_file != NULL) + dbox_file_unref(&out_file); + return ret; +} + +static int +dbox_sync_file_changes(struct dbox_sync_context *ctx, struct dbox_file *file, + const struct dbox_sync_file_entry *entry, uint32_t seq) +{ + uint32_t file_id; + uoff_t offset; + bool expunged; + int ret; + + if (!dbox_file_lookup(ctx->mbox, ctx->sync_view, seq, + &file_id, &offset)) + return 0; + i_assert(file_id == file->file_id); + + ret = dbox_file_metadata_seek_mail_offset(file, offset, &expunged); + if (ret <= 0) + return ret; + if (expunged) { + mail_index_expunge(ctx->trans, seq); + return 1; + } + + dbox_sync_update_metadata(ctx, file, entry, seq); + ret = dbox_file_metadata_write(file); + if (ret <= 0) { + /* FIXME: handle ret=0 case by splitting the file */ + return ret; + } + + mail_index_update_flags(ctx->trans, seq, MODIFY_REMOVE, + (enum mail_flags)MAIL_INDEX_MAIL_FLAG_DIRTY); + return 1; +} + +static int +dbox_sync_file_int(struct dbox_sync_context *ctx, struct dbox_file *file, + const struct dbox_sync_file_entry *entry, bool full_expunge) +{ + const struct seq_range *seqs; + unsigned int i, count; + uint32_t seq, first_expunge_seq; + int ret; + + if (array_is_created(&entry->expunges) && full_expunge) { + seqs = array_idx(&entry->expunges, 0); + first_expunge_seq = seqs->seq1; + } else { + first_expunge_seq = (uint32_t)-1; + } + + seqs = array_get(&entry->changes, &count); + for (i = 0; i < count; ) { + for (seq = seqs[i].seq1; seq <= seqs[i].seq2; seq++) { + if (seq >= first_expunge_seq) + return dbox_sync_file_expunge(ctx, file, entry); + + ret = dbox_sync_file_changes(ctx, file, entry, seq); + if (ret <= 0) + return ret; + } + i++; + } + if (first_expunge_seq != (uint32_t)-1) + return dbox_sync_file_expunge(ctx, file, entry); + return 1; +} + +static void +dbox_sync_mark_single_file_expunged(struct dbox_sync_context *ctx, + const struct dbox_sync_file_entry *entry) +{ + const struct seq_range *expunges; + unsigned int count; + + expunges = array_get(&entry->expunges, &count); + i_assert(count == 1 && expunges[0].seq1 == expunges[0].seq2); + mail_index_expunge(ctx->trans, expunges[0].seq1); +} + +int dbox_sync_file(struct dbox_sync_context *ctx, + const struct dbox_sync_file_entry *entry) +{ + struct dbox_file *file; + struct dbox_index_record *rec; + enum dbox_index_file_status status; + bool locked, deleted; + int ret; + + if ((entry->file_id & DBOX_FILE_ID_FLAG_UID) != 0) { + locked = TRUE; + status = DBOX_INDEX_FILE_STATUS_SINGLE_MESSAGE; + } else { + rec = dbox_index_record_lookup(ctx->mbox->dbox_index, + entry->file_id); + if (rec == NULL || + rec->status == DBOX_INDEX_FILE_STATUS_UNLINKED) { + /* file doesn't exist, nothing to do */ + return 1; + } + locked = rec->locked; + status = rec->status; + } + + file = dbox_file_init(ctx->mbox, entry->file_id); + if (status == DBOX_INDEX_FILE_STATUS_SINGLE_MESSAGE && + array_is_created(&entry->expunges)) { + /* fast path to expunging the whole file */ + if (dbox_sync_file_unlink(file) < 0) + ret = -1; + else { + dbox_sync_mark_single_file_expunged(ctx, entry); + ret = 1; + } + } else { + ret = dbox_file_open_or_create(file, TRUE, &deleted); + if (ret > 0 && !deleted) + ret = dbox_sync_file_int(ctx, file, entry, locked); + } + dbox_file_unref(&file); + return ret; +}
--- a/src/lib-storage/index/dbox/dbox-sync-full.c Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,237 +0,0 @@ -/* Copyright (C) 2005 Timo Sirainen */ - -#include "lib.h" -#include "array.h" -#include "seq-range-array.h" -#include "dbox-storage.h" -#include "dbox-uidlist.h" -#include "dbox-file.h" -#include "dbox-keywords.h" -#include "dbox-sync.h" - -#include <stdlib.h> -#include <unistd.h> -#include <fcntl.h> -#include <dirent.h> - -static int -dbox_mail_get_keywords(struct dbox_mailbox *mbox, struct dbox_file *file, - ARRAY_TYPE(keyword_indexes) *keywords) -{ - const unsigned int *map; - unsigned int i, count; - - if (!array_is_created(&file->file_idx_keywords)) { - if (dbox_file_read_keywords(mbox, file) < 0) - return -1; - } - - map = array_get(&file->file_idx_keywords, &count); - for (i = 0; i < count; i++) { - if (file->seeked_keywords[i] != '0') - array_append(keywords, &map[i], 1); - } - - return 0; -} - -static int dbox_sync_full_mail(struct dbox_sync_context *ctx, uint32_t *seq_r) -{ - struct dbox_mailbox *mbox = ctx->mbox; - const struct dbox_mail_header *hdr = &mbox->file->seeked_mail_header; - enum mail_flags flags; - struct mail_keywords *keywords; - ARRAY_TYPE(keyword_indexes) keywords_arr; - uint32_t seq; - uint64_t hdr_offset = mbox->file->seeked_offset; - - /* FIXME: mails can be in two places at the same time if we crashed - during copying expunge */ - - i_assert(hdr->expunged != '1'); - - if (mbox->file->seeked_uid >= ctx->mail_index_next_uid) { - /* new mail. append it. */ - mail_index_append(ctx->trans, mbox->file->seeked_uid, &seq); - *seq_r = 0; - } else { - mail_index_lookup_uid_range(ctx->sync_view, - mbox->file->seeked_uid, - mbox->file->seeked_uid, &seq, &seq); - if (seq == 0) { - /* not found. it should have been there. */ - mail_storage_set_critical(&mbox->storage->storage, - "dbox %s sync: " - "UID %u inserted in the middle of mailbox", - mbox->path, mbox->file->seeked_uid); - mail_index_mark_corrupted(mbox->ibox.index); - return -1; - } - *seq_r = seq; - } - - flags = 0; - if (hdr->answered == '1') - flags |= MAIL_ANSWERED; - if (hdr->flagged == '1') - flags |= MAIL_FLAGGED; - if (hdr->deleted == '1') - flags |= MAIL_DELETED; - if (hdr->seen == '1') - flags |= MAIL_SEEN; - if (hdr->draft == '1') - flags |= MAIL_DRAFT; - mail_index_update_flags(ctx->trans, seq, MODIFY_REPLACE, flags); - - t_push(); - t_array_init(&keywords_arr, mbox->file->keyword_count); - if (dbox_mail_get_keywords(mbox, mbox->file, &keywords_arr) < 0) { - t_pop(); - return -1; - } - keywords = mail_index_keywords_create_from_indexes(ctx->trans, - &keywords_arr); - mail_index_update_keywords(ctx->trans, seq, MODIFY_REPLACE, keywords); - mail_index_keywords_free(&keywords); - t_pop(); - - mail_index_update_ext(ctx->trans, seq, mbox->dbox_file_ext_idx, - &mbox->file->file_seq, NULL); - mail_index_update_ext(ctx->trans, seq, mbox->dbox_offset_ext_idx, - &hdr_offset, NULL); - return 0; -} - -static int dbox_sync_full_file(struct dbox_sync_context *ctx, uint32_t file_seq) -{ - struct dbox_mailbox *mbox = ctx->mbox; - struct dbox_uidlist_entry entry; - uint32_t seq; - int ret; - - if ((ret = dbox_file_seek(mbox, file_seq, 0, FALSE)) < 0) { - /* error / broken file */ - return -1; - } - if (ret == 0) { - /* broken file, but without any useful data in it */ - if (unlink(mbox->file->path) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "unlink(%s) failed: %m", mbox->file->path); - return -1; - } - return 0; - } - - memset(&entry, 0, sizeof(entry)); - entry.file_seq = file_seq; - t_array_init(&entry.uid_list, 64); - - if (mbox->file->seeked_mail_header.expunged != '0') { - /* first mail expunged */ - ret = dbox_file_seek_next_nonexpunged(mbox); - } - while (ret > 0) { - if (dbox_sync_full_mail(ctx, &seq) < 0) - return -1; - - /* add to this file's uid list */ - seq_range_array_add(&entry.uid_list, 0, - ctx->mbox->file->seeked_uid); - if (seq != 0) { - /* add to the whole mailbox's exist list so we can - expunge the mails that weren't found. seq=0 is - given for newly appended mails */ - seq_range_array_add(&ctx->exists, 0, seq); - } - - ret = dbox_file_seek_next_nonexpunged(mbox); - } - if (ret == 0 && array_count(&entry.uid_list) == 0) { - /* all mails expunged in the file */ - if (unlink(mbox->file->path) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "unlink(%s) failed: %m", mbox->file->path); - return -1; - } - } else { - dbox_uidlist_sync_append(ctx->uidlist_sync_ctx, &entry); - } - return ret; -} - -static void dbox_sync_full_expunge_nonfound(struct dbox_sync_context *ctx) -{ - const struct seq_range *exists; - const struct mail_index_header *hdr; - unsigned int i, count; - uint32_t seq = 1; - - exists = array_get(&ctx->exists, &count); - for (i = 0; i < count; i++) { - /* expunge seq .. exists[i]-1 */ - while (seq < exists[i].seq1) { - mail_index_expunge(ctx->trans, seq); - seq++; - } - seq = exists[i].seq2 + 1; - } - - hdr = mail_index_get_header(ctx->sync_view); - while (seq <= hdr->messages_count) { - mail_index_expunge(ctx->trans, seq); - seq++; - } -} - -int dbox_sync_full(struct dbox_sync_context *ctx) -{ - struct dbox_mailbox *mbox = ctx->mbox; - const struct mail_index_header *hdr; - unsigned int file_prefix_len = strlen(DBOX_MAIL_FILE_PREFIX); - uint32_t file_seq; - DIR *dirp; - struct dirent *dp; - int ret = 0; - - /* go through msg.* files, sync them to index and based on it - write dbox's index file */ - dirp = opendir(mbox->path); - if (dirp == NULL) { - mail_storage_set_critical(&mbox->storage->storage, - "opendir(%s) failed: %m", mbox->path); - return -1; - } - - hdr = mail_index_get_header(ctx->sync_view); - ctx->mail_index_next_uid = hdr->next_uid; - - dbox_uidlist_sync_from_scratch(ctx->uidlist_sync_ctx); - i_array_init(&ctx->exists, 128); - - while ((dp = readdir(dirp)) != NULL) { - if (strncmp(dp->d_name, DBOX_MAIL_FILE_PREFIX, - file_prefix_len) != 0 || - !is_numeric(dp->d_name + file_prefix_len, '\0')) - continue; - - file_seq = (uint32_t)strtoul(dp->d_name + file_prefix_len, - NULL, 10); - t_push(); - ret = dbox_sync_full_file(ctx, file_seq); - t_pop(); - if (ret < 0) - break; - } - if (closedir(dirp) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "closedir(%s) failed: %m", mbox->path); - ret = -1; - } - - if (ret == 0) - dbox_sync_full_expunge_nonfound(ctx); - - array_free(&ctx->exists); - return ret; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/dbox/dbox-sync-rebuild.c Sat Sep 01 03:04:02 2007 +0300 @@ -0,0 +1,199 @@ +/* Copyright (C) 2007 Timo Sirainen */ + +#include "lib.h" +#include "dbox-storage.h" +#include "dbox-index.h" +#include "dbox-file.h" +#include "dbox-sync.h" + +#include <stdlib.h> +#include <dirent.h> + +struct dbox_sync_rebuild_context { + struct dbox_mailbox *mbox; + struct mail_index_transaction *trans; +}; + +static int dbox_sync_set_uidvalidity(struct dbox_sync_rebuild_context *ctx) +{ + uint32_t uid_validity; + + if (dbox_index_get_uid_validity(ctx->mbox->dbox_index, + &uid_validity) < 0) + return -1; + + mail_index_update_header(ctx->trans, + offsetof(struct mail_index_header, uid_validity), + &uid_validity, sizeof(uid_validity), TRUE); + return 0; +} + +static void dbox_sync_index_metadata(struct dbox_sync_rebuild_context *ctx, + struct dbox_file *file, uint32_t seq) +{ + const char *value; + struct mail_keywords *keywords; + enum mail_flags flags = 0; + unsigned int i; + + value = dbox_file_metadata_get(file, DBOX_METADATA_FLAGS); + if (value != NULL) { + for (i = 0; value[i] != '\0'; i++) { + if (value[i] != '0' && i < DBOX_METADATA_FLAGS_COUNT) + flags |= dbox_mail_flags_map[i]; + } + mail_index_update_flags(ctx->trans, seq, MODIFY_REPLACE, flags); + } + + value = dbox_file_metadata_get(file, DBOX_METADATA_KEYWORDS); + if (value != NULL) { + t_push(); + keywords = mail_index_keywords_create(ctx->trans, + t_strsplit_spaces(value, " ")); + mail_index_update_keywords(ctx->trans, seq, MODIFY_REPLACE, + keywords); + mail_index_keywords_free(&keywords); + t_pop(); + } +} + +static int dbox_sync_index_file_next(struct dbox_sync_rebuild_context *ctx, + struct dbox_file *file, uoff_t *offset) +{ + uint32_t seq, uid; + uoff_t metadata_offset, physical_size; + bool expunged; + int ret; + + ret = dbox_file_seek_next(file, offset, &uid, &physical_size); + if (ret <= 0) { + if (ret < 0) + return -1; + + if (uid == 0 && (file->file_id & DBOX_FILE_ID_FLAG_UID) != 0) { + /* EOF */ + return 0; + } + + i_warning("%s: Ignoring broken file (header)", file->path); + return 0; + } + if ((file->file_id & DBOX_FILE_ID_FLAG_UID) != 0 && + uid != (file->file_id & ~DBOX_FILE_ID_FLAG_UID)) { + i_warning("%s: Header contains wrong UID %u", file->path, uid); + return 0; + } + + metadata_offset = + dbox_file_get_metadata_offset(file, *offset, physical_size); + ret = dbox_file_metadata_seek(file, metadata_offset, &expunged); + if (ret <= 0) { + if (ret < 0) + return -1; + i_warning("%s: Ignoring broken file (metadata)", file->path); + return 0; + } + if (!expunged) { + mail_index_append(ctx->trans, uid, &seq); + dbox_sync_index_metadata(ctx, file, seq); + } + return 1; +} + +static int +dbox_sync_index_uid_file(struct dbox_sync_rebuild_context *ctx, + const char *fname) +{ + struct dbox_file *file; + unsigned long uid; + char *p; + uoff_t offset = 0; + int ret; + + fname += sizeof(DBOX_MAIL_FILE_MULTI_PREFIX)-1; + uid = strtoul(fname, &p, 10); + if (*p != '\0' || uid == 0 || uid >= (uint32_t)-1) { + i_warning("dbox %s: Ignoring invalid filename %s", + ctx->mbox->path, fname); + return 0; + } + + file = dbox_file_init(ctx->mbox, uid | DBOX_FILE_ID_FLAG_UID); + ret = dbox_sync_index_file_next(ctx, file, &offset) < 0 ? -1 : 0; + dbox_file_unref(&file); + return ret; +} + +static int +dbox_sync_index_multi_file(struct dbox_sync_rebuild_context *ctx, + const char *fname) +{ + /* FIXME */ + return 0; +} + +static int dbox_sync_index_rebuild_ctx(struct dbox_sync_rebuild_context *ctx) +{ + struct mail_storage *storage = ctx->mbox->ibox.box.storage; + DIR *dir; + struct dirent *d; + int ret = 0; + + if (dbox_sync_set_uidvalidity(ctx) < 0) + return -1; + + dir = opendir(ctx->mbox->path); + if (dir == NULL) { + if (errno == ENOENT) { + ctx->mbox->ibox.mailbox_deleted = TRUE; + return -1; + } + mail_storage_set_critical(storage, + "opendir(%s) failed: %m", ctx->mbox->path); + return -1; + } + errno = 0; + for (; ret == 0 && (d = readdir(dir)) != NULL; errno = 0) { + if (strncmp(d->d_name, DBOX_MAIL_FILE_UID_PREFIX, + sizeof(DBOX_MAIL_FILE_UID_PREFIX)-1) == 0) + ret = dbox_sync_index_uid_file(ctx, d->d_name); + else if (strncmp(d->d_name, DBOX_MAIL_FILE_MULTI_PREFIX, + sizeof(DBOX_MAIL_FILE_MULTI_PREFIX)-1) == 0) + ret = dbox_sync_index_multi_file(ctx, d->d_name); + } + if (errno != 0) { + mail_storage_set_critical(storage, + "readdir(%s) failed: %m", ctx->mbox->path); + ret = -1; + } + + if (closedir(dir) < 0) { + mail_storage_set_critical(storage, + "closedir(%s) failed: %m", ctx->mbox->path); + ret = -1; + } + return ret; +} + +int dbox_sync_index_rebuild(struct dbox_mailbox *mbox) +{ + struct dbox_sync_rebuild_context ctx; + struct mail_index_view *view; + uint32_t seq; + uoff_t offset; + int ret; + + memset(&ctx, 0, sizeof(ctx)); + ctx.mbox = mbox; + view = mail_index_view_open(mbox->ibox.index); + ctx.trans = mail_index_transaction_begin(view, + MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL); + mail_index_reset(ctx.trans); + + if ((ret = dbox_sync_index_rebuild_ctx(&ctx)) < 0) + mail_index_transaction_rollback(&ctx.trans); + else + ret = mail_index_transaction_commit(&ctx.trans, &seq, &offset); + mail_index_view_close(&view); + return ret; +}
--- a/src/lib-storage/index/dbox/dbox-sync.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-sync.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,83 +1,68 @@ -/* Copyright (C) 2005 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" +#include "array.h" #include "ioloop.h" -#include "array.h" +#include "str.h" #include "hash.h" -#include "seq-range-array.h" -#include "write-full.h" +#include "dbox-storage.h" +#include "dbox-index.h" #include "dbox-file.h" -#include "dbox-keywords.h" #include "dbox-sync.h" -#include "dbox-uidlist.h" -#include "dbox-storage.h" -#include <stddef.h> - -int dbox_sync_get_file_offset(struct dbox_sync_context *ctx, uint32_t seq, - uint32_t *file_seq_r, uoff_t *offset_r) -{ - int ret; +#define DBOX_FLUSH_SECS_INTERACTIVE (4*60*60) +#define DBOX_FLUSH_SECS_CLOSE (4*60*60) +#define DBOX_FLUSH_SECS_IMMEDIATE (24*60*60) - ret = dbox_file_lookup_offset(ctx->mbox, ctx->sync_view, seq, - file_seq_r, offset_r); - if (ret <= 0) { - if (ret == 0) { - mail_storage_set_critical(&ctx->mbox->storage->storage, - "Unexpectedly lost seq %u in " - "dbox %s", seq, ctx->mbox->path); - } - return -1; - } - if (*file_seq_r == 0) { - mail_storage_set_critical(&ctx->mbox->storage->storage, - "Cached message offset lost for seq %u in " - "dbox %s", seq, ctx->mbox->path); - return -1; - } - return 0; -} +#define DBOX_REBUILD_COUNT 3 -static int dbox_sync_add_seq(struct dbox_sync_context *ctx, uint32_t seq, - const struct dbox_sync_rec *sync_rec) +static int dbox_sync_add_seq(struct dbox_sync_context *ctx, + enum mail_index_sync_type type, uint32_t seq) { - struct dbox_sync_rec new_sync_rec; struct dbox_sync_file_entry *entry; - const uint32_t *file_seqs; - unsigned int i, count; - uint32_t file_seq; + uint32_t file_id; uoff_t offset; + bool uid_file; - if (dbox_sync_get_file_offset(ctx, seq, &file_seq, &offset) < 0) + if (!dbox_file_lookup(ctx->mbox, ctx->sync_view, seq, + &file_id, &offset)) return -1; - file_seqs = array_get(&ctx->added_file_seqs, &count); - for (i = 0; i < count; i++) { - if (file_seqs[i] == file_seq) { - /* already added */ + entry = hash_lookup(ctx->syncs, POINTER_CAST(file_id)); + if (entry == NULL) { + if (type != MAIL_INDEX_SYNC_TYPE_EXPUNGE && + !ctx->flush_dirty_flags) { + mail_index_update_flags(ctx->trans, seq, MODIFY_ADD, + (enum mail_flags)MAIL_INDEX_MAIL_FLAG_DIRTY); return 0; } - } - array_append(&ctx->added_file_seqs, &file_seq, 1); - entry = hash_lookup(ctx->syncs, POINTER_CAST(file_seq)); - if (entry == NULL) { entry = p_new(ctx->pool, struct dbox_sync_file_entry, 1); - entry->file_seq = file_seq; - p_array_init(&entry->sync_recs, ctx->pool, 3); - hash_insert(ctx->syncs, POINTER_CAST(file_seq), entry); + entry->file_id = file_id; + hash_insert(ctx->syncs, POINTER_CAST(file_id), entry); } + uid_file = (file_id & DBOX_FILE_ID_FLAG_UID) != 0; - new_sync_rec = *sync_rec; - new_sync_rec.seq1 = seq; - array_append(&entry->sync_recs, &new_sync_rec, 1); + if (type == MAIL_INDEX_SYNC_TYPE_EXPUNGE) { + if (!array_is_created(&entry->expunges)) { + p_array_init(&entry->expunges, ctx->pool, + uid_file ? 1 : 3); + seq_range_array_add(&ctx->expunge_files, 0, file_id); + } + seq_range_array_add(&entry->expunges, 0, seq); + } else { + if (!array_is_created(&entry->changes)) { + p_array_init(&entry->changes, ctx->pool, + uid_file ? 1 : 8); + } + seq_range_array_add(&entry->changes, 0, seq); + } return 0; } static int dbox_sync_add(struct dbox_sync_context *ctx, const struct mail_index_sync_rec *sync_rec) { - struct dbox_sync_rec dbox_sync_rec; uint32_t seq, seq1, seq2; if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_APPEND) { @@ -85,6 +70,9 @@ return 0; } + /* we assume that anything else than appends are interactive changes */ + ctx->mbox->last_interactive_change = ioloop_time; + mail_index_lookup_uid_range(ctx->sync_view, sync_rec->uid1, sync_rec->uid2, &seq1, &seq2); @@ -93,495 +81,285 @@ return 0; } - /* convert to dbox_sync_rec, which takes a bit less space and has - sequences instead of UIDs. */ - memset(&dbox_sync_rec, 0, sizeof(dbox_sync_rec)); - dbox_sync_rec.type = sync_rec->type; - dbox_sync_rec.seq1 = seq1; - dbox_sync_rec.seq2 = seq2; - switch (sync_rec->type) { - case MAIL_INDEX_SYNC_TYPE_FLAGS: - dbox_sync_rec.value.flags.add = sync_rec->add_flags; - dbox_sync_rec.value.flags.remove = sync_rec->remove_flags; - break; - case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: - case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: - case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET: - dbox_sync_rec.value.keyword_idx = sync_rec->keyword_idx; - break; - case MAIL_INDEX_SYNC_TYPE_EXPUNGE: - case MAIL_INDEX_SYNC_TYPE_APPEND: - break; - } - - /* now, add the same sync_rec to each file_seq's entry */ - array_clear(&ctx->added_file_seqs); for (seq = seq1; seq <= seq2; seq++) { - if (dbox_sync_add_seq(ctx, seq, &dbox_sync_rec) < 0) + if (dbox_sync_add_seq(ctx, sync_rec->type, seq) < 0) return -1; } return 0; } static int -dbox_sync_write_mask(struct dbox_sync_context *ctx, - const struct dbox_sync_rec *sync_rec, - unsigned int first_flag_offset, unsigned int flag_count, - const unsigned char *array, const unsigned char *mask) +dbox_sync_lock_expunge_file(struct dbox_sync_context *ctx, unsigned int file_id) { - struct dbox_mailbox *mbox = ctx->mbox; - struct mailbox *box = &mbox->ibox.box; - enum mailbox_sync_type sync_type; - uint32_t file_seq, uid2, seq; - uoff_t offset; - unsigned int i, start; + struct dbox_index_record *rec; + enum dbox_index_file_lock_status lock_status; int ret; - if (dbox_sync_get_file_offset(ctx, sync_rec->seq1, - &file_seq, &offset) < 0) + ret = dbox_index_try_lock_file(ctx->mbox->dbox_index, file_id, + &lock_status); + if (ret < 0) return -1; - mail_index_lookup_uid(ctx->sync_view, sync_rec->seq2, &uid2); - if ((ret = dbox_file_seek(mbox, file_seq, offset, FALSE)) <= 0) - return ret; - - switch (sync_rec->type) { - case MAIL_INDEX_SYNC_TYPE_EXPUNGE: - sync_type = MAILBOX_SYNC_TYPE_EXPUNGE; - for (seq = sync_rec->seq1; seq != sync_rec->seq2; seq++) - mail_index_expunge(ctx->trans, seq); - break; - case MAIL_INDEX_SYNC_TYPE_FLAGS: - sync_type = MAILBOX_SYNC_TYPE_FLAGS; - break; - case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: - case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: - case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET: - sync_type = MAILBOX_SYNC_TYPE_KEYWORDS; + rec = dbox_index_record_lookup(ctx->mbox->dbox_index, file_id); + switch (lock_status) { + case DBOX_INDEX_FILE_LOCKED: + seq_range_array_add(&ctx->locked_files, 0, file_id); + rec->status = DBOX_INDEX_FILE_STATUS_NONAPPENDABLE; break; - default: - sync_type = 0; - i_unreached(); - } - while (mbox->file->seeked_uid <= uid2) { - if (box->v.sync_notify != NULL) { - box->v.sync_notify(box, mbox->file->seeked_uid, - sync_type); - } - for (i = 0; i < flag_count; ) { - if (!mask[i]) { - i++; - continue; - } - - start = i; - while (i < flag_count) { - if (!mask[i]) - break; - i++; - } - ret = pwrite_full(mbox->file->fd, - array + start, i - start, - offset + first_flag_offset + start); - if (ret < 0) { - mail_storage_set_critical( - &mbox->storage->storage, - "pwrite(%s) failed: %m", - mbox->file->path); - return -1; - } - } - - ret = dbox_file_seek_next_nonexpunged(mbox); - if (ret <= 0) { - if (ret == 0) - break; - return -1; - } - offset = mbox->file->seeked_offset; + case DBOX_INDEX_FILE_LOCK_NOT_NEEDED: + case DBOX_INDEX_FILE_LOCK_UNLINKED: + i_assert(rec == NULL || + rec->status != DBOX_INDEX_FILE_STATUS_APPENDABLE); + break; + case DBOX_INDEX_FILE_LOCK_TRY_AGAIN: + rec->expunges = TRUE; + break; } return 0; } -int dbox_sync_update_flags(struct dbox_sync_context *ctx, - const struct dbox_sync_rec *sync_rec) +static int dbox_sync_lock_expunge_files(struct dbox_sync_context *ctx) { - static enum mail_flags dbox_flag_list[] = { - MAIL_ANSWERED, - MAIL_FLAGGED, - MAIL_DELETED, - MAIL_SEEN, - MAIL_DRAFT - }; -#define DBOX_FLAG_COUNT (sizeof(dbox_flag_list)/sizeof(dbox_flag_list[0])) - unsigned char dbox_flag_array[DBOX_FLAG_COUNT]; - unsigned char dbox_flag_mask[DBOX_FLAG_COUNT]; - unsigned int i, first_flag_offset; - - /* first build flag array and mask */ - if (sync_rec->type == MAIL_INDEX_SYNC_TYPE_EXPUNGE) { - dbox_flag_array[0] = '1'; - dbox_flag_mask[0] = 1; - - first_flag_offset = offsetof(struct dbox_mail_header, expunged); - return dbox_sync_write_mask(ctx, sync_rec, - first_flag_offset, 1, - dbox_flag_array, dbox_flag_mask); - } else { - i_assert(sync_rec->type == MAIL_INDEX_SYNC_TYPE_FLAGS); - for (i = 0; i < DBOX_FLAG_COUNT; i++) { - dbox_flag_array[i] = - (sync_rec->value.flags.add & - dbox_flag_list[i]) != 0 ? '1' : '0'; - dbox_flag_mask[i] = - ((sync_rec->value.flags.remove | - sync_rec->value.flags.add) & - dbox_flag_list[i]) != 0; - } - - first_flag_offset = offsetof(struct dbox_mail_header, answered); - return dbox_sync_write_mask(ctx, sync_rec, - first_flag_offset, - DBOX_FLAG_COUNT, - dbox_flag_array, dbox_flag_mask); - } -} - -static int -dbox_sync_update_keyword(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *entry, - const struct dbox_sync_rec *sync_rec, bool set) -{ - unsigned char keyword_array, keyword_mask = 1; - unsigned int file_idx, first_flag_offset; - - if (dbox_file_seek(ctx->mbox, entry->file_seq, 0, FALSE) <= 0) - return -1; - - keyword_array = set ? '1' : '0'; - - if (!dbox_file_lookup_keyword(ctx->mbox, ctx->mbox->file, - sync_rec->value.keyword_idx, &file_idx)) { - /* not found. if removing, just ignore. - - if adding, it currently happens only if the maximum keyword - count was reached. once we support moving mails to new file - to grow keywords count, this should never happen. - for now, just ignore this. */ - return 0; - } - - first_flag_offset = sizeof(struct dbox_mail_header) + file_idx; - return dbox_sync_write_mask(ctx, sync_rec, first_flag_offset, 1, - &keyword_array, &keyword_mask); -} - -static int -dbox_sync_reset_keyword(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *entry, - const struct dbox_sync_rec *sync_rec) -{ - unsigned char *keyword_array, *keyword_mask; - unsigned int first_flag_offset; - int ret; - - if (dbox_file_seek(ctx->mbox, entry->file_seq, 0, FALSE) <= 0) - return -1; - - if (ctx->mbox->file->keyword_count == 0) - return 0; - - t_push(); - keyword_array = t_malloc(ctx->mbox->file->keyword_count); - keyword_mask = t_malloc(ctx->mbox->file->keyword_count); - memset(keyword_array, '0', ctx->mbox->file->keyword_count); - memset(keyword_mask, 1, ctx->mbox->file->keyword_count); + const struct seq_range *range; + unsigned int i, count, id; - first_flag_offset = sizeof(struct dbox_mail_header); - ret = dbox_sync_write_mask(ctx, sync_rec, first_flag_offset, - ctx->mbox->file->keyword_count, - keyword_array, keyword_mask); - t_pop(); - return ret; -} - -static int -dbox_sync_file_add_keywords(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *entry, - unsigned int i) -{ - ARRAY_TYPE(seq_range) keywords; - const struct dbox_sync_rec *sync_recs; - const struct seq_range *range; - unsigned int count, file_idx, keyword_idx; - int ret = 0; - - if (dbox_file_seek(ctx->mbox, entry->file_seq, 0, FALSE) <= 0) - return -1; - - /* Get a list of all new keywords. Using seq_range is the easiest - way to do this and should be pretty fast too. */ - t_push(); - t_array_init(&keywords, 16); - sync_recs = array_get(&entry->sync_recs, &count); - for (; i < count; i++) { - if (sync_recs[i].type != MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD) - continue; - - /* check if it's already in the file */ - keyword_idx = sync_recs[i].value.keyword_idx; - if (dbox_file_lookup_keyword(ctx->mbox, ctx->mbox->file, - keyword_idx, &file_idx)) - continue; - - /* add it. if it already exists, it's handled internally. */ - seq_range_array_add(&keywords, 0, keyword_idx); - } - - /* now, write them to file */ - range = array_get(&keywords, &count); - if (count > 0) { - ret = dbox_file_append_keywords(ctx->mbox, ctx->mbox->file, - range, count); - } - - t_pop(); - return ret; -} - -static int dbox_sync_file(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *entry) -{ - const struct dbox_sync_rec *sync_recs; - unsigned int i, count; - bool first_keyword = TRUE; - int ret; - - sync_recs = array_get(&entry->sync_recs, &count); + range = array_get(&ctx->expunge_files, &count); for (i = 0; i < count; i++) { - switch (sync_recs[i].type) { - case MAIL_INDEX_SYNC_TYPE_EXPUNGE: - t_push(); - ret = dbox_sync_expunge(ctx, entry, i); - t_pop(); - if (ret > 0) { - /* handled expunging by copying the file. - while at it, also wrote all the other sync - changes to the file. */ - return 0; - } - if (ret < 0) - return -1; - /* handled expunging by writing expunge flags */ - break; - case MAIL_INDEX_SYNC_TYPE_FLAGS: - if (dbox_sync_update_flags(ctx, &sync_recs[i]) < 0) + for (id = range[i].seq1; id <= range[i].seq2; id++) { + if (dbox_sync_lock_expunge_file(ctx, id) < 0) return -1; - break; - case MAIL_INDEX_SYNC_TYPE_KEYWORD_ADD: - if (first_keyword) { - /* add all new keywords in one go */ - first_keyword = FALSE; - if (dbox_sync_file_add_keywords(ctx, entry, - i) < 0) - return -1; - } - if (dbox_sync_update_keyword(ctx, entry, &sync_recs[i], - TRUE) < 0) - return -1; - break; - case MAIL_INDEX_SYNC_TYPE_KEYWORD_REMOVE: - if (dbox_sync_update_keyword(ctx, entry, &sync_recs[i], - FALSE) < 0) - return -1; - break; - case MAIL_INDEX_SYNC_TYPE_KEYWORD_RESET: - if (dbox_sync_reset_keyword(ctx, entry, - &sync_recs[i]) < 0) - return -1; - break; - case MAIL_INDEX_SYNC_TYPE_APPEND: - i_unreached(); } } return 0; } +static void dbox_sync_unlock_files(struct dbox_sync_context *ctx) +{ + const struct seq_range *range; + unsigned int i, count, id; + + range = array_get(&ctx->locked_files, &count); + for (i = 0; i < count; i++) { + for (id = range[i].seq1; id <= range[i].seq2; id++) + dbox_index_unlock_file(ctx->mbox->dbox_index, id); + } +} + +static void dbox_sync_update_header(struct dbox_sync_context *ctx) +{ + struct dbox_index_header hdr; + + if (!ctx->flush_dirty_flags) + return; + + hdr.last_dirty_flush_stamp = ioloop_time; + mail_index_update_header_ext(ctx->trans, ctx->mbox->dbox_hdr_ext_id, 0, + &hdr.last_dirty_flush_stamp, + sizeof(hdr.last_dirty_flush_stamp)); +} + static int dbox_sync_index(struct dbox_sync_context *ctx) { struct mailbox *box = &ctx->mbox->ibox.box; + const struct mail_index_header *hdr; struct mail_index_sync_rec sync_rec; struct hash_iterate_context *iter; void *key, *value; - int ret; + uint32_t seq1, seq2; + int ret = 1; + + hdr = mail_index_get_header(ctx->sync_view); + if (hdr->uid_validity == 0) { + /* newly created index file */ + return 0; + } - /* read all changes and sort them to file_seq order */ - ctx->pool = pool_alloconly_create("dbox sync pool", 10240); + /* mark the newly seen messages as recent */ + mail_index_lookup_uid_range(ctx->sync_view, hdr->first_recent_uid, + hdr->next_uid, &seq1, &seq2); + if (seq1 != 0) { + index_mailbox_set_recent_seq(&ctx->mbox->ibox, ctx->sync_view, + seq1, seq2); + } + + /* read all changes and sort them to file_id order */ + ctx->pool = pool_alloconly_create("dbox sync pool", 1024*32); ctx->syncs = hash_create(default_pool, ctx->pool, 0, NULL, NULL); - i_array_init(&ctx->added_file_seqs, 64); + i_array_init(&ctx->expunge_files, 32); + i_array_init(&ctx->locked_files, 32); + for (;;) { if (!mail_index_sync_next(ctx->index_sync_ctx, &sync_rec)) break; if (dbox_sync_add(ctx, &sync_rec) < 0) { - ret = -1; + ret = 0; break; } } - array_free(&ctx->added_file_seqs); - - iter = hash_iterate_init(ctx->syncs); - while (hash_iterate(iter, &key, &value)) { - const struct dbox_sync_file_entry *entry = value; + if (ret > 0) { + if (dbox_sync_lock_expunge_files(ctx) < 0) + ret = -1; + } + array_free(&ctx->expunge_files); - if (dbox_sync_file(ctx, entry) < 0) { - ret = -1; - break; + if (ret > 0) { + /* now sync each file separately */ + iter = hash_iterate_init(ctx->syncs); + while (hash_iterate(iter, &key, &value)) { + const struct dbox_sync_file_entry *entry = value; + + if ((ret = dbox_sync_file(ctx, entry)) <= 0) + break; } + hash_iterate_deinit(iter); } - hash_iterate_deinit(iter); + + if (ret > 0) + dbox_sync_update_header(ctx); if (box->v.sync_notify != NULL) box->v.sync_notify(box, 0, 0); + dbox_sync_unlock_files(ctx); hash_destroy(ctx->syncs); pool_unref(ctx->pool); - return ret; } -static int dbox_sync_init(struct dbox_mailbox *mbox, - struct dbox_sync_context *ctx, bool *force) +static bool dbox_sync_want_flush_dirty(struct dbox_mailbox *mbox, + bool close_flush_dirty_flags) { - const struct mail_index_header *hdr; - enum mail_index_sync_flags sync_flags; - time_t mtime; + const struct dbox_index_header *hdr; + const void *data; + size_t data_size; - memset(ctx, 0, sizeof(*ctx)); - ctx->mbox = mbox; - - /* uidlist locking is done before index locking. */ - if (dbox_uidlist_sync_init(mbox->uidlist, &ctx->uidlist_sync_ctx, - &mtime) < 0) - return -1; + if (mbox->last_interactive_change < + ioloop_time - DBOX_FLUSH_SECS_INTERACTIVE) + return TRUE; - sync_flags = MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY; - if (!mbox->ibox.keep_recent) - sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT; - if (mail_index_sync_begin(mbox->ibox.index, &ctx->index_sync_ctx, - &ctx->sync_view, &ctx->trans, - sync_flags) < 0) { - mail_storage_set_index_error(&mbox->ibox); - dbox_uidlist_sync_rollback(ctx->uidlist_sync_ctx); - return -1; + mail_index_get_header_ext(mbox->ibox.view, mbox->dbox_hdr_ext_id, + &data, &data_size); + if (data_size == 0 || data_size != sizeof(*hdr)) { + if (data_size != 0) { + i_warning("dbox %s: Invalid dbox header size", + mbox->path); + } + return TRUE; } + hdr = data; - hdr = mail_index_get_header(ctx->sync_view); - if ((uint32_t)mtime != hdr->sync_stamp) { - /* indexes aren't synced. we'll do a full sync. */ - *force = TRUE; + if (!close_flush_dirty_flags) { + if (hdr->last_dirty_flush_stamp < + ioloop_time - DBOX_FLUSH_SECS_IMMEDIATE) + return TRUE; + } else { + if (hdr->last_dirty_flush_stamp < + ioloop_time - DBOX_FLUSH_SECS_CLOSE) + return TRUE; } - return 1; + return FALSE; } -static int dbox_sync_finish(struct dbox_sync_context *ctx, bool force) +int dbox_sync_begin(struct dbox_mailbox *mbox, + struct dbox_sync_context **ctx_r, + bool close_flush_dirty_flags) { - const struct mail_index_header *hdr; - uint32_t uid_validity, next_uid; - time_t mtime; + struct mail_storage *storage = mbox->ibox.box.storage; + struct dbox_sync_context *ctx; + enum mail_index_sync_flags sync_flags = 0; + unsigned int i; int ret; - if (force) - ret = dbox_sync_full(ctx); - else - ret = dbox_sync_index(ctx); - - if (ret < 0) { - mail_index_sync_rollback(&ctx->index_sync_ctx); - dbox_uidlist_sync_rollback(ctx->uidlist_sync_ctx); - return -1; + if (dbox_sync_want_flush_dirty(mbox, close_flush_dirty_flags)) + sync_flags |= MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY; + else { + if (close_flush_dirty_flags) { + /* no need to sync */ + *ctx_r = NULL; + return 0; + } } - uid_validity = dbox_uidlist_sync_get_uid_validity(ctx->uidlist_sync_ctx); - next_uid = dbox_uidlist_sync_get_next_uid(ctx->uidlist_sync_ctx); + ctx = i_new(struct dbox_sync_context, 1); + ctx->mbox = mbox; + ctx->flush_dirty_flags = + (sync_flags & MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY) != 0; + + if (!mbox->ibox.keep_recent) + sync_flags |= MAIL_INDEX_SYNC_FLAG_DROP_RECENT; + /* don't write unnecessary dirty flag updates */ + sync_flags |= MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES; - hdr = mail_index_get_header(ctx->sync_view); - if (hdr->uid_validity != uid_validity) { - mail_index_update_header(ctx->trans, - offsetof(struct mail_index_header, uid_validity), - &uid_validity, sizeof(uid_validity), TRUE); - } - if (hdr->next_uid != next_uid) { - i_assert(next_uid > hdr->next_uid || - hdr->uid_validity != uid_validity); - mail_index_update_header(ctx->trans, - offsetof(struct mail_index_header, next_uid), - &next_uid, sizeof(next_uid), FALSE); - } + for (i = 0;; i++) { + if (mail_index_sync_begin(mbox->ibox.index, + &ctx->index_sync_ctx, + &ctx->sync_view, &ctx->trans, + sync_flags) < 0) { + mail_storage_set_index_error(&mbox->ibox); + i_free(ctx); + return -1; + } - if (dbox_uidlist_sync_commit(ctx->uidlist_sync_ctx, &mtime) < 0) { - mail_index_sync_rollback(&ctx->index_sync_ctx); - return -1; - } - - if ((uint32_t)mtime != hdr->sync_stamp) { - uint32_t sync_stamp = mtime; + if ((ret = dbox_sync_index(ctx)) > 0) + break; - mail_index_update_header(ctx->trans, - offsetof(struct mail_index_header, sync_stamp), - &sync_stamp, sizeof(sync_stamp), TRUE); - } - - if (force) { + /* failure. keep the index locked while we're doing a + rebuild. */ + if (ret == 0) { + if (i >= DBOX_REBUILD_COUNT) { + mail_storage_set_critical(storage, + "dbox %s: Index keeps breaking", + ctx->mbox->path); + ret = -1; + } else { + /* do a full resync and try again. */ + ret = dbox_sync_index_rebuild(mbox); + } + } mail_index_sync_rollback(&ctx->index_sync_ctx); - } else { - if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) { - mail_storage_set_index_error(&ctx->mbox->ibox); + if (ret < 0) { + i_free(ctx); return -1; } } + + *ctx_r = ctx; return 0; } -static int dbox_sync_int(struct dbox_mailbox *mbox, bool force) +int dbox_sync_finish(struct dbox_sync_context **_ctx, bool success) { - struct dbox_sync_context ctx; - int ret; + struct dbox_sync_context *ctx = *_ctx; + int ret = success ? 0 : -1; - if ((ret = dbox_sync_init(mbox, &ctx, &force)) <= 0) - return ret; - - if ((ret = dbox_sync_finish(&ctx, force)) < 0) - return ret; + *_ctx = NULL; - if (force) { - /* now that indexes are ok, sync changes from the index */ - force = FALSE; - if ((ret = dbox_sync_init(mbox, &ctx, &force)) <= 0) - return ret; + if (success) { + if (mail_index_sync_commit(&ctx->index_sync_ctx) < 0) { + mail_storage_set_index_error(&ctx->mbox->ibox); + ret = -1; + } + } else { + mail_index_sync_rollback(&ctx->index_sync_ctx); + } + if (ctx->path != NULL) + str_free(&ctx->path); + i_free(ctx); + return 0; +} - if (force) { - mail_storage_set_critical(&mbox->storage->storage, - "dbox_sync_full(%s) didn't work", - mbox->path); +int dbox_sync(struct dbox_mailbox *mbox, bool close_flush_dirty_flags) +{ + struct dbox_sync_context *sync_ctx; - mail_index_sync_rollback(&ctx.index_sync_ctx); - dbox_uidlist_sync_rollback(ctx.uidlist_sync_ctx); - return -1; - } - return dbox_sync_finish(&ctx, FALSE); - } else { + if (dbox_sync_begin(mbox, &sync_ctx, close_flush_dirty_flags) < 0) + return -1; + + if (sync_ctx == NULL) { + i_assert(close_flush_dirty_flags); return 0; } -} - -int dbox_sync(struct dbox_mailbox *mbox, bool force) -{ - int ret; - - mbox->syncing = TRUE; - ret = dbox_sync_int(mbox, force); - mbox->syncing = FALSE; - return ret; + return dbox_sync_finish(&sync_ctx, TRUE); } struct mailbox_sync_context * @@ -600,18 +378,3 @@ return index_mailbox_sync_init(box, flags, ret < 0); } - -int dbox_sync_is_changed(struct dbox_mailbox *mbox) -{ - const struct mail_index_header *hdr; - time_t mtime; - - hdr = mail_index_get_header(mbox->ibox.view); - if (hdr->sync_stamp == 0) - return 1; - - if (dbox_uidlist_get_mtime(mbox->uidlist, &mtime) < 0) - return -1; - - return (uint32_t)mtime == hdr->sync_stamp; -}
--- a/src/lib-storage/index/dbox/dbox-sync.h Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-sync.h Sat Sep 01 03:04:02 2007 +0300 @@ -1,67 +1,44 @@ #ifndef __DBOX_SYNC_H #define __DBOX_SYNC_H -#include "seq-range-array.h" -#include "mail-index.h" -#include "mail-storage.h" - +enum mailbox_sync_flags; struct mailbox; -struct dbox_mailbox; - -struct dbox_sync_rec { - uint32_t seq1, seq2; - enum mail_index_sync_type type; - - union { - /* MAIL_INDEX_SYNC_TYPE_FLAGS: */ - struct { - uint8_t add; - uint8_t remove; - } flags; - - /* MAIL_INDEX_SYNC_TYPE_KEYWORD_*: */ - unsigned int keyword_idx; - } value; -}; struct dbox_sync_file_entry { - uint32_t file_seq; + uint32_t file_id; - ARRAY_DEFINE(sync_recs, struct dbox_sync_rec); + ARRAY_TYPE(seq_range) changes; + ARRAY_TYPE(seq_range) expunges; }; struct dbox_sync_context { struct dbox_mailbox *mbox; - struct dbox_uidlist_sync_ctx *uidlist_sync_ctx; struct mail_index_sync_ctx *index_sync_ctx; struct mail_index_view *sync_view; struct mail_index_transaction *trans; + string_t *path; + unsigned int path_dir_prefix_len; + pool_t pool; struct hash_table *syncs; /* struct dbox_sync_file_entry */ - ARRAY_DEFINE(added_file_seqs, uint32_t); - - uint32_t dotlock_failed_file_seq; + ARRAY_TYPE(seq_range) expunge_files; + ARRAY_TYPE(seq_range) locked_files; - /* full sync: */ - uint32_t mail_index_next_uid; - ARRAY_TYPE(seq_range) exists; + unsigned int flush_dirty_flags:1; }; -int dbox_sync(struct dbox_mailbox *mbox, bool force); -int dbox_sync_is_changed(struct dbox_mailbox *mbox); +int dbox_sync_begin(struct dbox_mailbox *mbox, + struct dbox_sync_context **ctx_r, + bool close_flush_dirty_flags); +int dbox_sync_finish(struct dbox_sync_context **ctx, bool success); +int dbox_sync(struct dbox_mailbox *mbox, bool close_flush_dirty_flags); + +int dbox_sync_file(struct dbox_sync_context *ctx, + const struct dbox_sync_file_entry *entry); +int dbox_sync_index_rebuild(struct dbox_mailbox *mbox); struct mailbox_sync_context * dbox_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags); -int dbox_sync_get_file_offset(struct dbox_sync_context *ctx, uint32_t seq, - uint32_t *file_seq_r, uoff_t *offset_r); - -int dbox_sync_update_flags(struct dbox_sync_context *ctx, - const struct dbox_sync_rec *sync_rec); -int dbox_sync_expunge(struct dbox_sync_context *ctx, - const struct dbox_sync_file_entry *entry, - unsigned int sync_idx); -int dbox_sync_full(struct dbox_sync_context *ctx); - #endif
--- a/src/lib-storage/index/dbox/dbox-transaction.c Sat Sep 01 01:36:10 2007 +0300 +++ b/src/lib-storage/index/dbox/dbox-transaction.c Sat Sep 01 03:04:02 2007 +0300 @@ -1,9 +1,9 @@ -/* Copyright (C) 2005 Timo Sirainen */ +/* Copyright (C) 2007 Timo Sirainen */ #include "lib.h" #include "array.h" +#include "dbox-storage.h" #include "dbox-sync.h" -#include "dbox-storage.h" static void (*next_hook_mail_index_transaction_created) (struct mail_index_transaction *t) = NULL; @@ -13,7 +13,7 @@ uoff_t *log_file_offset_r) { struct dbox_transaction_context *dt = MAIL_STORAGE_CONTEXT(t); - struct dbox_mailbox *dbox = (struct dbox_mailbox *)dt->ictx.ibox; + struct dbox_mailbox *mbox = (struct dbox_mailbox *)dt->ictx.ibox; struct dbox_save_context *save_ctx; bool syncing = t->sync_transaction; int ret = 0; @@ -45,7 +45,7 @@ } if (ret == 0 && !syncing) { - if (dbox_sync(dbox, FALSE) < 0) + if (dbox_sync(mbox, FALSE) < 0) ret = -1; } @@ -67,7 +67,8 @@ struct mailbox *box = MAIL_STORAGE_CONTEXT(t->view); /* index can be for mailbox list index, in which case box=NULL */ - if (box != NULL && strcmp(box->storage->name, DBOX_STORAGE_NAME) == 0) { + if (box != NULL && + strcmp(box->storage->name, DBOX_STORAGE_NAME) == 0) { struct dbox_mailbox *dbox = (struct dbox_mailbox *)box; struct dbox_transaction_context *mt;
--- a/src/lib-storage/index/dbox/dbox-uidlist.c Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1402 +0,0 @@ -/* Copyright (C) 2005 Timo Sirainen */ - -#include "lib.h" -#include "hex-dec.h" -#include "array.h" -#include "bsearch-insert-pos.h" -#include "seq-range-array.h" -#include "str.h" -#include "istream.h" -#include "ostream.h" -#include "ostream-crlf.h" -#include "write-full.h" -#include "dbox-file.h" -#include "dbox-storage.h" -#include "dbox-uidlist.h" - -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <fcntl.h> -#include <unistd.h> -#include <utime.h> -#include <sys/stat.h> - -#define DBOX_SYNC_SECS 1 -#define DBOX_APPEND_MAX_OPEN_FDS 64 - -#define DBOX_UIDLIST_VERSION 1 -#define DBOX_UIDLIST_FILENAME "index" - -struct dbox_save_file { - struct dbox_file *file; - - dev_t dev; - ino_t ino; - - struct dotlock *dotlock; - ARRAY_DEFINE(seqs, unsigned int); -}; - -struct dbox_uidlist { - struct dbox_mailbox *mbox; - char *path; - int fd; - - struct dotlock *dotlock; - int lock_fd; - unsigned int lock_count; - - unsigned int version; - uint32_t uid_validity, last_uid, last_file_seq; - - ino_t ino; - time_t mtime; - - uint32_t file_seq_highwater; - - pool_t entry_pool; - ARRAY_DEFINE(entries, struct dbox_uidlist_entry *); - - unsigned int appending:1; - unsigned int need_full_rewrite:1; -}; - -struct dbox_uidlist_append_ctx { - pool_t pool; - struct dbox_uidlist *uidlist; - - time_t min_usable_timestamp; - unsigned int mail_count; - - ARRAY_DEFINE(files, struct dbox_save_file *); - unsigned int open_fds; - - unsigned int locked:1; -}; - -struct dbox_uidlist_sync_ctx { - struct dbox_uidlist *uidlist; - unsigned int modified:1; -}; - -static int dbox_uidlist_full_rewrite(struct dbox_uidlist *uidlist); - -struct dbox_uidlist *dbox_uidlist_init(struct dbox_mailbox *mbox) -{ - struct dbox_uidlist *uidlist; - - uidlist = i_new(struct dbox_uidlist, 1); - uidlist->mbox = mbox; - uidlist->fd = -1; - uidlist->mtime = -1; - uidlist->lock_fd = -1; - uidlist->entry_pool = - pool_alloconly_create("uidlist entry pool", 1024*32); - uidlist->path = i_strconcat(mbox->path, "/"DBOX_UIDLIST_FILENAME, NULL); - i_array_init(&uidlist->entries, 64); - return uidlist; -} - -void dbox_uidlist_deinit(struct dbox_uidlist *uidlist) -{ - i_assert(!uidlist->appending); - - array_free(&uidlist->entries); - pool_unref(uidlist->entry_pool); - i_free(uidlist->path); - i_free(uidlist); -} - -static int uidlist_merge(ARRAY_TYPE(seq_range) *uid_list, const struct seq_range *seqs) -{ - struct seq_range *range; - unsigned int count; - - range = array_get_modifiable(uid_list, &count); - i_assert(count > 0); - - if (seqs->seq1 <= range[count-1].seq2) - return FALSE; - - if (seqs->seq1-1 == range[count-1].seq2) { - /* we can just continue the existing range */ - range[count-1].seq2 = seqs->seq2; - } else { - array_append(uid_list, seqs, 1); - } - return TRUE; -} - -static int dbox_uidlist_entry_cmp(const void *key, const void *p) -{ - const unsigned int *file_seq = key; - struct dbox_uidlist_entry *const *entry = p; - - return (int)*file_seq - (int)(*entry)->file_seq; -} - -static void dbox_uidlist_update_last_uid(struct dbox_uidlist *uidlist, - const struct dbox_uidlist_entry *entry) -{ - const struct seq_range *range; - unsigned int count; - - range = array_get(&entry->uid_list, &count); - if (range[count-1].seq2 > uidlist->last_uid) - uidlist->last_uid = range[count-1].seq2; -} - -static bool dbox_uidlist_add_entry(struct dbox_uidlist *uidlist, - const struct dbox_uidlist_entry *src_entry) -{ - struct dbox_uidlist_entry *dest_entry, **entries; - const struct seq_range *range; - unsigned int i, idx, count; - - dbox_uidlist_update_last_uid(uidlist, src_entry); - - entries = array_get_modifiable(&uidlist->entries, &count); - if (count == 0 || src_entry->file_seq > entries[count-1]->file_seq) { - /* append new file sequence */ - idx = count; - } else { - bsearch_insert_pos(&src_entry->file_seq, entries, count, - sizeof(*entries), - dbox_uidlist_entry_cmp, - &idx); - } - - if (idx == count || entries[idx]->file_seq != src_entry->file_seq) { - /* new entry */ - dest_entry = p_new(uidlist->entry_pool, - struct dbox_uidlist_entry, 1); - *dest_entry = *src_entry; - i_assert(idx < count || idx == 0 || - src_entry->file_seq > entries[idx-1]->file_seq); - i_assert(idx == count || - src_entry->file_seq < entries[idx]->file_seq); - array_insert(&uidlist->entries, idx, &dest_entry, 1); - - if (src_entry->file_seq > uidlist->last_file_seq) - uidlist->last_file_seq = src_entry->file_seq; - } else { - /* merge to existing entry. UIDs must be growing since only - new mails are appended */ - dest_entry = entries[idx]; - if (src_entry->create_time > dest_entry->create_time) - dest_entry->create_time = src_entry->create_time; - if (src_entry->file_size > dest_entry->file_size) - dest_entry->file_size = src_entry->file_size; - - range = array_get(&src_entry->uid_list, &count); - for (i = 0; i < count; i++) { - if (!uidlist_merge(&dest_entry->uid_list, &range[i])) { - mail_storage_set_critical( - &uidlist->mbox->storage->storage, - "%s: UIDs not ordered (file_seq=%u)", - uidlist->path, src_entry->file_seq); - return FALSE; - } - } - } - return TRUE; -} - -static bool dbox_uidlist_next(struct dbox_uidlist *uidlist, const char *line) -{ - struct dbox_uidlist_entry *entry; - struct seq_range range; - const char *error = NULL; - uint32_t digit; - int ret; - - /* <uid list> <file seq> [<last write timestamp> <file size>] */ - t_push(); - entry = t_new(struct dbox_uidlist_entry, 1); - p_array_init(&entry->uid_list, uidlist->entry_pool, 8); - - /* get uid list */ - range.seq1 = range.seq2 = 0; - for (digit = 0; *line != '\0'; line++) { - if (*line >= '0' && *line <= '9') - digit = digit * 10 + *line-'0'; - else { - if (range.seq1 == 0) { - if (digit <= range.seq2) { - /* broken */ - error = t_strdup_printf("UID %u <= %u", - digit, - range.seq2); - break; - } - range.seq1 = digit; - } - if (*line == ',' || *line == ' ') { - if (range.seq1 > digit) { - /* broken */ - error = t_strdup_printf("UID %u > %u", - range.seq1, - digit); - break; - } - range.seq2 = digit; - array_append(&entry->uid_list, &range, 1); - range.seq1 = 0; - - if (digit > uidlist->last_uid) { - /* last_uid isn't up to date */ - uidlist->last_uid = digit; - } - - if (*line == ' ') - break; - } - digit = 0; - } - } - - if (error == NULL) { - if (*line != ' ') { - error = *line == '\0' ? "File sequence missing" : - "Expecting space after UID list"; - } else if (array_count(&entry->uid_list) == 0) - error = "UID list missing"; - } - - if (error != NULL) { - mail_storage_set_critical(&uidlist->mbox->storage->storage, - "%s: Corrupted entry: %s", uidlist->path, error); - t_pop(); - return FALSE; - } - - /* get file seq */ - for (digit = 0, line++; *line >= '0' && *line <= '9'; line++) - digit = digit * 10 + *line-'0'; - entry->file_seq = digit; - - /* get create timestamp */ - line++; - for (; *line >= '0' && *line <= '9'; line++) - entry->create_time = entry->create_time * 10 + *line-'0'; - - if (*line != ' ') { - mail_storage_set_critical(&uidlist->mbox->storage->storage, - "%s: Corrupted entry: Expecting space after timestamp", - uidlist->path); - - t_pop(); - return FALSE; - } - /* get file size */ - for (; *line >= '0' && *line <= '9'; line++) - entry->file_size = entry->file_size * 10 + *line-'0'; - - ret = dbox_uidlist_add_entry(uidlist, entry); - t_pop(); - return ret; -} - -static int dbox_uidlist_read(struct dbox_uidlist *uidlist) -{ - struct mail_storage *storage = &uidlist->mbox->storage->storage; - const char *line; - unsigned int uid_validity, last_uid, last_file_seq; - struct istream *input; - struct stat st; - int ret; - - if (uidlist->lock_count > 0 && uidlist->need_full_rewrite) { - i_assert(uidlist->mbox->ibox.keep_locked); - return 1; - } - - if (uidlist->fd != -1) { - if (stat(uidlist->path, &st) < 0) { - if (errno != ENOENT) { - mail_storage_set_critical(storage, - "stat(%s) failed: %m", uidlist->path); - return -1; - } - return 0; - } - - if (st.st_ino == uidlist->ino && - st.st_mtime == uidlist->mtime) { - /* unchanged */ - return 1; - } - } - - uidlist->mtime = -1; - if (uidlist->fd != -1) { - if (close(uidlist->fd) < 0) - i_error("close(%s) failed: %m", uidlist->path); - } - - uidlist->fd = open(uidlist->path, O_RDWR); - if (uidlist->fd == -1) { - if (errno == ENOENT) - return 0; - - mail_storage_set_critical(storage, - "open(%s) failed: %m", uidlist->path); - return -1; - } - - if (fstat(uidlist->fd, &st) < 0) { - mail_storage_set_critical(storage, - "fstat(%s) failed: %m", uidlist->path); - return -1; - } - uidlist->ino = st.st_ino; - uidlist->mtime = st.st_mtime; - - input = i_stream_create_fd(uidlist->fd, 65536, FALSE); - - /* read header: <version> <uidvalidity> <next-uid>. - Note that <next-uid> may be updated by UID lines, so it can't be - used directly. */ - line = i_stream_read_next_line(input); - if (line == NULL || sscanf(line, "%u %u %u %u", &uidlist->version, - &uid_validity, &last_uid, - &last_file_seq) != 4 || - uidlist->version != DBOX_UIDLIST_VERSION) { - mail_storage_set_critical(storage, - "Corrupted header in file %s (version = %u)", - uidlist->path, uidlist->version); - ret = 0; - } else { - uint32_t old_last_uid, old_last_file_seq; - - old_last_uid = uidlist->uid_validity == uid_validity ? - uidlist->last_uid : 0; - old_last_file_seq = uidlist->uid_validity == uid_validity ? - uidlist->last_file_seq : 0; - - uidlist->uid_validity = uid_validity; - uidlist->last_uid = last_uid; - uidlist->last_file_seq = last_file_seq; - p_clear(uidlist->entry_pool); - array_clear(&uidlist->entries); - - ret = 1; - while ((line = i_stream_read_next_line(input)) != NULL) { - if (!dbox_uidlist_next(uidlist, line)) { - ret = 0; - break; - } - } - - if (ret > 0 && uidlist->last_uid < old_last_uid) { - mail_storage_set_critical(storage, - "%s: last_uid was lowered (%u -> %u)", - uidlist->path, old_last_uid, uidlist->last_uid); - ret = 0; - } - if (ret > 0 && uidlist->last_file_seq < old_last_file_seq) { - mail_storage_set_critical(storage, - "%s: last_file_seq was lowered (%u -> %u)", - uidlist->path, old_last_file_seq, - uidlist->last_file_seq); - ret = 0; - } - - if (uidlist->file_seq_highwater < uidlist->last_file_seq) - uidlist->file_seq_highwater = uidlist->last_file_seq; - } - - if (ret == 0) { - /* broken file */ - (void)unlink(uidlist->path); - - if (close(uidlist->fd) < 0) - i_error("close(%s) failed: %m", uidlist->path); - uidlist->fd = -1; - uidlist->mtime = -1; - } - - i_stream_destroy(&input); - return ret; -} - -int dbox_uidlist_lock(struct dbox_uidlist *uidlist) -{ - struct dbox_mailbox *mbox = uidlist->mbox; - - if (uidlist->lock_count == 0) - i_assert(uidlist->lock_fd == -1); - else { - i_assert(mbox->ibox.keep_locked); - uidlist->lock_count++; - return 0; - } - - uidlist->lock_fd = - file_dotlock_open(&mbox->storage->uidlist_dotlock_set, - uidlist->path, 0, &uidlist->dotlock); - if (uidlist->lock_fd == -1) { - if (errno == EAGAIN) { - mail_storage_set_error(&mbox->storage->storage, - MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT); - return 0; - } - mail_storage_set_critical(&mbox->storage->storage, - "file_dotlock_open(%s) failed: %m", uidlist->path); - return -1; - } - - uidlist->lock_count++; - return 0; -} - -int dbox_uidlist_lock_touch(struct dbox_uidlist *uidlist) -{ - return file_dotlock_touch(uidlist->dotlock); -} - -void dbox_uidlist_unlock(struct dbox_uidlist *uidlist) -{ - i_assert(uidlist->lock_fd != -1); - - if (--uidlist->lock_count > 0) { - i_assert(uidlist->mbox->ibox.keep_locked); - return; - } - - if (uidlist->need_full_rewrite) { - i_assert(uidlist->mbox->ibox.keep_locked); - - (void)dbox_uidlist_full_rewrite(uidlist); - if (uidlist->lock_fd == -1) - return; - } - - (void)file_dotlock_delete(&uidlist->dotlock); - uidlist->lock_fd = -1; -} - -static struct dbox_uidlist_entry * -dbox_uidlist_entry_lookup_int(struct dbox_uidlist *uidlist, uint32_t file_seq, - unsigned int *idx_r) -{ - struct dbox_uidlist_entry *const *entries, **entry; - unsigned int count; - - entries = array_get(&uidlist->entries, &count); - entry = bsearch(&file_seq, entries, count, sizeof(*entries), - dbox_uidlist_entry_cmp); - if (entry == NULL) - return NULL; - - *idx_r = entry - entries; - return *entry; -} - -struct dbox_uidlist_entry * -dbox_uidlist_entry_lookup(struct dbox_uidlist *uidlist, uint32_t file_seq) -{ - unsigned int idx; - - return dbox_uidlist_entry_lookup_int(uidlist, file_seq, &idx); -} - -static time_t get_min_timestamp(unsigned int days) -{ - struct tm tm; - time_t stamp; - - if (days == 0) - return 0; - - /* get beginning of today */ - tm = *localtime(&ioloop_time); - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - stamp = mktime(&tm); - if (stamp == (time_t)-1) - i_panic("mktime(today) failed"); - - return stamp - (3600*24 * (days-1)); -} - -struct dbox_uidlist_append_ctx * -dbox_uidlist_append_init(struct dbox_uidlist *uidlist) -{ - struct dbox_uidlist_append_ctx *ctx; - pool_t pool; - - i_assert(!uidlist->appending); - - pool = pool_alloconly_create("dbox uidlist append context", 4096); - ctx = p_new(pool, struct dbox_uidlist_append_ctx, 1); - ctx->pool = pool; - ctx->uidlist = uidlist; - ctx->min_usable_timestamp = - get_min_timestamp(uidlist->mbox->rotate_days); - p_array_init(&ctx->files, pool, 16); - return ctx; -} - -static int dbox_uidlist_full_rewrite(struct dbox_uidlist *uidlist) -{ - struct dbox_uidlist_entry *const *entries; - struct ostream *output; - struct stat st, st2; - const char *lock_path; - const struct seq_range *range; - string_t *str; - unsigned int i, count, ui, range_count; - int ret = 0; - - i_assert(uidlist->lock_fd != -1); - - if (uidlist->lock_count > 1) { - i_assert(uidlist->mbox->ibox.keep_locked); - uidlist->need_full_rewrite = TRUE; - return 0; - } - - output = o_stream_create_fd_file(uidlist->lock_fd, 0, FALSE); - - t_push(); - str = t_str_new(256); - - /* header: <version> <uidvalidity> <next-uid> <last-file-seq>. */ - str_printfa(str, "%u %u %u %u\n", DBOX_UIDLIST_VERSION, - uidlist->uid_validity, uidlist->last_uid, - uidlist->last_file_seq); - o_stream_send(output, str_data(str), str_len(str)); - - entries = array_get(&uidlist->entries, &count); - for (i = 0; i < count; i++) { - i_assert(entries[i]->file_seq <= uidlist->last_file_seq); - str_truncate(str, 0); - - i_assert(i == 0 || - entries[i]->file_seq > entries[i-1]->file_seq); - - /* <uid list> <file seq> [<last write timestamp> <file size>] */ - range = array_get(&entries[i]->uid_list, &range_count); - i_assert(range_count != 0); - for (ui = 0; ui < range_count; ui++) { - i_assert(range[ui].seq2 <= uidlist->last_uid); - if (str_len(str) > 0) - str_append_c(str, ','); - if (range[ui].seq1 == range[ui].seq2) - str_printfa(str, "%u", range[ui].seq1); - else { - str_printfa(str, "%u-%u", - range[ui].seq1, range[ui].seq2); - } - } - str_printfa(str, " %u %lu %"PRIuUOFF_T, entries[i]->file_seq, - (unsigned long)entries[i]->create_time, - entries[i]->file_size); - str_append_c(str, '\n'); - if (o_stream_send(output, str_data(str), str_len(str)) < 0) - break; - } - t_pop(); - - if (output->stream_errno != 0) { - mail_storage_set_critical(&uidlist->mbox->storage->storage, - "write(%s) failed: %m", uidlist->path); - ret = -1; - } - o_stream_destroy(&output); - - if (ret < 0) - return -1; - - /* grow mtime by one if needed to make sure the last write is noticed */ - lock_path = file_dotlock_get_lock_path(uidlist->dotlock); - if (stat(uidlist->path, &st) < 0) { - if (errno != ENOENT) { - mail_storage_set_critical( - &uidlist->mbox->storage->storage, - "stat(%s) failed: %m", uidlist->path); - return -1; - } - st.st_mtime = 0; - } - if (fstat(uidlist->lock_fd, &st2) < 0) { - mail_storage_set_critical(&uidlist->mbox->storage->storage, - "fstat(%s) failed: %m", lock_path); - return -1; - } - - if (st2.st_mtime <= st.st_mtime) { - struct utimbuf ut; - - st2.st_mtime = st.st_mtime + 1; - ut.actime = ioloop_time; - ut.modtime = st2.st_mtime; - - if (utime(lock_path, &ut) < 0) { - mail_storage_set_critical( - &uidlist->mbox->storage->storage, - "utime(%s) failed: %m", lock_path); - return -1; - } - } - - uidlist->ino = st2.st_ino; - uidlist->mtime = st2.st_mtime; - - /* now, finish the uidlist update by renaming the lock file to - uidlist */ - uidlist->lock_fd = -1; - uidlist->lock_count--; - if (file_dotlock_replace(&uidlist->dotlock, 0) < 0) - return -1; - - uidlist->need_full_rewrite = FALSE; - return 0; -} - -static void dbox_uidlist_build_update_line(struct dbox_save_file *save_file, - string_t *str, uint32_t uid_start) -{ - const unsigned int *seqs; - unsigned int seq, seq_count, start; - - str_truncate(str, 0); - - /* build uidlist string */ - seqs = array_get(&save_file->seqs, &seq_count); - start = 0; - for (seq = 0; seq < seq_count; seq++) { - if (seq != seq_count-1) { - if (seq == 0 || seqs[seq-1]+1 == seqs[seq]) - continue; - } - - if (str_len(str) > 0) - str_append_c(str, ','); - str_printfa(str, "%u", uid_start + seqs[start] - 1); - if (seq != start) - str_printfa(str, "-%u", uid_start + seqs[seq] - 1); - start = seq + 1; - } - str_printfa(str, " %u", save_file->file->file_seq); - - /* add creation time and file size */ - str_printfa(str, " %s %s", dec2str(save_file->file->create_time), - dec2str(save_file->file->append_offset)); - str_append_c(str, '\n'); -} - -static void dbox_uidlist_update_changes(struct dbox_uidlist_append_ctx *ctx) -{ - struct dbox_save_file *const *files; - string_t *str; - unsigned int i, count; - uint32_t uid_start; - - uid_start = ctx->uidlist->last_uid + 1; - - t_push(); - str = t_str_new(256); - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - dbox_uidlist_build_update_line(files[i], str, uid_start); - if (!dbox_uidlist_next(ctx->uidlist, str_c(str))) - i_panic("dbox_uidlist_next() internal update failed"); - } - t_pop(); -} - -static int dbox_uidlist_append_changes(struct dbox_uidlist_append_ctx *ctx) -{ - struct dbox_save_file *const *files; - struct ostream *output; - struct utimbuf ut; - struct stat st; - unsigned int i, count; - uint32_t uid_start; - string_t *str; - int ret = 1; - - i_assert(ctx->uidlist->fd != -1); - i_assert(ctx->uidlist->lock_fd != -1); - - if (fstat(ctx->uidlist->fd, &st) < 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "fstat(%s) failed: %m", ctx->uidlist->path); - return -1; - } - if (st.st_mtime >= ioloop_time-DBOX_SYNC_SECS) { - /* we can't update this file without temporarily moving mtime - backwards */ - return 0; - } - - if (lseek(ctx->uidlist->fd, 0, SEEK_END) < 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "lseek(%s) failed: %m", ctx->uidlist->path); - return -1; - } - output = o_stream_create_fd_file(ctx->uidlist->fd, 0, FALSE); - - uid_start = ctx->uidlist->last_uid + 1; - - /* simply append the change-lines to the index file. if someone's - reading the file at the same time, it doesn't matter. the entries - are complete only after the LF has been written. */ - t_push(); - str = t_str_new(256); - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - dbox_uidlist_build_update_line(files[i], str, uid_start); - if (!dbox_uidlist_next(ctx->uidlist, str_c(str))) - i_panic("dbox_uidlist_next() internal update failed"); - o_stream_send(output, str_data(str), str_len(str)); - } - t_pop(); - - if (output->stream_errno != 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "write(%s) failed: %m", ctx->uidlist->path); - ret = -1; - } - o_stream_destroy(&output); - - if (ret < 0) - return -1; - - /* grow mtime by one to make sure the last write is noticed */ - if (fstat(ctx->uidlist->fd, &st) < 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "fstat(%s) failed: %m", ctx->uidlist->path); - return -1; - } - - ut.actime = ioloop_time; - ut.modtime = st.st_mtime + 1; - if (utime(ctx->uidlist->path, &ut) < 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "utime(%s) failed: %m", ctx->uidlist->path); - return -1; - } - - ctx->uidlist->ino = st.st_ino; - ctx->uidlist->mtime = ut.modtime; - return 1; -} - -static int -dbox_uidlist_write_append_offsets(struct dbox_uidlist_append_ctx *ctx) -{ - struct mail_storage *storage = &ctx->uidlist->mbox->storage->storage; - struct dbox_save_file *const *files; - struct dbox_file_header hdr; - unsigned int i, count; - int ret = 0; - - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - if (!ctx->uidlist->mbox->ibox.fsync_disable) { - if (fsync(files[i]->file->fd) < 0) { - mail_storage_set_critical(storage, - "fsync(%s) failed: %m", - files[i]->file->path); - ret = -1; - continue; - } - } - - DEC2HEX(hdr.append_offset_hex, - files[i]->file->output->offset); - - if (pwrite_full(files[i]->file->fd, hdr.append_offset_hex, - sizeof(hdr.append_offset_hex), - offsetof(struct dbox_file_header, - append_offset_hex)) < 0) { - mail_storage_set_critical(storage, - "pwrite_full(%s) failed: %m", - files[i]->file->path); - ret = -1; - } - } - return ret; -} - -int dbox_uidlist_append_commit(struct dbox_uidlist_append_ctx *ctx, - time_t *mtime_r) -{ - int ret = 0; - - if (ctx->mail_count == 0) { - /* nothing actually appended */ - dbox_uidlist_append_rollback(ctx); - *mtime_r = ctx->uidlist->mtime; - return 0; - } - - i_assert(ctx->locked); - - if (dbox_uidlist_write_append_offsets(ctx) < 0) - ret = -1; - else { - if (!ctx->uidlist->need_full_rewrite) { - ret = dbox_uidlist_append_changes(ctx); - if (ret < 0) - return -1; - if (ret == 0) - ctx->uidlist->need_full_rewrite = TRUE; - } - - if (ctx->uidlist->need_full_rewrite) { - dbox_uidlist_update_changes(ctx); - ret = dbox_uidlist_full_rewrite(ctx->uidlist); - if (ctx->uidlist->dotlock == NULL) - ctx->locked = FALSE; - } - } - - *mtime_r = ctx->uidlist->mtime; - dbox_uidlist_append_rollback(ctx); - return ret; -} - -void dbox_uidlist_append_rollback(struct dbox_uidlist_append_ctx *ctx) -{ - struct dbox_save_file *const *files; - unsigned int i, count; - - /* unlock files */ - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - file_dotlock_delete(&files[i]->dotlock); - dbox_file_close(files[i]->file); - } - - if (ctx->locked) - dbox_uidlist_unlock(ctx->uidlist); - ctx->uidlist->appending = FALSE; - pool_unref(ctx->pool); -} - -static int dbox_reopen_file(struct dbox_uidlist_append_ctx *ctx, - struct dbox_save_file *save_file) -{ - struct dbox_file *file = save_file->file; - struct stat st; - - if (file->fd != -1) - return 0; - - /* open the file and make sure it's the same as expected, - since we have it locked */ - file->fd = open(file->path, O_RDWR); - if (file->fd == -1) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "open(%s) failed: %m", file->path); - return -1; - } - - if (fstat(file->fd, &st) < 0) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "fstat(%s) failed: %m", file->path); - return -1; - } - - if (st.st_ino != save_file->ino || - !CMP_DEV_T(st.st_dev, save_file->dev)) { - mail_storage_set_critical(&ctx->uidlist->mbox->storage->storage, - "Appended file changed unexpectedly: %s", file->path); - return -1; - } - return 0; -} - -static int dbox_uidlist_files_lookup(struct dbox_uidlist_append_ctx *ctx, - uint32_t file_seq) -{ - struct dbox_save_file *const *files; - unsigned int i, count; - - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - if (files[i]->file->file_seq == file_seq) - return TRUE; - } - return FALSE; -} - -#define DBOX_CAN_APPEND(ctx, create_time, file_size) \ - (((create_time) >= (ctx)->min_usable_timestamp && \ - (file_size) < (ctx)->uidlist->mbox->rotate_size) || \ - (file_size) < (ctx)->uidlist->mbox->rotate_min_size) - -static int -dbox_file_append(struct dbox_uidlist_append_ctx *ctx, - const char *path, struct dbox_uidlist_entry *entry, - struct stat *st, struct dbox_file **file_r, bool existing) -{ - struct dbox_mailbox *mbox = ctx->uidlist->mbox; - struct dbox_file *file; - int fd; - - *file_r = NULL; - - fd = open(path, O_RDWR | (existing ? 0 : O_CREAT), 0600); - if (fd == -1) { - if (errno == ENOENT && existing) { - /* the file was unlinked just now, update its size - so that we don't get back here. */ - entry->file_size = (uoff_t)-1; - return 0; - } - mail_storage_set_critical(&mbox->storage->storage, - "open(%s) failed: %m", path); - return -1; - } - - if (fstat(fd, st) < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "fstat(%s) failed: %m", path); - (void)close(fd); - return -1; - } - - file = i_new(struct dbox_file, 1); - file->path = i_strdup(path); - file->fd = fd; - - file->input = i_stream_create_fd(file->fd, 65536, FALSE); - file->output = o_stream_create_fd_file(file->fd, 0, FALSE); - if ((uoff_t)st->st_size < sizeof(struct dbox_file_header)) { - if (dbox_file_write_header(mbox, file) < 0) { - dbox_file_close(file); - return -1; - } - } else { - if (dbox_file_read_header(mbox, file) < 0) { - dbox_file_close(file); - return -1; - } - - if (entry != NULL) { - entry->create_time = file->create_time; - entry->file_size = file->append_offset; - } - - if (!DBOX_CAN_APPEND(ctx, file->create_time, - file->append_offset)) { - dbox_file_close(file); - return 0; - } - } - - *file_r = file; - return 1; -} - -static int dbox_file_seq_was_used(struct dbox_mailbox *mbox, const char *path, - uint32_t file_seq) -{ - struct stat st; - - if (stat(path, &st) == 0) - return 0; - if (errno != ENOENT) { - mail_storage_set_critical(&mbox->storage->storage, - "stat(%s) failed: %m", path); - return -1; - } - - /* doesn't exist, make sure that index's last file seq is lower */ - if (dbox_uidlist_read(mbox->uidlist) < 0) - return -1; - return file_seq <= mbox->uidlist->last_file_seq ? 1 : 0; -} - -static int -dbox_file_append_lock(struct dbox_uidlist_append_ctx *ctx, string_t *path, - uoff_t mail_size, uint32_t *file_seq_r, - struct dbox_uidlist_entry **entry_r, - struct dotlock **dotlock_r, bool *existing_r) -{ - struct dbox_mailbox *mbox = ctx->uidlist->mbox; - struct dbox_uidlist_entry *const *entries; - unsigned int i, count; - uint32_t file_seq; - int ret; - - entries = array_get(&ctx->uidlist->entries, &count); - for (i = 0;; i++) { - file_seq = 0; - *existing_r = FALSE; - for (; i < count; i++) { - if (DBOX_CAN_APPEND(ctx, entries[i]->create_time, - entries[i]->file_size + - mail_size) && - !dbox_uidlist_files_lookup(ctx, - entries[i]->file_seq)) { - *existing_r = TRUE; - file_seq = entries[i]->file_seq; - break; - } - } - - if (file_seq == 0) { - /* create new file */ - file_seq = dbox_uidlist_get_new_file_seq(ctx->uidlist); - } - - /* try locking the file. */ - str_truncate(path, 0); - str_printfa(path, "%s/"DBOX_MAIL_FILE_FORMAT, - mbox->path, file_seq); - ret = file_dotlock_create(&mbox->storage->file_dotlock_set, - str_c(path), - DOTLOCK_CREATE_FLAG_NONBLOCK, - dotlock_r); - if (ret > 0) { - /* success. but since we don't have uidlist locked - here, it's possible that the file was just deleted - by someone else. in that case we really don't want - to create the file back and cause problems. */ - ret = dbox_file_seq_was_used(mbox, str_c(path), - file_seq); - - if (i < count) { - /* dbox file was re-read, find the entry - again */ - entries = array_get(&ctx->uidlist->entries, - &count); - for (i = 0; i < count; i++) { - if (entries[i]->file_seq == file_seq) - break; - } - } - if (ret == 0) { - i_assert(i < count || !*existing_r); - break; - } - - /* error / it was used, continue with another - file sequence */ - file_dotlock_delete(dotlock_r); - - if (ret < 0) - return -1; - } else if (ret < 0) { - mail_storage_set_critical(&mbox->storage->storage, - "file_dotlock_create(%s) failed: %m", - str_c(path)); - return -1; - } - - /* lock already exists, try next file */ - } - - *file_seq_r = file_seq; - *entry_r = i < count ? entries[i] : NULL; - return 0; -} - -int dbox_uidlist_append_locked(struct dbox_uidlist_append_ctx *ctx, - struct dbox_file **file_r, uoff_t mail_size) -{ - struct dbox_save_file *const *files, *save_file; - struct dbox_uidlist_entry *entry; - struct dbox_file *file = NULL; - struct dotlock *dotlock = NULL; - struct ostream *output; - string_t *path; - unsigned int i, count; - struct stat st; - uint32_t file_seq; - bool existing; - int ret; - - /* check first from already opened files */ - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - if (DBOX_CAN_APPEND(ctx, files[i]->file->create_time, - files[i]->file->append_offset + - mail_size)) { - if (dbox_reopen_file(ctx, files[i]) < 0) - return -1; - - *file_r = file = files[i]->file; - o_stream_seek(file->output, file->append_offset); - return 0; - } - } - - /* check from other existing files. use uidlist's file_size field. - it's not completely trustworthy though. */ - path = str_new(ctx->pool, 64); - do { - if (dotlock != NULL) - file_dotlock_delete(&dotlock); - if (dbox_file_append_lock(ctx, path, mail_size, &file_seq, - &entry, &dotlock, &existing) < 0) - return -1; - } while ((ret = dbox_file_append(ctx, str_c(path), entry, - &st, &file, existing)) == 0); - - if (ret < 0) { - i_assert(file == NULL); - file_dotlock_delete(&dotlock); - return -1; - } - file->file_seq = file_seq; - - /* we'll always use CRLF linefeeds for mails (but not the header, - so don't do this before dbox_file_write_header()) */ - output = o_stream_create_crlf(file->output); - o_stream_unref(&file->output); - file->output = output; - - o_stream_seek(file->output, file->append_offset); - - save_file = p_new(ctx->pool, struct dbox_save_file, 1); - save_file->file = file; - save_file->dotlock = dotlock; - save_file->dev = st.st_dev; - save_file->ino = st.st_ino; - p_array_init(&save_file->seqs, ctx->pool, 8); - - array_append(&ctx->files, &save_file, 1); - *file_r = file; - return 0; -} - -void dbox_uidlist_append_finish_mail(struct dbox_uidlist_append_ctx *ctx, - struct dbox_file *file) -{ - struct dbox_save_file *const *files, *save_file = NULL; - unsigned int i, count; - - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - if (files[i]->file == file) { - save_file = files[i]; - break; - } - } - i_assert(save_file != NULL); - - ctx->mail_count++; - array_append(&save_file->seqs, &ctx->mail_count, 1); - - file->append_offset = file->output->offset; -} - -struct dbox_file * -dbox_uidlist_append_lookup_file(struct dbox_uidlist_append_ctx *ctx, - uint32_t file_seq) -{ - struct dbox_save_file *const *files; - unsigned int i, count; - - files = array_get(&ctx->files, &count); - for (i = 0; i < count; i++) { - if (files[i]->file->file_seq == file_seq) - return files[i]->file; - } - - i_unreached(); - return NULL; -} - -uint32_t dbox_uidlist_get_new_file_seq(struct dbox_uidlist *uidlist) -{ - /* Note that unless uidlist is locked, it's not guaranteed that this - actually returns a new unused file sequence. */ - i_assert(uidlist->file_seq_highwater >= uidlist->last_file_seq); - return ++uidlist->file_seq_highwater; -} - -int dbox_uidlist_append_get_first_uid(struct dbox_uidlist_append_ctx *ctx, - uint32_t *uid_r, time_t *mtime_r) -{ - int ret; - - /* from now on we'll need to keep uidlist locked until it's - committed or rollbacked */ - if (!ctx->locked) { - if (dbox_uidlist_lock(ctx->uidlist) < 0) - return -1; - ctx->locked = TRUE; - - /* update uidlist to make sure we have the latest state */ - if ((ret = dbox_uidlist_read(ctx->uidlist)) < 0) - return -1; - if (ret == 0) { - /* file is deleted */ - ctx->uidlist->need_full_rewrite = TRUE; - } - } - - *mtime_r = ctx->uidlist->mtime; - *uid_r = ctx->uidlist->last_uid + 1; - return 0; -} - -int dbox_uidlist_sync_init(struct dbox_uidlist *uidlist, - struct dbox_uidlist_sync_ctx **ctx_r, - time_t *mtime_r) -{ - int ret; - - *mtime_r = -1; - if (dbox_uidlist_lock(uidlist) < 0) - return -1; - - if ((ret = dbox_uidlist_read(uidlist)) < 0) { - dbox_uidlist_unlock(uidlist); - return -1; - } - - if (ret == 0) { - /* file is deleted */ - uidlist->need_full_rewrite = TRUE; - } else { - *mtime_r = uidlist->mtime; - } - - *ctx_r = i_new(struct dbox_uidlist_sync_ctx, 1); - (*ctx_r)->uidlist = uidlist; - return 0; -} - -int dbox_uidlist_sync_commit(struct dbox_uidlist_sync_ctx *ctx, time_t *mtime_r) -{ - int ret = 0; - - if (ctx->modified) { - /* this call may or may not release the dotlock.. */ - ret = dbox_uidlist_full_rewrite(ctx->uidlist); - } - - *mtime_r = ctx->uidlist->mtime; - - if (ctx->uidlist->dotlock != NULL) - dbox_uidlist_unlock(ctx->uidlist); - i_free(ctx); - return ret; -} - -void dbox_uidlist_sync_rollback(struct dbox_uidlist_sync_ctx *ctx) -{ - array_clear(&ctx->uidlist->entries); - ctx->uidlist->ino = 0; - ctx->uidlist->mtime = 0; - ctx->uidlist->need_full_rewrite = FALSE; - - dbox_uidlist_unlock(ctx->uidlist); - i_free(ctx); -} - -void dbox_uidlist_sync_from_scratch(struct dbox_uidlist_sync_ctx *ctx) -{ - array_clear(&ctx->uidlist->entries); - ctx->uidlist->ino = 0; - ctx->uidlist->mtime = 0; - - ctx->modified = TRUE; - ctx->uidlist->need_full_rewrite = TRUE; -} - -void dbox_uidlist_sync_set_modified(struct dbox_uidlist_sync_ctx *ctx) -{ - ctx->modified = TRUE; -} - -void dbox_uidlist_sync_append(struct dbox_uidlist_sync_ctx *ctx, - const struct dbox_uidlist_entry *entry) -{ - struct dbox_uidlist_entry *const *entries; - struct dbox_uidlist_entry *new_entry; - unsigned int count; - - i_assert(array_count(&entry->uid_list) > 0); - - new_entry = p_new(ctx->uidlist->entry_pool, - struct dbox_uidlist_entry, 1); - *new_entry = *entry; - - p_array_init(&new_entry->uid_list, ctx->uidlist->entry_pool, - array_count(&entry->uid_list) + 1); - array_append_array(&new_entry->uid_list, &entry->uid_list); - - if (new_entry->file_seq > ctx->uidlist->last_file_seq) - ctx->uidlist->last_file_seq = new_entry->file_seq; - if (new_entry->file_seq > ctx->uidlist->file_seq_highwater) - ctx->uidlist->file_seq_highwater = new_entry->file_seq; - dbox_uidlist_update_last_uid(ctx->uidlist, new_entry); - - entries = array_get(&ctx->uidlist->entries, &count); - if (count == 0 || entries[count-1]->file_seq < new_entry->file_seq) - array_append(&ctx->uidlist->entries, &new_entry, 1); - else { - unsigned int idx; - - bsearch_insert_pos(&new_entry->file_seq, entries, - count, sizeof(*entries), - dbox_uidlist_entry_cmp, - &idx); - - i_assert(idx < count || idx == 0 || - new_entry->file_seq > entries[idx-1]->file_seq); - i_assert(idx == count || - new_entry->file_seq < entries[idx]->file_seq); - array_insert(&ctx->uidlist->entries, idx, &new_entry, 1); - } -} - -void dbox_uidlist_sync_unlink(struct dbox_uidlist_sync_ctx *ctx, - uint32_t file_seq) -{ - struct dbox_uidlist_entry *entry; - unsigned int idx; - - entry = dbox_uidlist_entry_lookup_int(ctx->uidlist, file_seq, &idx); - i_assert(entry != NULL); - - array_delete(&ctx->uidlist->entries, idx, 1); - - dbox_uidlist_sync_set_modified(ctx); -} - -uint32_t dbox_uidlist_sync_get_uid_validity(struct dbox_uidlist_sync_ctx *ctx) -{ - if (ctx->uidlist->uid_validity == 0) { - ctx->uidlist->uid_validity = ioloop_time; - ctx->modified = TRUE; - } - - return ctx->uidlist->uid_validity; -} - -uint32_t dbox_uidlist_sync_get_next_uid(struct dbox_uidlist_sync_ctx *ctx) -{ - return ctx->uidlist->last_uid + 1; -} - -int dbox_uidlist_get_mtime(struct dbox_uidlist *uidlist, time_t *mtime_r) -{ - struct stat st; - - if (stat(uidlist->path, &st) < 0) { - if (errno != ENOENT) { - mail_storage_set_critical( - &uidlist->mbox->storage->storage, - "stat(%s) failed: %m", uidlist->path); - return -1; - } - - *mtime_r = 0; - } else { - *mtime_r = st.st_mtime; - } - return 0; -}
--- a/src/lib-storage/index/dbox/dbox-uidlist.h Sat Sep 01 01:36:10 2007 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -#ifndef __DBOX_UIDLIST_H -#define __DBOX_UIDLIST_H - -#include "seq-range-array.h" - -struct dbox_file; -struct dbox_mailbox; -struct dbox_uidlist_sync_ctx; - -struct dbox_uidlist_entry { - ARRAY_TYPE(seq_range) uid_list; - - uint32_t file_seq; - /* file creation timestamp. used for rotation checks. */ - time_t create_time; - /* the used file size. the actual file size may be larger. */ - uoff_t file_size; -}; - -struct dbox_uidlist *dbox_uidlist_init(struct dbox_mailbox *mbox); -void dbox_uidlist_deinit(struct dbox_uidlist *uidlist); - -int dbox_uidlist_lock(struct dbox_uidlist *uidlist); -int dbox_uidlist_lock_touch(struct dbox_uidlist *uidlist); -void dbox_uidlist_unlock(struct dbox_uidlist *uidlist); - -struct dbox_uidlist_entry * -dbox_uidlist_entry_lookup(struct dbox_uidlist *uidlist, uint32_t file_seq); - -struct dbox_uidlist_append_ctx * -dbox_uidlist_append_init(struct dbox_uidlist *uidlist); -int dbox_uidlist_append_commit(struct dbox_uidlist_append_ctx *ctx, - time_t *mtime_r); -void dbox_uidlist_append_rollback(struct dbox_uidlist_append_ctx *ctx); - -/* Open/create a file for appending a new message and lock it. - Returns -1 if failed, 0 if ok. If new file is created, the file's header is - already appended. */ -int dbox_uidlist_append_locked(struct dbox_uidlist_append_ctx *ctx, - struct dbox_file **file_r, uoff_t mail_size); -void dbox_uidlist_append_finish_mail(struct dbox_uidlist_append_ctx *ctx, - struct dbox_file *file); - -struct dbox_file * -dbox_uidlist_append_lookup_file(struct dbox_uidlist_append_ctx *ctx, - uint32_t file_seq); - -uint32_t dbox_uidlist_get_new_file_seq(struct dbox_uidlist *uidlist); -int dbox_uidlist_append_get_first_uid(struct dbox_uidlist_append_ctx *ctx, - uint32_t *uid_r, time_t *mtime_r); - -int dbox_uidlist_sync_init(struct dbox_uidlist *uidlist, - struct dbox_uidlist_sync_ctx **ctx_r, - time_t *mtime_r); -int dbox_uidlist_sync_commit(struct dbox_uidlist_sync_ctx *ctx, - time_t *mtime_r); -void dbox_uidlist_sync_rollback(struct dbox_uidlist_sync_ctx *ctx); - -void dbox_uidlist_sync_from_scratch(struct dbox_uidlist_sync_ctx *ctx); -void dbox_uidlist_sync_set_modified(struct dbox_uidlist_sync_ctx *ctx); - -void dbox_uidlist_sync_append(struct dbox_uidlist_sync_ctx *ctx, - const struct dbox_uidlist_entry *entry); -void dbox_uidlist_sync_unlink(struct dbox_uidlist_sync_ctx *ctx, - uint32_t file_seq); - -uint32_t dbox_uidlist_sync_get_uid_validity(struct dbox_uidlist_sync_ctx *ctx); -uint32_t dbox_uidlist_sync_get_next_uid(struct dbox_uidlist_sync_ctx *ctx); - -int dbox_uidlist_get_mtime(struct dbox_uidlist *uidlist, time_t *mtime_r); - -#endif