logview.c revision 85144b5f0bc763de14c7d87291a90ef74ac241a2
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek/* Copyright (c) 2007-2008 Dovecot authors, see the included COPYING file */
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek#include "lib.h"
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek#include "mail-index-private.h"
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek#include "mail-transaction-log.h"
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek#include <stdio.h>
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekstatic struct mail_transaction_ext_intro prev_intro;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekuint32_t mail_index_offset_to_uint32(uint32_t offset)
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek{
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek const unsigned char *buf = (const unsigned char *) &offset;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek if ((offset & 0x80808080) != 0x80808080)
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek return 0;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek return (((uint32_t)buf[3] & 0x7f) << 2) |
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek (((uint32_t)buf[2] & 0x7f) << 9) |
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek (((uint32_t)buf[1] & 0x7f) << 16) |
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek (((uint32_t)buf[0] & 0x7f) << 23);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek}
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekstatic void dump_hdr(int fd, uint64_t *modseq_r)
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek{
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek struct mail_transaction_log_header hdr;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek ssize_t ret;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek ret = read(fd, &hdr, sizeof(hdr));
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek if (ret != sizeof(hdr)) {
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek i_fatal("file hdr read() %"PRIuSIZE_T" != %"PRIuSIZE_T,
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek ret, sizeof(hdr));
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek }
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek if (hdr.hdr_size < sizeof(hdr)) {
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek memset(PTR_OFFSET(&hdr, hdr.hdr_size), 0,
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek sizeof(hdr) - hdr.hdr_size);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek }
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek lseek(fd, hdr.hdr_size, SEEK_SET);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("version = %u.%u\n", hdr.major_version, hdr.minor_version);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("hdr size = %u\n", hdr.hdr_size);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("index id = %u\n", hdr.indexid);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("file seq = %u\n", hdr.file_seq);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("prev file = %u/%u\n", hdr.prev_file_seq, hdr.prev_file_offset);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("create stamp = %u\n", hdr.create_stamp);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek printf("initial modseq = %llu\n",
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek (unsigned long long)hdr.initial_modseq);
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek *modseq_r = hdr.initial_modseq;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek}
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekstatic bool
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekmail_transaction_header_has_modseq(const struct mail_transaction_header *hdr)
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek{
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek switch (hdr->type & MAIL_TRANSACTION_TYPE_MASK) {
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_EXPUNGE | MAIL_TRANSACTION_EXPUNGE_PROT:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek if ((hdr->type & MAIL_TRANSACTION_EXTERNAL) == 0) {
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek /* ignore expunge requests */
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek }
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_APPEND:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_FLAG_UPDATE:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_KEYWORD_UPDATE:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_KEYWORD_RESET:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek /* these changes increase modseq */
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek return TRUE;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek }
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek return FALSE;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek}
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozekstatic const char *log_record_type(unsigned int type)
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek{
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek const char *name;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek switch (type & MAIL_TRANSACTION_TYPE_MASK) {
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_EXPUNGE|MAIL_TRANSACTION_EXPUNGE_PROT:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "expunge";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_APPEND:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "append";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_FLAG_UPDATE:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "flag-update";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_HEADER_UPDATE:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "header-update";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_EXT_INTRO:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "ext-intro";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek case MAIL_TRANSACTION_EXT_RESET:
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek name = "ext-reset";
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek break;
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek case MAIL_TRANSACTION_EXT_HDR_UPDATE:
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek name = "ext-hdr";
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek break;
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek case MAIL_TRANSACTION_EXT_REC_UPDATE:
c0f9f5a0f6d71a1596ee3cef549b4b02295313c3Jakub Hrozek name = "ext-rec";
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek break;
c71e0a6710418991d759a329b8dcb77c7ad3e16eJakub Hrozek 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 print_try_uint(const void *data, size_t size)
{
size_t i;
switch (size) {
case 1: {
const uint8_t *n = data;
printf("%u", *n);
break;
}
case 2: {
const uint16_t *n = data;
printf("%u", *n);
break;
}
case 4: {
const uint32_t *n = data;
printf("%u", *n);
break;
}
case 8: {
const uint64_t *n = data;
printf("%llu", (unsigned long long)*n);
break;
}
default:
for (i = 0; i < size; i++)
printf("%02x", ((const unsigned char *)data)[i]);
}
}
#define HDRF(field) { \
#field, offsetof(struct mail_index_header, field), \
sizeof(((struct mail_index_header *)0)->field) }
static struct {
const char *name;
unsigned int offset, size;
} header_fields[] = {
HDRF(minor_version),
HDRF(base_header_size),
HDRF(header_size),
HDRF(record_size),
HDRF(compat_flags),
HDRF(indexid),
HDRF(flags),
HDRF(uid_validity),
HDRF(next_uid),
HDRF(messages_count),
HDRF(unused_old_recent_messages_count),
HDRF(seen_messages_count),
HDRF(deleted_messages_count),
HDRF(first_recent_uid),
HDRF(first_unseen_uid_lowwater),
HDRF(first_deleted_uid_lowwater),
HDRF(log_file_seq),
HDRF(log_file_tail_offset),
HDRF(log_file_head_offset),
HDRF(sync_size),
HDRF(sync_stamp),
HDRF(day_stamp)
};
static void log_header_update(const struct mail_transaction_header_update *u)
{
const void *data = u + 1;
unsigned int offset = u->offset, size = u->size;
unsigned int i;
while (size > 0) {
/* don't bother trying to handle header updates that include
unknown/unexpected fields offsets/sizes */
for (i = 0; i < N_ELEMENTS(header_fields); i++) {
if (header_fields[i].offset == offset &&
header_fields[i].size <= size)
break;
}
if (i == N_ELEMENTS(header_fields)) {
printf(" - offset = %u, size = %u: ", offset, size);
print_data(data, size);
printf("\n");
break;
}
printf(" - %s = ", header_fields[i].name);
print_try_uint(data, header_fields[i].size);
printf("\n");
data = CONST_PTR_OFFSET(data, header_fields[i].size);
offset += header_fields[i].size;
size -= header_fields[i].size;
}
}
static void log_record_print(const struct mail_transaction_header *hdr,
const void *data, uint64_t *modseq)
{
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;
log_header_update(u);
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(" - flags = %u\n", intro->flags);
printf(" - name_size = %u\n", intro->name_size);
if (intro->name_size > 0) {
const char *name = (const char *)(intro+1);
printf(" - name = '%.*s'\n", intro->name_size, name);
if (*modseq == 0 && intro->name_size == 6 &&
memcmp(name, "modseq", 6) == 0)
*modseq = 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);
printf(" - preserve_data = %u\n", reset->preserve_data);
break;
}
case MAIL_TRANSACTION_EXT_HDR_UPDATE: {
const struct mail_transaction_ext_hdr_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_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, uint64_t *modseq)
{
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",
offset, log_record_type(hdr.type), hdr.size);
if (*modseq > 0 && mail_transaction_header_has_modseq(&hdr)) {
*modseq += 1;
printf(", modseq=%llu", (unsigned long long)*modseq);
}
printf("\n");
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, modseq);
} else {
lseek(fd, hdr.size - sizeof(hdr), SEEK_CUR);
}
return 1;
}
int main(int argc, const char *argv[])
{
uint64_t modseq;
int fd, ret;
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, &modseq);
do {
T_BEGIN {
ret = dump_record(fd, &modseq);
} T_END;
} while (ret > 0);
return 0;
}