#!/usr/bin/env python

# Copyright C 2002-2010 by Isilon Systems, Inc. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
#
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
#
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems.
#
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.

import getopt
import os
import sys
import binascii
import struct
import signal

JD_MAGIC = '6a9e5bcd'
INODE_MAGIC = '6d7c8b9a'
L3_SB_MAGIC = 'fb13acd9'
SPARSE_MAGIC = 'sparse-journal-save-v4'
CHUNK_HEADER_LEN_655 = 12
CHUNK_HEADER_LEN_70 = 16
MAGIC_LEN = 4
SUPERBLOCK_LEN = 512
DATA_BLOCK_LEN = 8192
SPARSE_MAGIC_LEN = len(SPARSE_MAGIC)
header_count = 0
pid = -1

def error(str):
    global pid
    print >> sys.stderr, '%s' % str
    # Signal parent process
    os.kill(int(pid), signal.SIGUSR1)
    sys.exit(1)

def convert_to_7_0_chunk_header(fd):
    try:
        headerbuf = fd.read(CHUNK_HEADER_LEN_655)
        if len(headerbuf) ==  0:
            return ''
        headerbuf += '\0'
        headerbuf += '\0'
        headerbuf += '\0'
        headerbuf += '\0'
        return headerbuf
    except OSError, e:
        error(e)

def verify_chunk_header(fd, header_buf):
    # read data based on the bytes from the header
    # header_buf = 00000000000000000002000000000000
    # header_buf[8:] = 0002000000000000

    global header_count
    magic_buf = ''
    status = 0
    s = struct.Struct('q')
    unpacked_bytes = s.unpack(header_buf[8:])

    # if size is 512 bytes, it should be journal magic or inode magic or L3 superblock
    if unpacked_bytes[0] == SUPERBLOCK_LEN:
        magic_buf = fd.read(MAGIC_LEN)
        if binascii.hexlify(magic_buf) != JD_MAGIC:
            # we may have a L3 superblock
            if binascii.hexlify(magic_buf) != L3_SB_MAGIC:
                # we may have inode block, read next 4 bytes
                inode_magic = fd.read(MAGIC_LEN)
                if binascii.hexlify(inode_magic) != INODE_MAGIC:
                    status = 1
                    print >> sys.stderr, 'isi_convert_journal:Invalid magic 0x%s in %d chunk header' % \
                        (binascii.hexlify(inode_magic), header_count)
                else:
                    # inode block, so club both inode version + mode + magic
                    magic_buf += inode_magic
    elif unpacked_bytes[0] != DATA_BLOCK_LEN:
        # bail out
        print >> sys.stderr, 'unsupported size %d' % unpacked_bytes[0]
        status = 1

    return status, unpacked_bytes[0], magic_buf

def main():
    global header_count
    global pid
    sparse_magic_buf = ''
    header_buf = ''
    magic_buf = ''

    try:
        fd = os.fdopen(sys.stdin.fileno(), 'r', 0)
        sparse_magic_buf = fd.read(SPARSE_MAGIC_LEN+1)
        s = struct.Struct('%ds' % (SPARSE_MAGIC_LEN+1))
        unpacked_sparse_magic = s.unpack(sparse_magic_buf)
        # make sure sparse magic matches
        if unpacked_sparse_magic[0][:SPARSE_MAGIC_LEN] != SPARSE_MAGIC:
            # invalid sparse magic
            error('Invalid Sparse magic %s' % unpacked_sparse_magic[0])

        header_buf = fd.read(CHUNK_HEADER_LEN_655)
        magic_buf = fd.read(MAGIC_LEN)
        # on 6.5.5, chunk header is 12 bytes so next 4 bytes will,
        # always be superblock magic
        if binascii.hexlify(magic_buf) == JD_MAGIC:
            # we have 6.5.5 chunk header
            # write sparse magic
            sys.stdout.write(sparse_magic_buf)
            # write 6_5_5 header and extra 4 bytes to make it 7_0 chunk header
            sys.stdout.write(header_buf)
            sys.stdout.write('\0')
            sys.stdout.write('\0')
            sys.stdout.write('\0')
            sys.stdout.write('\0')
            # write magic for the superblock
            sys.stdout.write(magic_buf)
            # write remaining 508 bytes of the superblock
            rem_data = fd.read(SUPERBLOCK_LEN - MAGIC_LEN)
            if len(rem_data) != (SUPERBLOCK_LEN - MAGIC_LEN):
                error('on-disk journal broken for %d header' % header_count)
            sys.stdout.write(rem_data)

            # we have sent first header and superblock, need to
            # send remaining chunk header + data.
            header_count += 1

            # Go through each chunk header and verify it
            rem_data = ''
            while True:
                # since we have 6.5.5 chunk header, convert it to 7.0 first
                header_buf = convert_to_7_0_chunk_header(fd)
                if len(header_buf) == 0:
                    # we are done, bail out
                    break
                # Verify header
                status, unpacked_bytes, magic_buf = verify_chunk_header(fd, header_buf)
                if status == 1:
                    error('Invalid chunk header %d' % header_count)
                sys.stdout.write(header_buf)
                # magic_buf will contain magic if header points to inode block
                if len(magic_buf) != 0:
                    sys.stdout.write(magic_buf)
                # if no magic_buf means, we have 8192 block
                rem_data = fd.read(unpacked_bytes - len(magic_buf))
                if len(rem_data) != (unpacked_bytes - len(magic_buf)):
                    error('on-disk journal broken for %d header' % header_count)
                sys.stdout.write(rem_data)
                header_count += 1
                rem_data = ''
        elif binascii.hexlify(magic_buf) == '00000000':
            # we have 7.0 chunk header
            sys.stdout.write(sparse_magic_buf)

            # verify chunk header, 7.0 header = header_buf + magic_buf = 16 byes
            status, unpacked_bytes, magic = verify_chunk_header(fd, header_buf+magic_buf)
            if status == 1:
                error('Invalid chunk header %d' % header_count)

            sys.stdout.write(header_buf+magic_buf)
            sys.stdout.write(magic)
            rem_data = fd.read(unpacked_bytes - MAGIC_LEN)
            if len(rem_data) != (unpacked_bytes - MAGIC_LEN):
                error('on-disk journal broken for %d header' % header_count);
            sys.stdout.write(rem_data)

            header_count += 1
            # go through each chunk header and verify it
            rem_data = ''
            while True:
                header_buf = fd.read(CHUNK_HEADER_LEN_70)
                if len(header_buf) == 0:
                    break;
                # Verify header
                status, unpacked_bytes, magic = verify_chunk_header(fd, header_buf)
                if status == 1:
                    error('Invalid chunk header %d' % header_count)

                sys.stdout.write(header_buf)
                if len(magic) != 0:
                    sys.stdout.write(magic)
                rem_data = fd.read(unpacked_bytes - len(magic))
                if len(rem_data) != (unpacked_bytes - len(magic)):
                    error('on-disk journal broken for %d header' % header_count);
                sys.stdout.write(rem_data)
                header_count += 1
                rem_data = ''
        else:
            # Invalid chunk header, bail out
            error('Invalid chunk header');

        fd.close()
    except OSError, e:
        error(str(e))

if __name__ == "__main__":
    pid = sys.argv[1]
    main()

