e2fsck: improve the inline directory detector

Strengthen the checks that guess if the inode we're looking at is an
inline directory.  The current check sweeps up any inline inode if
its length is a multiple of four; now we'll at least try to see if
there's the beginning of a valid directory entry.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c
index 69a9ac1..9f22826 100644
--- a/e2fsck/pass1.c
+++ b/e2fsck/pass1.c
@@ -537,6 +537,9 @@
 			 EXT4_FEATURE_INCOMPAT_INLINE_DATA);
 	if (inlinedata_fs && (inode->i_flags & EXT4_INLINE_DATA_FL)) {
 		size_t size;
+		__u32 dotdot;
+		unsigned int rec_len;
+		struct ext2_dir_entry de;
 
 		if (ext2fs_inline_data_size(ctx->fs, pctx->ino, &size))
 			return;
@@ -546,6 +549,26 @@
 		 */
 		if (size & 3)
 			return;
+		/*
+		 * If the first 10 bytes don't look like a directory entry,
+		 * it's probably not a directory.
+		 */
+		memcpy(&dotdot, inode->i_block, sizeof(dotdot));
+		memcpy(&de, ((char *)inode->i_block) + EXT4_INLINE_DATA_DOTDOT_SIZE,
+		       EXT2_DIR_REC_LEN(0));
+		dotdot = ext2fs_le32_to_cpu(dotdot);
+		de.inode = ext2fs_le32_to_cpu(de.inode);
+		de.rec_len = ext2fs_le16_to_cpu(de.rec_len);
+		ext2fs_get_rec_len(ctx->fs, &de, &rec_len);
+		if (dotdot >= ctx->fs->super->s_inodes_count ||
+		    (dotdot < EXT2_FIRST_INO(ctx->fs->super) &&
+		     dotdot != EXT2_ROOT_INO) ||
+		    de.inode >= ctx->fs->super->s_inodes_count ||
+		    (de.inode < EXT2_FIRST_INO(ctx->fs->super) &&
+		     de.inode != 0) ||
+		    rec_len > EXT4_MIN_INLINE_DATA_SIZE -
+			      EXT4_INLINE_DATA_DOTDOT_SIZE)
+			return;
 		/* device files never have a "system.data" entry */
 		goto isdir;
 	} else if (extent_fs && (inode->i_flags & EXT4_EXTENTS_FL)) {
diff --git a/tests/f_inlinedata_repair/expect.1 b/tests/f_inlinedata_repair/expect.1
index faba192..cc220ba 100644
--- a/tests/f_inlinedata_repair/expect.1
+++ b/tests/f_inlinedata_repair/expect.1
@@ -8,11 +8,6 @@
 Inode 28 is a unknown file type with mode 00 but it looks like it is really a directory.
 Fix? yes
 
-Inode 36 is a unknown file type with mode 00 but it looks like it is really a directory.
-Fix? yes
-
-Inode 36, i_size is 5, should be 60.  Fix? yes
-
 Pass 2: Checking directory structure
 Directory inode 20, block #0, offset 4: directory corrupted
 Salvage? yes
@@ -26,26 +21,16 @@
 Directory inode 32, block #0, offset 4: directory corrupted
 Salvage? yes
 
-Entry '..' in ??? (36) has invalid inode #: 1633774699.
-Clear? yes
-
-Directory inode 36, block #0, offset 4: directory corrupted
-Salvage? yes
-
 Symlink /3 (inode #14) is invalid.
 Clear? yes
 
 Inode 38 (/B) has invalid mode (00).
 Clear? yes
 
-Entry 'A' in / (2) has an incorrect filetype (was 1, should be 2).
-Fix? yes
+Inode 36 (/A) has invalid mode (00).
+Clear? yes
 
 Pass 3: Checking directory connectivity
-'..' in /A (36) is ??? (1633774699), should be / (2).
-Fix? yes
-
-Error while adjusting inode count on inode 0
 Pass 4: Checking reference counts
 Unattached zero-length inode 22.  Clear? yes
 
@@ -63,13 +48,8 @@
 
 Unattached zero-length inode 35.  Clear? yes
 
-Inode 36 ref count is 1, should be 2.  Fix? yes
-
 Pass 5: Checking group summary information
-Directories count wrong for group #0 (7, counted=8).
-Fix? yes
-
 
 test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
-test_filesys: 28/128 files (0.0% non-contiguous), 18/512 blocks
+test_filesys: 27/128 files (0.0% non-contiguous), 18/512 blocks
 Exit status is 1
diff --git a/tests/f_inlinedata_repair/expect.2 b/tests/f_inlinedata_repair/expect.2
index 519f21d..2c400a5 100644
--- a/tests/f_inlinedata_repair/expect.2
+++ b/tests/f_inlinedata_repair/expect.2
@@ -3,5 +3,5 @@
 Pass 3: Checking directory connectivity
 Pass 4: Checking reference counts
 Pass 5: Checking group summary information
-test_filesys: 28/128 files (0.0% non-contiguous), 18/512 blocks
+test_filesys: 27/128 files (0.0% non-contiguous), 18/512 blocks
 Exit status is 0
diff --git a/tests/f_inlinedir_detector/expect.1 b/tests/f_inlinedir_detector/expect.1
new file mode 100644
index 0000000..72b7519
--- /dev/null
+++ b/tests/f_inlinedir_detector/expect.1
@@ -0,0 +1,20 @@
+Pass 1: Checking inodes, blocks, and sizes
+Special (device/socket/fifo) file (inode 12) has extents
+or inline-data flag set.  Clear? yes
+
+Special (device/socket/fifo) inode 12 has non-zero size.  Fix? yes
+
+Inode 13 is a named pipe but it looks like it is really a directory.
+Fix? yes
+
+Pass 2: Checking directory structure
+Entry 'moo' in / (2) has an incorrect filetype (was 1, should be 5).
+Fix? yes
+
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+
+test_filesys: ***** FILE SYSTEM WAS MODIFIED *****
+test_filesys: 13/128 files (0.0% non-contiguous), 17/512 blocks
+Exit status is 1
diff --git a/tests/f_inlinedir_detector/expect.2 b/tests/f_inlinedir_detector/expect.2
new file mode 100644
index 0000000..06886a4
--- /dev/null
+++ b/tests/f_inlinedir_detector/expect.2
@@ -0,0 +1,7 @@
+Pass 1: Checking inodes, blocks, and sizes
+Pass 2: Checking directory structure
+Pass 3: Checking directory connectivity
+Pass 4: Checking reference counts
+Pass 5: Checking group summary information
+test_filesys: 13/128 files (0.0% non-contiguous), 17/512 blocks
+Exit status is 0
diff --git a/tests/f_inlinedir_detector/image.gz b/tests/f_inlinedir_detector/image.gz
new file mode 100644
index 0000000..34b4518
--- /dev/null
+++ b/tests/f_inlinedir_detector/image.gz
Binary files differ
diff --git a/tests/f_inlinedir_detector/name b/tests/f_inlinedir_detector/name
new file mode 100644
index 0000000..3368af5
--- /dev/null
+++ b/tests/f_inlinedir_detector/name
@@ -0,0 +1 @@
+detect inline dirs correctly