cyrus-imapd/skiplist.py
2006-02-28 20:04:01 +00:00

173 lines
4.3 KiB
Python

#!/usr/bin/env python
# -*- Mode: Python; tab-width: 4 -*-
#
# Cyrus Imapd Skiplist db recovery tool
#
# Copyright (C) 2004 Gianluigi Tiesi <sherpya@netfarm.it>
# Copyright (C) 2004 NetFarm S.r.l. [http://www.netfarm.it]
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
# ======================================================================
__version__= '0.1'
__doc__="""Cyrus skiplist db recover"""
from sys import argv,exit,stdout,stderr
from struct import unpack
from time import localtime, strftime
### User Conf
debug = 0
###
TIMEFMT ='%a, %d %b %Y %H:%M:%S %z'
PADDING = '\xff' * 4
INORDER = 1
ADD = 2
DELETE = 4
COMMIT = 255
DUMMY = 257
HEADER = -1
MAIN = -2
types = {
1: 'INORDER',
2: 'ADD',
4: 'DELETE',
255: 'COMMIT',
257: 'DUMMY',
-1: 'HEADER',
-2: '*'
}
def log(rtype, text):
global debug
if debug:
out = '[%s] %s\n' % (types[rtype], text)
stdout.write(out)
stdout.flush()
def roundto4(value):
if value % 4:
return ((value / 4) + 1) * 4
return value
def get_header(fp):
#### Magic ??
fp.seek(4)
sign = fp.read(16)
log(HEADER, sign[:-3])
version = unpack('>I', fp.read(4))[0]
version_minor = unpack('>I', fp.read(4))[0]
log(HEADER, 'Version %d,%d' % (version, version_minor))
maxlevel = unpack('>I', fp.read(4))[0]
curlevel = unpack('>I', fp.read(4))[0]
log(HEADER, 'Level %d/%d' % (curlevel, maxlevel))
listsize = unpack('>I', fp.read(4))[0]
log(HEADER, 'List size %d' % listsize)
logstart = unpack('>I', fp.read(4))[0]
log(HEADER, 'Offset %d' % logstart)
lastrecovery = localtime(unpack('>I', fp.read(4))[0])
lastrecovery = strftime(TIMEFMT, lastrecovery)
log(HEADER, 'Last Recovery %s' % lastrecovery)
return { 'version' : [version, version_minor],
'level' : [curlevel, maxlevel],
'listsize' : listsize,
'logstart' : logstart,
'lastrecover': lastrecovery
}
def getkeys(fp):
values = []
keys = {}
keystring = ''
datastring = ''
while 1:
log(MAIN, '-'*78)
stype = fp.read(4)
### EOF
if len(stype) != 4:
break
rtype = unpack('>I', stype)[0]
if not types.has_key(rtype):
log(MAIN, 'Invalid type %d' % rtype)
continue
log(rtype, 'Record type %s' % types[rtype])
if rtype == DELETE:
ptr = unpack('>I', fp.read(4))[0]
log(rtype, 'DELETE %d (0x%x)' % (ptr, ptr))
continue
if rtype == COMMIT:
continue
ksize = unpack('>I', fp.read(4))[0]
log(rtype, 'Key size %d (%d)' % (ksize, roundto4(ksize)))
if ksize:
keystring = fp.read(roundto4(ksize))[:ksize]
log(rtype, 'Key String %s' % keystring)
datasize = unpack('>I', fp.read(4))[0]
log(rtype, 'Data size %d (%d)' % (datasize, roundto4(datasize)))
if datasize:
datastring = fp.read(roundto4(datasize))[:datasize]
log(rtype, 'Data String %s' % datastring)
n = 0
while 1:
str_p = fp.read(4)
if str_p == PADDING:
break
spointer = unpack('>I', str_p)[0]
n = n +1
if spointer: log(rtype, 'Skip pointer %d' % spointer)
log(rtype, 'Total Skip pointers: %d' % n)
if rtype != DUMMY:
if keystring not in values:
values.append(keystring)
keys[keystring] = datastring
return values, keys
if __name__ == '__main__':
if len(argv) != 2:
print 'Usage: %s skiplist.file' % argv[0]
exit()
fp = open(argv[1], 'rb')
header = get_header(fp)
values, keys = getkeys(fp)
fp.close()
if debug: exit()
for v in values:
print '%s\t%s' % (v, keys[v])