/*
 * Copyright 2000-2004 by Hans Reiser, licensing governed by 
 * reiserfsprogs/README
 */
  
#include "debugreiserfs.h"
#include <sys/resource.h>


#define print_usage_and_exit() die ("Usage: %s [-v] [-b filename] device\n\
-v		prints blocks number of every block unpacked\n\
-b filename	saves bitmap of blocks unpacked to filename\n\
-j filename     stores journal in the filename\n", argv[0]);


/* when super block gets unpacked for the first time - create a bitmap
   and mark in it what have been unpacked. Save that bitmap at the end */
reiserfs_bitmap_t * what_unpacked = 0;


int leaves, full;

int verbose = 0;

int Default_journal = 1;

static void unpack_offset (struct packed_item * pi, struct item_head * ih, int blocksize)
{

    if (get_pi_mask(pi) & OFFSET_BITS_64) {
	__u64 v64;

	if (get_ih_key_format (ih) != KEY_FORMAT_2)
	    die ("unpack_offset: key format is not set or wrong");
	fread_le64 (&v64);
	set_offset (KEY_FORMAT_2, &ih->ih_key, v64);
	return;
    }

    if (get_pi_mask(pi) & OFFSET_BITS_32) {
	__u32 v32;

	fread_le32 (&v32);
	set_offset (get_ih_key_format (ih), &ih->ih_key, v32);
	return;
    }

    if ((get_pi_mask(pi) & DIR_ID) == 0 && (get_pi_mask(pi) & OBJECT_ID) == 0) {
	/* offset was not sent, as it can be calculated looking at the
           previous item */
	if (is_stat_data_ih (ih - 1))
	    set_offset (get_ih_key_format (ih), &ih->ih_key, 1);
	if (is_indirect_ih (ih - 1))
	    set_offset (get_ih_key_format (ih), &ih->ih_key, 
			get_offset (&(ih - 1)->ih_key) + get_bytes_number (ih - 1, blocksize));
    }

    // offset is 0
    return;
}


static void unpack_type (struct packed_item * pi, struct item_head * ih)
{
    set_type (get_ih_key_format (ih), &ih->ih_key, get_pi_type(pi));
    if (type_unknown (&ih->ih_key))
	reiserfs_panic ("unpack_type: unknown type %d unpacked for %H\n",
			get_pi_type(pi), ih);
}


/* direntry item comes in the following format: 
   for each entry
      mask - 8 bits
      entry length - 16 bits
      entry itself
      deh_objectid - 32 bits
      	maybe deh_dir_id (32 bits)
	maybe gencounter (16)
	maybe deh_state (16)
*/
static void unpack_direntry (struct packed_item * pi, struct buffer_head * bh,
			     struct item_head * ih, hashf_t hash_func)
{
    __u16 entry_count, namelen, gen_counter, entry_len;
    __u8 mask;
    int i;
    struct reiserfs_de_head * deh;
    int location;
    char * item;

/*    if (!hash_func)
	die ("unpack_direntry: hash function is not set");*/

    if (!(get_pi_mask(pi) & IH_FREE_SPACE))
        die ("ih_entry_count must be packed for directory items");

    entry_count = get_ih_entry_count (ih);
/*    if (!entry_count)
	reiserfs_panic ("unpack_direntry: entry count should be set already");*/

    item = bh->b_data + get_ih_location (ih);
    deh = (struct reiserfs_de_head *)item;
    location = get_pi_item_len(pi);
    for (i = 0; i < entry_count; i ++, deh ++) {
	fread8 (&mask);
	fread_le16 (&entry_len);
	location -= entry_len;
	set_deh_location (deh, location);
	fread (item + location, entry_len, 1, stdin);

	/* find name length */
	if (*(item + location + entry_len - 1))
	    namelen = entry_len;
	else
	    namelen = strlen (item + location);

	fread32 (&deh->deh2_objectid);
	if (mask & HAS_DIR_ID)
	    fread32 (&deh->deh2_dir_id);
	else
	    set_deh_dirid (deh, get_key_objectid (&ih->ih_key));

	if (*(item + location) == '.' && namelen == 1)
	    /* old or new "." */
	    set_deh_offset (deh, DOT_OFFSET);
	else if (*(item + location) == '.' && *(item + location + 1) == '.' && namelen == 2)
	    /* old or new ".." */
	    set_deh_offset (deh, DOT_DOT_OFFSET);
	else if (hash_func)
	    set_deh_offset (deh, hash_value(hash_func, item + location, namelen));
	if (mask & HAS_GEN_COUNTER) {
	    fread_le16 (&gen_counter);
	    set_deh_offset (deh, get_deh_offset (deh) | gen_counter);
	}

	if (mask & HAS_STATE)
	    fread16 (&deh->deh2_state);
	else
	    set_deh_state (deh, (1 << DEH_Visible2));
    }

    return;
}


/* struct packed_item is already unpacked */
static void unpack_stat_data (struct packed_item * pi, struct buffer_head * bh,
			      struct item_head * ih)
{
    if (!(get_pi_mask(pi) & IH_FREE_SPACE)) {
        /* ih_free_space was not packed - set default */
        set_ih_entry_count (ih, 0xffff);
    }

    if (get_ih_key_format (ih) == KEY_FORMAT_1) {
	/* stat data comes in the following format:
	   if this is old stat data:
	   mode - 16 bits
	   nlink - 16 bits
	   size - 32 bits
	   blocks/rdev - 32 bits
	   maybe first_direct byte 32 bits
	*/
	struct stat_data_v1 * sd;

	sd = (struct stat_data_v1 *)B_I_PITEM (bh, ih);
	memset (sd, 0, sizeof (sd));

	fread16 (&sd->sd_mode);
	fread16 (&sd->sd_nlink);
	fread32 (&sd->sd_size);
	fread32 (&sd->u.sd_blocks);
	
	if (get_pi_mask(pi) & WITH_SD_FIRST_DIRECT_BYTE) {
	    fread32 (&sd->sd_first_direct_byte);
	} else {
	    sd->sd_first_direct_byte = 0xffffffff;
	}
    } else {
	/* for new stat data
	   mode - 16 bits
	   nlink in either 16 or 32 bits
	   size in either 32 or 64 bits
	   blocks - 32 bits
	*/
	struct stat_data * sd;

	sd = (struct stat_data *)B_I_PITEM (bh, ih);
	memset (sd, 0, sizeof (sd));
	
	fread16 (&sd->sd_mode);

	if (get_pi_mask(pi) & NLINK_BITS_32) {
	    fread32 (&sd->sd_nlink);
	} else {
	    __u16 nlink16;

	    fread16 (&nlink16);
            set_sd_v2_nlink (sd, le16_to_cpu(nlink16));
	}

	if (get_pi_mask(pi) & SIZE_BITS_64) {
	    fread64 (&sd->sd_size);
	} else {
	    __u32 size32;

            /* We need the endian conversions since sd->sd_size is 64 bit */
	    fread_le32 (&size32);
            set_sd_v2_size (sd, size32 );
	}

	fread32 (&sd->sd_blocks);
    }

    return;
}


/* indirect item comes either in packed form or as is. ih_free_space
   can go first */
static void unpack_indirect (struct packed_item * pi, struct buffer_head * bh,
			     struct item_head * ih)
{
    __u32 * ind_item, * end;
    int i;
    __u16 v16;

    if (!(get_pi_mask(pi) & IH_FREE_SPACE)) {
        /* ih_free_space was not packed - set default */
        set_ih_entry_count (ih, 0);
    }

    ind_item = (__u32 *)B_I_PITEM (bh, ih);

    if (get_pi_mask(pi) & SAFE_LINK) {
	d32_put(ind_item, 0, get_key_dirid(&ih->ih_key));
	set_key_dirid(&ih->ih_key, (__u32)-1);
	return;
    }

    if (get_pi_mask(pi) & WHOLE_INDIRECT) {
	fread (ind_item, get_pi_item_len(pi), 1, stdin);
	return;
    }

    end = ind_item + I_UNFM_NUM (ih);
    while (ind_item < end) {
        __u32 base;
	fread32 (ind_item);
	fread_le16 (&v16);
        base = d32_get(ind_item, 0);
	for (i = 1; i < v16; i ++) {
	    if (base != 0)
		d32_put(ind_item, i, base + i);
	    else
		d32_put(ind_item, i, 0);
	}
	ind_item += i;
    }
    return;
}


// FIXME: we have no way to preserve symlinks
static void unpack_direct (struct packed_item * pi, struct buffer_head * bh,
			   struct item_head * ih)
{
    __u32 * d_item = (__u32 *)B_I_PITEM (bh, ih);

    if (!(get_pi_mask(pi) & IH_FREE_SPACE))
        /* ih_free_space was not packed - set default */
        set_ih_entry_count (ih, 0xffff);

    if (get_pi_mask(pi) & SAFE_LINK) {
	d32_put(d_item, 0, get_key_dirid(&ih->ih_key));
	set_key_dirid(&ih->ih_key, (__u32)-1);
    } else {
	memset (d_item, 'a', get_pi_item_len(pi));
    }
    return;
}


static void unpack_leaf (int dev, hashf_t hash_func, __u16 blocksize)
{
    static int unpacked_leaves = 0;
    struct buffer_head * bh;
    struct packed_item pi;
    struct item_head * ih;
    int i;
    __u16 v16;
    __u32 v32;
    
    /* block number */
    fread_le32 (&v32);


    /* item number */
    fread_le16 (&v16);

    if (verbose)
	reiserfs_warning (stderr, "leaf %d: %d items\n", v32, v16);

    bh = getblk (dev, v32, blocksize);
    if (!bh)
	die ("unpack_leaf: getblk failed");

    set_blkh_nr_items (B_BLK_HEAD (bh), v16);
    set_blkh_level (B_BLK_HEAD (bh), DISK_LEAF_NODE_LEVEL);
    set_blkh_free_space (B_BLK_HEAD (bh), MAX_FREE_SPACE (bh->b_size));
    

    ih = B_N_PITEM_HEAD (bh, 0);
    for (i = 0; i < get_blkh_nr_items (B_BLK_HEAD (bh)); i ++, ih ++) {
#if 0
	fread32 (&v32);
	if (v32 != ITEM_START_MAGIC)
	    die ("unpack_leaf: no start item magic found: block %lu, item %i",
		 bh->b_blocknr, i);
#endif	

	fread (&pi, sizeof (struct packed_item), 1, stdin);
	
	/* dir_id - if it is there */
	if (get_pi_mask(&pi) & DIR_ID) {
	    fread32 (&v32);
            set_key_dirid (&ih->ih_key, le32_to_cpu(v32));
	} else {
	    if (!i)
		die ("unpack_leaf: dir_id is not set");
	    set_key_dirid (&ih->ih_key, get_key_dirid (&(ih - 1)->ih_key));
	}

	/* object_id - if it is there */
	if (get_pi_mask(&pi) & OBJECT_ID) {
	    fread32 (&v32);
            set_key_objectid (&ih->ih_key, le32_to_cpu(v32));
	} else {
	    if (!i)
		die ("unpack_leaf: object_id is not set");
	    set_key_objectid (&ih->ih_key, get_key_objectid (&(ih - 1)->ih_key));
	}

	// we need to set item format before offset unpacking
	set_ih_key_format (ih, (get_pi_mask(&pi) & NEW_FORMAT) ? KEY_FORMAT_2 : KEY_FORMAT_1);

	// offset
	unpack_offset (&pi, ih, bh->b_size);

	/* type */
	unpack_type (&pi, ih);

	/* ih_free_space and ih_format */
	if (get_pi_mask(&pi) & IH_FREE_SPACE) {
	    fread16 (&v16);
	    set_ih_entry_count (ih, le16_to_cpu(v16));
	}

	if (get_pi_mask(&pi) & IH_FORMAT)
	    fread16 (&ih->ih_format);

	/* item length and item location */
	set_ih_item_len (ih, get_pi_item_len(&pi));
	set_ih_location (ih, (i ? get_ih_location (ih - 1) : bh->b_size) - get_pi_item_len(&pi));

	// item itself
	if (is_direct_ih (ih)) {
	    unpack_direct (&pi, bh, ih);
	} else if (is_indirect_ih (ih)) {
	    unpack_indirect (&pi, bh, ih);
	} else if (is_direntry_ih (ih)) {
	    unpack_direntry (&pi, bh, ih, hash_func);
	} else if (is_stat_data_ih (ih)) {
	    unpack_stat_data (&pi, bh, ih);
	}
	set_blkh_free_space (B_BLK_HEAD (bh), get_blkh_free_space (B_BLK_HEAD (bh)) -
			     (IH_SIZE + get_ih_item_len (ih)));

#if 0
	fread32 (&v32);
	if (v32 != ITEM_END_MAGIC)
	    die ("unpack_leaf: no end item magic found: block %lu, item %i",
		 bh->b_blocknr, i);
	if (verbose)
	    reiserfs_warning (stderr, "%d: %H\n", i, ih);
#endif
    }

    fread_le16 (&v16);
    if (v16 != LEAF_END_MAGIC)
	die ("unpack_leaf: wrong end signature found - %x, block %lu", 
	     v16, bh->b_blocknr);

    mark_buffer_uptodate (bh, 1);
    mark_buffer_dirty (bh);
    bwrite (bh);
    /*
    if (!not_data_block (bh->b_blocknr))
	data_blocks_unpacked ++;
    */
    brelse (bh);

    if (what_unpacked)
	reiserfs_bitmap_set_bit (what_unpacked, bh->b_blocknr);
    /*unpacked ++;*/

    if (!(++ unpacked_leaves % 10))
	fprintf (stderr, "#");
}


static void unpack_full_block (int dev, int blocksize)
{
    static int full_blocks_unpacked = 0;
    __u32 block;
    struct buffer_head * bh;

    fread_le32 (&block);

    if (verbose)
	fprintf (stderr, "full #%d\n", block);

    bh = getblk (dev, block, blocksize);
    if (!bh)
	die ("unpack_full_block: getblk failed");

    fread (bh->b_data, bh->b_size, 1, stdin);

    if (who_is_this (bh->b_data, bh->b_size) == THE_SUPER && !what_unpacked) {
	unsigned long blocks;
	struct buffer_head * tmp;
	
	blocks = get_sb_block_count ((struct reiserfs_super_block *)(bh->b_data));
	fprintf (stderr, "There were %lu blocks on the device\n", blocks);
	what_unpacked = reiserfs_create_bitmap (blocks);

	/* make file as long as filesystem is */
	tmp = getblk (dev, blocks - 1, blocksize);
	mark_buffer_dirty (tmp);
	mark_buffer_uptodate (tmp, 0);
	bwrite (tmp);
	brelse (tmp);
    }

    mark_buffer_uptodate (bh, 1);
    mark_buffer_dirty (bh);
    bwrite (bh);
/*
    if (!not_data_block (bh->b_blocknr))
	data_blocks_unpacked ++;
*/
    brelse (bh);

    if (what_unpacked)
	reiserfs_bitmap_set_bit (what_unpacked, block);
    /*unpacked ++;*/

    if (!(++ full_blocks_unpacked % 50))
	fprintf (stderr, ".");
}


/* just skip bitmaps of unformatted nodes */
static void unpack_unformatted_bitmap (int dev, int blocksize)
{
    __u16 bmap_num;
    __u32 block_count;
    int i;
    char * buf;
 
    fread_le16 (&bmap_num);
    fread_le32 (&block_count);
    
    buf = malloc (blocksize);
    if (!buf)
	reiserfs_panic ("unpack_unformatted_bitmap: malloc failed: %m");

    for (i = 0; i < bmap_num; i ++) {
	if (fread (buf, blocksize, 1, stdin) != 1)
	    reiserfs_panic ("unpack_unformatted_bitmap: "
			    "could not read bitmap #%d: %m", i);
    }
    free (buf);
}


// read packed reiserfs partition metadata from stdin
void unpack_partition (int fd, int jfd)
{
    __u32 magic32;
    long position;
    __u16 magic16;
    __u16 blocksize;
    int dev = fd;
    
    fread_le32 (&magic32);
    if (magic32 != REISERFS_SUPER_MAGIC)
	die ("unpack_partition: reiserfs magic number (0x%x) not found - %x\n",
             REISERFS_SUPER_MAGIC, magic32);
    
    fread_le16 (&blocksize);
    
    if (verbose)
	fprintf (stderr, "Blocksize %d\n", blocksize);
    
    while (!feof (stdin)) {
	char c[2];

	fread (c, 1, 1, stdin);
	switch (c[0]) {
	case '.':
	    if (verbose)
		fprintf (stderr, "\".\" skipped\n");
	    continue;

	case '1':
	    fread (c, 1, 1, stdin); /* that was 100%, read in first 0 */
	case '2':
	case '4':
	case '6':
	case '8':
	    fread (c, 1, 1, stdin);
	case '0':
	    fread (c + 1, 1, 1, stdin); /* read % */
		
	    if (c[0] != '0' || c[1] != '%')
		die ("0%% expected\n");

	    if (verbose)
		fprintf (stderr, "0%% skipped\n");
	    continue;
	}

	fread (c + 1, 1, 1, stdin);
	magic16 = le16_to_cpu(*(__u16 *)c);
	/*fread16 (&magic16);*/
	
	switch (magic16 & 0xff) {
	case LEAF_START_MAGIC:
	    leaves ++;
	    unpack_leaf (dev, code2func (magic16 >> 8), blocksize);
	    break;
	    
	case SEPARATED_JOURNAL_START_MAGIC:
	    if (Default_journal)
		die ("file name for separated journal has to be specified");	
	    dev = jfd;
	    break;    

	case SEPARATED_JOURNAL_END_MAGIC:
	    dev = fd;
	    break;
	    
	case FULL_BLOCK_START_MAGIC:
	    full ++;
	    unpack_full_block (dev, blocksize);
	    break;

	case UNFORMATTED_BITMAP_START_MAGIC:
	    fprintf (stderr, "\nBitmap of unformatted - ignored\n");
	    unpack_unformatted_bitmap (dev, blocksize);
	    break;
	    
	case END_MAGIC:
	    goto out;

	default:
	    position = ftell(stdin);
	    if (position == ~(long)0)
		die ("unpack_partition: bad magic found - %x", magic16 & 0xff);
	    else
		die ("unpack_partition: bad magic found - %x, position %lu", 
		    magic16 & 0xff, ftell(stdin));
	}
    }
out:
    fprintf (stderr, "Unpacked %d leaves, %d full blocks\n", leaves, full);


    /*    fclose (block_list);*/
}


int do_unpack(char *host, char *j_filename, char *filename, int verbose) {
    int fd, fdj = -2;
    struct rlimit lim = {RLIM_INFINITY, RLIM_INFINITY};

    if (filename == NULL)
	    filename = ".bitmap";
    
    if (j_filename)
	    Default_journal = 0;
    
    /* with this 2.4.0-test9's file_write does not send SIGXFSZ */
    if (setrlimit (RLIMIT_FSIZE, &lim)) {
	fprintf  (stderr, "sertlimit failed: %m\n");
    }

    if (misc_device_mounted(host) > 0) {
	fprintf(stderr, "%s seems mounted, umount it first\n", host);
	return 0;
    }
  
    fd = open (host, O_RDWR | O_LARGEFILE);
    if (fd == -1) {
	perror ("open failed");
	return 0;
    }

    if (!Default_journal) {
	fdj = open (j_filename, O_RDWR | O_LARGEFILE);
	if (fdj == -1) {
	    perror ("open failed");
	    return 0;
	}
    }

    unpack_partition (fd, fdj);

    if (what_unpacked && filename) {
        FILE * file = open_file(filename, "w+");
        reiserfs_bitmap_save (file,  what_unpacked);
        close_file(file);
    }

    close (fd);
    if (!Default_journal)
	close (fdj);	
    return 0;
}
