/*
 * Copyright 2002-2004 by Hans Reiser, licensing governed by 
 * reiserfsprogs/README
 */

#define _GNU_SOURCE

#include "includes.h"
#include "progbar.h"

/* compares description block with commit block. returns 0 if they differ, 1
   if they match */
static int does_desc_match_commit (struct buffer_head *d_bh, 
			    struct buffer_head *c_bh) 
{
    return (get_commit_trans_id (c_bh) == get_desc_trans_id (d_bh) &&
	    get_commit_trans_len (c_bh) == get_desc_trans_len (d_bh));
}


/* d_bh is descriptor, return number of block where commit block of this
   transaction is to be */
unsigned long commit_expected (reiserfs_filsys_t * fs, struct buffer_head * d_bh)
{
    unsigned long offset;
    struct journal_params * sb_jp;


    sb_jp = sb_jp (fs->fs_ondisk_sb);
    //desc = (struct reiserfs_journal_desc *)d_bh->b_data;
    offset = d_bh->b_blocknr - get_jp_journal_1st_block (sb_jp);
    return get_jp_journal_1st_block (sb_jp) + 
	((offset + get_desc_trans_len (d_bh) + 1) % get_jp_journal_size (sb_jp));
}


/* d_bh contains journal descriptor, returns number of block where descriptor
   block of next transaction should be */
unsigned long next_desc_expected (reiserfs_filsys_t * fs, struct buffer_head * d_bh)
{
    unsigned long offset;
    struct journal_params * sb_jp;


    sb_jp = sb_jp (fs->fs_ondisk_sb);
    //desc = (struct reiserfs_journal_desc *)d_bh->b_data;
    offset = d_bh->b_blocknr - get_jp_journal_1st_block (sb_jp);
    return get_jp_journal_1st_block (sb_jp) + 
	((offset + get_desc_trans_len (d_bh) + 2) % get_jp_journal_size (sb_jp));
}

/* common checks for validness of a transaction */
int transaction_check_content (reiserfs_filsys_t * fs, reiserfs_trans_t * trans) {
    struct buffer_head *d_bh, *c_bh;
    struct reiserfs_journal_desc * desc;
    struct reiserfs_journal_commit * commit;
    unsigned long block;
    unsigned int trans_half, i;

    d_bh = bread (fs->fs_journal_dev, trans->desc_blocknr, fs->fs_blocksize);

    if (!d_bh || who_is_this (d_bh->b_data, d_bh->b_size) != THE_JDESC)
	goto error_desc_brelse;

    /* read expected commit block and compare with descriptor block */
    c_bh = bread (fs->fs_journal_dev, commit_expected (fs, d_bh), fs->fs_blocksize);
    if (!c_bh)
	goto error_desc_brelse;
 
    if (!does_desc_match_commit (d_bh, c_bh)) 
	goto error_commit_brelse;

    /* Check that all target blocks are journalable */
    desc = (struct reiserfs_journal_desc *)(d_bh->b_data);
    commit = (struct reiserfs_journal_commit *)(c_bh->b_data);

    trans_half = journal_trans_half (d_bh->b_size);
    for (i = 0; i < get_desc_trans_len(d_bh); i++) {
	if (i < trans_half)
	    block = le32_to_cpu (desc->j2_realblock[i]);
	else
	    block = le32_to_cpu (commit->j3_realblock[i - trans_half]);

	if (not_journalable(fs, block)) 
	    goto error_commit_brelse;
    }
    
    brelse (d_bh);
    brelse (c_bh);
    return 1;
    
error_commit_brelse:
    brelse (c_bh);
error_desc_brelse:
    brelse(d_bh);
    return 0;
}

/* common checks for validness of a transaction */
int transaction_check_desc(reiserfs_filsys_t * fs, struct buffer_head * d_bh) {
    struct buffer_head * c_bh;
    int ret = 1;

    if (!d_bh || who_is_this (d_bh->b_data, d_bh->b_size) != THE_JDESC)
	return 0;

    /* read expected commit block and compare with descriptor block */
    c_bh = bread (fs->fs_journal_dev, commit_expected (fs, d_bh), fs->fs_blocksize);
    if (!c_bh)
	return 0;
 
    if (!does_desc_match_commit (d_bh, c_bh)) 
	ret = 0;

    brelse (c_bh);
    return ret;
}


/* read the journal and find the oldest and newest transactions, return number
   of transactions found */
int get_boundary_transactions (reiserfs_filsys_t * fs,
			       reiserfs_trans_t * oldest,
			       reiserfs_trans_t * newest)
{
    struct reiserfs_super_block * sb;
    unsigned long j_cur;
    unsigned long j_start;
    unsigned long j_size;
    struct buffer_head * d_bh;
    __u32 newest_trans_id, oldest_trans_id, trans_id;
    int trans_nr;

    sb = fs->fs_ondisk_sb;
    
    j_start = get_jp_journal_1st_block (sb_jp (sb));
    j_size = get_jp_journal_size (sb_jp (sb));
    
    oldest_trans_id = 0xffffffff;
    newest_trans_id = 0;

    trans_nr = 0;
    for (j_cur = 0; j_cur < j_size; j_cur ++) {
	d_bh = bread (fs->fs_journal_dev, j_start + j_cur, fs->fs_blocksize);
	if (!transaction_check_desc (fs, d_bh)) {
	    brelse (d_bh);
	    continue;
	}

	trans_nr ++;

	trans_id = get_desc_trans_id (d_bh);
	if (trans_id < oldest_trans_id) {
	    oldest_trans_id = trans_id;

	    oldest->mount_id = get_desc_mount_id (d_bh);
	    oldest->trans_id = get_desc_trans_id (d_bh);
	    oldest->desc_blocknr = d_bh->b_blocknr;
	    oldest->trans_len = get_desc_trans_len (d_bh);
	    oldest->commit_blocknr = commit_expected (fs, d_bh);
	    oldest->next_trans_offset = next_desc_expected (fs, d_bh) - j_start;
	}

	if (trans_id > newest_trans_id) {
	    newest_trans_id = trans_id;

	    newest->mount_id = get_desc_mount_id (d_bh);
	    newest->trans_id = get_desc_trans_id (d_bh);
	    newest->desc_blocknr = d_bh->b_blocknr;
	    newest->trans_len = get_desc_trans_len (d_bh);
	    newest->commit_blocknr = commit_expected (fs, d_bh);
	    newest->next_trans_offset = next_desc_expected (fs, d_bh) - j_start;
	}

	j_cur += get_desc_trans_len (d_bh) + 1;
	brelse (d_bh);
    }

    return trans_nr;
}

#define TRANS_FOUND     1
#define TRANS_NOT_FOUND 0

/* trans is a valid transaction. Look for valid transaction with smallest
   trans id which is greater than the id of the current one */
int next_transaction (reiserfs_filsys_t * fs, reiserfs_trans_t * trans, reiserfs_trans_t break_trans)
{
    struct buffer_head * d_bh, * next_d_bh;
    int found;
    unsigned long j_start;
    unsigned long j_offset;
    unsigned long block;


    j_start = get_jp_journal_1st_block (sb_jp (fs->fs_ondisk_sb));

    found = TRANS_NOT_FOUND;

    if (trans->trans_id == break_trans.trans_id)
	return found;
	
    /* make sure that 'trans' is a valid transaction */
    d_bh = bread (fs->fs_journal_dev, trans->desc_blocknr, fs->fs_blocksize);
    if (!transaction_check_desc (fs, d_bh))
	die ("next_transaction: valid transaction is expected");

    block = next_desc_expected (fs, d_bh);
    j_offset = block - j_start;

    while (1) {
	next_d_bh = bread (fs->fs_journal_dev, block, fs->fs_blocksize);
	if (transaction_check_desc (fs, next_d_bh))
	    break;

	brelse (next_d_bh);
	j_offset ++;
	block = j_start + (j_offset % get_jp_journal_size (sb_jp (fs->fs_ondisk_sb)));
    }

    //next_desc = (struct reiserfs_journal_desc *)next_d_bh->b_data;
    
    if (break_trans.trans_id >= get_desc_trans_id (next_d_bh)) {
	/* found transaction is newer */
	trans->mount_id = get_desc_mount_id (next_d_bh);
	trans->trans_id = get_desc_trans_id (next_d_bh);
	trans->desc_blocknr = next_d_bh->b_blocknr;
	trans->trans_len = get_desc_trans_len (next_d_bh);
	trans->commit_blocknr = commit_expected (fs, next_d_bh);
	trans->next_trans_offset = next_desc_expected (fs, next_d_bh) - j_start;
	found = TRANS_FOUND;
    }

    brelse (d_bh);
    brelse (next_d_bh);
    return found;
}

static void read_journal_write_in_place (reiserfs_filsys_t * fs, reiserfs_trans_t * trans, unsigned int index, 
                                         unsigned long in_journal, unsigned long in_place)
{
    struct buffer_head * j_bh, * bh;

    j_bh = bread (fs->fs_journal_dev, in_journal, fs->fs_blocksize);
    if (!j_bh) {
	fprintf (stderr, "replay_one_transaction: transaction %lu: reading %lu block failed\n",
		 trans->trans_id, in_journal);
	return;
    }
    if (not_journalable (fs, in_place)) {
	fprintf (stderr, "replay_one_transaction: transaction %lu: block %ld should not be journalled (%lu)\n",
		 trans->trans_id, in_journal, in_place);
	brelse (j_bh);
	return;
    }

    bh = getblk (fs->fs_dev, in_place, fs->fs_blocksize);
    
    memcpy (bh->b_data, j_bh->b_data, bh->b_size);
    mark_buffer_dirty (bh);
    mark_buffer_uptodate (bh, 1);
    bwrite (bh);
    brelse (bh);
    brelse (j_bh);
    
}


/* go through all blocks of transaction and call 'action' each of them */
void for_each_block (reiserfs_filsys_t * fs, reiserfs_trans_t * trans,
		     action_on_block_t action)
{
    struct buffer_head * d_bh, * c_bh;
    struct reiserfs_journal_desc * desc;
    struct reiserfs_journal_commit * commit;
    unsigned long j_start, j_offset, j_size;
    unsigned int i, trans_half;
    unsigned long block;
 
    d_bh = bread (fs->fs_journal_dev, trans->desc_blocknr, fs->fs_blocksize);
    if (!d_bh) {
	reiserfs_warning (stdout, "reading descriptor block %lu failed\n", trans->desc_blocknr);
	return;
    }

    c_bh = bread (fs->fs_journal_dev, trans->commit_blocknr, fs->fs_blocksize);
    if (!c_bh) {
	reiserfs_warning (stdout, "reading commit block %lu failed\n", trans->commit_blocknr);
	brelse (d_bh);
	return;
    }

    desc = (struct reiserfs_journal_desc *)(d_bh->b_data);
    commit = (struct reiserfs_journal_commit *)(c_bh->b_data);

    /* first block of journal and size of journal */
    j_start = get_jp_journal_1st_block (sb_jp (fs->fs_ondisk_sb));
    j_size = get_jp_journal_size (sb_jp (fs->fs_ondisk_sb));

    /* offset in the journal where the transaction starts */
    j_offset = trans->desc_blocknr - j_start + 1;

    trans_half = journal_trans_half (d_bh->b_size);
    for (i = 0; i < trans->trans_len; i ++, j_offset ++) {
	if (i < trans_half)
	    block = le32_to_cpu (desc->j2_realblock[i]);
	else
	    block = le32_to_cpu (commit->j3_realblock[i - trans_half]);
	action (fs, trans, i, j_start + (j_offset % j_size), block);
    }

    brelse (d_bh);
    brelse (c_bh);
}


/* transaction is supposed to be valid */
int replay_one_transaction (reiserfs_filsys_t * fs,
			    reiserfs_trans_t * trans)
{
    for_each_block (fs, trans, read_journal_write_in_place);
    fsync(fs->fs_dev);
    return 0;
}


void for_each_transaction (reiserfs_filsys_t * fs, action_on_trans_t action)
{
    reiserfs_trans_t oldest, newest;
    int ret = 0;

    if (!get_boundary_transactions (fs, &oldest, &newest))
	return;

    while (1) {
	action (fs, &oldest);	
	if ((ret = next_transaction (fs, &oldest, newest)) == TRANS_NOT_FOUND)
	    break;
    }
}

unsigned long get_size_of_journal_or_reserved_area(
    struct reiserfs_super_block * sb)
{
	if (is_reiserfs_jr_magic_string (sb))
		return get_sb_reserved_for_journal (sb);

	/* with standard journal */
	return get_jp_journal_size (sb_jp (sb)) + 1;
}


__u32 advise_journal_max_trans_len (__u32 desired, __u32 journal_size /* no j_header */, 
    int blocksize, int verbose)
{
    __u32 saved;
    __u32 ratio = 1;

    if (blocksize < 4096)
	ratio = 4096/blocksize;
	
    saved = desired;
    if (!desired)
		desired = JOURNAL_TRANS_MAX/ratio;
    
    if (journal_size / desired < JOURNAL_MIN_RATIO)
		desired = journal_size / JOURNAL_MIN_RATIO;
    
    if (desired > JOURNAL_TRANS_MAX/ratio)
		desired = JOURNAL_TRANS_MAX/ratio;
    
    if (desired < JOURNAL_TRANS_MIN/ratio)
		desired = JOURNAL_TRANS_MIN/ratio;

    if (verbose) {
	if (saved && saved != desired)
		reiserfs_warning (stderr,
		    "WARNING: wrong transaction max size (%u). Changed to %u\n", 
		    saved, desired);
    }

    return desired;
}
#if 0
    __u32 ret_val;
    ret_val = 0;
    if (!desired)                   ret_val = JOURNAL_TRANS_MAX;
    if (desired<journal_size/8)     ret_val = journal_size/8;
    if (desired>journal_size/2)     ret_val = journal_size/2;
    if (desired>JOURNAL_TRANS_MAX)  ret_val = JOURNAL_TRANS_MAX;
    if (ret_val) {
        reiserfs_warning (stderr, "WARNING: Journal max trans length is wrong seting: %u, resetting to available possible %u\n",
                          desired, ret_val);
    } else {
        ret_val = desired;
    }
    return ret_val;
}
#endif

__u32 advise_journal_max_batch (unsigned long journal_trans_max)
{
    return journal_trans_max*JOURNAL_MAX_BATCH/JOURNAL_TRANS_MAX;
}


__u32 advise_journal_max_commit_age (void)
{
    return JOURNAL_MAX_COMMIT_AGE;
}


__u32 advise_journal_max_trans_age (void)
{
    return JOURNAL_MAX_TRANS_AGE;
}


int reiserfs_journal_params_check (reiserfs_filsys_t * fs) {
    struct reiserfs_journal_header * j_head;
    struct reiserfs_super_block * sb = fs->fs_ondisk_sb;
    
    j_head = (struct reiserfs_journal_header *)(fs->fs_jh_bh->b_data);
	
    /* Check the superblock's journal parameters. */
    if (!is_reiserfs_jr_magic_string (sb)) {    
	if (get_jp_journal_dev (sb_jp(sb)) != 0 || 
	    get_jp_journal_1st_block (sb_jp(sb)) != get_journal_start_must (fs) || 
	    get_jp_journal_size (sb_jp(sb)) != 
	    journal_default_size(fs->fs_super_bh->b_blocknr, fs->fs_blocksize))
	{
	    reiserfs_warning (stderr, 
		"\nreiserfs_open_journal: wrong journal parameters found in the "
		"super block. \nYou should run reiserfsck with --rebuild-sb to "
		"check your superblock consistency.\n\n");
		
	    return 1;
	}
    }
	
    if (memcmp(&j_head->jh_journal, sb_jp (sb), sizeof(struct journal_params))) {
	if (!is_reiserfs_jr_magic_string (sb)) {
	    reiserfs_warning (stderr, 
		"\nreiserfs_open_journal: journal parameters from the superblock "
		"does not match \nto the journal headers ones. It looks like that "
		"you created your fs with old\nreiserfsprogs. Journal header is "
		"fixed.\n\n", fs->fs_j_file_name);
		
	    memcpy(&j_head->jh_journal, sb_jp(sb), sizeof(struct journal_params));
	    mark_buffer_dirty(fs->fs_jh_bh);
	    bwrite(fs->fs_jh_bh);
	} else {
	    reiserfs_warning (stderr,
		"\nreiserfs_open_journal: journal parameters from the super block "
		"does not match \nto journal parameters from the journal. You should "
		"run  reiserfsck with --rebuild-sb to check your superblock consistency.\n\n");
		
	    return 1;	
	}
    }
    
    return 0;
}

/* read journal header and make sure that it matches with the filesystem
   opened */
int reiserfs_open_journal (reiserfs_filsys_t * fs, char * j_filename, int flags) {
    struct reiserfs_super_block * sb;
    __u64 count;
    
    sb = fs->fs_ondisk_sb;

    if (!j_filename) {
	/*
        if (is_reiserfs_jr_magic_string (sb)) {
            // create a special file to access journal
            return 1;
        } */
	
	j_filename = fs->fs_file_name;
    } else if (!is_reiserfs_jr_magic_string (sb)) {
        /* make sure that name specified is a correct name */
	if (strcmp (j_filename, fs->fs_file_name)) {
	    reiserfs_warning (stderr, "Filesystem with standard journal found, "
		"wrong name of specified journal device %s \n", j_filename);
	    return 2;
	}
    }

    fs->fs_journal_dev = open (j_filename, flags 
#if defined(O_LARGEFILE)
			       | O_LARGEFILE
#endif
			       );
    if (fs->fs_journal_dev == -1) 
        return -1;
    
    asprintf (&fs->fs_j_file_name, "%s", j_filename);
    
    if (get_jp_journal_size(sb_jp(sb)) < JOURNAL_MIN_SIZE) {
	reiserfs_warning (stderr, "Journal of (%lu) block size found on "
	    "specified journal device %s.\nMust be not less than (%lu).\n",
	    get_jp_journal_size (sb_jp (sb)) + 1, j_filename, 
	    JOURNAL_MIN_SIZE + 1);
	close(fs->fs_journal_dev);
	return 1;
    }
    
    if (!(count = count_blocks (j_filename, fs->fs_blocksize))) {
	close(fs->fs_journal_dev);
	return -1;
    }

    if (get_jp_journal_1st_block (sb_jp (sb)) + 
	get_jp_journal_size (sb_jp (sb)) + 1 > count) 
    {
	reiserfs_warning (stderr, "Detected journal on specified device %s "
			  "does not fit to the device.\nStart block (%lu) + "
			  "size (%lu) less than device size (%lu).\n", 
			  j_filename, get_jp_journal_1st_block(sb_jp (sb)), 
			  get_jp_journal_size(sb_jp (sb)) + 1, count);
	close(fs->fs_journal_dev);
	return 1;
    }
    
    /* read journal header */
    fs->fs_jh_bh = bread (fs->fs_journal_dev, 
			  get_jp_journal_1st_block (sb_jp (sb)) + 
			  get_jp_journal_size (sb_jp (sb)), 
			  fs->fs_blocksize);

    if (!fs->fs_jh_bh) {
	reiserfs_warning (stderr, "reiserfs_open_journal: bread failed "
			  "reading journal  header.\n");
	close(fs->fs_journal_dev);
	return -1;
    }

    return 0;
}

/* initialize super block's journal related fields and journal header fields. 
 * If len is 0 - make journal of default size */
int reiserfs_create_journal(
    reiserfs_filsys_t * fs, 
    char * j_device,		/* journal device name */
    unsigned long offset,	/* journal offset on the j_device */
    unsigned long len,		/* including journal header */
    int transaction_max_size,
    int force)
{
    struct stat st;
    struct buffer_head * bh;
    struct reiserfs_journal_header * jh;
    struct reiserfs_super_block * sb;
    unsigned long blocks;

    sb = fs->fs_ondisk_sb;
    
    if (!j_device || !strcmp (j_device, fs->fs_file_name)) {
	/* Journal is to be on the host device, check the amount space for the 
	 * journal on it. */
	len = len ? len : journal_default_size(fs->fs_super_bh->b_blocknr, 
	    fs->fs_blocksize) + 1;
		
	offset = offset ? offset : get_journal_start_must(fs);
	
	if (offset < get_journal_start_must(fs)) {
	    reiserfs_warning (stderr, "reiserfs_create_journal: offset is "
		"%lu, but it cannot be less then %llu on the device %s\n", 
		offset, get_journal_start_must(fs), j_device);
	    return 0;
	}
	
	if (!is_block_count_correct(offset, fs->fs_blocksize, 
	    get_sb_block_count(sb), len)) 
	{
	    /* host device does not contain enough blocks */
	    reiserfs_warning (stderr, "reiserfs_create_journal: cannot create "
		"a journal of %lu blocks with %lu offset on %u blocks\n",
		len, offset, get_sb_block_count(sb));
		return 0;
	}
	
	j_device = fs->fs_file_name;
	
	
	st.st_rdev = 0;
    } else {
	/* journal is to be on separate device */
	if (!(blocks = count_blocks (j_device, fs->fs_blocksize)))
		return 0;

	if (!len) {
	    /* default size of a journal on a separate device is whole device */
	    if (blocks < offset) {
		reiserfs_warning (stderr, "reiserfs_create_journal: offset is "
		    "%lu, blocks on device %lu\n", offset, blocks);
		return 0;
	    }
	    /* XXX jdm: This can end up being huge and could result
	     * in an unmountable file system:
	     * len = blocks - offset; */
	    len = journal_default_size(fs->fs_super_bh->b_blocknr,
	                               fs->fs_blocksize) + 1;

	}

	if (!force && len > journal_default_size (fs->fs_super_bh->b_blocknr,
	    fs->fs_blocksize) + 1) 
	{
	    unsigned long journal_max = journal_max_size (fs->fs_super_bh->b_blocknr, fs->fs_blocksize);
	    fflush(stderr);
	    
	    reiserfs_warning (stdout, "\n*** You've specified a journal "
	                      "size larger than the default size of "
	                      "%lu\n*** blocks. This may slow down "
	                      "journal initialization and mounting "
	                      "of\n*** the file system.%s",
	                      journal_default_size(fs->fs_super_bh->b_blocknr, fs->fs_blocksize) + 1,
	                      len > journal_max ? " " : "\n");
	    if (len > journal_max)
		reiserfs_warning (stdout, "On 32-bit systems, and on "
		                  "64-bit systems with\n*** limited "
		                  "memory, this may also cause the file "
		                  "system to be unmountable.\n*** Please "
		                  "consider using a journal size "
		                  "<= %lu blocks.\n\nFile system creation "
				  "failed. You may override this behavior "
				  "with the -f option.\n", journal_max);
		    return 0;
	}

	if (len < blocks)
		reiserfs_warning (stdout, "\n\n*** Your journal device is %lu "
		                  "blocks, but your journal is only %lu "
				  "blocks.\n*** You may want to consider "
				  "resizing the journal device to avoid "
				  "wasting space.\n\n", blocks, len);

	if (blocks < offset + len) {
	    reiserfs_warning (stderr, "reiserfs_create_journal: no enough "
		"blocks on device %lu, needed %lu\n", blocks, offset + len);
	    return 0;
	}
	
	if (stat (j_device, &st) == -1) {
	    reiserfs_warning (stderr, "reiserfs_create_journal: stat %s failed"
		": %s\n", j_device, strerror(errno));
	    return 0;
	}
/*
	if (!S_ISBLK (st.st_mode)) {
		reiserfs_warning (stderr, "reiserfs_create_journal: "
		"%s is not a block device (%x)\n", j_device, st.st_rdev);
		return 0;
	}
*/
    }

    fs->fs_journal_dev = open (j_device, O_RDWR 
#if defined(O_LARGEFILE)
			       | O_LARGEFILE
#endif
			       );
    if (fs->fs_journal_dev == -1) {
	reiserfs_warning (stderr, "reiserfs_create_journal: could not open "
	    "%s: %s\n", j_device, strerror(errno));
	return 0;
    }

    asprintf (&fs->fs_j_file_name, "%s", j_device);

    if (len < JOURNAL_MIN_SIZE + 1) {
	reiserfs_warning (stderr, "WARNING: Journal size (%u) is less, than "
	    "minimal supported journal size (%u).\n", len, JOURNAL_MIN_SIZE + 1);
        return 0;
    }
    /* get journal header */
    bh = getblk (fs->fs_journal_dev, offset + len - 1, fs->fs_blocksize);
    if (!bh) {
	reiserfs_warning (stderr, "reiserfs_create_journal: getblk failed\n");
	return 0;
    }

    /* fill journal header */
    jh = (struct reiserfs_journal_header *)bh->b_data;
    set_jp_journal_1st_block(&jh->jh_journal, offset);
    set_jp_journal_dev(&jh->jh_journal, st.st_rdev);
    set_jp_journal_magic(&jh->jh_journal, get_random());

    set_jp_journal_size(&jh->jh_journal, len - 1);
    set_jp_journal_max_trans_len(&jh->jh_journal, advise_journal_max_trans_len(
	transaction_max_size, len - 1, fs->fs_blocksize, 1));
    set_jp_journal_max_batch(&jh->jh_journal, advise_journal_max_batch(
	get_jp_journal_max_trans_len(&jh->jh_journal)));
    set_jp_journal_max_commit_age(&jh->jh_journal, 
	advise_journal_max_commit_age());
    set_jp_journal_max_trans_age(&jh->jh_journal, 
	advise_journal_max_trans_age ());

    mark_buffer_uptodate (bh, 1);
    mark_buffer_dirty (bh);
    
    fs->fs_jh_bh = bh;
    
    /* make a copy of journal header in the super block */
    memcpy (sb_jp (sb), &jh->jh_journal, sizeof (struct journal_params));
    mark_buffer_dirty (fs->fs_super_bh);

    return 1;
}


/* brelse journal header, flush all dirty buffers, close device, open, read
   journal header */
void reiserfs_reopen_journal (reiserfs_filsys_t * fs, int flag)
{
    unsigned long jh_block;


    if (!reiserfs_journal_opened (fs))
	return;

    jh_block = fs->fs_jh_bh->b_blocknr;
    brelse (fs->fs_jh_bh);
    flush_buffers (fs->fs_journal_dev);
    invalidate_buffers (fs->fs_journal_dev);
    if (close (fs->fs_journal_dev))
	die ("reiserfs_reopen_journal: closed failed: %s", strerror(errno));

    fs->fs_journal_dev = open (fs->fs_j_file_name, flag 
#if defined(O_LARGEFILE)
			       | O_LARGEFILE
#endif
			       );
    if (fs->fs_journal_dev == -1)
	die ("reiserfs_reopen_journal: could not reopen journal device");

    fs->fs_jh_bh = bread (fs->fs_journal_dev, jh_block, fs->fs_blocksize);
    if (!fs->fs_jh_bh)
	die ("reiserfs_reopen_journal: reading journal header failed");
}


int reiserfs_journal_opened (reiserfs_filsys_t * fs)
{
    return fs->fs_jh_bh ? 1 : 0;
}


void reiserfs_flush_journal (reiserfs_filsys_t * fs)
{
    if (!reiserfs_journal_opened (fs))
		return;
    flush_buffers (fs->fs_journal_dev);
}


void reiserfs_free_journal (reiserfs_filsys_t * fs)
{
    if (!reiserfs_journal_opened (fs))
		return;
    brelse (fs->fs_jh_bh);
    fs->fs_jh_bh = 0;
    free (fs->fs_j_file_name);
    fs->fs_j_file_name = 0;
}


void reiserfs_close_journal (reiserfs_filsys_t * fs)
{
    reiserfs_flush_journal (fs);
    reiserfs_free_journal (fs);

}

/* update journal header */
static void update_journal_header (reiserfs_filsys_t * fs, 
				   struct buffer_head * bh_jh, 
				   reiserfs_trans_t *trans) 
{
    struct reiserfs_journal_header * j_head;
	
    j_head = (struct reiserfs_journal_header *)(bh_jh->b_data);

    /* update journal header */
    set_jh_last_flushed (j_head, trans->trans_id);
    set_jh_mount_id (j_head, trans->mount_id);
    set_jh_replay_start_offset (j_head, trans->next_trans_offset);
    mark_buffer_dirty (bh_jh);
    bwrite (bh_jh);
    fsync(fs->fs_journal_dev);
}

/* fixme: what should be done when not all transactions can be replayed in proper order? */
int replay_journal (reiserfs_filsys_t * fs)
{
    struct buffer_head * bh;
    struct reiserfs_journal_header * j_head;
    reiserfs_trans_t cur, newest, control;
    int replayed, ret;
    struct progbar progbar;
    int trans_count;

    if (!reiserfs_journal_opened (fs))
        reiserfs_panic ("replay_journal: journal is not opened");
    
    if (!is_opened_rw (fs))
        reiserfs_panic ("replay_journal: fs is not opened with write perms");


    reiserfs_warning (stderr, "Replaying journal: ");
    bh = fs->fs_jh_bh;
	
    j_head = (struct reiserfs_journal_header *)(bh->b_data);
    control.mount_id = get_jh_mount_id (j_head);
    control.trans_id = get_jh_last_flushed (j_head);
    control.desc_blocknr = get_jh_replay_start_offset (j_head);

    if (!get_boundary_transactions (fs, &cur, &newest)) {
	reiserfs_warning (stderr, "No transactions found\n");
	return 0;
    }

    trans_count = newest.trans_id - cur.trans_id;

    /*  Smth strange with journal header or journal. We cannot say for sure what was the last 
	replaied transaction, but relying on JH data is preferable. */

    replayed = 0;
    ret = TRANS_FOUND;
    
    progbar_init(&progbar, " trans", stderr);
    /* Looking to the first valid not replayed transaction. */
    while (1) {
	if (cur.mount_id == control.mount_id && 
	    cur.trans_id > control.trans_id)
	    break;

	if ((ret = next_transaction (fs, &cur, newest)) != TRANS_FOUND)
	    break;
    }
    
    while (ret == TRANS_FOUND) {
	/* If not the next transaction to be replayed, break out here. */
	if ((cur.mount_id != control.mount_id) || 
	    (cur.trans_id != control.trans_id + 1 && control.trans_id))
	    break;
	
	if (!transaction_check_content(fs, &cur)) {
	    progbar_clear(&progbar);
	    reiserfs_warning (stderr, "Trans broken: mountid %lu, transid %lu, desc %lu, "
		"len %lu, commit %lu, next trans offset %lu\n", cur.mount_id, cur.trans_id, 
		cur.desc_blocknr, cur.trans_len, cur.commit_blocknr, cur.next_trans_offset);
	    break;
	}

        reiserfs_warning (stderr, "Trans replayed: mountid %lu, transid %lu, desc %lu, "
            "len %lu, commit %lu, next trans offset %lu\n",
	    cur.mount_id, cur.trans_id, cur.desc_blocknr, cur.trans_len,
	    cur.commit_blocknr, cur.next_trans_offset);
	replay_one_transaction (fs, &cur);
	update_journal_header (fs, bh, &cur);
	control = cur;
        replayed ++;

	progbar_update(&progbar, "Replaying journal", replayed, trans_count,
	               replayed);

	ret = next_transaction (fs, &cur, newest);
    }
    progbar_clear(&progbar);

    reiserfs_warning (stderr, "\rReplaying journal: Done.\nReiserfs journal '%s' in blocks [%u..%u]: %d "
		      "transactions replayed\n", fs->fs_j_file_name, 
		      get_jp_journal_1st_block(sb_jp(fs->fs_ondisk_sb)),
		      get_jp_journal_1st_block(sb_jp(fs->fs_ondisk_sb)) + 
		      get_jp_journal_size(sb_jp(fs->fs_ondisk_sb)) + 1,
		      replayed);
	
    mark_buffer_dirty (fs->fs_super_bh);
    bwrite (fs->fs_super_bh);
	
    update_journal_header (fs, bh, &newest);

    return 0;
}
