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