/*
 * pass5.c --- check block and inode bitmaps against on-disk bitmaps
 */

#include "et/com_err.h"

#include "e2fsck.h"

static void check_block_bitmaps(ext2_filsys fs);
static void check_inode_bitmaps(ext2_filsys fs);
static void check_inode_end(ext2_filsys fs);
static void check_block_end(ext2_filsys fs);

void pass5(ext2_filsys fs)
{
	struct resource_track	rtrack;
	
#ifdef MTRACE
	mtrace_print("Pass 5");
#endif

	init_resource_track(&rtrack);
	
	if (!preen)
		printf("Pass 5: Checking group summary information.\n");

	read_bitmaps(fs);

	check_block_bitmaps(fs);
	check_inode_bitmaps(fs);
	check_inode_end(fs);
	check_block_end(fs);

	free(inode_used_map);
	free(inode_dir_map);
	free(block_found_map);

	if (tflag > 1) {
		printf("Pass 5: ");
		print_resource_track(&rtrack);
	}
}

static void check_block_bitmaps(ext2_filsys fs)
{
	int	i;
	int	group_free = 0;
	int	group = 0;
	int	blocks = 0;
	int	free = 0;
	int	do_fix = -1;
	int	actual, bitmap;
	
	for (i = fs->super->s_first_data_block;
	     i < fs->super->s_blocks_count;
	     i++) {
		actual = ext2fs_test_block_bitmap(fs, block_found_map, i);
		bitmap = ext2fs_test_block_bitmap(fs, fs->block_map, i);
		
		if (actual == bitmap)
			goto do_counts;

		if (!actual && bitmap) {
			/*
			 * Block not used, but marked in use in the bitmap.
			 */
			if (do_fix < 0) {
				do_fix = ask("Fix block bitmap", 1);
				if (do_fix)
					ext2fs_mark_bb_dirty(fs);
				else
					ext2fs_unmark_valid(fs);
				printf("Block bitmap differences: ");
			}
			printf("-%d ", i);
			if (do_fix) {
				ext2fs_unmark_block_bitmap(fs, fs->block_map,
							   i);
				bitmap = 0;
			}
		} else if (actual && !bitmap) {
			/*
			 * Block used, but not marked in use in the bitmap.
			 */
			if (do_fix < 0) {
				do_fix = ask("Fix block bitmap", 1);
				if (do_fix)
					ext2fs_mark_bb_dirty(fs);
				else
					ext2fs_unmark_valid(fs);
				printf("Block bitmap differences: ");
			}
			printf("+%d ", i);
			if (do_fix) {
				ext2fs_mark_block_bitmap(fs, fs->block_map,
							 i);
				bitmap = 1;
			}
		}
	do_counts:
		if (!bitmap) {
			group_free++;
			free++;
		}
		blocks ++;
		if (blocks == fs->super->s_blocks_per_group) {
			if (group_free !=
			    fs->group_desc[group].bg_free_blocks_count) {
				printf("Free blocks count wrong for group %d (%d, counted=%d) ",
				       group, fs->group_desc[group].bg_free_blocks_count,
				       group_free);
				if (ask("Repair", 1)) {
					fs->group_desc[group].bg_free_blocks_count = group_free;
					ext2fs_mark_super_dirty(fs);
				} else
					ext2fs_unmark_valid(fs);
			}
			group ++;
			blocks = 0;
			group_free = 0;
		}
	}
	if (do_fix >= 0)
		printf("\n\n");
	if (free != fs->super->s_free_blocks_count) {
		printf ("Free blocks count wrong (%d, counted=%d). ",
			fs->super->s_free_blocks_count, free);
		if (ask("Repair", 1)) {
			fs->super->s_free_blocks_count = free;
			ext2fs_mark_super_dirty(fs);
		} else
			ext2fs_unmark_valid(fs);
	}
}
			
static void check_inode_bitmaps(ext2_filsys fs)
{
	int	i;
	int	free = 0;
	int	group_free = 0;
	int	dirs_count = 0;
	int	group = 0;
	int	inodes = 0;
	int	do_fix = -1;
	int	actual, bitmap;
	
	for (i = 1; i <= fs->super->s_inodes_count; i++) {
		if (i == EXT2_BAD_INO || 
		    (i > EXT2_ROOT_INO && i < EXT2_FIRST_INO))
			continue;
		actual = ext2fs_test_inode_bitmap(fs, inode_used_map, i);
		bitmap = ext2fs_test_inode_bitmap(fs, fs->inode_map, i);
		if (actual == bitmap)
			continue;
		
		if (!actual && bitmap) {
			/*
			 * Inode wasn't used, but marked in bitmap
			 */
			if (do_fix < 0) {
				do_fix = ask("Fix inode bitmap", 1);
				if (do_fix)
					ext2fs_mark_ib_dirty(fs);
				else
					ext2fs_unmark_valid(fs);
				printf("Inode bitmap differences: ");
			}
			printf("-%d ", i);
			if (do_fix)
				ext2fs_unmark_inode_bitmap(fs, fs->inode_map,
							   i);
		} else if (actual && !bitmap) {
			/*
			 * Inode used, but not in bitmap
			 */
			if (do_fix < 0) {
				do_fix = ask("Fix inode bitmap", 1);
				if (do_fix)
					ext2fs_mark_ib_dirty(fs);
				else
					ext2fs_unmark_valid(fs);
				printf("Inode bitmap differences: ");
			}
			printf ("+%d ", i);
			if (do_fix)
				ext2fs_mark_inode_bitmap(fs, fs->inode_map, i);
		}
	}
	if (do_fix >= 0)
		printf("\n\n");
	for (i = 1; i <= fs->super->s_inodes_count; i++) {
		if (!ext2fs_test_inode_bitmap(fs, fs->inode_map, i)) {
			group_free ++;
			free ++;
		} else {
			if (ext2fs_test_inode_bitmap(fs, inode_dir_map, i))
				dirs_count++;
		}
		inodes ++;
		if (inodes == fs->super->s_inodes_per_group) {
			if (group_free != fs->group_desc[group].bg_free_inodes_count) {
				printf ("Free inodes count wrong for group #%d (%d, counted=%d) ",
					group, fs->group_desc[group].bg_free_inodes_count,
					group_free);
				if (ask("Repair", 1)) {
					fs->group_desc[group].bg_free_inodes_count = group_free;
					ext2fs_mark_super_dirty(fs);
				} else
					ext2fs_unmark_valid(fs);
			}
			if (dirs_count != fs->group_desc[group].bg_used_dirs_count) {
				printf ("Directories count wrong for group #%d (%d, counted=%d) ",
					group, fs->group_desc[group].bg_used_dirs_count,
					dirs_count);
				if (ask("Repair", 1)) {
					fs->group_desc[group].bg_used_dirs_count = dirs_count;
					ext2fs_mark_super_dirty(fs);
				} else
					ext2fs_unmark_valid(fs);
			}
			group ++;
			inodes = 0;
			group_free = 0;
			dirs_count = 0;
		}
	}
	if (free != fs->super->s_free_inodes_count) {
		printf ("Free inodes count wrong (%d, counted=%d). ",
			fs->super->s_free_inodes_count, free);
		if (ask("Repair", 1)) {
			fs->super->s_free_inodes_count = free;
			ext2fs_mark_super_dirty(fs);
		} else
			ext2fs_unmark_valid(fs);
	}
}

static void check_inode_end(ext2_filsys fs)
{
	ino_t	end;
	ino_t	save_inodes_count = fs->super->s_inodes_count;
	ino_t	i;
	int	bad_bitmap = 0;

	end = EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count;
	if (save_inodes_count == end)
		return;
	
	fs->super->s_inodes_count = end;

	for (i = save_inodes_count + 1; i <= end; i++) {
		if (!ext2fs_test_inode_bitmap(fs, fs->inode_map, i)) {
			if (!bad_bitmap) {
				printf("Inodes past end of bitmap cleared:");
				bad_bitmap = 1;
			}
			printf(" %d", i);
		}
	}

	if (bad_bitmap) {
		if (ask("Fix", 1)) {
			for (i = save_inodes_count + 1; i <= end; i++)
				ext2fs_mark_inode_bitmap(fs, fs->inode_map, i);
			ext2fs_mark_ib_dirty(fs);
		} else
			ext2fs_unmark_valid(fs);
	}

	fs->super->s_inodes_count = save_inodes_count;
}

static void check_block_end(ext2_filsys fs)
{
	blk_t	end;
	blk_t	save_blocks_count = fs->super->s_blocks_count;
	blk_t	i;
	int	bad_bitmap = 0;
	blk_t	start_bad = 0;

	end = EXT2_BLOCKS_PER_GROUP(fs->super) * fs->group_desc_count;
	if (save_blocks_count == end)
		return;
	
	fs->super->s_blocks_count = end;

	for (i = save_blocks_count + 1; i < end; i++) {
		if (!ext2fs_test_block_bitmap(fs, fs->block_map, i)) {
			if (!bad_bitmap) {
				printf("Blocks past end of bitmap cleared:");
				bad_bitmap = 1;
			}
			if (!start_bad) {
				printf(" %d", i);
				start_bad = i;
			}
		} else {
			if (start_bad && start_bad != i-1)
				printf("--%d", i-1);
			start_bad = 0;
		}
	}

	if (bad_bitmap) {
		if (ask("\nFix blocks at end of bitmap", 1)) {
			for (i = save_blocks_count + 1; i < end; i++)
				ext2fs_mark_block_bitmap(fs, fs->block_map, i);
			ext2fs_mark_bb_dirty(fs);
		} else
			ext2fs_unmark_valid(fs);
	}

	fs->super->s_blocks_count = save_blocks_count;
}

