/*
 * pass1.c -- pass #1 of e2fsck: sequential scan of the inode table
 *
 * Pass 1 of e2fsck iterates over all the inodes in the filesystems,
 * and applies the following tests to each inode:
 *
 * 	- The mode field of the inode must be legal.
 * 	- The size and block count fields of the inode are correct.
 * 	- A data block must not be used by another inode
 *
 * Pass 1 also gathers the collects the following information:
 *
 * 	- A bitmap of which inodes are in use.		(inode_used_map)
 * 	- A bitmap of which inodes are directories.	(inode_dir_map)
 * 	- A bitmap of which inodes have bad fields.	(inode_bad_map)
 * 	- A bitmap of which blocks are in use.		(block_found_map)
 * 	- A bitmap of which blocks are in use by two inodes	(block_dup_map)
 * 	- The data blocks of the directory inodes.	(dir_map)
 *
 * Pass 1 is designed to stash away enough information so that the
 * other passes should not need to read in the inode information
 * during the normal course of a filesystem check.  (Althogh if an
 * inconsistency is detected, other passes may need to read in an
 * inode to fix it.)
 *
 * Note that pass 1B will be invoked if there are any duplicate blocks
 * found.
 */

#include <time.h>

#include <et/com_err.h>
#include "e2fsck.h"

/* Files counts */
int fs_directory_count = 0;
int fs_regular_count = 0;
int fs_blockdev_count = 0;
int fs_chardev_count = 0;
int fs_links_count = 0;
int fs_symlinks_count = 0;
int fs_fast_symlinks_count = 0;
int fs_fifo_count = 0;
int fs_total_count = 0;
int fs_badblocks_count = 0;
int fs_sockets_count = 0;

char * inode_used_map = 0;	/* Inodes which are in use */
char * inode_bad_map = 0;	/* Inodes which are bad in some way */
char * inode_dir_map = 0;	/* Inodes which are directories */

char * block_found_map = 0;
char * block_dup_map = 0;

unsigned short * inode_link_info = NULL;

static int process_block(ext2_filsys fs, blk_t	*blocknr,
			 int	blockcnt, void	*private);
static int process_bad_block(ext2_filsys fs, blk_t *block_nr,
			     int blockcnt, void *private);
static void check_blocks(ext2_filsys fs, ino_t ino, struct ext2_inode *inode,
			 char *block_buf);
static void mark_table_blocks(ext2_filsys fs);
static errcode_t pass1_check_directory(ext2_filsys fs, ino_t ino);
static errcode_t pass1_get_blocks(ext2_filsys fs, ino_t ino, blk_t *blocks);
static void alloc_bad_map(ext2_filsys fs);

struct process_block_struct {
	ino_t	ino;
	int	num_blocks;
	int	dup_blocks;
	int	last_block;
};

/*
 * For pass1_check_directory and pass1_get_blocks
 */
ino_t stashed_ino;
struct ext2_inode *stashed_inode;

void pass1(ext2_filsys fs)
{
	ino_t	ino;
	struct ext2_inode inode;
	inode_scan	scan;
	char		*block_buf;
	errcode_t	retval;
	struct resource_track	rtrack;
	
	init_resource_track(&rtrack);
	
	if (!preen)
		printf("Pass 1: Checking inodes, blocks, and sizes\n");

#ifdef MTRACE
	mtrace_print("Pass 1");
#endif

	/*
	 * Allocate bitmaps structures
	 */
	retval = ext2fs_allocate_inode_bitmap(fs, &inode_used_map);
	if (retval) {
		com_err("ext2fs_allocate_inode_bitmap", retval,
			"while allocating inode_used_map");
		fatal_error(0);
	}
	retval = ext2fs_allocate_inode_bitmap(fs, &inode_dir_map);
	if (retval) {
		com_err("ext2fs_allocate_inode_bitmap", retval,
			"while allocating inode_dir_map");
		fatal_error(0);
	}
	retval = ext2fs_allocate_block_bitmap(fs, &block_found_map);
	if (retval) {
		com_err("ext2fs_allocate_block_bitmap", retval,
			"while allocating block_found_map");
		fatal_error(0);
	}
	inode_link_info = allocate_memory((fs->super->s_inodes_count + 1) *
					  sizeof(unsigned short),
					  "inode link count array");

	mark_table_blocks(fs);
	block_buf = allocate_memory(fs->blocksize * 3, "block interate buffer");
	fs->get_blocks = pass1_get_blocks;
	fs->check_directory = pass1_check_directory;
	retval = ext2fs_open_inode_scan(fs, inode_buffer_blocks, &scan);
	if (retval) {
		com_err(program_name, retval, "while opening inode scan");
		fatal_error(0);
	}
	retval = ext2fs_get_next_inode(scan, &ino, &inode);
	if (retval) {
		com_err(program_name, retval, "while starting inode scan");
		fatal_error(0);
	}
	stashed_inode = &inode;
	while (ino) {
		stashed_ino = ino;
		inode_link_info[ino] = inode.i_links_count;
		if (ino == EXT2_BAD_INO) {
			struct process_block_struct pb;
			
			pb.ino = EXT2_BAD_INO;
			pb.dup_blocks = pb.num_blocks = pb.last_block = 0;
			retval = ext2fs_block_iterate(fs, ino, 0, block_buf,
						      process_bad_block, &pb);
			if (retval)
				com_err(program_name, retval, "while calling e2fsc_block_interate in pass 1");

			ext2fs_mark_inode_bitmap(fs, inode_used_map, ino);
			goto next;
		}
		/*
		 * Make sure the root inode is a directory; if not,
		 * offer to clear it.  It will be regnerated in pass #3.
		 */
		if (ino == EXT2_ROOT_INO) {
			if (!S_ISDIR(inode.i_mode)) {
				printf("Root inode is not a directory.  ");
				preenhalt();
				if (ask("Clear", 1)) {
					inode.i_dtime = time(0);
					inode.i_links_count = 0;
					ext2fs_write_inode(fs, ino, &inode);
				} else 
					ext2fs_unmark_valid(fs);
			}
		}
		if ((ino != EXT2_ROOT_INO) && (ino < EXT2_FIRST_INO))
			goto next;
		/*
		 * This code assumes that deleted inodes have
		 * i_links_count set to 0.  
		 */
		if (!inode.i_links_count) {
#if 0
			if (!inode.i_dtime && inode.i_mode) {
				printf("Deleted inode %d has zero dtime.\n",
				       ino);
				if (ask("Set dtime", 1)) {
					inode.i_dtime = time(0);
					ext2fs_write_inode(fs, ino, &inode);
				} else
					ext2fs_unmark_valid(fs);
			}
#endif
			goto next;
		}
		/*
		 * Old EXT2FS code didn't clear i_links_count for
		 * deleted files.  Oops.
		 */
		if (inode.i_dtime)
			goto next;
		ext2fs_mark_inode_bitmap(fs, inode_used_map, ino);
		if (inode.i_flags || inode.i_faddr || inode.i_frag ||
		    inode.i_fsize || inode.i_file_acl || inode.i_dir_acl) {
			if (!inode_bad_map)
				alloc_bad_map(fs);
			ext2fs_mark_inode_bitmap(fs, inode_bad_map, ino);
		}
		
		if (S_ISDIR(inode.i_mode)) {
			ext2fs_mark_inode_bitmap(fs, inode_dir_map, ino);
			add_dir_info(fs, ino, 0, &inode);
			fs_directory_count++;
		} else if (S_ISREG (inode.i_mode))
			fs_regular_count++;
		else if (S_ISCHR (inode.i_mode))
			fs_chardev_count++;
		else if (S_ISBLK (inode.i_mode))
			fs_blockdev_count++;
		else if (S_ISLNK (inode.i_mode)) {
			fs_symlinks_count++;
			if (!inode.i_blocks)
				fs_fast_symlinks_count++;
		}
		else if (S_ISFIFO (inode.i_mode))
			fs_fifo_count++;
		else if (S_ISSOCK (inode.i_mode))
		        fs_sockets_count++;
		else {
			if (!inode_bad_map)
				alloc_bad_map(fs);
			ext2fs_mark_inode_bitmap(fs, inode_bad_map, ino);
		}
		check_blocks(fs, ino, &inode, block_buf);
		inode_link_info[ino] = inode.i_links_count;
	next:
		retval = ext2fs_get_next_inode(scan, &ino, &inode);
		if (retval) {
			com_err(program_name, retval,
				"while doing inode scan");
			fatal_error(0);
		}
	}
	ext2fs_close_inode_scan(scan);
	if (block_dup_map) {
		if (preen) {
			printf("Duplicate blocks found!\n");
			preenhalt();
		}
		pass1_dupblocks(fs, block_buf);
	}
	free(block_buf);
	fs->get_blocks = 0;
	fs->check_directory = 0;
	if (tflag > 1) {
		printf("Pass 1: ");
		print_resource_track(&rtrack);
	}
}

/*
 * This procedure will allocate the inode bad map table
 */
static void alloc_bad_map(ext2_filsys fs)
{
	errcode_t	retval;
	
	retval = ext2fs_allocate_inode_bitmap(fs, &inode_bad_map);
	if (retval) {
		com_err("ext2fs_allocate_inode_bitmap", retval,
			"while allocating inode_bad_map");
		fatal_error(0);
	}
}	

/*
 * This subroutine is called on each inode to account for all of the
 * blocks used by that inode.
 */
static void check_blocks(ext2_filsys fs, ino_t ino, struct ext2_inode *inode,
			 char *block_buf)
{
	struct process_block_struct pb;
	errcode_t	retval;
	
	if (!inode_has_valid_blocks(inode))
		return;
	
	pb.ino = ino;
	pb.dup_blocks = pb.num_blocks = pb.last_block = 0;
	retval = ext2fs_block_iterate(fs, ino, 0, block_buf,
				      process_block, &pb);
	if (retval)
		com_err(program_name, retval,
			"while calling ext2fs_block_iterate in check_blocks");
	
	pb.num_blocks *= (fs->blocksize / 512);
#if 0
	printf("inode %d, i_size = %d, last_block = %d, i_blocks=%d, num_blocks = %d\n",
	       ino, inode->i_size, pb.last_block, inode->i_blocks,
	       pb.num_blocks);
#endif
	if (inode->i_size < pb.last_block * fs->blocksize) {
		printf ("Inode %d, incorrect size, %d (counted = %d). ",
			ino, inode->i_size,
			(pb.last_block+1) * fs->blocksize);
		if (ask ("Set size to counted", 1)) {
			inode->i_size = (pb.last_block+1) * fs->blocksize;
			ext2fs_write_inode(fs, ino, inode);
		} else
			ext2fs_unmark_valid(fs);
	}
	if (pb.num_blocks != inode->i_blocks) {
		printf ("Inode %d, i_blocks wrong %d (counted=%d) .",
			ino, inode->i_blocks, pb.num_blocks);
		if (ask ("Set i_blocks to counted", 1)) {
			inode->i_blocks = pb.num_blocks;
			ext2fs_write_inode(fs, ino, inode);
		} else
				ext2fs_unmark_valid(fs);
	}
}	

int process_bad_block(ext2_filsys fs,
		      blk_t *block_nr,
		      int blockcnt,
		      void *private)
{
	if (!*block_nr)
		return 0;
#if 0 
	printf ("DEBUG: Marking %d as bad.\n", *block_nr);
#endif
	fs_badblocks_count++;
	ext2fs_mark_block_bitmap(fs, block_found_map, *block_nr);
	return 0;
}

int process_block(ext2_filsys fs,
		  blk_t	*block_nr,
		  int blockcnt,
		  void *private)
{
	struct process_block_struct *p;
	int group;
	int illegal_block = 0;
	int firstblock;
	errcode_t	retval;

	if (!*block_nr)
		return 0;
	p = (struct process_block_struct *) private;

#if 0
	printf("Process_block, inode %d, block %d, #%d\n", p->ino, *block_nr,
	       blockcnt);
#endif	
	
	p->num_blocks++;
	if (blockcnt > 0)
		p->last_block = blockcnt;

	firstblock = fs->super->s_first_data_block;
	group = (*block_nr - firstblock) / fs->super->s_blocks_per_group;
	if (*block_nr < firstblock) {
		printf ("Block nr %d < FIRSTBLOCK (%d)", *block_nr,
			firstblock);
		illegal_block++;
	} else if (*block_nr >= fs->super->s_blocks_count) {
		printf ("Block nr %d > BLOCKS (%d)", *block_nr,
			fs->super->s_blocks_count);
		illegal_block++;
	} else if (*block_nr == fs->group_desc[group].bg_block_bitmap) {
		printf ("Block nr %d is the block bitmap of group %d",
			*block_nr, group);
		illegal_block++;
	} else if (*block_nr == fs->group_desc[group].bg_inode_bitmap) {
		printf ("Block nr %d is the inode bitmap of group %d",
			*block_nr, group);
		illegal_block++;
	} else if (*block_nr >= fs->group_desc[group].bg_inode_table &&
		   *block_nr < fs->group_desc[group].bg_inode_table + fs->inode_blocks_per_group) {
		printf ("Block nr %d is in the inode table of group %d",
			*block_nr, group);
		illegal_block++;
	}
	if (illegal_block) {
		printf(" of inode %d.  ", p->ino);
		preenhalt();
		if (ask("Remove block", 1)) {
			*block_nr = 0;
			return BLOCK_CHANGED;
		} else {
			ext2fs_unmark_valid(fs);
			return 0;
		}
	}

	if (ext2fs_test_block_bitmap(fs, block_found_map, *block_nr)) {
		p->dup_blocks++;
		if (!block_dup_map) {
			retval = ext2fs_allocate_block_bitmap(fs,
							      &block_dup_map);
			if (retval) {
				com_err("ext2fs_allocate_block_bitmap", retval,
					"while allocating block_dup_map");
				fatal_error(0);
			}
		}
		ext2fs_mark_block_bitmap(fs, block_dup_map, *block_nr);
	} else {
		ext2fs_mark_block_bitmap(fs, block_found_map, *block_nr);
	}
#if 0
	printf("process block, inode %d, block #%d is %d\n",
	       p->ino, blockcnt, *block_nr);
#endif
	
	return 0;
}

/*
 * This routine marks all blocks which are used by the superblock,
 * group descriptors, inode bitmaps, and block bitmaps.
 */
static void mark_table_blocks(ext2_filsys fs)
{
	blk_t	block;
	int	i,j;
	
	block = fs->super->s_first_data_block;
	for (i = 0; i < fs->group_desc_count; i++) {
		/*
		 * Mark block used for the block bitmap 
		 */
		ext2fs_mark_block_bitmap(fs, block_found_map,
					 fs->group_desc[i].bg_block_bitmap);
		/*
		 * Mark block used for the inode bitmap 
		 */
		ext2fs_mark_block_bitmap(fs, block_found_map,
					 fs->group_desc[i].bg_inode_bitmap);
		/*
		 * Mark the blocks used for the inode table
		 */
		for (j = 0; j < fs->inode_blocks_per_group; j++)
			ext2fs_mark_block_bitmap(fs, block_found_map,
						 fs->group_desc[i].bg_inode_table + j);
		/*
		 * Mark this group's copy of the superblock
		 */
		ext2fs_mark_block_bitmap(fs, block_found_map, block);
		
		/*
		 * Mark this group's copy of the descriptors
		 */
		for (j = 0; j < fs->desc_blocks; j++)
			ext2fs_mark_block_bitmap(fs, block_found_map,
						 block + j + 1);
		block += fs->super->s_blocks_per_group;
	}
}
	
/*
 * This subroutines short circuits ext2fs_get_blocks and
 * ext2fs_check_directory; we use them since we already have the inode
 * structure, so there's no point in letting the ext2fs library read
 * the inode again.
 */
static errcode_t pass1_get_blocks(ext2_filsys fs, ino_t ino, blk_t *blocks)
{
	int	i;
	
	if (ino == stashed_ino) {
		for (i=0; i < EXT2_N_BLOCKS; i++)
			blocks[i] = stashed_inode->i_block[i];
		return 0;
	}
	printf("INTERNAL ERROR: pass1_get_blocks: unexpected inode #%d\n",
	       ino);
	printf("\t(was expecting %d)\n", stashed_ino);
	exit(FSCK_ERROR);
}

static errcode_t pass1_check_directory(ext2_filsys fs, ino_t ino)
{
	if (ino == stashed_ino) {
		if (!S_ISDIR(stashed_inode->i_mode))
			return ENOTDIR;
		return 0;
	}
	printf("INTERNAL ERROR: pass1_check_directory: unexpected inode #%d\n",
	       ino);
	printf("\t(was expecting %d)\n", stashed_ino);
	exit(FSCK_ERROR);
}
