Check for inodes which are too big (either too many blocks, or
would cause i_size to be too big), and offer to truncate the inode.
Remove old bogus i_size checks.

Add test case which tests e2fsck's handling of large sparse files.
Older e2fsck with the old(er) bogus i_size checks didn't handle
this correctly.

diff --git a/e2fsck/ChangeLog b/e2fsck/ChangeLog
index 5c1df96..15b5d48 100644
--- a/e2fsck/ChangeLog
+++ b/e2fsck/ChangeLog
@@ -1,3 +1,15 @@
+2002-05-21  Theodore Ts'o  <tytso@mit.edu>
+
+	* pass1.c (process_block): If an inode has too many blocks or
+		is too big, then offer to truncate the inode.
+		(check_blocks): Don't bother checking the size to see if
+		it's too big, since that's just a symptom, not the disease
+		(which we're now appropriately checking in process_block).
+
+	* problem.c, problem.h: Add new problem codes PR_1_INODE_TOOBIG,
+		PR_1_TOOBIG_DIR, PR_1_TOOBIG_REG, PR_1_TOOBIG_SYMLINK, and
+		add the latch code PR_LATCH_TOOBIG.
+
 2002-05-20  Theodore Ts'o  <tytso@mit.edu>
 
 	* e2fsck.h, pass1.c (e2fsck_pass1_check_symlink), pass2.c
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index 5828b13..c8eee89 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -76,9 +76,10 @@
 
 struct process_block_struct {
 	ext2_ino_t	ino;
-	int		is_dir:1, clear:1, suppress:1,
+	int		is_dir:1, is_reg:1, clear:1, suppress:1,
 				fragmented:1, compressed:1;
 	blk_t		num_blocks;
+	blk_t		max_blocks;
 	e2_blkcnt_t	last_block;
 	int		num_illegal_blocks;
 	blk_t		previous_block;
@@ -410,7 +411,7 @@
 			pb.num_blocks = pb.last_block = 0;
 			pb.num_illegal_blocks = 0;
 			pb.suppress = 0; pb.clear = 0; pb.is_dir = 0;
-			pb.fragmented = 0;
+			pb.is_reg = 0; pb.fragmented = 0;
 			pb.inode = &inode;
 			pb.pctx = &pctx;
 			pb.ctx = ctx;
@@ -1150,6 +1151,8 @@
 	pb.compressed = 0;
 	pb.previous_block = 0;
 	pb.is_dir = LINUX_S_ISDIR(inode->i_mode);
+	pb.is_reg = LINUX_S_ISREG(inode->i_mode);
+	pb.max_blocks = 1 << (31 - fs->super->s_log_block_size);
 	pb.inode = inode;
 	pb.pctx = pctx;
 	pb.ctx = ctx;
@@ -1174,6 +1177,7 @@
 	if (ctx->flags & E2F_FLAG_SIGNAL_MASK)
 		return;
 	end_problem_latch(ctx, PR_LATCH_BLOCK);
+	end_problem_latch(ctx, PR_LATCH_TOOBIG);
 	if (pctx->errcode)
 		fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx);
 
@@ -1221,25 +1225,21 @@
 	}
 	if (pb.is_dir) {
 		int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super);
-		/* We don't let a directory become larger than 2GB */
-		if (nblock > (pb.last_block + 1) ||
-		    (inode->i_size & ((fs->blocksize-1) | 0x80000000UL)) != 0)
+		if (nblock > (pb.last_block + 1))
 			bad_size = 1;
 		else if (nblock < (pb.last_block + 1)) {
 			if (((pb.last_block + 1) - nblock) >
 			    fs->super->s_prealloc_dir_blocks)
 				bad_size = 2;
 		}
-	} else if (!LINUX_S_ISREG(inode->i_mode)) {
-		if (inode->i_size_high)
-			bad_size = 5;
 	} else {
+		if (!LINUX_S_ISREG(inode->i_mode) && inode->i_size_high)
+			bad_size = 5;
 		size = inode->i_size | ((__u64) inode->i_size_high << 32);
 		if ((size < pb.last_block * fs->blocksize))
 			bad_size = 3;
 		else if (size > ext2_max_sizes[fs->super->s_log_block_size])
 			bad_size = 4;
-		/* FIXME: need to ensure pb.num_blocks < 2^32 */
 	}
 	if (bad_size) {
 		pctx->num = (pb.last_block+1) * fs->blocksize;
@@ -1400,6 +1400,13 @@
 	}
 	p->previous_block = blk;
 	
+	if (p->is_dir && blockcnt > 2*1024*1024/fs->blocksize)
+		problem = PR_1_TOOBIG_DIR;
+	if (p->is_reg && p->num_blocks+1 >= p->max_blocks)
+		problem = PR_1_TOOBIG_REG;
+	if (!p->is_dir && !p->is_reg && blockcnt > 0)
+		problem = PR_1_TOOBIG_SYMLINK;
+	    
 	if (blk < fs->super->s_first_data_block ||
 	    blk >= fs->super->s_blocks_count)
 		problem = PR_1_ILLEGAL_BLOCK_NUM;
diff --git a/e2fsck/problem.c b/e2fsck/problem.c
index aaaa270..e786fa2 100644
--- a/e2fsck/problem.c
+++ b/e2fsck/problem.c
@@ -89,6 +89,7 @@
 	N_("FILE DELETED"),	/* 15 */
 	N_("SUPPRESSED"),	/* 16 */
 	N_("UNLINKED"),		/* 17 */
+	"",			/* 18 */
 };
 
 static const struct e2fsck_problem problem_table[] = {
@@ -645,6 +646,25 @@
 	  N_("@a @b %b is corrupt (invalid value).  "),
 	  PROMPT_CLEAR, 0},
 
+	/* Inode too big (latch question) */
+	{ PR_1_INODE_TOOBIG,
+	  N_("@i %i is too big.  "), PROMPT_TRUNCATE, 0 },
+
+	/* Directory too big */
+	{ PR_1_TOOBIG_DIR, 
+	  N_("@b #%B (%b) causes @d to be too big.  "),
+	  PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
+	/* Regular file too big */
+	{ PR_1_TOOBIG_REG,
+	  N_("@b #%B (%b) causes file to be too big.  "),
+	  PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
+	/* Symlink too big */
+	{ PR_1_TOOBIG_SYMLINK,
+	  N_("@b #%B (%b) causes symlink to be too big.  "),
+	  PROMPT_CLEAR, PR_LATCH_TOOBIG },
+
 	/* Pass 1b errors */
 
 	/* Pass 1B: Rescan for duplicate/bad blocks */
@@ -1255,6 +1275,7 @@
 	{ PR_LATCH_RELOC, PR_0_RELOCATE_HINT, 0 },
 	{ PR_LATCH_DBLOCK, PR_1B_DUP_BLOCK_HEADER, PR_1B_DUP_BLOCK_END },
 	{ PR_LATCH_LOW_DTIME, PR_1_ORPHAN_LIST_REFUGEES, 0 },
+	{ PR_LATCH_TOOBIG, PR_1_INODE_TOOBIG, 0 },
 	{ -1, 0, 0 },
 };
 
diff --git a/e2fsck/problem.h b/e2fsck/problem.h
index f5eae27..3c4b162 100644
--- a/e2fsck/problem.h
+++ b/e2fsck/problem.h
@@ -36,6 +36,7 @@
 #define PR_LATCH_RELOC	0x0050  /* Latch for superblock relocate hint */
 #define PR_LATCH_DBLOCK	0x0060	/* Latch for pass 1b dup block headers */
 #define PR_LATCH_LOW_DTIME 0x0070 /* Latch for pass1 orphaned list refugees */
+#define PR_LATCH_TOOBIG	0x0080	/* Latch for file to big errors */
 
 #define PR_LATCH(x)	((((x) & PR_LATCH_MASK) >> 4) - 1)
 
@@ -372,6 +373,18 @@
 /* Bad extended attribute value */
 #define PR_1_EA_BAD_VALUE		0x010042
 
+/* Inode too big (latch question) */
+#define PR_1_INODE_TOOBIG		0x010043
+
+/* Directory too big */
+#define PR_1_TOOBIG_DIR			0x010044
+
+/* Regular file too big */
+#define PR_1_TOOBIG_REG			0x010045
+
+/* Symlink too big */
+#define PR_1_TOOBIG_SYMLINK		0x010046
+
 /*
  * Pass 1b errors
  */