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