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