Commit c7414892 authored by Andreas Dilger's avatar Andreas Dilger Committed by Theodore Ts'o

ext4: fix dir_nlink behaviour

The dir_nlink feature has been enabled by default for new ext4
filesystems since e2fsprogs-1.41 in 2008, and was automatically
enabled by the kernel for older ext4 filesystems since the
dir_nlink feature was added with ext4 in kernel 2.6.28+ when
the subdirectory count exceeded EXT4_LINK_MAX-1.

Automatically adding the file system features such as dir_nlink is
generally frowned upon, since it could cause the file system to not be
mountable on older kernel, thus preventing the administrator from
rolling back to an older kernel if necessary.

In this case, the administrator might also want to disable the feature
because glibc's fts_read() function does not correctly optimize
directory traversal for directories that use st_nlinks field of 1 to
indicate that the number of links in the directory are not tracked by
the file system, and could fail to traverse the full directory
hierarchy.  Fortunately, in the past ten years very few users have
complained about incomplete file system traversal by glibc's
fts_read().

This commit also changes ext4_inc_count() to allow i_nlinks to reach
the full EXT4_LINK_MAX links on the parent directory (including "."
and "..") before changing i_links_count to be 1.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=196405Signed-off-by: default avatarAndreas Dilger <adilger@dilger.ca>
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
parent 381cebfe
...@@ -2020,7 +2020,8 @@ static inline __le16 ext4_rec_len_to_disk(unsigned len, unsigned blocksize) ...@@ -2020,7 +2020,8 @@ static inline __le16 ext4_rec_len_to_disk(unsigned len, unsigned blocksize)
#define is_dx(dir) (ext4_has_feature_dir_index((dir)->i_sb) && \ #define is_dx(dir) (ext4_has_feature_dir_index((dir)->i_sb) && \
ext4_test_inode_flag((dir), EXT4_INODE_INDEX)) ext4_test_inode_flag((dir), EXT4_INODE_INDEX))
#define EXT4_DIR_LINK_MAX(dir) (!is_dx(dir) && (dir)->i_nlink >= EXT4_LINK_MAX) #define EXT4_DIR_LINK_MAX(dir) unlikely((dir)->i_nlink >= EXT4_LINK_MAX && \
!(ext4_has_feature_dir_nlink((dir)->i_sb) && is_dx(dir)))
#define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1) #define EXT4_DIR_LINK_EMPTY(dir) ((dir)->i_nlink == 2 || (dir)->i_nlink == 1)
/* Legal values for the dx_root hash_version field: */ /* Legal values for the dx_root hash_version field: */
......
...@@ -2395,19 +2395,22 @@ static int ext4_delete_entry(handle_t *handle, ...@@ -2395,19 +2395,22 @@ static int ext4_delete_entry(handle_t *handle,
} }
/* /*
* DIR_NLINK feature is set if 1) nlinks > EXT4_LINK_MAX or 2) nlinks == 2, * Set directory link count to 1 if nlinks > EXT4_LINK_MAX, or if nlinks == 2
* since this indicates that nlinks count was previously 1. * since this indicates that nlinks count was previously 1 to avoid overflowing
* the 16-bit i_links_count field on disk. Directories with i_nlink == 1 mean
* that subdirectory link counts are not being maintained accurately.
*
* The caller has already checked for i_nlink overflow in case the DIR_LINK
* feature is not enabled and returned -EMLINK. The is_dx() check is a proxy
* for checking S_ISDIR(inode) (since the INODE_INDEX feature will not be set
* on regular files) and to avoid creating huge/slow non-HTREE directories.
*/ */
static void ext4_inc_count(handle_t *handle, struct inode *inode) static void ext4_inc_count(handle_t *handle, struct inode *inode)
{ {
inc_nlink(inode); inc_nlink(inode);
if (is_dx(inode) && inode->i_nlink > 1) { if (is_dx(inode) &&
/* limit is 16-bit i_links_count */ (inode->i_nlink > EXT4_LINK_MAX || inode->i_nlink == 2))
if (inode->i_nlink >= EXT4_LINK_MAX || inode->i_nlink == 2) { set_nlink(inode, 1);
set_nlink(inode, 1);
ext4_set_feature_dir_nlink(inode->i_sb);
}
}
} }
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment