Thread (6 messages) 6 messages, 2 authors, 2017-08-31

Re: [PATCH] e2fsck: set dir_nlink feature if large dir exists

From: Andreas Dilger <hidden>
Date: 2017-08-18 18:45:30

On Aug 18, 2017, at 12:19 PM, Andreas Dilger [off-list ref] wrote:
If there is a directory with more than EXT2_LINK_MAX (65000)
subdirectories, but the DIR_NLINK feature is not set in the
superblock, the feature should be set before continuing on
to change the on-disk directory link count to 1.

While most filesystems should have DIR_NLINK set (it was set
by default for all ext4 filesystems, and the kernel before
4.12 automatically set it if the directory link count grew
too large), it is possible that this flag is lost due to disk
corruption or for an upgraded filesystem.  We no longer want
the kernel to automatically enable this feature.

Addresses: https://bugzilla.kernel.org/show_bug.cgi?id=196405
Signed-off-by: Andreas Dilger <redacted>
---
Note that this is using the f_large_dir test for verification,
since it was already creating a directory with 48k entries in
it and already took ages to run because directory processing
in libext2fs is O(n^2) (it took about 2h to finish in my VM).
The alternative is storing a 100MB image file (though it may
be possible to compress it significantly).

I'm working on a patch to improve the debugfs "expand" command
so that it can insert multiple directory blocks at once, rather
than one-at-a-time (after a full directory scan).  That at least
fixes half of the problem.

Cheers, Andreas
quoted hunk ↗ jump to hunk
e2fsck/pass4.c           | 12 +++++++++-
e2fsck/problem.c         |  5 ++++
e2fsck/problem.h         |  3 +++
tests/f_large_dir/expect |  7 ++++--
tests/f_large_dir/script | 60 ++++++++++++++++++++++++++++++------------------
5 files changed, 62 insertions(+), 25 deletions(-)
diff --git a/e2fsck/pass4.c b/e2fsck/pass4.c
index 663f87a..d0ff8e9 100644
--- a/e2fsck/pass4.c
+++ b/e2fsck/pass4.c
@@ -170,6 +170,7 @@ void e2fsck_pass4(e2fsck_t ctx)
#endif
	struct problem_context	pctx;
	__u16	link_count, link_counted;
+	int dir_nlink_fs;
	char	*buf = 0;
	dgrp_t	group, maxgroup;
@@ -193,6 +194,8 @@ void e2fsck_pass4(e2fsck_t ctx)
	if (!(ctx->options & E2F_OPT_PREEN))
		fix_problem(ctx, PR_4_PASS_HEADER, &pctx);

+	dir_nlink_fs = ext2fs_has_feature_dir_nlink(fs->super);
+
	group = 0;
	maxgroup = fs->group_desc_count;
	if (ctx->progress)
@@ -249,8 +252,15 @@ void e2fsck_pass4(e2fsck_t ctx)
					    &link_counted);
		}
		isdir = ext2fs_test_inode_bitmap2(ctx->inode_dir_map, i);
-		if (isdir && (link_counted > EXT2_LINK_MAX))
+		if (isdir && (link_counted > EXT2_LINK_MAX)) {
+			if (!dir_nlink_fs &&
+			    fix_problem(ctx, PR_4_DIR_NLINK_FEATURE, &pctx)) {
+				ext2fs_set_feature_dir_nlink(fs->super);
+				ext2fs_mark_super_dirty(fs);
+				dir_nlink_fs = 1;
+			}
			link_counted = 1;
+		}
		if (link_counted != link_count) {
			e2fsck_read_inode_full(ctx, i, EXT2_INODE(inode),
					       inode_size, "pass4");
diff --git a/e2fsck/problem.c b/e2fsck/problem.c
index 9706933..25c1de9 100644
--- a/e2fsck/problem.c
+++ b/e2fsck/problem.c
@@ -1873,6 +1873,11 @@ static struct e2fsck_problem problem_table[] = {
	  N_("@a @i %i ref count is %N, @s %n. "),
	  PROMPT_FIX, PR_PREEN_OK },

+	/* directory exceeds max links, but no DIR_NLINK feature in superblock*/
+	{ PR_4_DIR_NLINK_FEATURE,
+	  N_("@d exceeds max links, but no DIR_NLINK feature in @S.\n"),
+	  PROMPT_FIX, 0 },
+
	/* Pass 5 errors */

	/* Pass 5: Checking group summary information */
diff --git a/e2fsck/problem.h b/e2fsck/problem.h
index f30f8f0..07ed0a7 100644
--- a/e2fsck/problem.h
+++ b/e2fsck/problem.h
@@ -1134,6 +1134,9 @@ struct problem_context {
/* Extended attribute inode ref count wrong */
#define PR_4_EA_INODE_REF_COUNT		0x040005

+/* directory exceeds max links, but no DIR_NLINK feature in superblock */
+#define PR_4_DIR_NLINK_FEATURE		0x040006
+
/*
 * Pass 5 errors
 */
diff --git a/tests/f_large_dir/expect b/tests/f_large_dir/expect
index b099460..4b9ca6f 100644
--- a/tests/f_large_dir/expect
+++ b/tests/f_large_dir/expect
@@ -3,10 +3,13 @@ Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 3A: Optimizing directories
Pass 4: Checking reference counts
-Inode 13 ref count is 1, should be 47245.  Fix? yes
+Directory exceeds max links, but no DIR_NLINK feature in superblock.
+Fix? yes
+
+Inode 12 ref count is 65012, should be 1.  Fix? yes

Pass 5: Checking group summary information

test.img: ***** FILE SYSTEM WAS MODIFIED *****
-test.img: 13/115368 files (0.0% non-contiguous), 32817/460800 blocks
+test.img: 65023/65104 files (0.0% non-contiguous), 96668/100937 blocks
Exit status is 1
diff --git a/tests/f_large_dir/script b/tests/f_large_dir/script
index 0b5fdff..a10fe16 100644
--- a/tests/f_large_dir/script
+++ b/tests/f_large_dir/script
@@ -5,43 +5,59 @@ E2FSCK=../e2fsck/e2fsck
NAMELEN=255
DIRENT_SZ=8
BLOCKSZ=1024
+INODESZ=128
DIRENT_PER_LEAF=$((BLOCKSZ / (NAMELEN + DIRENT_SZ)))
HEADER=32
INDEX_SZ=8
INDEX_L1=$(((BLOCKSZ - HEADER) / INDEX_SZ))
INDEX_L2=$(((BLOCKSZ - DIRENT_SZ) / INDEX_SZ))
ENTRIES=$((INDEX_L1 * INDEX_L2 * DIRENT_PER_LEAF))
+DIRBLK=$((2 + INDEX_L1 * INDEX_L2))
+EXT4_LINK_MAX=65000
+[ $ENTRIES -lt $((EXT4_LINK_MAX + 10)) ] && ENTRIES=$((EXT4_LINK_MAX + 10))
+FSIZE=$(((DIRBLK + EXT4_LINK_MAX * ((BLOCKSZ + INODESZ) / BLOCKSZ)) * 5 / 4))

-cp /dev/null $OUT
-$MKE2FS -b 1024 -O large_dir,uninit_bg,dir_nlink -F $TMPFILE 460800 \
-	> /dev/null 2>&1
+> $OUT
+$MKE2FS -b 1024 -O large_dir,uninit_bg -N $((ENTRIES + 50)) \
+	-I $INODESZ -F $TMPFILE $FSIZE > $OUT 2>&1
+RC=$?
+if [ $RC -eq 0 ]; then
{
-	echo "feature large_dir"
+	START=$SECONDS
	echo "mkdir /foo"
	echo "cd /foo"
-	touch foofile
-	echo "write foofile foofile"
+	touch $TMPFILE.tmp
+	echo "write $TMPFILE.tmp foofile"
	i=0
-	while test $i  -lt $ENTRIES ; do
-	    if test $(( i % DIRENT_PER_LEAF )) -eq 0 ; then
-		echo "expand ./"
+	while test $i -lt $ENTRIES ; do
+	    if test $((i % DIRENT_PER_LEAF)) -eq 0; then
+	    	echo "expand ./"
	    fi
-	    if test $(( i % 5000 )) -eq 0 -a $i -gt 0 ; then
-		>&2 echo "$test_name: $i processed"
+	    if test $((i % 5000)) -eq 0 -a $i -gt 0; then
+		ELAPSED=$((SECONDS - START))
+		RATE=$((i / ELAPSED))
+		>&2 echo "$test_name: $i processed in ${ELAPSED}s @ $RATE/s"
	    fi
-	    printf "ln foofile %0255X\n" $i
-	    i=$(($i + 1))
+	    if test $i -lt $((EXT4_LINK_MAX + 10)); then
+		printf "mkdir d%0254u\n" $i
+	    else
+		printf "ln foofile f%0254u\n" $i
+	    fi
+	    i=$((i + 1))
	done
-} | $DEBUGFS -w -f /dev/stdin $TMPFILE > /dev/null 2>&1
-
-$E2FSCK -yfD $TMPFILE > $OUT.new 2>&1
-status=$?
-echo Exit status is $status >> $OUT.new
-sed -f $cmd_dir/filter.sed -e "s;$TMPFILE;test.img;" $OUT.new >> $OUT
-rm -f $OUT.new
+} | $DEBUGFS -w -f /dev/stdin $TMPFILE > $OUT
+	RC=$?
+fi
+if [ $RC -eq 0 ]; then
+	$E2FSCK -yfD $TMPFILE > $OUT.new 2>&1
+	status=$?
+	echo "Exit status is $status" >> $OUT.new
+	sed -f $cmd_dir/filter.sed -e "s;$TMPFILE;test.img;" $OUT.new > $OUT
+	rm -f $OUT.new

-cmp -s $OUT $EXP
-RC=$?
+	cmp -s $OUT $EXP
+	RC=$?
+fi
if [ $RC -eq 0 ]; then
	echo "$test_name: $test_description: ok"
	touch $test_name.ok
--
1.8.0

Cheers, Andreas




Attachments

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help