add patch fix-dir_nlink-behaviour
[ext4-patch-queue.git] / fix-dir_nlink-behaviour
blob61791f03c329e74f778970990c0ebdd76b241b09
1 ext4: fix dir_nlink behaviour
3 From: Andreas Dilger <adilger@dilger.ca>
5 The dir_nlink feature has been enabled by default for new ext4
6 filesystems since e2fsprogs-1.41 in 2008, and was automatically
7 enabled by the kernel for older ext4 filesystems since the
8 dir_nlink feature was added with ext4 in kernel 2.6.28+ when
9 the subdirectory count exceeded EXT4_LINK_MAX-1.
11 Automatically adding the file system features such as dir_nlink is
12 generally frowned upon, since it could cause the file system to not be
13 mountable on older kernel, thus preventing the administrator from
14 rolling back to an older kernel if necessary.
16 In this case, the administrator might also want to disable the feature
17 because glibc's fts_read() function does not correctly optimize
18 directory traversal for directories that use st_nlinks field of 1 to
19 indicate that the number of links in the directory are not tracked by
20 the file system, and could fail to traverse the full directory
21 hierarchy.  Fortunately, in the past ten years very few users have
22 complained about incomplete file system traversal by glibc's
23 fts_read().
25 This commit also changes ext4_inc_count() to allow i_nlinks to reach
26 the full EXT4_LINK_MAX links on the parent directory (including "."
27 and "..") before changing i_links_count to be 1.
29 Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=196405
30 Signed-off-by: Andreas Dilger <adilger@dilger.ca>
31 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
32 ---
33  fs/ext4/ext4.h  |  3 ++-
34  fs/ext4/namei.c | 21 ++++++++++++---------
35  2 files changed, 14 insertions(+), 10 deletions(-)
37 diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
38 index 8e80461..e163b87 100644
39 --- a/fs/ext4/ext4.h
40 +++ b/fs/ext4/ext4.h
41 @@ -2016,7 +2016,8 @@ static inline __le16 ext4_rec_len_to_disk(unsigned len, unsigned blocksize)
43  #define is_dx(dir) (ext4_has_feature_dir_index((dir)->i_sb) && \
44                     ext4_test_inode_flag((dir), EXT4_INODE_INDEX))
45 -#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX)
46 +#define EXT4_DIR_LINK_MAX(dir) unlikely((dir)->i_nlink >= EXT4_LINK_MAX && \
47 +                   !(ext4_has_feature_dir_nlink((dir)->i_sb) && is_dx(dir)))
48  #define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)
50  /* Legal values for the dx_root hash_version field: */
51 diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
52 index b81f7d4..566c149 100644
53 --- a/fs/ext4/namei.c
54 +++ b/fs/ext4/namei.c
55 @@ -2351,19 +2351,22 @@ static int ext4_delete_entry(handle_t *handle,
56  }
58  /*
59 - * DIR_NLINK feature is set if 1) nlinks > EXT4_LINK_MAX or 2) nlinks == 2,
60 - * since this indicates that nlinks count was previously 1.
61 + * Set directory link count to 1 if nlinks > EXT4_LINK_MAX, or if nlinks == 2
62 + * since this indicates that nlinks count was previously 1 to avoid overflowing
63 + * the 16-bit i_links_count field on disk.  Directories with i_nlink == 1 mean
64 + * that subdirectory link counts are not being maintained accurately.
65 + *
66 + * The caller has already checked for i_nlink overflow in case the DIR_LINK
67 + * feature is not enabled and returned -EMLINK.  The is_dx() check is a proxy
68 + * for checking S_ISDIR(inode) (since the INODE_INDEX feature will not be set
69 + * on regular files) and to avoid creating huge/slow non-HTREE directories.
70   */
71  static void ext4_inc_count(handle_t *handle, struct inode *inode)
72  {
73         inc_nlink(inode);
74 -       if (is_dx(inode) && inode->i_nlink > 1) {
75 -               /* limit is 16-bit i_links_count */
76 -               if (inode->i_nlink >= EXT4_LINK_MAX || inode->i_nlink == 2) {
77 -                       set_nlink(inode, 1);
78 -                       ext4_set_feature_dir_nlink(inode->i_sb);
79 -               }
80 -       }
81 +       if (is_dx(inode) &&
82 +           (inode->i_nlink > EXT4_LINK_MAX || inode->i_nlink == 2))
83 +               set_nlink(inode, 1);
84  }
86  /*
87 -- 
88 1.8.0