Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-index/mail-index-write.c @ 8999:afc1b0ef120d HEAD
When :MAILBOXDIR= was empty, we might have appended extra '/' to it, which caused problems.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 30 Apr 2009 20:00:09 -0400 |
parents | b9faf4db2a9f |
children | 00cd9aacd03c |
line wrap: on
line source
/* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "read-full.h" #include "write-full.h" #include "ostream.h" #include "mail-index-private.h" #include "mail-transaction-log-private.h" #include <stdio.h> #define MAIL_INDEX_MIN_UPDATE_SIZE 1024 /* if we're updating >= count-n messages, recreate the index */ #define MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT 10 static int mail_index_recreate(struct mail_index *index) { struct mail_index_map *map = index->map; struct ostream *output; unsigned int base_size; const char *path; int ret = 0, fd; i_assert(!MAIL_INDEX_IS_IN_MEMORY(index)); i_assert(map->hdr.indexid == index->indexid); fd = mail_index_create_tmp_file(index, &path); if (fd == -1) return -1; output = o_stream_create_fd_file(fd, 0, FALSE); o_stream_cork(output); base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr)); if (o_stream_send(output, &map->hdr, base_size) < 0 || o_stream_send(output, CONST_PTR_OFFSET(map->hdr_base, base_size), map->hdr.header_size - base_size) < 0 || o_stream_send(output, map->rec_map->records, map->rec_map->records_count * map->hdr.record_size) < 0 || o_stream_flush(output) < 0) { mail_index_file_set_syscall_error(index, path, "write()"); ret = -1; } o_stream_destroy(&output); if (ret == 0 && !index->fsync_disable && fdatasync(fd) < 0) { mail_index_file_set_syscall_error(index, path, "fdatasync()"); ret = -1; } if (close(fd) < 0) { mail_index_file_set_syscall_error(index, path, "close()"); ret = -1; } if (ret == 0 && rename(path, index->filepath) < 0) { mail_index_set_error(index, "rename(%s, %s) failed: %m", path, index->filepath); ret = -1; } if (ret < 0) { if (unlink(path) < 0) { mail_index_set_error(index, "unlink(%s) failed: %m", path); } } return ret; } static int mail_index_write_map_over(struct mail_index *index) { struct mail_index_map *map = index->map; struct mail_index_record_map *rec_map = map->rec_map; unsigned int base_size; if (MAIL_INDEX_IS_IN_MEMORY(index)) return 0; /* write extended headers */ if (map->write_ext_header) { base_size = map->hdr.base_header_size; if (pwrite_full(index->fd, CONST_PTR_OFFSET(map->hdr_base, base_size), map->hdr.header_size - base_size, base_size) < 0) return -1; } /* write records. */ if (rec_map->write_seq_first != 0) { size_t rec_offset = (rec_map->write_seq_first-1) * map->hdr.record_size; size_t recs_size = map->hdr.record_size * (rec_map->write_seq_last - rec_map->write_seq_first + 1); if (pwrite_full(index->fd, CONST_PTR_OFFSET(rec_map->records, rec_offset), recs_size, map->hdr.header_size + rec_offset) < 0) return -1; } /* Write base header last. If we happen to crash in above pwrites, it doesn't matter because we haven't yet written log file offsets, so all the changes will be re-applied and the header/data state will stay valid. The base header changes practically always, so map->write_base_header might not be TRUE here in all situations. It's used only to figure out if we want to write the map at all. */ base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr)); if (pwrite_full(index->fd, &map->hdr, base_size, 0) < 0) return -1; return 0; } static bool mail_index_has_last_changed(struct mail_index *index) { struct mail_index_header hdr; int ret; if ((ret = pread_full(index->fd, &hdr, sizeof(hdr), 0)) <= 0) { if (ret < 0 && errno != ESTALE) mail_index_set_syscall_error(index, "pread_full()"); return TRUE; } return hdr.log_file_head_offset != index->last_read_log_file_head_offset || hdr.log_file_seq != index->last_read_log_file_seq; } #define mail_index_map_has_changed(map) \ ((map)->write_base_header || (map)->write_ext_header || \ (map)->rec_map->write_seq_first != 0) void mail_index_write(struct mail_index *index, bool want_rotate) { struct mail_index_map *map = index->map; const struct mail_index_header *hdr = &map->hdr; struct stat st; unsigned int lock_id; int ret; if (!mail_index_map_has_changed(map)) return; if (hdr->base_header_size < sizeof(*hdr)) { /* header size growed. we can't update this file anymore. */ map->write_atomic = TRUE; } if (index->fd == -1 || index->last_read_log_file_seq == 0) { /* index file doesn't exist, it's corrupted or we haven't opened it for some reason */ map->write_atomic = TRUE; } if (index->last_read_stat.st_size < MAIL_INDEX_MIN_UPDATE_SIZE || (map->rec_map->write_seq_last - map->rec_map->write_seq_first + 1) + MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT >= map->rec_map->records_count) { /* the file is so small that we don't even bother trying to update it / changes are so large we might as well recreate */ map->write_atomic = TRUE; } if (!map->write_atomic) { /* we can't update the file unless it's the same as it was when we last read it. this is the first quick check before locking. */ if (stat(index->filepath, &st) < 0) { if (errno != ENOENT) mail_index_set_syscall_error(index, "stat()"); map->write_atomic = TRUE; } else if (st.st_ino != index->last_read_stat.st_ino || !CMP_ST_CTIME(&st, &index->last_read_stat)) map->write_atomic = TRUE; } if (!map->write_atomic) { if (mail_index_try_lock_exclusive(index, &lock_id) <= 0) { /* locking failed, recreate */ map->write_atomic = TRUE; } else if (mail_index_has_last_changed(index)) { /* changed, we can't trust updating it anymore */ map->write_atomic = TRUE; mail_index_unlock(index, &lock_id); } } if (map->write_atomic) { if (!MAIL_INDEX_IS_IN_MEMORY(index)) { if (mail_index_recreate(index) < 0) { mail_index_move_to_memory(index); return; } } } else { ret = mail_index_write_map_over(index); if (ret < 0) mail_index_set_syscall_error(index, "pwrite_full()"); else if (index->nfs_flush) { ret = fdatasync(index->fd); if (ret < 0) { mail_index_set_syscall_error(index, "fdatasync()"); } } mail_index_unlock(index, &lock_id); if (ret < 0) { /* hopefully didn't break badly */ mail_index_move_to_memory(index); return; } } index->last_read_log_file_seq = hdr->log_file_seq; index->last_read_log_file_head_offset = hdr->log_file_head_offset; index->last_read_log_file_tail_offset = hdr->log_file_tail_offset; map->rec_map->write_seq_first = map->rec_map->write_seq_last = 0; map->write_atomic = FALSE; map->write_base_header = FALSE; map->write_ext_header = FALSE; if (want_rotate && hdr->log_file_seq == index->log->head->hdr.file_seq && hdr->log_file_tail_offset == hdr->log_file_head_offset) (void)mail_transaction_log_rotate(index->log, FALSE); }