]> git.ipfire.org Git - thirdparty/e2fsprogs.git/commitdiff
e2fsck: set dir_nlink feature if large dir exists
authorAndreas Dilger <adilger@dilger.ca>
Fri, 22 Jun 2018 22:08:54 +0000 (18:08 -0400)
committerTheodore Ts'o <tytso@mit.edu>
Fri, 22 Jun 2018 22:08:54 +0000 (18:08 -0400)
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 all kernels between
2.6.23 and 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 kernels to automatically enable features.

Addresses: https://bugzilla.kernel.org/show_bug.cgi?id=196405
Signed-off-by: Andreas Dilger <adilger@dilger.ca>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
e2fsck/pass4.c
e2fsck/problem.c
e2fsck/problem.h
tests/f_large_dir/expect
tests/f_large_dir/script

index 9a491b136b7f605d981de1c0728cbf0df1082afd..10be7f87180d007fab6d6b3cc0d766e8c8dd32fa 100644 (file)
@@ -145,6 +145,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;
 
@@ -168,6 +169,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)
@@ -224,8 +227,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");
index efe98c920661e23a2e71ecf73e5082c79278d62e..a98bf8c835704a6f14ac86829ed3adca42473d47 100644 (file)
@@ -1883,6 +1883,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 */
index b6bca668bb1b59039319fa7272221242b4b1fbb4..7db122abe7b28acaa18a0b8e6c5bf8582f5632ee 100644 (file)
@@ -1139,6 +1139,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
  */
index b099460571509f2f057294b571bde21400560a8f..8f7d99dc1ee731bb244dff39571bf74b4fafe86a 100644 (file)
@@ -1,12 +1,23 @@
+Creating filesystem with 108341 1k blocks and 65072 inodes
+Superblock backups stored on blocks: 
+       8193, 24577, 40961, 57345, 73729
+
+Allocating group tables:      \b\b\b\b\bdone                            
+Writing inode tables:      \b\b\b\b\bdone                            
+Writing superblocks and filesystem accounting information:      \b\b\b\b\bdone
+
 Pass 1: Checking inodes, blocks, and sizes
 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/65072 files (0.0% non-contiguous), 96666/108341 blocks
 Exit status is 1
index e3151817dcd5c8ac2ca5f27dfcc88339b40eeaa5..9af042ca6ca81912aa37ee1237400b0df0878385 100644 (file)
@@ -5,42 +5,65 @@ 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))
+ENTRIES=$((DIRBLK * DIRENT_PER_LEAF))
+EXT4_LINK_MAX=65000
+if [ $ENTRIES -lt $((EXT4_LINK_MAX + 10)) ]; then
+       ENTRIES=$((EXT4_LINK_MAX + 10))
+       DIRBLK=$((ENTRIES / DIRENT_PER_LEAF + 3))
+fi
+# directory leaf blocks plus inode count and 25% for the rest of the fs
+FSIZE=$(((DIRBLK + EXT4_LINK_MAX * ((BLOCKSZ + INODESZ) / BLOCKSZ)) * 5 / 4))
 
-$MKE2FS -b 1024 -O large_dir,uninit_bg,dir_nlink -F $TMPFILE 460800 \
-       > /dev/null 2>&1
+$MKE2FS -b 1024 -O large_dir,uninit_bg -N $((ENTRIES + 50)) \
+       -I $INODESZ -F $TMPFILE $FSIZE > $OUT.new 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 ./"
+       last=0
+       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/$ENTRIES processed"
+           ELAPSED=$((SECONDS - START))
+           if test $((i % 5000)) -eq 0 -a $ELAPSED -gt 10; then
+               RATE=$(((i - last) / ELAPSED))
+               echo "$test_name: $i/$ENTRIES links, ${ELAPSED}s @ $RATE/s" >&2
+               START=$SECONDS
+               last=$i
            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 $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 $TMPFILE > /dev/null 2>> $OUT.new
+       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