mail-cache-transaction.c revision 2ca4cb08680aebb1474d762738cf436871f095fb
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (C) 2003-2004 Timo Sirainen */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen uint32_t update_header_offsets[MAIL_CACHE_HEADERS_COUNT];
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen uint32_t reserved_space_offset, reserved_space;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic const unsigned char *null4[] = { 0, 0, 0, 0 };
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_get_transaction(struct mail_cache_view *view,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ctx = i_new(struct mail_cache_transaction_ctx, 1);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_create_dynamic(system_pool, 32768, (size_t)-1);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_create_dynamic(system_pool, 256, (size_t)-1);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_create_dynamic(system_pool, 256, (size_t)-1);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenstatic void mail_cache_transaction_free(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenstatic int mail_cache_grow_file(struct mail_cache *cache, size_t size)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* grow the file */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen new_fsize = cache->hdr_copy.used_file_size + size;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen grow_size = new_fsize / 100 * MAIL_CACHE_GROW_PERCENTAGE;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "fstat()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (file_set_size(cache->fd, new_fsize) < 0) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "file_set_size()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenstatic int mail_cache_unlink_hole(struct mail_cache *cache, size_t size,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen struct mail_cache_header *hdr = &cache->hdr_copy;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen while (offset != 0) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (pread_full(cache->fd, &hole, sizeof(hole), offset) <= 0) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pread_full()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (hole.magic != MAIL_CACHE_HOLE_HEADER_MAGIC) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen "Invalid magic in hole header");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pwrite_full()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_add_reservation(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_append(ctx->reservations, &ctx->reserved_space_offset,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_append(ctx->reservations, &ctx->reserved_space,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_reserve_more(struct mail_cache_transaction_ctx *ctx,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen struct mail_cache_header *hdr = &cache->hdr_copy;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (mail_cache_unlink_hole(cache, size, &hole)) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* found a large enough hole. */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen ctx->reserved_space_offset = hole.next_offset;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* mail_cache_unlink_hole() could have noticed corruption */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if ((uoff_t)hdr->used_file_size + size > (uint32_t)-1) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_index_set_error(cache->index, "Cache file too large: %s",
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if ((uoff_t)hdr->used_file_size + size > (uint32_t)-1)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (mail_cache_grow_file(ctx->cache, size) < 0)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (ctx->reserved_space_offset + ctx->reserved_space ==
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* we can simply grow it */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen ctx->reserved_space = size - ctx->reserved_space;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* grow reservation. it's probably the last one in the buffer,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen but it's not guarateed because we might have used holes
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buf = buffer_get_modifyable_data(ctx->reservations, &size);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (buf[size] == ctx->reserved_space_offset) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen ctx->reserved_space_offset = hdr->used_file_size;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen hdr->used_file_size = ctx->reserved_space_offset + ctx->reserved_space;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_free_space(struct mail_cache *cache, uint32_t offset, uint32_t size)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (offset + size == cache->hdr_copy.used_file_size) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* we can just set used_file_size back */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen } else if (size >= MAIL_CACHE_MIN_HOLE_SIZE) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* set it up as a hole */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen hole.next_offset = cache->hdr_copy.hole_offset;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (pwrite_full(cache->fd, &hole, sizeof(hole), offset) < 0) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pwrite_full()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_free_space(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_free_space(ctx->cache, ctx->reserved_space_offset,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_get_space(struct mail_cache_transaction_ctx *ctx,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen ret = mail_cache_transaction_reserve_more(ctx, max_size,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* final commit - see if we can free the rest of the
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen reserved space */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_flush(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen const struct mail_cache_record *rec, *tmp_rec;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen uint32_t write_offset, old_offset, rec_offset;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen size_t size, max_size, seq_idx, seq_limit, seq_count;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* committing, remove the last dummy record */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_set_used_size(ctx->cache_data, ctx->prev_pos);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen rec = buffer_get_data(ctx->cache_data, &size);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen seq = buffer_get_data(ctx->cache_data_seq, &seq_count);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen for (seq_idx = 0, rec_offset = 0; rec_offset < ctx->prev_pos;) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen write_offset = mail_cache_transaction_get_space(ctx, rec->size,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* nothing to write / error */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* see how much we can really write there */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen for (size = 0; size + tmp_rec->size <= max_size; ) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* write it to file */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (pwrite_full(cache->fd, rec, max_size, write_offset) < 0) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pwrite_full()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* write the cache_offsets to index file. records' prev_offset
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen is updated to point to old cache record when index is being
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_index_update_cache(ctx->trans, seq[seq_idx],
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* we added records for this message multiple
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen times in this same uncommitted transaction.
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen only the new one will be written to
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen transaction log, we need to do the linking
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen ourself here. */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* drop the written data from buffer */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_set_used_size(ctx->cache_data, size - ctx->prev_pos);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_switch_seq(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* fix record size */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen data = buffer_get_modifyable_data(ctx->cache_data, &size);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_append(ctx->cache_data_seq, &ctx->prev_seq,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buffer_append(ctx->cache_data, &new_rec, sizeof(new_rec));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenint mail_cache_transaction_commit(struct mail_cache_transaction_ctx *ctx)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* make sure everything's written before updating offsets */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "fdatasync()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen for (i = 0; i < MAIL_CACHE_HEADERS_COUNT; i++) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (ctx->next_unused_header_lowwater == MAIL_CACHE_HEADERS_COUNT) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* they're all used - compress the cache to get more */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenvoid mail_cache_transaction_rollback(struct mail_cache_transaction_ctx *ctx)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int i;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buf = buffer_get_data(ctx->reservations, &size);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* free flushed data as well. do it from end to beginning so
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen we have a better chance of updating used_file_size instead
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen of adding holes */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen } while (size > 0);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* make sure we don't cache the headers */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen for (i = 0; i < ctx->next_unused_header_lowwater; i++) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen uint32_t offset = cache->hdr->header_offsets[i];
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic const char *write_header_string(const char *const headers[],
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen buffer = buffer_create_dynamic(pool_datastack_create(),
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen buffer_append(buffer, *headers, strlen(*headers));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenint mail_cache_set_header_fields(struct mail_cache_transaction_ctx *ctx,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int idx, const char *const headers[])
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_assert(idx >= ctx->next_unused_header_lowwater);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_assert(mail_cache_offset_to_uint32(cache->hdr->
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen header_str = write_header_string(headers, &size);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen prev_str = mail_cache_get_header_fields_str(cache, idx-1);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen offset = mail_cache_transaction_get_space(ctx, total_size, total_size,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (pwrite_full(cache->fd, &size, sizeof(size), offset) < 0 ||
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pwrite_full()");
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* update cached headers */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen cache->split_offsets[idx] = cache->hdr->header_offsets[idx];
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* make sure get_header_fields() still works for this header
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen while the transaction isn't yet committed. */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenmail_cache_transaction_get_insert_pos(struct mail_cache_transaction_ctx *ctx,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned int mask;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen data = buffer_get_data(ctx->cache_data, NULL);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen cache_rec = CONST_PTR_OFFSET(data, ctx->prev_pos);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen for (i = 0, mask = 1; i < 31; i++, mask <<= 1) {
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen memcpy(&data_size, CONST_PTR_OFFSET(data, pos),
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenvoid mail_cache_add(struct mail_cache_transaction_ctx *ctx, uint32_t seq,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen unsigned char *buf;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen i_assert(mail_cache_field_sizes[field_idx] == data_size);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen } else if ((field & MAIL_CACHE_STRING_MASK) != 0) {
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen i_assert(((char *) data)[data_size-1] == '\0');
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* remember roughly what we have modified, so cache lookups can
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen look into transactions to see changes. */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (seq < ctx->first_seq || ctx->first_seq == 0)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (buffer_get_used_size(ctx->cache_data) + full_size >
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* time to flush our buffer */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen /* fields must be ordered. find where to insert it. */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen pos = mail_cache_transaction_get_insert_pos(ctx, field);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen cache_rec = buffer_get_space_unsafe(ctx->cache_data, ctx->prev_pos,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* @UNSAFE */
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen buf = buffer_get_space_unsafe(ctx->cache_data, pos, full_size);
a40d26f83af808a0ea1e212c001d682a96d870b0Timo Sirainen memcpy(buf, &data_size32, sizeof(data_size32));
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen memcpy(buf, data, data_size); buf += data_size;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenint mail_cache_update_record_flags(struct mail_cache_view *view, uint32_t seq,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenint mail_cache_link(struct mail_cache *cache, uint32_t old_offset,
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (new_offset + sizeof(struct mail_cache_record) >
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen "Cache record offset %u points outside file",
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen new_offset += offsetof(struct mail_cache_record, prev_offset);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen mail_cache_set_syscall_error(cache, "pwrite_full()");
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainenint mail_cache_delete(struct mail_cache *cache, uint32_t offset)
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen cache_rec = mail_cache_get_record(cache, offset);
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen /* we'll only update the deleted_space in header. we can't really
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen do any actual deleting as other processes might still be using
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen the data. also it's actually useful as some index views are still
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen able to ask cached data from messages that have already been
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen cache->hdr_copy.deleted_space += cache_rec->size;