mail-index-map-read.c [plain text]
#include "lib.h"
#include "array.h"
#include "nfs-workarounds.h"
#include "mmap-util.h"
#include "read-full.h"
#include "mail-index-private.h"
#include "mail-index-sync-private.h"
#include "mail-transaction-log-private.h"
static void mail_index_map_copy_hdr(struct mail_index_map *map,
const struct mail_index_header *hdr)
{
if (hdr->base_header_size < sizeof(map->hdr)) {
memset(&map->hdr, 0, sizeof(map->hdr));
memcpy(&map->hdr, hdr, hdr->base_header_size);
} else {
map->hdr = *hdr;
}
map->hdr.unused_old_recent_messages_count = 0;
}
static int mail_index_mmap(struct mail_index_map *map, uoff_t file_size)
{
struct mail_index *index = map->index;
struct mail_index_record_map *rec_map = map->rec_map;
const struct mail_index_header *hdr;
i_assert(rec_map->mmap_base == NULL);
buffer_free(&rec_map->buffer);
if (file_size > SSIZE_T_MAX) {
mail_index_set_error(index, "Index file too large: %s",
index->filepath);
return -1;
}
rec_map->mmap_base = mmap(NULL, file_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE, index->fd, 0);
if (rec_map->mmap_base == MAP_FAILED) {
rec_map->mmap_base = NULL;
mail_index_set_syscall_error(index, "mmap()");
return -1;
}
rec_map->mmap_size = file_size;
hdr = rec_map->mmap_base;
if (rec_map->mmap_size >
offsetof(struct mail_index_header, major_version) &&
hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
return 0;
}
if (rec_map->mmap_size < MAIL_INDEX_HEADER_MIN_SIZE) {
mail_index_set_error(index, "Corrupted index file %s: "
"File too small (%"PRIuSIZE_T")",
index->filepath, rec_map->mmap_size);
return 0;
}
if (!mail_index_check_header_compat(index, hdr, rec_map->mmap_size)) {
return 0;
}
rec_map->mmap_used_size = hdr->header_size +
hdr->messages_count * hdr->record_size;
if (rec_map->mmap_used_size <= rec_map->mmap_size)
rec_map->records_count = hdr->messages_count;
else {
rec_map->records_count =
(rec_map->mmap_size - hdr->header_size) /
hdr->record_size;
rec_map->mmap_used_size = hdr->header_size +
rec_map->records_count * hdr->record_size;
mail_index_set_error(index, "Corrupted index file %s: "
"messages_count too large (%u > %u)",
index->filepath, hdr->messages_count,
rec_map->records_count);
}
mail_index_map_copy_hdr(map, hdr);
map->hdr_base = rec_map->mmap_base;
rec_map->records = PTR_OFFSET(rec_map->mmap_base, map->hdr.header_size);
return 1;
}
static int mail_index_read_header(struct mail_index *index,
void *buf, size_t buf_size, size_t *pos_r)
{
size_t pos;
int ret;
memset(buf, 0, sizeof(struct mail_index_header));
pos = 0;
do {
ret = pread(index->fd, PTR_OFFSET(buf, pos),
buf_size - pos, pos);
if (ret > 0)
pos += ret;
} while (ret > 0 && pos < sizeof(struct mail_index_header));
*pos_r = pos;
return ret;
}
static int
mail_index_try_read_map(struct mail_index_map *map,
uoff_t file_size, bool *retry_r, bool try_retry)
{
struct mail_index *index = map->index;
const struct mail_index_header *hdr;
unsigned char read_buf[IO_BLOCK_SIZE];
const void *buf;
void *data = NULL;
ssize_t ret;
size_t pos, records_size, initial_buf_pos = 0;
unsigned int records_count = 0, extra;
i_assert(map->rec_map->mmap_base == NULL);
*retry_r = FALSE;
ret = mail_index_read_header(index, read_buf, sizeof(read_buf), &pos);
buf = read_buf; hdr = buf;
if (pos > (ssize_t)offsetof(struct mail_index_header, major_version) &&
hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
return 0;
}
if (ret >= 0 && pos >= MAIL_INDEX_HEADER_MIN_SIZE &&
(ret > 0 || pos >= hdr->base_header_size)) {
if (!mail_index_check_header_compat(index, hdr, file_size)) {
return 0;
}
initial_buf_pos = pos;
if (pos > hdr->header_size)
pos = hdr->header_size;
buffer_reset(map->hdr_copy_buf);
buffer_append(map->hdr_copy_buf, buf, pos);
if (pos != hdr->header_size) {
data = buffer_append_space_unsafe(map->hdr_copy_buf,
hdr->header_size -
pos);
ret = pread_full(index->fd, data,
hdr->header_size - pos, pos);
}
}
if (ret > 0) {
records_size = (size_t)hdr->messages_count * hdr->record_size;
records_count = hdr->messages_count;
if (file_size - hdr->header_size < records_size ||
(hdr->record_size != 0 &&
records_size / hdr->record_size != hdr->messages_count)) {
records_count = (file_size - hdr->header_size) /
hdr->record_size;
records_size = (size_t)records_count * hdr->record_size;
mail_index_set_error(index, "Corrupted index file %s: "
"messages_count too large (%u > %u)",
index->filepath, hdr->messages_count,
records_count);
}
if (map->rec_map->buffer == NULL) {
map->rec_map->buffer =
buffer_create_dynamic(default_pool,
records_size);
}
buffer_set_used_size(map->rec_map->buffer, 0);
if (initial_buf_pos <= hdr->header_size)
extra = 0;
else {
extra = initial_buf_pos - hdr->header_size;
buffer_append(map->rec_map->buffer,
CONST_PTR_OFFSET(buf, hdr->header_size),
extra);
}
if (records_size > extra) {
data = buffer_append_space_unsafe(map->rec_map->buffer,
records_size - extra);
ret = pread_full(index->fd, data, records_size - extra,
hdr->header_size + extra);
}
}
if (ret < 0) {
if (errno == ESTALE && try_retry) {
*retry_r = TRUE;
return 0;
}
mail_index_set_syscall_error(index, "pread_full()");
return -1;
}
if (ret == 0) {
mail_index_set_error(index,
"Corrupted index file %s: File too small",
index->filepath);
return 0;
}
map->rec_map->records =
buffer_get_modifiable_data(map->rec_map->buffer, NULL);
map->rec_map->records_count = records_count;
mail_index_map_copy_hdr(map, hdr);
map->hdr_base = map->hdr_copy_buf->data;
return 1;
}
static int mail_index_read_map(struct mail_index_map *map, uoff_t file_size,
unsigned int *lock_id)
{
struct mail_index *index = map->index;
mail_index_sync_lost_handler_t *const *handlerp;
struct stat st;
unsigned int i;
int ret;
bool try_retry, retry;
array_foreach(&index->sync_lost_handlers, handlerp)
(**handlerp)(index);
for (i = 0;; i++) {
try_retry = i < MAIL_INDEX_ESTALE_RETRY_COUNT;
if (file_size == (uoff_t)-1) {
ret = 0;
retry = try_retry;
} else {
ret = mail_index_try_read_map(map, file_size,
&retry, try_retry);
}
if (ret != 0 || !retry)
break;
mail_index_close_file(index);
*lock_id = 0;
ret = mail_index_try_open_only(index);
if (ret <= 0) {
if (ret == 0) {
errno = ENOENT;
mail_index_set_syscall_error(index, "open()");
}
return -1;
}
if (mail_index_lock_shared(index, lock_id) < 0)
return -1;
if (fstat(index->fd, &st) == 0)
file_size = st.st_size;
else {
if (!ESTALE_FSTAT(errno)) {
mail_index_set_syscall_error(index, "fstat()");
return -1;
}
file_size = (uoff_t)-1;
}
}
return ret;
}
static int mail_index_map_latest_file(struct mail_index *index)
{
struct mail_index_map *old_map, *new_map;
struct stat st;
unsigned int lock_id;
uoff_t file_size;
bool use_mmap, unusable = FALSE;
int ret, try;
ret = mail_index_reopen_if_changed(index);
if (ret <= 0) {
if (ret < 0)
return -1;
return 1;
}
if (mail_index_lock_shared(index, &lock_id) < 0)
return -1;
if ((index->flags & MAIL_INDEX_OPEN_FLAG_NFS_FLUSH) != 0)
nfs_flush_attr_cache_fd_locked(index->filepath, index->fd);
if (fstat(index->fd, &st) == 0)
file_size = st.st_size;
else {
if (!ESTALE_FSTAT(errno)) {
mail_index_set_syscall_error(index, "fstat()");
mail_index_unlock(index, &lock_id);
return -1;
}
file_size = (uoff_t)-1;
}
use_mmap = (index->flags & MAIL_INDEX_OPEN_FLAG_MMAP_DISABLE) == 0 &&
file_size != (uoff_t)-1 && file_size > MAIL_INDEX_MMAP_MIN_SIZE;
new_map = mail_index_map_alloc(index);
if (use_mmap) {
new_map->rec_map->lock_id = lock_id;
ret = mail_index_mmap(new_map, file_size);
} else {
ret = mail_index_read_map(new_map, file_size, &lock_id);
mail_index_unlock(index, &lock_id);
}
if (ret == 0) {
unusable = TRUE;
}
for (try = 0; ret > 0; try++) {
ret = mail_index_map_check_header(new_map);
if (ret > 0) T_BEGIN {
if (mail_index_map_parse_extensions(new_map) < 0)
ret = 0;
else if (mail_index_map_parse_keywords(new_map) < 0)
ret = 0;
} T_END;
if (ret != 0 || try == 2) {
if (ret < 0) {
unusable = TRUE;
ret = 0;
}
break;
}
old_map = index->map;
index->map = new_map;
if (mail_index_fsck(index) < 0) {
ret = -1;
break;
}
new_map = index->map;
index->map = old_map;
}
if (ret <= 0) {
mail_index_unmap(&new_map);
return ret < 0 ? -1 : (unusable ? 0 : 1);
}
i_assert(new_map->rec_map->records != NULL);
index->last_read_log_file_seq = new_map->hdr.log_file_seq;
index->last_read_log_file_head_offset =
new_map->hdr.log_file_head_offset;
index->last_read_log_file_tail_offset =
new_map->hdr.log_file_tail_offset;
index->last_read_stat = st;
mail_index_unmap(&index->map);
index->map = new_map;
return 1;
}
int mail_index_map(struct mail_index *index,
enum mail_index_sync_handler_type type)
{
int ret;
i_assert(index->lock_type != F_WRLCK);
i_assert(!index->mapping);
index->mapping = TRUE;
if (index->map == NULL)
index->map = mail_index_map_alloc(index);
if (index->initial_mapped) {
ret = mail_index_sync_map(&index->map, type, FALSE);
} else {
ret = 0;
}
if (ret == 0) {
ret = mail_index_map_latest_file(index);
if (ret > 0) {
if (index->log->head != NULL && index->indexid != 0) {
ret = mail_index_sync_map(&index->map, type,
TRUE);
}
} else if (ret == 0 && !index->readonly) {
if (unlink(index->filepath) < 0 && errno != ENOENT)
mail_index_set_syscall_error(index, "unlink()");
}
}
if (ret >= 0)
index->initial_mapped = TRUE;
index->mapping = FALSE;
return ret;
}