Mercurial > dovecot > original-hg > dovecot-1.2
view src/plugins/fts-squat/squat-uidlist.c @ 6418:46d9ee79f292 HEAD
Removed _ prefix from all public APIs.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 16 Sep 2007 12:43:21 +0300 |
parents | a6a49d5efc59 |
children | 7cad076906eb |
line wrap: on
line source
/* Copyright (C) 2006 Timo Sirainen */ #include "lib.h" #include "array.h" #include "ostream.h" #include "file-cache.h" #include "mmap-util.h" #include "read-full.h" #include "write-full.h" #include "squat-trie.h" #include "squat-uidlist.h" #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #define UIDLIST_COMPRESS_PERCENTAGE 30 #define UIDLIST_UID_COMPRESS_PERCENTAGE 20 #define UIDLIST_COMPRESS_MIN_SIZE (1024*8) #define SQUAT_UIDLIST_FLUSH_THRESHOLD (1024*1024*31) #define UID_NODE_PREV_FLAG_OLD 0x00000001 #define UID_LIST_IDX_FLAG_SINGLE 0x80000000 struct squat_uidlist_header { uint32_t uidvalidity; uint32_t used_file_size; uint32_t deleted_space; uint32_t uid_max; uint32_t uid_count; uint8_t uids_expunged; /* updated without locking */ uint8_t unused[3]; uint32_t node_count; }; struct uid_node { struct uid_node *prev; uint32_t uid; }; struct squat_uidlist_get_context { struct squat_uidlist *uidlist; ARRAY_TYPE(seq_range) *result; uint32_t filter_uid_pos; }; struct squat_uidlist { struct squat_trie *trie; uint32_t uidvalidity; char *filepath; int fd; struct ostream *output; dev_t dev; ino_t ino; void *mmap_base; const uint8_t *const_mmap_base; size_t mmap_size; struct file_cache *file_cache; struct squat_uidlist_header hdr; ARRAY_DEFINE(lists, struct uid_node); uint32_t first_new_list_idx; uint32_t current_uid; pool_t node_pool; size_t node_pool_used; buffer_t *tmp_buf, *list_buf; unsigned int check_expunges:1; unsigned int write_failed:1; unsigned int mmap_disable:1; }; struct squat_uidlist_compress_ctx { struct squat_uidlist *uidlist; const ARRAY_TYPE(seq_range) *existing_uids; struct ostream *output; char *tmp_path; pool_t node_pool; struct uid_node *last_node; ARRAY_TYPE(seq_range) seen_uids; struct squat_uidlist_header hdr; unsigned int seen_expunged:1; unsigned int failed:1; }; static void squat_uidlist_close(struct squat_uidlist *uidlist); static void squat_uidlist_set_syscall_error(struct squat_uidlist *uidlist, const char *function) { i_error("%s failed with index search uidlist file %s: %m", function, uidlist->filepath); } static int squat_uidlist_check_header(struct squat_uidlist *uidlist, const struct squat_uidlist_header *hdr, uoff_t file_size) { if (hdr->used_file_size == 0) { /* crashed before writing was finished */ return -1; } if (hdr->uidvalidity != uidlist->uidvalidity) { squat_trie_set_corrupted(uidlist->trie, "uidlist: uidvalidity changed"); return -1; } if (hdr->used_file_size > file_size) { squat_trie_set_corrupted(uidlist->trie, "uidlist: used_file_size too large"); return -1; } return 0; } static int squat_uidlist_read_header(struct squat_uidlist *uidlist) { int ret; ret = pread_full(uidlist->fd, &uidlist->hdr, sizeof(uidlist->hdr), 0); if (ret < 0) squat_uidlist_set_syscall_error(uidlist, "pread_full()"); return ret; } static int squat_uidlist_map(struct squat_uidlist *uidlist) { struct stat st; int ret; if (!uidlist->mmap_disable) { const struct squat_uidlist_header *hdr = uidlist->mmap_base; if (hdr != NULL && hdr->used_file_size <= uidlist->mmap_size) { /* everything is already mapped */ uidlist->hdr = *hdr; return 1; } } else { if ((ret = squat_uidlist_read_header(uidlist)) < 0) return -1; if (ret == 0) return 0; } if (fstat(uidlist->fd, &st) < 0) { squat_uidlist_set_syscall_error(uidlist, "fstat()"); return -1; } uidlist->dev = st.st_dev; uidlist->ino = st.st_ino; if (st.st_size <= (off_t)sizeof(uidlist->hdr)) return 0; if (uidlist->mmap_base != NULL) { if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0) squat_uidlist_set_syscall_error(uidlist, "munmap()"); } uidlist->const_mmap_base = NULL; if (!uidlist->mmap_disable) { uidlist->mmap_size = st.st_size; uidlist->mmap_base = mmap(NULL, uidlist->mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, uidlist->fd, 0); if (uidlist->mmap_base == MAP_FAILED) { uidlist->mmap_size = 0; uidlist->mmap_base = NULL; squat_uidlist_set_syscall_error(uidlist, "mmap()"); return -1; } uidlist->const_mmap_base = uidlist->mmap_base; memcpy(&uidlist->hdr, uidlist->mmap_base, sizeof(uidlist->hdr)); } else { /* the header is always read separately. everything between it and the used_file_size doesn't change */ file_cache_invalidate(uidlist->file_cache, uidlist->hdr.used_file_size, (uoff_t)-1); } if (squat_uidlist_check_header(uidlist, &uidlist->hdr, st.st_size) < 0) return 0; if (uidlist->hdr.uids_expunged) uidlist->check_expunges = TRUE; uidlist->first_new_list_idx = uidlist->hdr.used_file_size; return 1; } static int squat_uidlist_open(struct squat_uidlist *uidlist) { int ret; i_assert(uidlist->fd == -1); uidlist->fd = open(uidlist->filepath, O_RDWR, 0600); if (uidlist->fd == -1) { if (errno == ENOENT) return 0; squat_uidlist_set_syscall_error(uidlist, "open()"); return -1; } if (uidlist->mmap_disable) uidlist->file_cache = file_cache_new(uidlist->fd); if ((ret = squat_uidlist_map(uidlist)) == 0) { /* broken */ if (unlink(uidlist->filepath) < 0) squat_uidlist_set_syscall_error(uidlist, "unlink()"); squat_uidlist_close(uidlist); } return ret; } static int squat_uidlist_create(struct squat_uidlist *uidlist) { i_assert(uidlist->fd == -1); /* we should get here only if normal file opening failed */ uidlist->fd = open(uidlist->filepath, O_RDWR | O_CREAT | O_TRUNC, 0600); if (uidlist->fd == -1) { squat_uidlist_set_syscall_error(uidlist, "open()"); return -1; } if (uidlist->mmap_disable) uidlist->file_cache = file_cache_new(uidlist->fd); return 0; } static void squat_uidlist_close(struct squat_uidlist *uidlist) { if (uidlist->file_cache != NULL) file_cache_free(&uidlist->file_cache); if (uidlist->mmap_base != NULL) { if (munmap(uidlist->mmap_base, uidlist->mmap_size) < 0) squat_uidlist_set_syscall_error(uidlist, "munmap()"); uidlist->mmap_base = NULL; } uidlist->const_mmap_base = NULL; uidlist->mmap_size = 0; if (uidlist->fd != -1) { if (close(uidlist->fd) < 0) squat_uidlist_set_syscall_error(uidlist, "close()"); uidlist->fd = -1; } } struct squat_uidlist * squat_uidlist_init(struct squat_trie *trie, const char *path, uint32_t uidvalidity, bool mmap_disable) { struct squat_uidlist *uidlist; uidlist = i_new(struct squat_uidlist, 1); uidlist->trie = trie; uidlist->filepath = i_strdup(path); uidlist->uidvalidity = uidvalidity; uidlist->fd = -1; uidlist->first_new_list_idx = 1; uidlist->mmap_disable = mmap_disable; i_array_init(&uidlist->lists, 65536); uidlist->node_pool = pool_alloconly_create(MEMPOOL_GROWING"squat uidlist node pool", 65536); uidlist->tmp_buf = buffer_create_dynamic(default_pool, 16); uidlist->list_buf = buffer_create_dynamic(default_pool, 256); return uidlist; } void squat_uidlist_deinit(struct squat_uidlist *uidlist) { squat_uidlist_close(uidlist); pool_unref(uidlist->node_pool); array_free(&uidlist->lists); buffer_free(&uidlist->tmp_buf); buffer_free(&uidlist->list_buf); i_free(uidlist->filepath); i_free(uidlist); } int squat_uidlist_refresh(struct squat_uidlist *uidlist) { struct stat st; int ret; if (uidlist->fd != -1) { if (stat(uidlist->filepath, &st) < 0) { if (errno == ENOENT) return 0; squat_uidlist_set_syscall_error(uidlist, "stat()"); return -1; } if (st.st_ino == uidlist->ino && CMP_DEV_T(st.st_dev, uidlist->dev)) { /* no need to reopen, just remap */ if ((ret = squat_uidlist_map(uidlist)) != 0) return ret < 0 ? -1 : 0; /* broken file */ } squat_uidlist_close(uidlist); } if (squat_uidlist_open(uidlist) < 0) return -1; return 0; } int squat_uidlist_get_last_uid(struct squat_uidlist *uidlist, uint32_t *uid_r) { *uid_r = uidlist->hdr.uid_max; return 0; } int squat_uidlist_add(struct squat_uidlist *uidlist, uint32_t *_uid_list_idx, uint32_t uid) { uint32_t uid_list_idx = *_uid_list_idx; struct uid_node *node, *old_node; i_assert(uid > uidlist->hdr.uid_max || uid == uidlist->current_uid); if (uid_list_idx == 0) { *_uid_list_idx = uid | UID_LIST_IDX_FLAG_SINGLE; return 0; } if (uid > uidlist->hdr.uid_max) { uidlist->current_uid = uid; uidlist->hdr.uid_max = uid; uidlist->hdr.uid_count++; } if (uid_list_idx < uidlist->first_new_list_idx) { /* continue an existing list in the uidlist file */ old_node = POINTER_CAST((uid_list_idx << 1) | UID_NODE_PREV_FLAG_OLD); uid_list_idx = uidlist->first_new_list_idx + array_count(&uidlist->lists); node = array_append_space(&uidlist->lists); uidlist->hdr.node_count++; } else if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) { uint32_t old_uid = uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE; if (uid == old_uid) { /* trying to add the same uid again */ return 0; } /* convert single UID to a list */ uidlist->node_pool_used += sizeof(struct uid_node); old_node = p_new(uidlist->node_pool, struct uid_node, 1); old_node->uid = old_uid; uid_list_idx = uidlist->first_new_list_idx + array_count(&uidlist->lists); node = array_append_space(&uidlist->lists); uidlist->hdr.node_count++; } else { /* update an in-memory list */ uint32_t arr_idx = uid_list_idx - uidlist->first_new_list_idx; if (arr_idx >= array_count(&uidlist->lists)) { /* broken */ squat_trie_set_corrupted(uidlist->trie, "corrupted uidlist index (adding)"); return -1; } node = array_idx_modifiable(&uidlist->lists, arr_idx); if (node->uid == uid) { /* trying to add the same uid again */ return 0; } uidlist->node_pool_used += sizeof(struct uid_node); old_node = p_new(uidlist->node_pool, struct uid_node, 1); *old_node = *node; } node->prev = old_node; node->uid = uid; *_uid_list_idx = uid_list_idx; return 0; } static int squat_uidlist_map_area(struct squat_uidlist *uidlist, size_t offset, size_t size) { ssize_t ret; if (uidlist->file_cache == NULL) return 0; ret = file_cache_read(uidlist->file_cache, offset, size); if (ret < 0) { squat_uidlist_set_syscall_error(uidlist, "file_cache_read()"); return -1; } uidlist->const_mmap_base = file_cache_get_map(uidlist->file_cache, &uidlist->mmap_size); return 0; } static int squat_uidlist_map_list(struct squat_uidlist *uidlist, size_t offset, const uint8_t **data_r, uint32_t *size_r) { const uint8_t *data, *end; size_t data_offset; uint32_t size; if (squat_uidlist_map_area(uidlist, offset, 128) < 0) return -1; if (offset >= uidlist->mmap_size) return -1; data = uidlist->const_mmap_base + offset; end = uidlist->const_mmap_base + uidlist->mmap_size; size = squat_trie_unpack_num(&data, end); data_offset = data - uidlist->const_mmap_base; if (squat_uidlist_map_area(uidlist, data_offset, size) < 0) return -1; if (data_offset + size > uidlist->mmap_size) return -1; *data_r = uidlist->const_mmap_base + data_offset; *size_r = size; return 0; } static int squat_uidlist_copy_existing(struct squat_uidlist *uidlist, size_t offset, uint32_t *prev_uid_r, uint32_t *written_uid_r) { const uint8_t *data, *data_start, *end, *p = NULL; uint32_t size, num, prev_uid, next_uid; if (squat_uidlist_map_list(uidlist, offset, &data, &size) < 0) return -1; data_start = data; end = data + size; prev_uid = next_uid = squat_trie_unpack_num(&data, end); p = data; while (data != end) { num = squat_trie_unpack_num(&data, end); next_uid = prev_uid + (num >> 1) + 1; if ((num & 1) != 0) { /* prev_uid..next_uid */ if (data == end) { /* try to increase this range */ break; } /* beginning a new uid/range */ num = squat_trie_unpack_num(&data, end); next_uid += num + 1; prev_uid = next_uid; p = data; } prev_uid = next_uid; p = data; } *written_uid_r = prev_uid; *prev_uid_r = next_uid; uidlist->hdr.deleted_space += (end - (const uint8_t *)uidlist->const_mmap_base) - offset; buffer_append(uidlist->list_buf, data_start, p - data_start); return 0; } static int squat_uidlist_write_range(struct squat_uidlist *uidlist, const struct uid_node *node, uint32_t *prev_uid_r, uint32_t *written_uid_r, int level) { buffer_t *buffer = uidlist->list_buf; uint32_t written_uid, prev_uid; uint32_t prev_idx = POINTER_CAST_TO(node->prev, uint32_t); *prev_uid_r = node->uid; if (node->prev == NULL) { /* first UID */ squat_trie_pack_num(buffer, node->uid); } else { if ((prev_idx & UID_NODE_PREV_FLAG_OLD) != 0) { prev_idx >>= 1; if (squat_uidlist_copy_existing(uidlist, prev_idx, &prev_uid, &written_uid) < 0 || prev_uid >= node->uid) { squat_trie_set_corrupted(uidlist->trie, "corrupted continued uidlist index"); return -1; } } else { if (squat_uidlist_write_range(uidlist, node->prev, &prev_uid, &written_uid, level+1) < 0) return -1; } /* prev_uid contains the previous node's UID. written_uid contains the last written UID. */ if (prev_uid + 1 == node->uid) { if (level != 0) { /* this node continue the range */ *written_uid_r = written_uid; return 0; } else { /* finishing range */ squat_trie_pack_num(buffer, 1 | ((node->uid - written_uid - 1) << 1)); return 0; } } i_assert(prev_uid < node->uid); if (written_uid != prev_uid) { i_assert(written_uid < prev_uid); /* range ends at prev_uid */ squat_trie_pack_num(buffer, 1 | ((prev_uid - written_uid - 1) << 1)); /* next uid/range */ squat_trie_pack_num(buffer, node->uid - prev_uid - 1); } else { /* no range */ squat_trie_pack_num(buffer, ((node->uid - prev_uid - 1) << 1)); } } *written_uid_r = node->uid; return 0; } static int squat_uidlist_write_init(struct squat_uidlist *uidlist) { i_assert(uidlist->output == NULL); if (uidlist->fd == -1) { if (squat_uidlist_create(uidlist) < 0) return -1; } uidlist->output = o_stream_create_fd_file(uidlist->fd, (uoff_t)-1, FALSE); o_stream_cork(uidlist->output); if (uidlist->hdr.used_file_size < sizeof(uidlist->hdr)) { /* creating a new file, write a dummy header */ o_stream_seek(uidlist->output, 0); o_stream_send(uidlist->output, &uidlist->hdr, sizeof(uidlist->hdr)); } else { o_stream_seek(uidlist->output, uidlist->hdr.used_file_size); } return 0; } static int squat_uidlist_write_listbuf(struct squat_uidlist *uidlist, struct ostream *output) { /* write size + buffer */ buffer_set_used_size(uidlist->tmp_buf, 0); squat_trie_pack_num(uidlist->tmp_buf, uidlist->list_buf->used); if (o_stream_send(output, uidlist->tmp_buf->data, uidlist->tmp_buf->used) < 0 || o_stream_send(output, uidlist->list_buf->data, uidlist->list_buf->used) < 0) { return -1; } return 0; } int squat_uidlist_finish_list(struct squat_uidlist *uidlist, uint32_t *_uid_list_idx) { uint32_t uid_list_idx = *_uid_list_idx; const struct uid_node *node; uint32_t prev_uid, written_uid; if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) { /* this is a single UID "list" */ return 0; } if (uid_list_idx < uidlist->first_new_list_idx) { /* the list hasn't changed */ return 0; } uid_list_idx -= uidlist->first_new_list_idx; if (uid_list_idx >= array_count(&uidlist->lists)) { /* broken */ squat_trie_set_corrupted(uidlist->trie, "corrupted uidlist index (finishing)"); return -1; } /* write the uidlist into a buffer */ node = array_idx(&uidlist->lists, uid_list_idx); buffer_set_used_size(uidlist->list_buf, 0); if (squat_uidlist_write_range(uidlist, node, &prev_uid, &written_uid, 0) < 0) { uidlist->write_failed = TRUE; return -1; } if (uidlist->output == NULL) { if (squat_uidlist_write_init(uidlist) < 0) { uidlist->write_failed = TRUE; return -1; } } /* new uidlist index is the offset in uidlist file */ *_uid_list_idx = uidlist->output->offset; if (squat_uidlist_write_listbuf(uidlist, uidlist->output) < 0) uidlist->write_failed = TRUE; return 0; } static void squat_uidlist_write_header(struct squat_uidlist *uidlist) { uidlist->hdr.used_file_size = uidlist->output->offset; o_stream_seek(uidlist->output, 0); o_stream_send(uidlist->output, &uidlist->hdr, sizeof(uidlist->hdr)); } int squat_uidlist_flush(struct squat_uidlist *uidlist, uint32_t uid_validity) { int ret = uidlist->write_failed ? -1 : 0; if (uidlist->output != NULL) { if (ret == 0) { uidlist->hdr.uidvalidity = uid_validity; squat_uidlist_write_header(uidlist); } o_stream_destroy(&uidlist->output); } array_clear(&uidlist->lists); p_clear(uidlist->node_pool); uidlist->node_pool_used = 0; uidlist->write_failed = FALSE; uidlist->current_uid = 0; if (uidlist->fd != -1) { if (squat_uidlist_map(uidlist) <= 0) ret = -1; } return ret; } bool squat_uidlist_need_compress(struct squat_uidlist *uidlist, unsigned int current_message_count) { uint32_t max_del_space, max_uid_del_count; if (uidlist->hdr.used_file_size >= UIDLIST_COMPRESS_MIN_SIZE) { /* see if we've reached the max. deleted space in file */ max_del_space = uidlist->hdr.used_file_size / 100 * UIDLIST_COMPRESS_PERCENTAGE; if (uidlist->hdr.deleted_space > max_del_space) return TRUE; } if (uidlist->hdr.uid_count > current_message_count) { if (current_message_count == 0) return TRUE; max_uid_del_count = uidlist->hdr.uid_count * UIDLIST_UID_COMPRESS_PERCENTAGE / 100; if ((uidlist->hdr.uid_count - current_message_count) > max_uid_del_count) return TRUE; } return FALSE; } int squat_uidlist_mark_having_expunges(struct squat_uidlist *uidlist, bool update_disk) { uint8_t flag = 1; size_t offset; uidlist->check_expunges = TRUE; if (update_disk) { /* NOTE: we're writing this flag without locking */ offset = offsetof(struct squat_uidlist_header, uids_expunged); if (pwrite_full(uidlist->fd, &flag, sizeof(flag), offset) < 0) { squat_uidlist_set_syscall_error(uidlist, "pwrite_full()"); return -1; } } return 0; } struct squat_uidlist_compress_ctx * squat_uidlist_compress_begin(struct squat_uidlist *uidlist, const ARRAY_TYPE(seq_range) *existing_uids) { struct squat_uidlist_compress_ctx *ctx; int fd; ctx = i_new(struct squat_uidlist_compress_ctx, 1); ctx->uidlist = uidlist; ctx->tmp_path = i_strconcat(uidlist->filepath, ".tmp", NULL); if (existing_uids != NULL) { ctx->node_pool = pool_alloconly_create("compress node pool", 1024); ctx->existing_uids = existing_uids; i_array_init(&ctx->seen_uids, I_MIN(128, array_count(existing_uids))); } fd = open(ctx->tmp_path, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) { ctx->failed = TRUE; i_error("open(%s) failed: %m", ctx->tmp_path); } else { ctx->output = o_stream_create_fd_file(fd, 0, TRUE); o_stream_send(ctx->output, &ctx->hdr, sizeof(ctx->hdr)); } if (squat_uidlist_refresh(uidlist) < 0) ctx->failed = TRUE; return ctx; } static bool squat_uidlist_is_expunged(struct squat_uidlist_compress_ctx *ctx, uint32_t uid) { if (ctx->existing_uids == NULL) return FALSE; return !seq_range_exists(ctx->existing_uids, uid); } static void squat_uidlist_compress_add_uid(struct squat_uidlist_compress_ctx *ctx, uint32_t uid) { struct uid_node *node; if (squat_uidlist_is_expunged(ctx, uid)) { ctx->seen_expunged = TRUE; return; } if (!seq_range_exists(&ctx->seen_uids, uid)) { if (uid > ctx->hdr.uid_max) ctx->hdr.uid_max = uid; ctx->hdr.uid_count++; seq_range_array_add(&ctx->seen_uids, 0, uid); } node = p_new(ctx->node_pool, struct uid_node, 1); node->prev = ctx->last_node; node->uid = uid; ctx->last_node = node; } static int squat_uidlist_remove_expunged(struct squat_uidlist_compress_ctx *ctx, const uint8_t *data, size_t size, bool *all_expunged_r) { const uint8_t *end; uint32_t num, prev_uid, next_uid, written_uid; end = data + size; p_clear(ctx->node_pool); ctx->seen_expunged = FALSE; ctx->last_node = NULL; prev_uid = squat_trie_unpack_num(&data, end); squat_uidlist_compress_add_uid(ctx, prev_uid); while (data != end) { num = squat_trie_unpack_num(&data, end); next_uid = prev_uid + (num >> 1) + 1; if ((num & 1) != 0) { for (prev_uid++; prev_uid <= next_uid; prev_uid++) squat_uidlist_compress_add_uid(ctx, prev_uid); if (data == end) break; num = squat_trie_unpack_num(&data, end); next_uid += num + 1; } squat_uidlist_compress_add_uid(ctx, next_uid); prev_uid = next_uid; } if (!ctx->seen_expunged) { /* no changes */ return 0; } if (ctx->last_node == NULL) { /* everything expunged */ *all_expunged_r = TRUE; return 1; } /* recreate the list and write it */ buffer_set_used_size(ctx->uidlist->list_buf, 0); if (squat_uidlist_write_range(ctx->uidlist, ctx->last_node, &prev_uid, &written_uid, 0) < 0) return -1; if (squat_uidlist_write_listbuf(ctx->uidlist, ctx->output) < 0) return -1; *all_expunged_r = FALSE; return 1; } int squat_uidlist_compress_next(struct squat_uidlist_compress_ctx *ctx, uint32_t *uid_list_idx) { struct squat_uidlist *uidlist = ctx->uidlist; const uint8_t *data, *data_start; uint32_t size, old_offset; int ret; if ((*uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) { uint32_t uid = *uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE; if (ctx->uidlist->check_expunges) { if (squat_uidlist_is_expunged(ctx, uid)) return 0; } return 1; } if (ctx->output == NULL) return -1; if (squat_uidlist_map_list(uidlist, *uid_list_idx, &data, &size) < 0) { squat_trie_set_corrupted(uidlist->trie, "corrupted uidlist index (compressing)"); return -1; } old_offset = *uid_list_idx; *uid_list_idx = ctx->output->offset; if (!ctx->uidlist->check_expunges) ret = 0; else { bool all_expunged; ret = squat_uidlist_remove_expunged(ctx, data, size, &all_expunged); if (ret < 0) { ctx->failed = TRUE; return -1; } if (ret > 0 && all_expunged) return 0; } if (ret == 0) { data_start = data = uidlist->const_mmap_base + old_offset; (void)squat_trie_unpack_num(&data, NULL); if (o_stream_send(ctx->output, data_start, data - data_start + size) < 0) { ctx->failed = TRUE; return -1; } } ctx->hdr.node_count++; return 1; } void squat_uidlist_compress_rollback(struct squat_uidlist_compress_ctx **_ctx) { struct squat_uidlist_compress_ctx *ctx = *_ctx; *_ctx = NULL; if (ctx->node_pool != NULL) pool_unref(ctx->node_pool); if (array_is_created(&ctx->seen_uids)) array_free(&ctx->seen_uids); if (ctx->output != NULL) { if (ctx->failed) (void)unlink(ctx->tmp_path); o_stream_destroy(&ctx->output); } i_free(ctx->tmp_path); i_free(ctx); } int squat_uidlist_compress_commit(struct squat_uidlist_compress_ctx **_ctx) { struct squat_uidlist_compress_ctx *ctx = *_ctx; int ret = 0; if (ctx->failed) { squat_uidlist_compress_rollback(_ctx); return -1; } /* write the header */ ctx->hdr.uidvalidity = ctx->uidlist->uidvalidity; ctx->hdr.used_file_size = ctx->output->offset; if (ctx->existing_uids == NULL) { ctx->hdr.uid_max = ctx->uidlist->hdr.uid_max; ctx->hdr.uid_count = ctx->uidlist->hdr.uid_count; } o_stream_seek(ctx->output, 0); if (o_stream_send(ctx->output, &ctx->hdr, sizeof(ctx->hdr)) < 0) ret = -1; if (ret == 0) { if (rename(ctx->tmp_path, ctx->uidlist->filepath) < 0) { i_error("rename(%s, %s) failed: %m", ctx->tmp_path, ctx->uidlist->filepath); ret = -1; } else { /* reopen */ ctx->uidlist->check_expunges = FALSE; squat_uidlist_close(ctx->uidlist); (void)squat_uidlist_open(ctx->uidlist); } } if (ret < 0) ctx->failed = TRUE; squat_uidlist_compress_rollback(_ctx); return ret; } static void squat_uidlist_get_add_uid(struct squat_uidlist_get_context *ctx, uint32_t uid) { if (ctx->filter_uid_pos == 0) { seq_range_array_add(ctx->result, 0, uid); return; } if (ctx->filter_uid_pos < uid) { seq_range_array_remove_range(ctx->result, ctx->filter_uid_pos, uid-1); } ctx->filter_uid_pos = uid+1; } static int squat_uidlist_get_range_list(struct squat_uidlist_get_context *ctx, size_t offset) { const uint8_t *data, *end; uint32_t size, num, prev_uid, next_uid; if (squat_uidlist_map_list(ctx->uidlist, offset, &data, &size) < 0) return -1; end = data + size; prev_uid = squat_trie_unpack_num(&data, end); squat_uidlist_get_add_uid(ctx, prev_uid); while (data != end) { num = squat_trie_unpack_num(&data, end); next_uid = prev_uid + (num >> 1) + 1; if ((num & 1) != 0) { for (prev_uid++; prev_uid <= next_uid; prev_uid++) squat_uidlist_get_add_uid(ctx, prev_uid); if (data == end) break; num = squat_trie_unpack_num(&data, end); next_uid += num + 1; } squat_uidlist_get_add_uid(ctx, next_uid); prev_uid = next_uid; } return 0; } static int squat_uidlist_get_ctx(struct squat_uidlist_get_context *ctx, uint32_t uid_list_idx) { if ((uid_list_idx & UID_LIST_IDX_FLAG_SINGLE) != 0) { uint32_t uid = uid_list_idx & ~UID_LIST_IDX_FLAG_SINGLE; squat_uidlist_get_add_uid(ctx, uid); return 0; } return squat_uidlist_get_range_list(ctx, uid_list_idx); } int squat_uidlist_get(struct squat_uidlist *uidlist, uint32_t uid_list_idx, ARRAY_TYPE(seq_range) *result) { struct squat_uidlist_get_context ctx; memset(&ctx, 0, sizeof(ctx)); ctx.uidlist = uidlist; ctx.result = result; return squat_uidlist_get_ctx(&ctx, uid_list_idx); } int squat_uidlist_filter(struct squat_uidlist *uidlist, uint32_t uid_list_idx, ARRAY_TYPE(seq_range) *result) { struct squat_uidlist_get_context ctx; const struct seq_range *range; unsigned int count; memset(&ctx, 0, sizeof(ctx)); ctx.uidlist = uidlist; ctx.result = result; ctx.filter_uid_pos = 1; if (squat_uidlist_get_ctx(&ctx, uid_list_idx) < 0) return -1; range = array_get(ctx.result, &count); if (count > 0) { seq_range_array_remove_range(result, ctx.filter_uid_pos, range[count-1].seq2); } return 0; } bool squat_uidlist_want_flush(struct squat_uidlist *uidlist) { return uidlist->node_pool_used >= SQUAT_UIDLIST_FLUSH_THRESHOLD; } size_t squat_uidlist_mem_used(struct squat_uidlist *uidlist, unsigned int *count_r) { *count_r = uidlist->hdr.node_count; return uidlist->hdr.used_file_size; }