jshoward
5/31/2013 - 3:28 PM

Revert ZFS changes by destroying uberblocks

Revert ZFS changes by destroying uberblocks

#!/usr/bin/python
# -*- coding: utf-8 -*-

#Script for reverting ZFS changes by destroying uberblocks
#Author: Martin Vool
#E-mail: mardicas@gmail.com
#Version: 0.1
#Date: 16 November 2009


import time
import subprocess
import sys
import os
#Default blocksize
bs=512
#default total blocks (sorry programming in estonian :-/)
suurus=None

if len(sys.argv) > 2:
    for arg in sys.argv:
        arg=arg.split('=')
        if len(arg) == 1:
            file=arg[0]
        elif arg[0] == '-bs':
            bs=int(arg[1])
        elif arg[0] == '-tb':
            suurus=int(arg[1])
else:
    print 'Usage: zfs_revert.py [-bs=n default:n=512 blocksize] \\n [-tb=n total block size in blocks] [file/device] You have to set -tb'
    exit(1)
print int(bs)
if suurus == None:
    print 'Total block size in blocks is undefined'
    exit(1)
#make solaris use gnu grep.
if os.uname()[0] == 'SunOS':
    grep_cmd='ggrep'
else:
    grep_cmd='grep'


#to format program output
def formatstd(inp):
    inp=inp.split('\n')
    ret=[]
    for line in inp:
        columns=line.split(' ')
        nc=[]
        for c in columns:
            if c != '':
                nc.append(c)
        ret.append(nc)
    return ret


#read blocks from beginning(64mb)
a_count=(256*bs)
#read blocks from end (64mb)
l_skip=suurus-(256*bs)


print 'Total of %s blocks'%suurus
print 'Reading from the beginning to %s blocks'%a_count
print 'Reading from %s blocks to the end'%l_skip

#get the uberblocks from the beginning and end
yberblocks_a=formatstd(subprocess.Popen('sync && dd bs=%s if=%s count=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"'%(bs,file, a_count,grep_cmd,grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0])
yberblocks_l=formatstd(subprocess.Popen('sync && dd bs=%s if=%s skip=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"'%(bs,file, l_skip,grep_cmd,grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0])


yberblocks=[]

for p in yberblocks_a:
    if len(p) > 0:
        #format the hex address to decmal so dd would eat it.
        p[0]=(int(p[0], 16)/bs)
        yberblocks.append(p)

for p in yberblocks_l:
    if len(p) > 0:
        #format the hex address to decmal so dd would eat it and add the skipped part.
        p[0]=((int(p[0], 16)/bs)+int(l_skip)) #we have to add until the place we skipped so the adresses would mach.
        yberblocks.append(p)
print '----'
#here will be kept the output that you will see later(TXG, timestamp and the adresses, should be 4, might be less)
koik={}
i=0
for p in yberblocks:
    if len(p) > 0:
        if i == 0:#the first output line
            address=p[0]
        elif i == 1:#second output line
            #this is the output of od that is in hex and needs to be reversed
            txg=int(p[4]+p[3]+p[2]+p[1], 16)
        elif i == 2:#third output line
            timestamp=int(p[4]+p[3]+p[2]+p[1], 16)
            try:
                aeg=time.strftime("%d %b %Y %H:%M:%S", time.localtime(timestamp))
            except:
                aeg='none'
            if koik.has_key(txg):
                koik[txg]['addresses'].append(address)
            else:
                koik[txg]={
                    'txg':txg,
                    'timestamp':timestamp,
                    'htime': aeg,
                    'addresses':[address]
                }
        if i == 2:
            i=0
        else:
            i+=1
    keys = koik.keys()
    keys.sort()
    
while True:
    keys = koik.keys()
    keys.sort()
    print 'TXG\tTIME\tTIMESTAMP\tBLOCK ADDRESSES'
    for k in keys:
        print '%s\t%s\t%s\t%s'%(k, koik[k]['htime'],koik[k]['timestamp'],koik[k]['addresses'])
    try:
        save_txg=int(input('What is the last TXG you wish to keep?\n'))
        keys = koik.keys()
        keys.sort()
        for k in keys:
            if k > save_txg:
                for adress in koik[k]['addresses']:
                    #wrtie zeroes to the unwanted uberblocks
                    format=formatstd(subprocess.Popen('dd bs=%s if=/dev/zero of=%s seek=%s count=1 conv=notrunc'%(bs, file, adress), shell=True, stdout=subprocess.PIPE).communicate()[0])
                del(koik[k])
        #sync changes to disc!
        sync=formatstd(subprocess.Popen('sync', shell=True, stdout=subprocess.PIPE).communicate()[0])
    except:
        print ''
        break