Add support for with empty directory blocks in 64k blocksize filesystems

The rec_len field in the directory entry is 16 bits, so if the
filesystem is completely empty, rec_len of 0 is used to designate
65536, for the case where the directory entry takes the entire 64k
block.

Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
diff --git a/debugfs/htree.c b/debugfs/htree.c
index bee78d7..1659b63 100644
--- a/debugfs/htree.c
+++ b/debugfs/htree.c
@@ -39,7 +39,7 @@
 	char		tmp[EXT2_NAME_LEN + 16];
 	blk_t		pblk;
 	ext2_dirhash_t 	hash, minor_hash;
-	int		hash_alg;
+	int		rec_len, hash_alg;
 	
 	errcode = ext2fs_bmap(fs, ino, inode, buf, 0, blk, &pblk);
 	if (errcode) {
@@ -62,10 +62,12 @@
 
 	while (offset < fs->blocksize) {
 		dirent = (struct ext2_dir_entry *) (buf + offset);
-		if (((offset + dirent->rec_len) > fs->blocksize) ||
-		    (dirent->rec_len < 8) ||
-		    ((dirent->rec_len % 4) != 0) ||
-		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
+		if (((offset + rec_len) > fs->blocksize) ||
+		    (rec_len < 8) ||
+		    ((rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > rec_len)) {
 			fprintf(pager, "Corrupted directory block (%u)!\n", blk);
 			break;
 		}
@@ -80,7 +82,7 @@
 			com_err("htree_dump_leaf_node", errcode,
 				"while calculating hash");
 		sprintf(tmp, "%u 0x%08x-%08x (%d) %s   ", dirent->inode,
-			hash, minor_hash, dirent->rec_len, name);
+			hash, minor_hash, rec_len, name);
 		thislen = strlen(tmp);
 		if (col + thislen > 80) {
 			fprintf(pager, "\n");
@@ -88,7 +90,7 @@
 		}
 		fprintf(pager, "%s", tmp);
 		col += thislen;
-		offset += dirent->rec_len;
+		offset += rec_len;
 	}
 	fprintf(pager, "\n");
 }
@@ -373,6 +375,7 @@
 	struct ext2_dir_entry *dirent;
 	errcode_t	       	errcode;
 	unsigned int		offset = 0;
+	int			rec_len;
 
 	if (blockcnt < 0)
 		return 0;
@@ -388,7 +391,8 @@
 
 	while (offset < fs->blocksize) {
 		dirent = (struct ext2_dir_entry *) (p->buf + offset);
-
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
 		if (dirent->inode &&
 		    p->len == (dirent->name_len & 0xFF) && 
 		    strncmp(p->search_name, dirent->name,
@@ -399,7 +403,7 @@
 			printf("offset %u\n", offset);
 			return BLOCK_ABORT;
 		}
-		offset += dirent->rec_len;
+		offset += rec_len;
 	}
 	return 0;
 }
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index 0e245dd..f2f72ed 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -404,7 +404,7 @@
 	const char		*old_op;
 	errcode_t		retval;
 	blk_t			blk;
-	int			i, not_device = 0;
+	int			i, rec_len, not_device = 0;
 
 	if (LINUX_S_ISDIR(inode->i_mode) || LINUX_S_ISREG(inode->i_mode) ||
 	    LINUX_S_ISLNK(inode->i_mode) || inode->i_block[0] == 0)
@@ -434,20 +434,24 @@
 		return;
 
 	dirent = (struct ext2_dir_entry *) buf;
+	rec_len = (dirent->rec_len || ctx->fs->blocksize < 65536) ?
+		dirent->rec_len : 65536;
 	if (((dirent->name_len & 0xFF) != 1) ||
 	    (dirent->name[0] != '.') ||
 	    (dirent->inode != pctx->ino) ||
-	    (dirent->rec_len < 12) ||
-	    (dirent->rec_len % 4) ||
-	    (dirent->rec_len >= ctx->fs->blocksize - 12))
+	    (rec_len < 12) ||
+	    (rec_len % 4) ||
+	    (rec_len >= ctx->fs->blocksize - 12))
 		return;
 
-	dirent = (struct ext2_dir_entry *) (buf + dirent->rec_len);
+	dirent = (struct ext2_dir_entry *) (buf + rec_len);
+	rec_len = (dirent->rec_len || ctx->fs->blocksize < 65536) ?
+		dirent->rec_len : 65536;
 	if (((dirent->name_len & 0xFF) != 2) ||
 	    (dirent->name[0] != '.') ||
 	    (dirent->name[1] != '.') ||
-	    (dirent->rec_len < 12) ||
-	    (dirent->rec_len % 4))
+	    (rec_len < 12) ||
+	    (rec_len % 4))
 		return;
 
 	if (fix_problem(ctx, PR_1_TREAT_AS_DIRECTORY, pctx)) {
diff --git a/e2fsck/pass2.c b/e2fsck/pass2.c
index f4fa93e..1992479 100644
--- a/e2fsck/pass2.c
+++ b/e2fsck/pass2.c
@@ -355,7 +355,7 @@
 	struct ext2_dir_entry *nextdir;
 	int	status = 0;
 	int	created = 0;
-	int	new_len;
+	int	rec_len, new_len;
 	int	problem = 0;
 	
 	if (!dirent->inode)
@@ -365,11 +365,13 @@
 		problem = PR_2_1ST_NOT_DOT;
 	else if (dirent->name[1] != '\0')
 		problem = PR_2_DOT_NULL_TERM;
-	
+
+	rec_len = (dirent->rec_len || ctx->fs->blocksize < 65536) ?
+		dirent->rec_len : 65536;
 	if (problem) {
 		if (fix_problem(ctx, problem, pctx)) {
-			if (dirent->rec_len < 12)
-				dirent->rec_len = 12;
+			if (rec_len < 12)
+				rec_len = dirent->rec_len = 12;
 			dirent->inode = ino;
 			dirent->name_len = 1;
 			dirent->name[0] = '.';
@@ -384,8 +386,8 @@
 			status = 1;
 		}
 	}
-	if (dirent->rec_len > 12) {
-		new_len = dirent->rec_len - 12;
+	if (rec_len > 12) {
+		new_len = rec_len - 12;
 		if (new_len > 12) {
 			if (created ||
 			    fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) {
@@ -411,7 +413,7 @@
 			struct ext2_dir_entry *dirent,
 			ext2_ino_t ino, struct problem_context *pctx)
 {
-	int			problem = 0;
+	int	rec_len, problem = 0;
 	
 	if (!dirent->inode)
 		problem = PR_2_MISSING_DOT_DOT;
@@ -422,9 +424,11 @@
 	else if (dirent->name[2] != '\0')
 		problem = PR_2_DOT_DOT_NULL_TERM;
 
+	rec_len = (dirent->rec_len || ctx->fs->blocksize < 65536) ?
+		dirent->rec_len : 65536;
 	if (problem) {
 		if (fix_problem(ctx, problem, pctx)) {
-			if (dirent->rec_len < 12)
+			if (rec_len < 12)
 				dirent->rec_len = 12;
 			/*
 			 * Note: we don't have the parent inode just
@@ -644,14 +648,18 @@
 			      unsigned int *offset)
 {
 	char	*cp = (char *) dirent;
-	int left = fs->blocksize - *offset - dirent->rec_len;
+	int	left, rec_len;
 	unsigned int name_len = dirent->name_len & 0xFF;
 
+	rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+		dirent->rec_len : 65536;
+	left = fs->blocksize - *offset - rec_len;
+
 	/*
 	 * Special case of directory entry of size 8: copy what's left
 	 * of the directory block up to cover up the invalid hole.
 	 */
-	if ((left >= 12) && (dirent->rec_len == 8)) {
+	if ((left >= 12) && (rec_len == 8)) {
 		memmove(cp, cp+8, left);
 		memset(cp + left, 0, 8);
 		return;
@@ -662,7 +670,7 @@
 	 * record length.
 	 */
 	if ((left < 0) &&
-	    (name_len + 8 <= dirent->rec_len + (unsigned) left) &&
+	    (name_len + 8 <= rec_len + (unsigned) left) &&
 	    dirent->inode <= fs->super->s_inodes_count &&
 	    strnlen(dirent->name, name_len) == name_len) {
 		dirent->rec_len += left;
@@ -673,10 +681,10 @@
 	 * of four, and not too big, such that it is valid, let the
 	 * previous directory entry absorb the invalid one.
 	 */
-	if (prev && dirent->rec_len && (dirent->rec_len % 4) == 0 &&
-	    (*offset + dirent->rec_len <= fs->blocksize)) {
-		prev->rec_len += dirent->rec_len;
-		*offset += dirent->rec_len;
+	if (prev && rec_len && (rec_len % 4) == 0 &&
+	    (*offset + rec_len <= fs->blocksize)) {
+		prev->rec_len += rec_len;
+		*offset += rec_len;
 		return;
 	}
 	/*
@@ -709,6 +717,7 @@
 	const char *		old_op;
 	int			dir_modified = 0;
 	int			dot_state;
+	int			rec_len;
 	blk_t			block_nr = db->blk;
 	ext2_ino_t 		ino = db->ino;
 	ext2_ino_t 		subdir_parent;
@@ -800,6 +809,8 @@
 		dx_db->max_hash = 0;
 			
 		dirent = (struct ext2_dir_entry *) buf;
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
 		limit = (struct ext2_dx_countlimit *) (buf+8);
 		if (db->blockcnt == 0) {
 			root = (struct ext2_dx_root_info *) (buf + 24);
@@ -819,7 +830,7 @@
 				dx_dir->hashversion += 3;
 			dx_dir->depth = root->indirect_levels + 1;
 		} else if ((dirent->inode == 0) &&
-			   (dirent->rec_len == fs->blocksize) &&
+			   (rec_len == fs->blocksize) &&
 			   (dirent->name_len == 0) &&
 			   (ext2fs_le16_to_cpu(limit->limit) == 
 			    ((fs->blocksize-8) / 
@@ -837,12 +848,14 @@
 
 		problem = 0;
 		dirent = (struct ext2_dir_entry *) (buf + offset);
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
 		cd->pctx.dirent = dirent;
 		cd->pctx.num = offset;
-		if (((offset + dirent->rec_len) > fs->blocksize) ||
-		    (dirent->rec_len < 12) ||
-		    ((dirent->rec_len % 4) != 0) ||
-		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+		if (((offset + rec_len) > fs->blocksize) ||
+		    (rec_len < 12) ||
+		    ((rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > rec_len)) {
 			if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) {
 				salvage_directory(fs, dirent, prev, &offset);
 				dir_modified++;
@@ -1092,7 +1105,10 @@
 		ctx->fs_total_count++;
 	next:
 		prev = dirent;
-		offset += dirent->rec_len;
+		if (dir_modified)
+			rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+				dirent->rec_len : 65536;
+		offset += rec_len;
 		dot_state++;
 	} while (offset < fs->blocksize);
 #if 0
@@ -1112,7 +1128,7 @@
 	}
 #endif /* ENABLE_HTREE */
 	if (offset != fs->blocksize) {
-		cd->pctx.num = dirent->rec_len - fs->blocksize + offset;
+		cd->pctx.num = rec_len - fs->blocksize + offset;
 		if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) {
 			dirent->rec_len = cd->pctx.num;
 			dir_modified++;
diff --git a/e2fsck/problem.c b/e2fsck/problem.c
index 9d4c4e8..27e2bf0 100644
--- a/e2fsck/problem.c
+++ b/e2fsck/problem.c
@@ -1136,7 +1136,7 @@
 
 	/* Directory entry for '.' is big.  Split? */
 	{ PR_2_SPLIT_DOT,
-	  N_("@d @e for '.' is big.  "),
+	  N_("@d @e for '.' in %p (%i) is big.\n"),
 	  PROMPT_SPLIT, PR_NO_OK },
 
 	/* Illegal FIFO inode */
diff --git a/e2fsck/rehash.c b/e2fsck/rehash.c
index e75774a..6c7d051 100644
--- a/e2fsck/rehash.c
+++ b/e2fsck/rehash.c
@@ -89,7 +89,7 @@
 	struct ext2_dir_entry 	*dirent;
 	char			*dir;
 	unsigned int		offset, dir_offset;
-	int			hash_alg;
+	int			rec_len, hash_alg;
 	
 	if (blockcnt < 0)
 		return 0;
@@ -117,14 +117,16 @@
 	dir_offset = 0;
 	while (dir_offset < fs->blocksize) {
 		dirent = (struct ext2_dir_entry *) (dir + dir_offset);
-		if (((dir_offset + dirent->rec_len) > fs->blocksize) ||
-		    (dirent->rec_len < 8) ||
-		    ((dirent->rec_len % 4) != 0) ||
-		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
+		if (((dir_offset + rec_len) > fs->blocksize) ||
+		    (rec_len < 8) ||
+		    ((rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > rec_len)) {
 			fd->err = EXT2_ET_DIR_CORRUPTED;
 			return BLOCK_ABORT;
 		}
-		dir_offset += dirent->rec_len;
+		dir_offset += rec_len;
 		if (dirent->inode == 0)
 			continue;
 		if (!fd->compress && ((dirent->name_len&0xFF) == 1) &&
diff --git a/lib/ext2fs/dir_iterate.c b/lib/ext2fs/dir_iterate.c
index 3e7b7b0..6bb536b 100644
--- a/lib/ext2fs/dir_iterate.c
+++ b/lib/ext2fs/dir_iterate.c
@@ -29,16 +29,20 @@
  * undeleted entry.  Returns 1 if the deleted entry looks valid, zero
  * if not valid.
  */
-static int ext2fs_validate_entry(char *buf, int offset, int final_offset)
+static int ext2fs_validate_entry(ext2_filsys fs, char *buf, int offset,
+				 int final_offset)
 {
 	struct ext2_dir_entry *dirent;
+	int	rec_len;
 	
 	while (offset < final_offset) {
 		dirent = (struct ext2_dir_entry *)(buf + offset);
-		offset += dirent->rec_len;
-		if ((dirent->rec_len < 8) ||
-		    ((dirent->rec_len % 4) != 0) ||
-		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len))
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
+		offset += rec_len;
+		if ((rec_len < 8) ||
+		    ((rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > rec_len))
 			return 0;
 	}
 	return (offset == final_offset);
@@ -144,7 +148,7 @@
 	int		ret = 0;
 	int		changed = 0;
 	int		do_abort = 0;
-	int		entry, size;
+	int		rec_len, entry, size;
 	struct ext2_dir_entry *dirent;
 
 	if (blockcnt < 0)
@@ -158,10 +162,12 @@
 
 	while (offset < fs->blocksize) {
 		dirent = (struct ext2_dir_entry *) (ctx->buf + offset);
-		if (((offset + dirent->rec_len) > fs->blocksize) ||
-		    (dirent->rec_len < 8) ||
-		    ((dirent->rec_len % 4) != 0) ||
-		    (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) {
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
+		if (((offset + rec_len) > fs->blocksize) ||
+		    (rec_len < 8) ||
+		    ((rec_len % 4) != 0) ||
+		    (((dirent->name_len & 0xFF)+8) > rec_len)) {
 			ctx->errcode = EXT2_ET_DIR_CORRUPTED;
 			return BLOCK_ABORT;
 		}
@@ -178,33 +184,36 @@
 		if (entry < DIRENT_OTHER_FILE)
 			entry++;
 			
-		if (ret & DIRENT_CHANGED)
+		if (ret & DIRENT_CHANGED) {
+			rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+				dirent->rec_len : 65536;
 			changed++;
+		}
 		if (ret & DIRENT_ABORT) {
 			do_abort++;
 			break;
 		}
 next:		
  		if (next_real_entry == offset)
-			next_real_entry += dirent->rec_len;
+			next_real_entry += rec_len;
  
  		if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) {
 			size = ((dirent->name_len & 0xFF) + 11) & ~3;
 
-			if (dirent->rec_len != size)  {
+			if (rec_len != size)  {
 				unsigned int final_offset;
 
-				final_offset = offset + dirent->rec_len;
+				final_offset = offset + rec_len;
 				offset += size;
 				while (offset < final_offset &&
-				       !ext2fs_validate_entry(ctx->buf,
+				       !ext2fs_validate_entry(fs, ctx->buf,
 							      offset,
 							      final_offset))
 					offset += 4;
 				continue;
 			}
 		}
-		offset += dirent->rec_len;
+		offset += rec_len;
 	}
 
 	if (changed) {
diff --git a/lib/ext2fs/dirblock.c b/lib/ext2fs/dirblock.c
index fb20fa0..c61e001 100644
--- a/lib/ext2fs/dirblock.c
+++ b/lib/ext2fs/dirblock.c
@@ -46,12 +46,12 @@
 		if (flags & EXT2_DIRBLOCK_V2_STRUCT)
 			dirent->name_len = ext2fs_swab16(dirent->name_len);
 #endif
-		rec_len = dirent->rec_len;
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
 		if ((rec_len < 8) || (rec_len % 4)) {
 			rec_len = 8;
 			retval = EXT2_ET_DIR_CORRUPTED;
-		}
-		if (((name_len & 0xFF) + 8) > dirent->rec_len)
+		} else if (((name_len & 0xFF) + 8) > rec_len)
 			retval = EXT2_ET_DIR_CORRUPTED;
 		p += rec_len;
 	}
@@ -72,6 +72,7 @@
 	errcode_t	retval;
 	char		*p, *end;
 	char		*buf = 0;
+	int		rec_len;
 	struct ext2_dir_entry *dirent;
 
 	retval = ext2fs_get_mem(fs->blocksize, &buf);
@@ -82,12 +83,14 @@
 	end = buf + fs->blocksize;
 	while (p < end) {
 		dirent = (struct ext2_dir_entry *) p;
-		if ((dirent->rec_len < 8) ||
-		    (dirent->rec_len % 4)) {
+		rec_len = (dirent->rec_len || fs->blocksize < 65536) ?
+			dirent->rec_len : 65536;
+		if ((rec_len < 8) ||
+		    (rec_len % 4)) {
 			ext2fs_free_mem(&buf);
 			return (EXT2_ET_DIR_CORRUPTED);
 		}
-		p += dirent->rec_len;
+		p += rec_len;
 		dirent->inode = ext2fs_swab32(dirent->inode);
 		dirent->rec_len = ext2fs_swab16(dirent->rec_len);
 		dirent->name_len = ext2fs_swab16(dirent->name_len);
diff --git a/lib/ext2fs/link.c b/lib/ext2fs/link.c
index 5e0f4f3..b283488 100644
--- a/lib/ext2fs/link.c
+++ b/lib/ext2fs/link.c
@@ -24,6 +24,7 @@
 	ext2_ino_t	inode;
 	int		flags;
 	int		done;
+	unsigned int	blocksize;
 	struct ext2_super_block *sb;
 };	
 
@@ -35,20 +36,24 @@
 {
 	struct link_struct *ls = (struct link_struct *) priv_data;
 	struct ext2_dir_entry *next;
-	int rec_len, min_rec_len;
+	int rec_len, min_rec_len, curr_rec_len;
 	int ret = 0;
 
 	rec_len = EXT2_DIR_REC_LEN(ls->namelen);
 
+	curr_rec_len = (dirent->rec_len || ls->blocksize < 65536) ?
+		dirent->rec_len : 65536;
+
 	/*
 	 * See if the following directory entry (if any) is unused;
 	 * if so, absorb it into this one.
 	 */
-	next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len);
-	if ((offset + dirent->rec_len < blocksize - 8) &&
+	next = (struct ext2_dir_entry *) (buf + offset + curr_rec_len);
+	if ((offset + curr_rec_len < blocksize - 8) &&
 	    (next->inode == 0) &&
-	    (offset + dirent->rec_len + next->rec_len <= blocksize)) {
+	    (offset + curr_rec_len + next->rec_len <= blocksize)) {
 		dirent->rec_len += next->rec_len;
+		curr_rec_len = dirent->rec_len;
 		ret = DIRENT_CHANGED;
 	}
 
@@ -59,9 +64,9 @@
 	 */
 	if (dirent->inode) {
 		min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF);
-		if (dirent->rec_len < (min_rec_len + rec_len))
+		if (curr_rec_len < (min_rec_len + rec_len))
 			return ret;
-		rec_len = dirent->rec_len - min_rec_len;
+		rec_len = curr_rec_len - min_rec_len;
 		dirent->rec_len = min_rec_len;
 		next = (struct ext2_dir_entry *) (buf + offset +
 						  dirent->rec_len);
@@ -75,7 +80,7 @@
 	 * If we get this far, then the directory entry is not used.
 	 * See if we can fit the request entry in.  If so, do it.
 	 */
-	if (dirent->rec_len < rec_len)
+	if (curr_rec_len < rec_len)
 		return ret;
 	dirent->inode = ls->inode;
 	dirent->name_len = ls->namelen;
@@ -112,6 +117,7 @@
 	ls.flags = flags;
 	ls.done = 0;
 	ls.sb = fs->super;
+	ls.blocksize = fs->blocksize;
 
 	retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY,
 				    0, link_proc, &ls);
diff --git a/lib/ext2fs/newdir.c b/lib/ext2fs/newdir.c
index 3904d91..c2ca903 100644
--- a/lib/ext2fs/newdir.c
+++ b/lib/ext2fs/newdir.c
@@ -53,7 +53,7 @@
 		dir->inode = dir_ino;
 		dir->name_len = 1 | filetype;
 		dir->name[0] = '.';
-		rec_len = dir->rec_len - EXT2_DIR_REC_LEN(1);
+		rec_len = fs->blocksize - EXT2_DIR_REC_LEN(1);
 		dir->rec_len = EXT2_DIR_REC_LEN(1);
 
 		/*
diff --git a/misc/e2image.c b/misc/e2image.c
index 081c66f..358b361 100644
--- a/misc/e2image.c
+++ b/misc/e2image.c
@@ -350,6 +350,8 @@
 #ifdef WORDS_BIGENDIAN
 		rec_len = ext2fs_swab16(rec_len);
 #endif
+		rec_len = (rec_len || fs->blocksize < 65536) ?
+			rec_len : 65536;
 #if 0
 		printf("rec_len = %d, name_len = %d\n", rec_len, dirent->name_len);
 #endif