changeset 5366:a42014a7d8be HEAD

Added idxview and logview to dump index/cache/log file contents.
author Timo Sirainen <tss@iki.fi>
date Tue, 20 Mar 2007 17:00:25 +0200
parents 35f18edd5f17
children fab770c51321
files src/util/Makefile.am src/util/idxview.c src/util/logview.c
diffstat 3 files changed, 722 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/src/util/Makefile.am	Mon Mar 19 15:59:20 2007 +0200
+++ b/src/util/Makefile.am	Tue Mar 20 17:00:25 2007 +0200
@@ -1,10 +1,12 @@
 pkglibexecdir = $(libexecdir)/dovecot
 
-pkglibexec_PROGRAMS = rawlog gdbhelper
+pkglibexec_PROGRAMS = rawlog gdbhelper idxview logview
 sbin_PROGRAMS = dovecotpw
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-mail \
+	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/auth
 
 rawlog_LDADD = \
@@ -19,6 +21,18 @@
 gdbhelper_SOURCES = \
 	gdbhelper.c
 
+idxview_LDADD = \
+	../lib/liblib.a
+
+idxview_SOURCES = \
+	idxview.c
+
+logview_LDADD = \
+	../lib/liblib.a
+
+logview_SOURCES = \
+	logview.c
+
 dovecotpw_LDADD = \
 	../auth/libpassword.a \
 	../lib-ntlm/libntlm.a \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util/idxview.c	Tue Mar 20 17:00:25 2007 +0200
@@ -0,0 +1,419 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "hex-binary.h"
+#include "mail-index-private.h"
+#include "mail-cache-private.h"
+#include "mail-transaction-log.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+static struct mail_index_header hdr;
+static ARRAY_DEFINE(extensions, struct mail_index_ext);
+static struct mail_cache_header cache_hdr;
+static ARRAY_DEFINE(cache_fields, struct mail_cache_field);
+static unsigned int cache_ext = (unsigned int)-1;
+static unsigned int cache_search_offset = 0;
+static int cache_fd = -1;
+
+uint32_t mail_index_offset_to_uint32(uint32_t offset)
+{
+	const unsigned char *buf = (const unsigned char *) &offset;
+
+	if ((offset & 0x80808080) != 0x80808080)
+		return 0;
+
+	return (((uint32_t)buf[3] & 0x7f) << 2) |
+		(((uint32_t)buf[2] & 0x7f) << 9) |
+		(((uint32_t)buf[1] & 0x7f) << 16) |
+		(((uint32_t)buf[0] & 0x7f) << 23);
+}
+
+static size_t get_align(size_t name_len)
+{
+	size_t size = sizeof(struct mail_index_ext_header) + name_len;
+	return MAIL_INDEX_HEADER_SIZE_ALIGN(size) - size;
+}
+
+static void dump_hdr(int fd)
+{
+	const struct mail_index_ext_header *ext_hdr;
+	struct mail_index_ext ext;
+	char *base;
+	ssize_t ret;
+	unsigned int i, offset, name_offset;
+
+	ret = read(fd, &hdr, sizeof(hdr));
+	if (ret != sizeof(hdr)) {
+		i_fatal("file hdr read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
+			ret, sizeof(hdr));
+	}
+
+	printf("version = %u.%u\n", hdr.major_version, hdr.minor_version);
+	printf("base header size = %u\n", hdr.base_header_size);
+	printf("header size = %u\n", hdr.header_size);
+	printf("record size = %u\n", hdr.record_size);
+	printf("compat flags = %u\n", hdr.compat_flags);
+	printf("index id = %u\n", hdr.indexid);
+	printf("flags = %u\n", hdr.flags);
+	printf("uid validity = %u\n", hdr.uid_validity);
+	printf("next uid = %u\n", hdr.next_uid);
+	printf("messages count = %u\n", hdr.messages_count);
+	printf("recent messages count = %u\n", hdr.recent_messages_count);
+	printf("seen messages count = %u\n", hdr.seen_messages_count);
+	printf("deleted messages count = %u\n", hdr.deleted_messages_count);
+	printf("first recent uid lowwater = %u\n", hdr.first_recent_uid_lowwater);
+	printf("first unseen uid lowwater = %u\n", hdr.first_unseen_uid_lowwater);
+	printf("first deleted uid lowwater = %u\n", hdr.first_deleted_uid_lowwater);
+	printf("log file seq = %u\n", hdr.log_file_seq);
+	printf("log file int offset = %u\n", hdr.log_file_int_offset);
+	printf("log file ext offset = %u\n", hdr.log_file_ext_offset);
+	printf("sync size = %llu\n", (unsigned long long)hdr.sync_size);
+	printf("sync stamp = %u\n", hdr.sync_stamp);
+	printf("day stamp = %u\n", hdr.day_stamp);
+	for (i = 0; i < 8; i++)
+		printf("day first uid[%u] = %u\n", i, hdr.day_first_uid[i]);
+
+	i_array_init(&extensions, 16);
+	offset = MAIL_INDEX_HEADER_SIZE_ALIGN(hdr.base_header_size);
+	if (offset >= hdr.header_size) {
+		printf("no extensions\n");
+		return;
+	}
+
+	base = i_malloc(hdr.header_size);
+	ret = pread(fd, base, hdr.header_size, 0);
+	if (ret != hdr.header_size) {
+		i_fatal("file hdr read() %"PRIuSIZE_T" != %u",
+			ret, hdr.header_size);
+	}
+
+	memset(&ext, 0, sizeof(ext)); i = 0;
+	while (offset < hdr.header_size) {
+		ext_hdr = CONST_PTR_OFFSET(base, offset);
+
+		offset += sizeof(*ext_hdr);
+		name_offset = offset;
+		offset += ext_hdr->name_size + get_align(ext_hdr->name_size);
+
+		ext.name = i_strndup(CONST_PTR_OFFSET(base, name_offset),
+				     ext_hdr->name_size);
+		ext.record_offset = ext_hdr->record_offset;
+		ext.record_size = ext_hdr->record_size;
+		ext.record_align = ext_hdr->record_align;
+
+		if (strcmp(ext.name, "cache") == 0)
+                        cache_ext = i;
+
+		printf("-- Extension %u --\n", i);
+		printf("name: %s\n", ext.name);
+		printf("hdr_size: %u\n", ext_hdr->hdr_size);
+		printf("reset_id: %u\n", ext_hdr->reset_id);
+		printf("record_offset: %u\n", ext_hdr->record_offset);
+		printf("record_size: %u\n", ext_hdr->record_size);
+		printf("record_align: %u\n", ext_hdr->record_align);
+		printf("name_size: %u\n", ext_hdr->name_size);
+
+		offset += MAIL_INDEX_HEADER_SIZE_ALIGN(ext_hdr->hdr_size);
+		array_append(&extensions, &ext, 1);
+		i++;
+	}
+}
+
+static void dump_cache_hdr(int fd)
+{
+        struct mail_cache_header_fields fields;
+	struct mail_cache_field field;
+	uint32_t field_offset, next_offset;
+	char *buf;
+	ssize_t ret;
+	const uint32_t *last_used, *size;
+	const uint8_t *type, *decision;
+	const char *names;
+	unsigned int i;
+
+	ret = read(fd, &cache_hdr, sizeof(cache_hdr));
+	if (ret != sizeof(cache_hdr)) {
+		i_fatal("cache file hdr read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
+			ret, sizeof(cache_hdr));
+	}
+
+	field_offset =
+		mail_index_offset_to_uint32(cache_hdr.field_header_offset);
+
+	printf("Cache header:\n");
+	printf("version: %u\n", cache_hdr.version);
+	printf("indexid: %u\n", cache_hdr.indexid);
+	printf("file_seq: %u\n", cache_hdr.file_seq);
+	printf("continued_record_count: %u\n", cache_hdr.continued_record_count);
+	printf("hole_offset: %u\n", cache_hdr.hole_offset);
+	printf("used_file_size: %u\n", cache_hdr.used_file_size);
+	printf("deleted_space: %u\n", cache_hdr.deleted_space);
+	printf("field_header_offset: %u / %u\n",
+	       cache_hdr.field_header_offset, field_offset);
+
+	for (;;) {
+		ret = pread(fd, &fields, sizeof(fields), field_offset);
+		if (ret != sizeof(fields)) {
+			i_fatal("cache file fields read() %"
+				PRIuSIZE_T" != %"PRIuSIZE_T,
+				ret, sizeof(fields));
+		}
+
+		next_offset =
+			mail_index_offset_to_uint32(fields.next_offset);
+		if (next_offset == 0)
+			break;
+
+		field_offset = next_offset;
+	}
+
+	printf("-- Cache fields: --\n");
+	printf("actual used header offset: %u\n", field_offset);
+
+	buf = i_malloc(fields.size);
+	ret = pread(fd, buf, fields.size, field_offset);
+	if (ret != fields.size) {
+		i_fatal("cache file fields read() %"PRIuSIZE_T" != %u",
+			ret, fields.size);
+	}
+
+	last_used = CONST_PTR_OFFSET(buf, MAIL_CACHE_FIELD_LAST_USED());
+	size = CONST_PTR_OFFSET(buf, MAIL_CACHE_FIELD_SIZE(fields.fields_count));
+	type = CONST_PTR_OFFSET(buf, MAIL_CACHE_FIELD_TYPE(fields.fields_count));
+	decision = CONST_PTR_OFFSET(buf, MAIL_CACHE_FIELD_DECISION(fields.fields_count));
+	names = CONST_PTR_OFFSET(buf, MAIL_CACHE_FIELD_NAMES(fields.fields_count));
+
+	i_array_init(&cache_fields, 64);
+	memset(&field, 0, sizeof(field));
+	for (i = 0; i < fields.fields_count; i++) {
+		field.name = names;
+
+		field.field_size = size[i];
+		field.type = type[i];
+		field.decision = decision[i];
+		array_append(&cache_fields, &field, 1);
+
+		printf("%u: name=%s size=%u type=%u decision=%u last_used=%u\n",
+		       i, names, size[i], type[i], decision[i], last_used[i]);
+		names += strlen(names) + 1;
+	}
+}
+
+static void dump_cache(uint32_t offset)
+{
+	const struct mail_cache_field *fields;
+	struct mail_cache_record rec;
+	ssize_t ret;
+	char *buf;
+	unsigned int idx, size, pos, next_pos, cache_fields_count;
+	string_t *str;
+
+	if (offset == 0 || cache_fd == -1)
+		return;
+
+	ret = pread(cache_fd, &rec, sizeof(rec), offset);
+	if (ret != sizeof(rec)) {
+		printf(" - cache at %u BROKEN: points outside file\n", offset);
+		return;
+	}
+
+	if (rec.size > 1000000) {
+		printf(" - cache at %u BROKEN: rec.size = %u\n",
+		       offset, rec.size);
+		return;
+	}
+
+	if (offset <= cache_search_offset &&
+	    offset + rec.size > cache_search_offset)
+		printf(" - SEARCH MATCH\n");
+
+	buf = t_malloc(rec.size);
+	ret = pread(cache_fd, buf, rec.size, offset);
+	if (ret != rec.size)
+		i_fatal("cache rec read() %"PRIuSIZE_T" != %u", ret, rec.size);
+	printf(" - cache at %u + %u (prev_offset = %u)\n",
+	       offset, rec.size, rec.prev_offset);
+
+	fields = array_get(&cache_fields, &cache_fields_count);
+	str = t_str_new(512);
+	for (pos = sizeof(rec); pos < rec.size; ) {
+		idx = *((const uint32_t *)(buf+pos));
+		pos += sizeof(uint32_t);
+
+		if (idx >= cache_fields_count) {
+			printf("BROKEN: file_field = %u > %u\n",
+			       idx, cache_fields_count);
+			return;
+		}
+
+		size = fields[idx].field_size;
+		if (size == (unsigned int)-1) {
+			size = *((const uint32_t *)(buf+pos));
+			pos += sizeof(uint32_t);
+		}
+
+		next_pos = pos + ((size + 3) & ~3);
+		if (size > rec.size || next_pos > rec.size) {
+			printf("BROKEN: record continues outside its allocated size\n");
+			return;
+		}
+
+		str_truncate(str, 0);
+		str_printfa(str, "    - %s: ", fields[idx].name);
+		switch (fields[idx].type) {
+		case MAIL_CACHE_FIELD_FIXED_SIZE:
+			if (size == sizeof(uint32_t)) {
+				str_printfa(str, "%u", *((const uint32_t *)(buf+pos)));
+				break;
+			}
+		case MAIL_CACHE_FIELD_VARIABLE_SIZE:
+		case MAIL_CACHE_FIELD_BITMASK:
+			str_printfa(str, " (%s)", binary_to_hex((const unsigned char *)buf+pos, size));
+			break;
+		case MAIL_CACHE_FIELD_STRING:
+			if (size > 0)
+				str_printfa(str, "%.*s", (int)size, buf+pos);
+			break;
+		case MAIL_CACHE_FIELD_HEADER: {
+			const uint32_t *lines = (void *)(buf + pos);
+			int i;
+
+			for (i = 0;; i++) {
+				if (size < sizeof(uint32_t)) {
+					if (i == 0 && size == 0) {
+						/* header doesn't exist */
+						break;
+					}
+
+					str_append(str, "\n - BROKEN: header field doesn't end with 0 line");
+					size = 0;
+					break;
+				}
+
+				size -= sizeof(uint32_t);
+				pos += sizeof(uint32_t);
+				if (lines[i] == 0)
+					break;
+
+				if (i > 0)
+					str_append(str, ", ");
+				str_printfa(str, "%u", lines[i]);
+			}
+
+			if (i == 1 && size > 0 && buf[pos+size-1] == '\n') size--;
+			if (size > 0)
+				str_printfa(str, ": %.*s", (int)size, buf+pos);
+			break;
+		}
+		case MAIL_CACHE_FIELD_COUNT:
+			i_unreached();
+			break;
+		}
+
+		printf("%s\n", str_c(str));
+		pos = next_pos;
+	}
+
+	dump_cache(rec.prev_offset);
+}
+
+static int dump_record(int fd, void *buf, unsigned int seq)
+{
+	off_t offset;
+	ssize_t ret;
+	const struct mail_index_record *rec = buf;
+	const struct mail_index_ext *ext;
+	const void *ptr;
+	unsigned int i, ext_count;
+	string_t *str;
+
+	ret = read(fd, buf, hdr.record_size);
+	if (ret == 0)
+		return 0;
+
+	if (ret != hdr.record_size) {
+		i_fatal("rec hdr read() %"PRIuSIZE_T" != %u",
+			ret, hdr.record_size);
+	}
+
+	offset = lseek(fd, 0, SEEK_CUR);
+
+	printf("RECORD: offset=%"PRIuUOFF_T", seq=%u, uid=%u, flags=%x\n",
+	       offset, seq, rec->uid, rec->flags);
+	str = t_str_new(256);
+	ext = array_get(&extensions, &ext_count);
+	for (i = 0; i < ext_count; i++) {
+		str_truncate(str, 0);
+		str_printfa(str, " - ext %s(%u): ", ext[i].name, i);
+
+		ptr = CONST_PTR_OFFSET(buf, ext[i].record_offset);
+		if (ext[i].record_size == sizeof(uint32_t) &&
+		    ext[i].record_align == sizeof(uint32_t))
+			str_printfa(str, "%u", *((const uint32_t *)ptr));
+		else if (ext[i].record_size == sizeof(uint64_t) &&
+			 ext[i].record_align == sizeof(uint64_t)) {
+			uint64_t value = *((const uint64_t *)ptr);
+			str_printfa(str, "%llu", (unsigned long long)value);
+		}
+		str_printfa(str, " (%s)", binary_to_hex(ptr, ext[i].record_size));
+		printf("%s\n", str_c(str));
+
+		if (i == cache_ext)
+			dump_cache(*((const uint32_t *)ptr));
+	}
+	return 1;
+}
+
+int main(int argc, const char *argv[])
+{
+	unsigned int seq;
+	void *buf;
+	int fd, ret;
+
+	lib_init();
+
+	if (argc < 2)
+		i_fatal("Usage: idxview dovecot.index [dovecot.index.cache]");
+
+	fd = open(argv[1], O_RDONLY);
+	if (fd < 0) {
+		i_error("open(): %m");
+		return 1;
+	}
+
+	printf("-- INDEX: %s\n", argv[1]);
+
+	dump_hdr(fd);
+	lseek(fd, hdr.header_size, SEEK_SET);
+
+	printf("---------------\n");
+
+	if (argv[2] != NULL) {
+		cache_fd = open(argv[2], O_RDONLY);
+		if (cache_fd < 0) {
+			i_error("open(): %m");
+			return 1;
+		}
+
+		dump_cache_hdr(cache_fd);
+
+		printf("---------------\n");
+
+		if (argv[3] != NULL)
+			cache_search_offset = atoi(argv[3]);
+	}
+
+	buf = i_malloc(hdr.record_size);
+	seq = 1;
+	do {
+		t_push();
+		ret = dump_record(fd, buf, seq);
+		t_pop();
+		seq++;
+	} while (ret);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util/logview.c	Tue Mar 20 17:00:25 2007 +0200
@@ -0,0 +1,288 @@
+/* Copyright (C) 2007 Timo Sirainen */
+
+#include "lib.h"
+#include "mail-index-private.h"
+#include "mail-transaction-log.h"
+
+#include <stdio.h>
+
+static struct mail_transaction_ext_intro prev_intro;
+
+uint32_t mail_index_offset_to_uint32(uint32_t offset)
+{
+	const unsigned char *buf = (const unsigned char *) &offset;
+
+	if ((offset & 0x80808080) != 0x80808080)
+		return 0;
+
+	return (((uint32_t)buf[3] & 0x7f) << 2) |
+		(((uint32_t)buf[2] & 0x7f) << 9) |
+		(((uint32_t)buf[1] & 0x7f) << 16) |
+		(((uint32_t)buf[0] & 0x7f) << 23);
+}
+
+static void dump_hdr(int fd)
+{
+	struct mail_transaction_log_header hdr;
+	ssize_t ret;
+
+	ret = read(fd, &hdr, sizeof(hdr));
+	if (ret != sizeof(hdr)) {
+		i_fatal("file hdr read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
+			ret, sizeof(hdr));
+	}
+
+	printf("version = %u.%u\n", hdr.major_version, hdr.minor_version);
+	printf("hdr size = %u\n", hdr.hdr_size);
+	printf("index id = %u\n", hdr.indexid);
+	printf("file seq = %u\n", hdr.file_seq);
+	printf("prev file = %u/%u\n", hdr.prev_file_seq, hdr.prev_file_offset);
+	printf("create stamp = %u\n", hdr.create_stamp);
+}
+
+static const char *log_record_type(unsigned int type)
+{
+	const char *name;
+
+	switch (type & MAIL_TRANSACTION_TYPE_MASK) {
+	case MAIL_TRANSACTION_EXPUNGE|MAIL_TRANSACTION_EXPUNGE_PROT:
+		name = "expunge";
+		break;
+	case MAIL_TRANSACTION_APPEND:
+		name = "append";
+		break;
+	case MAIL_TRANSACTION_FLAG_UPDATE:
+		name = "flag-update";
+		break;
+	case MAIL_TRANSACTION_HEADER_UPDATE:
+		name = "header-update";
+		break;
+	case MAIL_TRANSACTION_EXT_INTRO:
+		name = "ext-intro";
+		break;
+	case MAIL_TRANSACTION_EXT_RESET:
+		name = "ext-reset";
+		break;
+	case MAIL_TRANSACTION_EXT_HDR_UPDATE:
+		name = "ext-hdr";
+		break;
+	case MAIL_TRANSACTION_EXT_REC_UPDATE:
+		name = "ext-rec";
+		break;
+	case MAIL_TRANSACTION_KEYWORD_UPDATE:
+		name = "keyword-update";
+		break;
+	case MAIL_TRANSACTION_KEYWORD_RESET:
+		name = "keyword-reset";
+		break;
+	default:
+		name = t_strdup_printf("unknown: %x", type);
+		break;
+	}
+
+	if (type & MAIL_TRANSACTION_EXTERNAL)
+		name = t_strconcat(name, " (ext)", NULL);
+	return name;
+}
+
+static void print_data(const void *data, size_t size)
+{
+	size_t i;
+
+	for (i = 0; i < size; i++)
+		printf("%02x", ((const unsigned char *)data)[i]);
+	if (size == 4) {
+		const uint32_t *n = (const uint32_t *)data;
+
+		printf(" (dec=%u)", *n);
+	}
+}
+
+static void log_record_print(const struct mail_transaction_header *hdr,
+			     const void *data)
+{
+	unsigned int size = hdr->size - sizeof(*hdr);
+
+	switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
+	case MAIL_TRANSACTION_EXPUNGE|MAIL_TRANSACTION_EXPUNGE_PROT: {
+		const struct mail_transaction_expunge *exp = data;
+
+		printf(" -");
+		for (; size > 0; size -= sizeof(*exp), exp++) {
+			printf(" %u-%u", exp->uid1, exp->uid2);
+		}
+		printf("\n");
+		break;
+	}
+	case MAIL_TRANSACTION_APPEND: {
+		const struct mail_index_record *rec = data;
+
+		printf(" - ");
+		for (; size > 0; size -= sizeof(*rec), rec++) {
+			printf("%u", rec->uid);
+			if (rec->flags != 0)
+				printf(" (flags=%x)", rec->flags);
+			printf(",");
+		}
+		printf("\n");
+		break;
+	}
+	case MAIL_TRANSACTION_FLAG_UPDATE: {
+		const struct mail_transaction_flag_update *u = data;
+
+		for (; size > 0; size -= sizeof(*u), u++) {
+			printf(" - %u-%u (flags +%x-%x)\n", u->uid1, u->uid2,
+			       u->add_flags, u->remove_flags);
+		}
+		break;
+	}
+	case MAIL_TRANSACTION_HEADER_UPDATE: {
+		const struct mail_transaction_header_update *u = data;
+
+		printf(" - offset = %u, size = %u: ", u->offset, u->size);
+		print_data(u + 1, u->size);
+		printf("\n");
+		break;
+	}
+	case MAIL_TRANSACTION_EXT_INTRO: {
+		const struct mail_transaction_ext_intro *intro = data;
+
+		prev_intro = *intro;
+		printf(" - ext_id = %u\n", intro->ext_id);
+		printf(" - reset_id = %u\n", intro->reset_id);
+		printf(" - hdr_size = %u\n", intro->hdr_size);
+		printf(" - record_size = %u\n", intro->record_size);
+		printf(" - record_align = %u\n", intro->record_align);
+		printf(" - name_size = %u\n", intro->name_size);
+		if (intro->name_size > 0) {
+			printf(" - name = '%.*s'\n",
+			       intro->name_size, (const char *)(intro+1));
+		}
+		break;
+	}
+	case MAIL_TRANSACTION_EXT_RESET: {
+		const struct mail_transaction_ext_reset *reset = data;
+
+		printf(" - new_reset_id = %u\n", reset->new_reset_id);
+		break;
+	}
+	case MAIL_TRANSACTION_EXT_HDR_UPDATE:
+		break;
+	case MAIL_TRANSACTION_EXT_REC_UPDATE: {
+		const struct mail_transaction_ext_rec_update *rec = data, *end;
+		size_t record_size;
+
+		end = CONST_PTR_OFFSET(data, size);
+		record_size = (sizeof(*rec) + prev_intro.record_size + 3) & ~3;
+		while (rec < end) {
+			printf(" - %u: ", rec->uid);
+			print_data(rec + 1, prev_intro.record_size);
+			printf("\n");
+			rec = CONST_PTR_OFFSET(rec, record_size);
+		}
+		break;
+	}
+	case MAIL_TRANSACTION_KEYWORD_UPDATE: {
+		const struct mail_transaction_keyword_update *u = data;
+		const uint32_t *uid;
+		unsigned int uid_offset;
+
+		printf(" - modify=%d, name=%.*s, ",
+		       u->modify_type, u->name_size, (const char *)(u+1));
+
+		uid_offset = sizeof(*u) + u->name_size +
+			((u->name_size % 4) == 0 ? 0 : 4 - (u->name_size%4));
+		uid = (const uint32_t *)((const char *)u + uid_offset);
+		size -= uid_offset;
+
+		for (; size > 0; size -= sizeof(*uid)*2, uid += 2) {
+			printf("%u-%u,", uid[0], uid[1]);
+		}
+		printf("\n");
+		break;
+	}
+	case MAIL_TRANSACTION_KEYWORD_RESET: {
+		const struct mail_transaction_keyword_reset *u = data;
+
+		printf(" - ");
+		for (; size > 0; size -= sizeof(*u), u++) {
+			printf("%u-%u, ", u->uid1, u->uid2);
+		}
+		printf("\n");
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static int dump_record(int fd)
+{
+	off_t offset;
+	ssize_t ret;
+	struct mail_transaction_header hdr;
+	unsigned int orig_size;
+
+	offset = lseek(fd, 0, SEEK_CUR);
+
+	ret = read(fd, &hdr, sizeof(hdr));
+	if (ret == 0)
+		return 0;
+
+	if (ret != sizeof(hdr)) {
+		i_fatal("rec hdr read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
+			ret, sizeof(hdr));
+	}
+
+	orig_size = hdr.size;
+	hdr.size = mail_index_offset_to_uint32(hdr.size);
+	if (hdr.size == 0) {
+		printf("record: offset=%"PRIuUOFF_T", "
+		       "type=%s, size=broken (%x)\n",
+		       offset, log_record_type(hdr.type), orig_size);
+		return 0;
+	}
+
+	printf("record: offset=%"PRIuUOFF_T", type=%s, size=%u\n",
+	       offset, log_record_type(hdr.type), hdr.size);
+
+	if (hdr.size < 1024*1024) {
+		unsigned char *buf = t_malloc(hdr.size);
+
+		ret = read(fd, buf, hdr.size - sizeof(hdr));
+		if (ret != (ssize_t)(hdr.size - sizeof(hdr))) {
+			i_fatal("rec data read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
+				ret, hdr.size - sizeof(hdr));
+		}
+		log_record_print(&hdr, buf);
+	} else {
+		lseek(fd, hdr.size - sizeof(hdr), SEEK_CUR);
+	}
+	return 1;
+}
+
+int main(int argc, const char *argv[])
+{
+	int fd;
+
+	lib_init();
+
+	if (argc < 2)
+		i_fatal("Usage: logview dovecot.index.log");
+
+	fd = open(argv[1], O_RDONLY);
+	if (fd < 0) {
+		i_error("open(): %m");
+		return 1;
+	}
+
+	dump_hdr(fd);
+	for (;;) {
+		t_push();
+		if (!dump_record(fd))
+			break;
+		t_pop();
+	}
+	t_pop();
+	return 0;
+}