/*
 * pass1.c -- pass #1 of e2fsck: sequential scan of the inode table
 * 
 * Copyright (C) 1993, 1994 Theodore Ts'o.  This file may be
 * redistributed under the terms of the GNU Public License.
 * 
 * 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;
static char * bad_fs_block_map = 0;

static int fix_link_count = -1;

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 int process_fs_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);
static void handle_fs_bad_blocks(ext2_filsys fs, char *block_buf);
static void process_inodes(ext2_filsys fs, char *block_buf);
static int process_inode_cmp(const void *a, const void *b);
static int dir_block_cmp(const void *a, const void *b);

struct process_block_struct {
	ino_t	ino;
	int	is_dir;
	int	num_blocks;
	int	last_block;
	int	fix;
};

struct process_inode_block {
	ino_t	ino;
	struct ext2_inode inode;
};

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

/*
 * For the inodes to process list.
 */
static struct process_inode_block *inodes_to_process;
static int process_inode_count;
int process_inode_size = 128;

/*
 * For the directory blocks list.
 */
struct dir_block_struct *dir_blocks = 0;
int	dir_block_count = 0;
int	dir_block_size = 0;

void pass1(ext2_filsys fs)
{
	ino_t	ino;
	struct ext2_inode inode;
	ext2_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");
	inodes_to_process = allocate_memory(process_inode_size *
					    sizeof(struct process_inode_block),
					    "array of inodes to process");
	process_inode_count = 0;

	dir_block_size = get_num_dirs(fs) * 4;
	dir_block_count = 0;
	dir_blocks = allocate_memory(sizeof(struct dir_block_struct) *
				     dir_block_size,
				     "directory block information");

	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;
	ehandler_operation("doing inode scan");
	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.num_blocks = pb.last_block = 0;
			pb.is_dir = 0;
			pb.fix = -1;
			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;
		}
		if (ino == EXT2_ROOT_INO) {
			/*
			 * Make sure the root inode is a directory; if
			 * not, offer to clear it.  It will be
			 * regnerated in pass #3.
			 */
			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 dtime is set, offer to clear it.  mke2fs
			 * version 0.2b created filesystems with the
			 * dtime field set for the root and lost+found
			 * directories.  We won't worry about
			 * /lost+found, since that can be regenerated
			 * easily.  But we will fix the root directory
			 * as a special case.
			 */
			if (inode.i_dtime && inode.i_links_count) {
				if (ask("Root inode has dtime set "
					"(probably due to old mke2fs).  Fix",
					1)) {
					inode.i_dtime = 0;
					ext2fs_write_inode(fs, ino, &inode);
					printf("Note: /lost+found will "
					       "probably be deleted as well, "
					       "due to the mke2fs bug.\n"
					       "Be sure to run mklost+found "
					       "to recreate it after e2fsck "
					       "finishes.\n\n");
				} else
					ext2fs_unmark_valid(fs);
			}
		}
		if ((ino != EXT2_ROOT_INO) && (ino < EXT2_FIRST_INO)) {
			ext2fs_mark_inode_bitmap(fs, inode_used_map, ino);
			check_blocks(fs, ino, &inode, block_buf);
			goto next;
		}
		/*
		 * This code assumes that deleted inodes have
		 * i_links_count set to 0.  
		 */
		if (!inode.i_links_count) {
			if (!inode.i_dtime && inode.i_mode) {
				printf("Deleted inode %ld 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);
			}
			goto next;
		}
		/*
		 * 0.3c ext2fs code didn't clear i_links_count for
		 * deleted files.  Oops.
		 * 
		 * In the future, when the new ext2fs behavior is the
		 * norm, we may want to handle the case of a non-zero
		 * i_links_count and non-zero dtime by clearing dtime
		 * and assuming the inode is in use, instead of
		 * assuming the inode is not in use.
		 */
		if (inode.i_dtime) {
			if (fix_link_count == -1) {
				printf("\nDeleted inode detected with non-zero link count.\n");
				printf("This is probably due to old ext2fs kernel code.  \n");
				fix_link_count = ask("Fix inode(s)", 1);
			}
			printf("Inode %ld is deleted w/ non-zero link_count.  %s\n",
			       ino, clear_msg[fix_link_count]);
			if (fix_link_count) {
				inode.i_links_count = 0;
				ext2fs_write_inode(fs, ino, &inode);
			} else
				ext2fs_unmark_valid(fs);
			goto next;
		}
		
		ext2fs_mark_inode_bitmap(fs, inode_used_map, ino);
		if (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);
		}
		if (inode.i_block[EXT2_IND_BLOCK] ||
		    inode.i_block[EXT2_DIND_BLOCK] ||
		    inode.i_block[EXT2_TIND_BLOCK]) {
			inodes_to_process[process_inode_count].ino = ino;
			inodes_to_process[process_inode_count].inode = inode;
			process_inode_count++;
		} else 
			check_blocks(fs, ino, &inode, block_buf);
		inode_link_info[ino] = inode.i_links_count;

		if (process_inode_count >= process_inode_size)
			process_inodes(fs, block_buf);
	next:
		retval = ext2fs_get_next_inode(scan, &ino, &inode);
		if (retval) {
			com_err(program_name, retval,
				"while doing inode scan");
			fatal_error(0);
		}
	}
	process_inodes(fs, block_buf);
	ext2fs_close_inode_scan(scan);
	ehandler_operation(0);

	qsort(dir_blocks, dir_block_count, sizeof(struct dir_block_struct),
	      dir_block_cmp);

	if (block_dup_map) {
		if (preen) {
			printf("Duplicate or bad blocks in use!\n");
			preenhalt();
		}
		pass1_dupblocks(fs, block_buf);
	}
	fs->get_blocks = 0;
	fs->check_directory = 0;
	free(inodes_to_process);
	if (bad_fs_block_map) {
		handle_fs_bad_blocks(fs, block_buf);
		free(bad_fs_block_map);
	}
	free(block_buf);

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

/*
 * Process the inodes in the "inodes to process" list.
 */
static void process_inodes(ext2_filsys fs, char *block_buf)
{
	int			i;
	struct ext2_inode	*old_stashed_inode;
	ino_t			ino;
	const char		*old_operation;
	char			buf[80];

#if 0
	printf("process_inodes: ");
#endif
	old_operation = ehandler_operation(0);
	old_stashed_inode = stashed_inode;
	qsort(inodes_to_process, process_inode_count,
		      sizeof(struct process_inode_block), process_inode_cmp);
	for (i=0; i < process_inode_count; i++) {
		stashed_inode = &inodes_to_process[i].inode;
		ino = inodes_to_process[i].ino;
		stashed_ino = ino;
#if 0
		printf("%d ", ino);
#endif
		sprintf(buf, "reading indirect blocks of inode %ld", ino);
		ehandler_operation(buf);
		check_blocks(fs, ino, stashed_inode, block_buf);
		
	}
	stashed_inode = old_stashed_inode;
	process_inode_count = 0;
#if 0
	printf("\n");
#endif
	ehandler_operation(old_operation);
}

static int process_inode_cmp(const void *a, const void *b)
{
	const struct process_inode_block *ib_a =
		(const struct process_inode_block *) a;
	const struct process_inode_block *ib_b =
		(const struct process_inode_block *) b;

	return (ib_a->inode.i_block[EXT2_IND_BLOCK] -
		ib_b->inode.i_block[EXT2_IND_BLOCK]);
}

static int dir_block_cmp(const void *a, const void *b)
{
	const struct dir_block_struct *db_a =
		(const struct dir_block_struct *) a;
	const struct dir_block_struct *db_b =
		(const struct dir_block_struct *) b;

	return (db_a->blk - db_b->blk);
}

/*
 * 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);
	}
}

/*
 * Marks a block as in use, setting the dup_map if it's been set
 * already.  Called by process_block and process_bad_block.
 */
static void mark_block_used(ext2_filsys fs, blk_t block)
{
	errcode_t	retval;
	
	if (ext2fs_test_block_bitmap(fs, block_found_map, block)) {
		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);
	} else {
		ext2fs_mark_block_bitmap(fs, block_found_map, block);
	}
}

/*
 * 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.num_blocks = pb.last_block = 0;
	pb.is_dir = S_ISDIR(inode->i_mode);
	pb.fix = -1;
	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 (!pb.num_blocks && pb.is_dir) {
		printf("Inode %ld is a zero length directory.  ", ino);
		if (ask("Clear", 1)) {
			inode->i_links_count = 0;
			inode->i_dtime = time(0);
			ext2fs_write_inode(fs, ino, inode);
			ext2fs_unmark_inode_bitmap(fs, inode_dir_map, ino);
			ext2fs_unmark_inode_bitmap(fs, inode_used_map, ino);
			fs_directory_count--;
		} else
			ext2fs_unmark_valid(fs);
	}
	if (inode->i_size < pb.last_block * fs->blocksize) {
		printf ("Inode %ld, incorrect size, %ld (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 %ld, i_blocks wrong %ld (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);
	}
}	

/*
 * This is a helper function for check_blocks().
 */
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;
	char	problem[80];
	blk_t	firstblock;
	blk_t	blk = *block_nr;

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

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

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

	mark_block_used(fs, blk);
	
	if (p->is_dir && (blockcnt >= 0)) {
		if (dir_block_count >= dir_block_size) {
			dir_block_size += 100;
			dir_blocks = realloc(dir_blocks,
					     dir_block_size *
					     sizeof(struct dir_block_struct));
		}

		dir_blocks[dir_block_count].blk = blk;
		dir_blocks[dir_block_count].ino = p->ino;
		dir_blocks[dir_block_count].blockcnt = blockcnt;
		dir_block_count++;
	}
	
#if 0
	printf("process block, inode %d, block #%d is %d\n",
	       p->ino, blockcnt, blk);
#endif
	
	return 0;
}

int process_bad_block(ext2_filsys fs,
		      blk_t *block_nr,
		      int blockcnt,
		      void *private)
{
	struct process_block_struct *p;
	errcode_t	retval;
	blk_t		blk = *block_nr;
	
	if (!blk)
		return 0;
	p = (struct process_block_struct *) private;

	if ((blk < fs->super->s_first_data_block) ||
	    (blk >= fs->super->s_blocks_count)) {
		if (preen) {
			printf("Illegal block %ld in bad block inode\n", blk);
			preenhalt();
		}
		if (p->fix == -1)
			p->fix = ask("Remove illegal block(s) in bad block inode", 1);
		printf("Illegal block %ld in bad block inode.  %s\n", blk,
		       clear_msg[p->fix]);
		if (p->fix) {
			*block_nr = 0;
			return BLOCK_CHANGED;
		} else {
			ext2fs_unmark_valid(fs);
			return 0;
		}
	}

	if (blockcnt < 0) {
		mark_block_used(fs, blk);
		return 0;
	}
#if 0 
	printf ("DEBUG: Marking %d as bad.\n", blk);
#endif
	fs_badblocks_count++;
	/*
	 * If the block is not used, then mark it as used and return.
	 * If it is already marked as found, this must mean that
	 * there's an overlap between the filesystem table blocks
	 * (bitmaps and inode table) and the bad block list.
	 */
	if (!ext2fs_test_block_bitmap(fs, block_found_map, blk)) {
		ext2fs_mark_block_bitmap(fs, block_found_map, blk);
		return 0;
	}
	if (!bad_fs_block_map) {
		retval = ext2fs_allocate_block_bitmap(fs, &bad_fs_block_map);
		if (retval) {
			com_err("ext2fs_allocate_block_bitmap", retval,
				"while allocating bad_fs_block_map");
		fatal_error(0);
		}
	}
	ext2fs_mark_block_bitmap(fs, bad_fs_block_map, blk);
	return 0;
}

/*
 * This routine gets called at the end of pass 1 if bad blocks are
 * detected in the superblock, group descriptors, inode_bitmaps, or
 * block bitmaps.  At this point, all of the blocks have been mapped
 * out, so we can try to allocate new block(s) to replace the bad
 * blocks.
 */
static void handle_fs_bad_blocks(ext2_filsys fs, char *block_buf)
{
	errcode_t	retval;
	
	printf("Warning: Bad block(s) found in filesystem-reserved blocks.\n");
	
	retval = ext2fs_block_iterate(fs, EXT2_BAD_INO, 0, block_buf,
				      process_fs_bad_block, 0);
}

static void new_table_block(ext2_filsys fs, blk_t first_block,
			    const char *name, int num, blk_t *new_block)
{
	errcode_t	retval;
	blk_t		old_block = *new_block;
	int		i;
	char		*buf;
	
	retval = ext2fs_get_free_blocks(fs, first_block,
			first_block + fs->super->s_blocks_per_group,
					num, block_found_map, new_block);
	if (retval) {
		printf("Could not allocate %d block(s) for %s: %s\n",
		       num, name, error_message(retval));
		ext2fs_unmark_valid(fs);
		return;
	}
	buf = malloc(fs->blocksize);
	if (!buf) {
		printf("Could not allocate block buffer for relocating %s\n",
		       name);
		ext2fs_unmark_valid(fs);
		return;
	}
	ext2fs_mark_super_dirty(fs);
	for (i = 0; i < num; i++) {
		ext2fs_mark_block_bitmap(fs, block_found_map, (*new_block)+i);
		retval = io_channel_read_blk(fs->io, old_block + i,
					     1, buf);
		if (retval)
			printf("Warning: could not read block %ld of %s: %s\n",
			       old_block + i, name, error_message(retval));
		retval = io_channel_write_blk(fs->io, (*new_block) + i,
					      1, buf);
		if (retval)
			printf("Warning: could not write block %ld for %s: %s\n",
			       (*new_block) + i, name, error_message(retval));
		/*
		 * If this particular block is not marked as bad, then
		 * clear its bit in the block_found map.  Otherwise,
		 * leave it set, since it is included in the bad
		 * blocks inode.
		 */
		if (!ext2fs_test_block_bitmap(fs, bad_fs_block_map,
					      old_block + i))
			ext2fs_unmark_block_bitmap(fs, block_found_map,
						   old_block + i);
		/*
		 * Clear the bitmap since this block has now been moved.
		 */
		ext2fs_unmark_block_bitmap(fs, bad_fs_block_map,
					   old_block + i);
	}
	free(buf);
}

/*
 * Helper function for handle_fs_bad_blocks()
 */
static int process_fs_bad_block(ext2_filsys fs, blk_t *block_nr,
			     int blockcnt, void *private)
{
	int	i;
	blk_t	block = *block_nr;
	int	first_block = fs->super->s_first_data_block;

	/*
	 * If this block isn't one that is marked as a bad block in
	 * the filesystem tables, return
	 */
	if (!ext2fs_test_block_bitmap(fs, bad_fs_block_map, block))
		return 0;

	for (i = 0; i < fs->group_desc_count; i++) {
		if (block == first_block)
			printf("Bad block %ld in group %d's superblock.\n",
			       block, i);
		if (block == fs->group_desc[i].bg_block_bitmap) {
			printf("Bad block %ld in group %d's block bitmap.  ",
			       block, i);
			if (ask("Relocate", 1)) {
				new_table_block(fs, first_block,
						"block bitmap", 1, 
					&fs->group_desc[i].bg_block_bitmap);
			} else
				ext2fs_unmark_valid(fs);
		}
		if (block == fs->group_desc[i].bg_inode_bitmap) {
			printf("Bad block %ld in group %d's inode bitmap.  ",
			       block, i);
			if (ask("Relocate", 1)) {
				new_table_block(fs, first_block,
						"inode bitmap", 1, 
					&fs->group_desc[i].bg_inode_bitmap);
			} else
				ext2fs_unmark_valid(fs);
		}
		if ((block >= fs->group_desc[i].bg_inode_table) &&
		    (block < (fs->group_desc[i].bg_inode_table +
			      fs->inode_blocks_per_group))) {
			printf("WARNING: Severe data loss possible!!!!\n");
			printf("Bad block %ld in group %d's inode table.  ",
			       block, i);
			if (ask("Relocate", 1)) {
				new_table_block(fs, first_block,
						"inode table",
						fs->inode_blocks_per_group, 
					&fs->group_desc[i].bg_inode_table);
			} else
				ext2fs_unmark_valid(fs);
		}
		if ((block > first_block) &&
		    (block <= first_block + fs->desc_blocks))
			printf("Bad block %ld in group %d's copy of the descriptors.\n",
			       block, i);
		first_block += fs->super->s_blocks_per_group;
	}
	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 #%ld\n",
	       ino);
	printf("\t(was expecting %ld)\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 #%ld\n",
	       ino);
	printf("\t(was expecting %ld)\n", stashed_ino);
	exit(FSCK_ERROR);
}
