Mercurial > dovecot > original-hg > dovecot-1.2
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; +}