]> git.ipfire.org Git - thirdparty/e2fsprogs.git/commitdiff
libext2fs: create link count adjustment helpers for dir_nlink
authorDarrick J. Wong <djwong@kernel.org>
Fri, 3 Oct 2025 21:56:10 +0000 (14:56 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Mon, 9 Mar 2026 02:14:02 +0000 (19:14 -0700)
Create some helpers to deal with link count adjustments for directories
on dir_nlink filesystems that become large enough to have an htree
index.  In other words, fix the problem that creating a new child
subdirectory can overflow the link count of the parent directory.

The unused library functions created by this patch will be used in the
next patch to fix problems with fuse2fs.

Cc: <linux-ext4@vger.kernel.org> # v1.40
Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
debian/libext2fs2t64.symbols
lib/ext2fs/ext2fs.h
lib/ext2fs/link.c
lib/ext2fs/mkdir.c

index b4d80161f1e1b474d34bf6e861310deba09e999d..01f4f269a2660e980076048d0dbb65e3b346277a 100644 (file)
@@ -173,6 +173,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_dblist_iterate@Base 1.37
  ext2fs_dblist_sort2@Base 1.42
  ext2fs_dblist_sort@Base 1.37
+ ext2fs_dec_nlink@Base 1.47.4
  ext2fs_decode_extent@Base 1.46.0
  ext2fs_default_journal_size@Base 1.40
  ext2fs_default_orphan_file_blocks@Base 1.47.0
@@ -182,6 +183,9 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_dir_block_csum_verify@Base 1.43
  ext2fs_dir_iterate2@Base 1.37
  ext2fs_dir_iterate@Base 1.37
+ ext2fs_dir_is_dx@Base 1.47.4
+ ext2fs_dir_link_empty@Base 1.47.4
+ ext2fs_dir_link_max@Base 1.47.4
  ext2fs_dirent_csum_verify@Base 1.43
  ext2fs_dirent_file_type@Base 1.43
  ext2fs_dirent_has_tail@Base 1.43
@@ -376,6 +380,7 @@ libext2fs.so.2 libext2fs2t64 #MINVER#
  ext2fs_image_inode_write@Base 1.37
  ext2fs_image_super_read@Base 1.37
  ext2fs_image_super_write@Base 1.37
+ ext2fs_inc_nlink@Base 1.47.4
  ext2fs_init_csum_seed@Base 1.43
  ext2fs_init_dblist@Base 1.37
  ext2fs_initialize@Base 1.37
index d9df007c499f16228c14fe18c65646cac16f79d5..02212e8ffa597475e8ce0f8108a30c11b2e0777c 100644 (file)
@@ -1842,6 +1842,11 @@ errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name,
                      ext2_ino_t ino, int flags);
 errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, const char *name,
                        ext2_ino_t ino, int flags);
+int ext2fs_dir_is_dx(ext2_filsys fs, const struct ext2_inode *inode);
+void ext2fs_inc_nlink(ext2_filsys fs, struct ext2_inode *inode);
+void ext2fs_dec_nlink(struct ext2_inode *inode);
+int ext2fs_dir_link_max(ext2_filsys fs, struct ext2_inode_large *inode);
+int ext2fs_dir_link_empty(struct ext2_inode *inode);
 
 /* symlink.c */
 errcode_t ext2fs_symlink(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t ino,
index 83d3ea7d00c3ed7509800a0ccb7b6bc56ff3da9b..eccbf0d23b5d6cd31860405ebdbbb17b897ddf4e 100644 (file)
@@ -919,3 +919,52 @@ retry:
        }
        return 0;
 }
+
+/* Does this directory have an htree index? */
+int ext2fs_dir_is_dx(ext2_filsys fs, const struct ext2_inode *inode)
+{
+       return ext2fs_has_feature_dir_index(fs->super) &&
+               S_ISDIR(inode->i_mode) && (inode->i_flags & EXT2_INDEX_FL);
+}
+
+/*
+ * Set directory link count to 1 if nlinks > EXT2_LINK_MAX, or if nlinks == 2
+ * 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.
+ */
+void ext2fs_inc_nlink(ext2_filsys fs, struct ext2_inode *inode)
+{
+       inode->i_links_count++;
+
+       if (ext2fs_dir_is_dx(fs, inode) &&
+           (inode->i_links_count > EXT2_LINK_MAX || inode->i_links_count == 2))
+               inode->i_links_count = 1;
+}
+
+/*
+ * If a directory had nlink == 1, then we should let it be 1. This indicates
+ * directory has >EXT2_LINK_MAX subdirs.
+ */
+void ext2fs_dec_nlink(struct ext2_inode *inode)
+{
+       if (!S_ISDIR(inode->i_mode) || inode->i_links_count > 2)
+               inode->i_links_count--;
+}
+
+int ext2fs_dir_link_max(ext2_filsys fs, struct ext2_inode_large *inode)
+{
+       return inode->i_links_count >= EXT2_LINK_MAX &&
+              !(ext2fs_dir_is_dx(fs, EXT2_INODE(inode)) &&
+                ext2fs_has_feature_dir_nlink(fs->super));
+}
+
+int ext2fs_dir_link_empty(struct ext2_inode *inode)
+{
+       return inode->i_links_count == 2 || inode->i_links_count == 1;
+}
index 45f6e9e27164d21578c8428897a3c381ce824b26..c0a08c88560bd275b9fc695f1a65fec1ebd7b58e 100644 (file)
@@ -182,7 +182,7 @@ errcode_t ext2fs_mkdir2(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t ino,
                retval = ext2fs_read_inode(fs, parent, &parent_inode);
                if (retval)
                        goto cleanup;
-               parent_inode.i_links_count++;
+               ext2fs_inc_nlink(fs, &parent_inode);
                retval = ext2fs_write_inode(fs, parent, &parent_inode);
                if (retval)
                        goto cleanup;