Last active
July 17, 2020 17:04
-
-
Save jakeogh/2f51ac3572443f827033790d777f4d1a to your computer and use it in GitHub Desktop.
Revert ZFS changes by destroying uberblocks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# -*- coding: utf-8 -*- | |
# Script for reverting ZFS changes by destroying uberblocks | |
# Author: Martin Vool | |
# E-mail: [email protected] | |
# Version: 0.1 | |
# Date: 16 November 2009 | |
import time | |
import subprocess | |
import sys | |
import os | |
from pathlib import Path | |
import click | |
def eprint(*args, **kwargs): | |
print(*args, file=sys.stderr, **kwargs) | |
def verify(thing): | |
if not thing: | |
raise ValueError(thing) | |
def get_block_device_size(device): | |
assert Path(device).is_block_device() | |
fd = os.open(device, os.O_RDONLY) | |
try: | |
return os.lseek(fd, 0, os.SEEK_END) | |
finally: | |
os.close(fd) | |
@click.command() | |
@click.argument("device", type=click.Path(exists=True, dir_okay=False)) | |
@click.option("--bs", type=int, default=512, help="blocksize") # todo sanity check | |
@click.option("--tb", type=int, required=True, help="total device size in blocks") | |
@click.option("--verbose", is_flag=True) | |
def main(device, bs, tb, verbose): | |
device_size_bytes = get_block_device_size(device) | |
verify(device_size_bytes % bs == 0) | |
verify(device_size_bytes / bs == tb) | |
eprint("device:", device) | |
eprint("bs:", bs) | |
eprint("tb:", bs) | |
# make solaris use gnu grep. | |
if os.uname()[0] == 'SunOS': | |
grep_cmd = 'ggrep' | |
else: | |
grep_cmd = 'grep' | |
# to format program output | |
def formatstd(inp): | |
verify(isinstance(inp, bytes)) | |
inp = inp.split(b'\n') | |
ret = [] | |
for line in inp: | |
columns = line.split(b' ') | |
nc = [] | |
for c in columns: | |
if c != b'': | |
nc.append(c) | |
ret.append(nc) | |
return ret | |
# read blocks from beginning(64mb) | |
a_count = (256 * bs) | |
# read blocks from end (64mb) | |
l_skip = tb - (256 * bs) | |
if verbose: | |
eprint("a_count:", a_count) | |
eprint("l_skip:", l_skip) | |
eprint('Total of %s blocks' % tb) | |
eprint('Reading from the beginning to %s blocks' % a_count) | |
eprint('Reading from %s blocks to the end' % l_skip) | |
# get the uberblocks from the beginning and end | |
command = 'sync && dd bs=%s if=%s count=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"' | |
yberblocks_a = \ | |
formatstd( | |
subprocess.Popen( | |
command % (bs, device, a_count, grep_cmd, grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0]) | |
command = 'sync && dd bs=%s if=%s skip=%s | od -A x -x | %s -A 2 "b10c 00ba" | %s -v "\-\-"' | |
yberblocks_l = \ | |
formatstd( | |
subprocess.Popen( | |
command % (bs, device, l_skip, grep_cmd, grep_cmd), shell=True, stdout=subprocess.PIPE).communicate()[0]) | |
if verbose: | |
eprint("yberblocks_a:", yberblocks_a) | |
eprint("yberblocks_l:", yberblocks_l) | |
yberblocks = [] | |
for p in yberblocks_a: | |
if len(p) > 0: | |
# format the hex address to decmal so dd would eat it. | |
p[0] = int(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((int(p[0], 16) / bs) + int(l_skip)) # we add until the place we skipped so the adresses will mach. | |
yberblocks.append(p) | |
if verbose: | |
eprint("yberblocks:") | |
for y in yberblocks: | |
eprint(y) | |
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 Exception as e: | |
print(e) | |
aeg = 'none' | |
if txg in koik: | |
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 = list(koik.keys()) | |
keys.sort() | |
if verbose: | |
eprint("keys:", keys) | |
while True: | |
keys = list(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(eval(input('What is the last TXG you wish to keep? (CTRL-c if done)\n'))) | |
verify(save_txg in keys) | |
keys = list(koik.keys()) | |
keys.sort() | |
for k in keys: | |
if k > save_txg: | |
for adress in koik[k]['addresses']: | |
# wrtie zeroes to the unwanted uberblocks | |
command = 'dd bs=%s if=/dev/zero of=%s seek=%s count=1 conv=notrunc' % (bs, device, adress) | |
eprint("command:", command) | |
fmt = formatstd(subprocess.Popen(command, 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 Exception as e: | |
print(e) | |
print('') | |
break | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment