static int
mail_transaction_log_file_insert_read(struct mail_transaction_log_file *file,
				      uoff_t offset)
{
	void *data;
	size_t size;
	ssize_t ret;

	size = file->buffer_offset - offset;
	buffer_copy(file->buffer, size, file->buffer, 0, (size_t)-1);

	data = buffer_get_space_unsafe(file->buffer, 0, size);
	ret = pread_full(file->fd, data, size, offset);
	if (ret > 0) {
		/* success */
		file->buffer_offset -= size;
		return 1;
	}

	/* failure. don't leave ourself to inconsistent state */
	buffer_copy(file->buffer, 0, file->buffer, size, (size_t)-1);
	buffer_set_used_size(file->buffer, file->buffer->used - size);

	if (ret == 0) {
		mail_transaction_log_file_set_corrupted(file, "file shrank");
		return 0;
	} else if (errno == ESTALE) {
		/* log file was deleted in NFS server, fail silently */
		return 0;
	} else {
		log_file_set_syscall_error(file, "pread()");
		return -1;
	}
}
static int
log_get_synced_record(struct mail_transaction_log_file *file, uoff_t *offset,
		      const struct mail_transaction_header **hdr_r)
{
	const struct mail_transaction_header *hdr;
	uint32_t trans_size;

	hdr = CONST_PTR_OFFSET(file->buffer->data,
			       *offset - file->buffer_offset);

	/* we've already synced this record at some point. it should
	   be valid. */
	trans_size = mail_index_offset_to_uint32(hdr->size);
	if (trans_size < sizeof(*hdr) ||
	    *offset - file->buffer_offset + trans_size > file->buffer->used) {
		mail_transaction_log_file_set_corrupted(file,
			"Transaction log corrupted unexpectedly at "
			"%"PRIuUOFF_T": Invalid size %u (type=%x)",
			*offset, trans_size, hdr->type);
		return -1;
	}
	*offset += trans_size;
	*hdr_r = hdr;
	return 0;
}
static int
log_file_track_mailbox_sync_offset_hdr(struct mail_transaction_log_file *file,
				       const void *data, unsigned int trans_size)
{
	const struct mail_transaction_header_update *u = data;
	const struct mail_index_header *ihdr;
	const unsigned int size = trans_size - sizeof(struct mail_transaction_header);
	const unsigned int offset_pos =
		offsetof(struct mail_index_header, log_file_tail_offset);
	const unsigned int offset_size = sizeof(ihdr->log_file_tail_offset);
	uint32_t tail_offset;

	i_assert(offset_size == sizeof(tail_offset));

	if (size < sizeof(*u) || size < sizeof(*u) + u->size) {
		mail_transaction_log_file_set_corrupted(file,
			"header update extends beyond record size");
		return -1;
	}

	if (u->offset <= offset_pos &&
	    u->offset + u->size >= offset_pos + offset_size) {
		memcpy(&tail_offset,
		       CONST_PTR_OFFSET(u + 1, offset_pos - u->offset),
		       sizeof(tail_offset));

		if (tail_offset < file->saved_tail_offset) {
			/* ignore shrinking tail offsets */
			return 1;
		} else if (tail_offset > file->sync_offset + trans_size) {
			mail_transaction_log_file_set_corrupted(file,
				"log_file_tail_offset %u goes past sync offset %"PRIuUOFF_T,
				tail_offset, file->sync_offset + trans_size);
		} else {
			file->saved_tail_offset = tail_offset;
			if (tail_offset > file->max_tail_offset)
				file->max_tail_offset = tail_offset;
			return 1;
		}
	}
	return 0;
}
static int
mail_transaction_log_file_map_mmap(struct mail_transaction_log_file *file,
				   uoff_t start_offset)
{
	struct stat st;
	int ret;

	/* we are going to mmap() this file, but it's not necessarily
	   mmaped currently. */
	i_assert(file->buffer_offset == 0 || file->mmap_base == NULL);
	i_assert(file->mmap_size == 0 || file->mmap_base != NULL);

	if (fstat(file->fd, &st) < 0) {
		log_file_set_syscall_error(file, "fstat()");
		return -1;
	}
	file->last_size = st.st_size;

	if ((uoff_t)st.st_size < file->sync_offset) {
		mail_transaction_log_file_set_corrupted(file,
			"file size shrank (%"PRIuUOFF_T" < %"PRIuUOFF_T")",
			(uoff_t)st.st_size, file->sync_offset);
		return 0;
	}

	if (file->buffer != NULL && file->buffer_offset <= start_offset &&
	    (uoff_t)st.st_size == file->buffer_offset + file->buffer->used) {
		/* we already have the whole file mapped */
		if ((ret = mail_transaction_log_file_sync(file)) < 0)
			return 0;
		if (ret > 0)
			return 1;
		/* size changed, re-mmap */
	}

	do {
		mail_transaction_log_file_munmap(file);

		if (file->last_size - start_offset < mmap_get_page_size()) {
			/* just reading the file is probably faster */
			return mail_transaction_log_file_read(file,
							      start_offset,
							      FALSE);
		}

		if (mail_transaction_log_file_mmap(file) < 0)
			return -1;
		if ((ret = mail_transaction_log_file_sync(file)) < 0)
			return 0;
	} while (ret == 0);

	return 1;
}
Exemplo n.º 5
0
void
mail_transaction_log_view_set_corrupted(struct mail_transaction_log_view *view,
					const char *fmt, ...)
{
	va_list va;

	view->broken = TRUE;

	va_start(va, fmt);
	T_BEGIN {
		mail_transaction_log_file_set_corrupted(view->log->head, "%s",
			t_strdup_vprintf(fmt, va));
	} T_END;
	va_end(va);
}
Exemplo n.º 6
0
void mail_transaction_log_indexid_changed(struct mail_transaction_log *log)
{
	struct mail_transaction_log_file *file;

	mail_transaction_logs_clean(log);

	for (file = log->files; file != NULL; file = file->next) {
		if (file->hdr.indexid != log->index->indexid) {
			mail_transaction_log_file_set_corrupted(file,
				"indexid changed: %u -> %u",
				file->hdr.indexid, log->index->indexid);
		}
	}

	if (log->head != NULL &&
	    log->head->hdr.indexid != log->index->indexid) {
		if (--log->head->refcount == 0)
			mail_transaction_log_file_free(&log->head);
		(void)mail_transaction_log_create(log, FALSE);
	}
}
static int
mail_transaction_log_file_read_hdr(struct mail_transaction_log_file *file,
				   bool ignore_estale)
{
        struct mail_transaction_log_file *f;
	int ret;

	i_assert(!MAIL_TRANSACTION_LOG_FILE_IN_MEMORY(file));

	if (file->corrupted)
		return 0;

	ret = mail_transaction_log_file_read_header(file);
	if (ret < 0) {
                if (errno != ESTALE || !ignore_estale)
			log_file_set_syscall_error(file, "pread()");
		return -1;
	}
	if (file->hdr.major_version != MAIL_TRANSACTION_LOG_MAJOR_VERSION) {
		/* incompatible version - fix silently */
		return 0;
	}
	if (ret < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) {
		mail_transaction_log_file_set_corrupted(file,
			"unexpected end of file while reading header");
		return 0;
	}

	if (file->hdr.minor_version >= 2 || file->hdr.major_version > 1) {
		/* we have compatibility flags */
		enum mail_index_header_compat_flags compat_flags = 0;

#if !WORDS_BIGENDIAN
		compat_flags |= MAIL_INDEX_COMPAT_LITTLE_ENDIAN;
#endif
		if (file->hdr.compat_flags != compat_flags) {
			/* architecture change */
			mail_index_set_error(file->log->index,
					     "Rebuilding index file %s: "
					     "CPU architecture changed",
					     file->log->index->filepath);
			return 0;
		}
	}
	if (file->hdr.hdr_size < MAIL_TRANSACTION_LOG_HEADER_MIN_SIZE) {
		mail_transaction_log_file_set_corrupted(file,
			"Header size too small");
		return 0;
	}
	if (file->hdr.hdr_size < sizeof(file->hdr)) {
		/* @UNSAFE: smaller than we expected - zero out the fields we
		   shouldn't have filled */
		memset(PTR_OFFSET(&file->hdr, file->hdr.hdr_size), 0,
		       sizeof(file->hdr) - file->hdr.hdr_size);
	}

	if (file->hdr.indexid == 0) {
		/* corrupted */
		file->corrupted = TRUE;
		mail_index_set_error(file->log->index,
			"Transaction log file %s: marked corrupted",
			file->filepath);
		return 0;
	}
	if (file->hdr.indexid != file->log->index->indexid) {
		if (file->log->index->indexid != 0 &&
		    !file->log->index->initial_create) {
			/* index file was probably just rebuilt and we don't
			   know about it yet */
			mail_transaction_log_file_set_corrupted(file,
				"indexid changed %u -> %u",
				file->log->index->indexid, file->hdr.indexid);
			return 0;
		}

		/* creating index file. since transaction log is created
		   first, use the indexid in it to create the main index
		   to avoid races. */
		file->log->index->indexid = file->hdr.indexid;
	}

	/* make sure we already don't have a file with the same sequence
	   opened. it shouldn't happen unless the old log file was
	   corrupted. */
	for (f = file->log->files; f != NULL; f = f->next) {
		if (f->hdr.file_seq == file->hdr.file_seq) {
			if (strcmp(f->filepath, f->log->head->filepath) != 0) {
				/* old "f" is the .log.2 */
				return mail_transaction_log_file_fail_dupe(f);
			} else {
				/* new "file" is probably the .log.2 */
				return mail_transaction_log_file_fail_dupe(file);
			}
		}
	}

	file->sync_highest_modseq = file->hdr.initial_modseq;
	return 1;
}
static int
mail_transaction_log_file_sync(struct mail_transaction_log_file *file)
{
        const struct mail_transaction_header *hdr;
	const void *data;
	struct stat st;
	size_t size, avail;
	uint32_t trans_size = 0;
	int ret;

	i_assert(file->sync_offset >= file->buffer_offset);

	data = buffer_get_data(file->buffer, &size);
	if (file->buffer_offset + size < file->sync_offset) {
		mail_transaction_log_file_set_corrupted(file,
			"log file shrank (%"PRIuUOFF_T" < %"PRIuUOFF_T")",
			file->buffer_offset + (uoff_t)size, file->sync_offset);
		return -1;
	}
	while (file->sync_offset - file->buffer_offset + sizeof(*hdr) <= size) {
		hdr = CONST_PTR_OFFSET(data, file->sync_offset -
				       file->buffer_offset);
		trans_size = mail_index_offset_to_uint32(hdr->size);
		if (trans_size == 0) {
			/* unfinished */
			return 1;
		}
		if (trans_size < sizeof(*hdr)) {
			mail_transaction_log_file_set_corrupted(file,
				"hdr.size too small (%u)", trans_size);
			return -1;
		}

		if (file->sync_offset - file->buffer_offset + trans_size > size)
			break;

		/* transaction has been fully written */
		if ((ret = log_file_track_sync(file, hdr, trans_size)) <= 0) {
			if (ret < 0)
				return -1;
			break;
		}

		file->sync_offset += trans_size;
	}

	if (file->mmap_base != NULL && !file->locked) {
		/* Now that all the mmaped pages have page faulted, check if
		   the file had changed while doing that. Only after the last
		   page has faulted, the size returned by fstat() can be
		   trusted. Otherwise it might point to a page boundary while
		   the next page is still being written.

		   Without this check we might see partial transactions,
		   sometimes causing "Extension record updated without intro
		   prefix" errors. */
		if (fstat(file->fd, &st) < 0) {
			log_file_set_syscall_error(file, "fstat()");
			return -1;
		}
		if ((uoff_t)st.st_size != file->last_size) {
			file->last_size = st.st_size;
			return 0;
		}
	}

	avail = file->sync_offset - file->buffer_offset;
	if (avail != size) {
		/* There's more data than we could sync at the moment. If the
		   last record's size wasn't valid, we can't know if it will
		   be updated unless we've locked the log. */
		if (file->locked) {
			mail_transaction_log_file_set_corrupted(file,
				"Unexpected garbage at EOF");
			return -1;
		}
		/* The size field will be updated soon */
		mail_index_flush_read_cache(file->log->index, file->filepath,
					    file->fd, file->locked);
	}

	if (file->next != NULL &&
	    file->hdr.file_seq == file->next->hdr.prev_file_seq &&
	    file->next->hdr.prev_file_offset != file->sync_offset) {
		mail_transaction_log_file_set_corrupted(file,
			"Invalid transaction log size "
			"(%"PRIuUOFF_T" vs %u): %s", file->sync_offset,
			file->log->head->hdr.prev_file_offset, file->filepath);
		return -1;
	}

	return 1;
}