BACKPORT: FROMGIT: f2fs: add lookup_mode mount option
For casefolded directories, f2fs may fall back to a linear search if
a hash-based lookup fails. This can cause severe performance
regressions.
While this behavior can be controlled by userspace tools (e.g. mkfs,
fsck) by setting an on-disk flag, a kernel-level solution is needed
to guarantee the lookup behavior regardless of the on-disk state.
This commit introduces the 'lookup_mode' mount option to provide this
kernel-side control.
The option accepts three values:
- perf: (Default) Enforces a hash-only lookup. The linear fallback
is always disabled.
- compat: Enables the linear search fallback for compatibility with
directory entries from older kernels.
- auto: Determines the mode based on the on-disk flag, preserving the
userspace-based behavior.
Bug: 432807936
(cherry picked from commit 632f0b6c3e32758e5c93d4e3c2860a3708b9853e
https: //git.kernel.org/pub/scm/linux/kernel/git/jaegeuk/f2fs.git dev)
Link: https://lore.kernel.org/linux-f2fs-devel/20250805065228.1473089-1-chullee@google.com/
Change-Id: I51c4cb6eb40c8753c48f6e5de76e2edf24d20422
[chullee: adapted the mount option parsing to an older API]
Signed-off-by: Daniel Lee <chullee@google.com>
Reviewed-by: Chao Yu <chao@kernel.org>
Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst
index b91e5a8..1e57361 100644
--- a/Documentation/filesystems/f2fs.rst
+++ b/Documentation/filesystems/f2fs.rst
@@ -300,7 +300,34 @@
Documentation/block/inline-encryption.rst.
atgc Enable age-threshold garbage collection, it provides high
effectiveness and efficiency on background GC.
-======================== ============================================================
+memory=%s Control memory mode. This supports "normal" and "low" modes.
+ "low" mode is introduced to support low memory devices.
+ Because of the nature of low memory devices, in this mode, f2fs
+ will try to save memory sometimes by sacrificing performance.
+ "normal" mode is the default mode and same as before.
+age_extent_cache Enable an age extent cache based on rb-tree. It records
+ data block update frequency of the extent per inode, in
+ order to provide better temperature hints for data block
+ allocation.
+lookup_mode=%s Control the directory lookup behavior for casefolded
+ directories. This option has no effect on directories
+ that do not have the casefold feature enabled.
+
+ ================== ========================================
+ Value Description
+ ================== ========================================
+ perf (Default) Enforces a hash-only lookup.
+ The linear search fallback is always
+ disabled, ignoring the on-disk flag.
+ compat Enables the linear search fallback for
+ compatibility with directory entries
+ created by older kernel that used a
+ different case-folding algorithm.
+ This mode ignores the on-disk flag.
+ auto F2FS determines the mode based on the
+ on-disk `SB_ENC_NO_COMPAT_FALLBACK_FL`
+ flag.
+ ================== ========================================
Debugfs Entries
===============
diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
index b9dde81..d38e5c6 100644
--- a/fs/f2fs/dir.c
+++ b/fs/f2fs/dir.c
@@ -16,6 +16,21 @@
#include "xattr.h"
#include <trace/events/f2fs.h>
+static inline bool f2fs_should_fallback_to_linear(struct inode *dir)
+{
+ struct f2fs_sb_info *sbi = F2FS_I_SB(dir);
+
+ switch (f2fs_get_lookup_mode(sbi)) {
+ case LOOKUP_PERF:
+ return false;
+ case LOOKUP_COMPAT:
+ return true;
+ case LOOKUP_AUTO:
+ return !sb_no_casefold_compat_fallback(sbi->sb);
+ }
+ return false;
+}
+
#ifdef CONFIG_UNICODE
extern struct kmem_cache *f2fs_cf_name_slab;
#endif
@@ -438,7 +453,7 @@ struct f2fs_dir_entry *__f2fs_find_entry(struct inode *dir,
out:
#if IS_ENABLED(CONFIG_UNICODE)
- if (!sb_no_casefold_compat_fallback(dir->i_sb) &&
+ if (f2fs_should_fallback_to_linear(dir) &&
IS_CASEFOLDED(dir) && !de && use_hash) {
use_hash = false;
goto start_find_entry;
diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
index 7d77d2a..a844abe 100644
--- a/fs/f2fs/f2fs.h
+++ b/fs/f2fs/f2fs.h
@@ -4448,6 +4448,47 @@ static inline bool is_journalled_quota(struct f2fs_sb_info *sbi)
return false;
}
+enum f2fs_lookup_mode {
+ LOOKUP_PERF,
+ LOOKUP_COMPAT,
+ LOOKUP_AUTO,
+};
+
+/*
+ * For bit-packing in f2fs_mount_info->alloc_mode
+ */
+#define ALLOC_MODE_BITS 1
+#define LOOKUP_MODE_BITS 2
+
+#define ALLOC_MODE_SHIFT 0
+#define LOOKUP_MODE_SHIFT (ALLOC_MODE_SHIFT + ALLOC_MODE_BITS)
+
+#define ALLOC_MODE_MASK (((1 << ALLOC_MODE_BITS) - 1) << ALLOC_MODE_SHIFT)
+#define LOOKUP_MODE_MASK (((1 << LOOKUP_MODE_BITS) - 1) << LOOKUP_MODE_SHIFT)
+
+static inline int f2fs_get_alloc_mode(struct f2fs_sb_info *sbi)
+{
+ return (F2FS_OPTION(sbi).alloc_mode & ALLOC_MODE_MASK) >> ALLOC_MODE_SHIFT;
+}
+
+static inline void f2fs_set_alloc_mode(struct f2fs_sb_info *sbi, int mode)
+{
+ F2FS_OPTION(sbi).alloc_mode &= ~ALLOC_MODE_MASK;
+ F2FS_OPTION(sbi).alloc_mode |= (mode << ALLOC_MODE_SHIFT);
+}
+
+static inline enum f2fs_lookup_mode f2fs_get_lookup_mode(struct f2fs_sb_info *sbi)
+{
+ return (F2FS_OPTION(sbi).alloc_mode & LOOKUP_MODE_MASK) >> LOOKUP_MODE_SHIFT;
+}
+
+static inline void f2fs_set_lookup_mode(struct f2fs_sb_info *sbi,
+ enum f2fs_lookup_mode mode)
+{
+ F2FS_OPTION(sbi).alloc_mode &= ~LOOKUP_MODE_MASK;
+ F2FS_OPTION(sbi).alloc_mode |= (mode << LOOKUP_MODE_SHIFT);
+}
+
#define EFSBADCRC EBADMSG /* Bad CRC detected */
#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c
index 2348781..f8188fc 100644
--- a/fs/f2fs/segment.c
+++ b/fs/f2fs/segment.c
@@ -2615,7 +2615,7 @@ static unsigned int __get_next_segno(struct f2fs_sb_info *sbi, int type)
return SIT_I(sbi)->last_victim[ALLOC_NEXT];
/* find segments from 0 to reuse freed segments */
- if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_REUSE)
+ if (f2fs_get_alloc_mode(sbi) == ALLOC_MODE_REUSE)
return 0;
return curseg->segno;
diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c
index e7704a3..257bb2d 100644
--- a/fs/f2fs/super.c
+++ b/fs/f2fs/super.c
@@ -153,6 +153,7 @@ enum {
Opt_atgc,
Opt_gc_merge,
Opt_nogc_merge,
+ Opt_lookup_mode,
Opt_err,
};
@@ -228,6 +229,7 @@ static match_table_t f2fs_tokens = {
{Opt_atgc, "atgc"},
{Opt_gc_merge, "gc_merge"},
{Opt_nogc_merge, "nogc_merge"},
+ {Opt_lookup_mode, "lookup_mode=%s"},
{Opt_err, NULL},
};
@@ -949,9 +951,9 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
return -ENOMEM;
if (!strcmp(name, "default")) {
- F2FS_OPTION(sbi).alloc_mode = ALLOC_MODE_DEFAULT;
+ f2fs_set_alloc_mode(sbi, ALLOC_MODE_DEFAULT);
} else if (!strcmp(name, "reuse")) {
- F2FS_OPTION(sbi).alloc_mode = ALLOC_MODE_REUSE;
+ f2fs_set_alloc_mode(sbi, ALLOC_MODE_REUSE);
} else {
kfree(name);
return -EINVAL;
@@ -1147,6 +1149,24 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
case Opt_nogc_merge:
clear_opt(sbi, GC_MERGE);
break;
+
+ case Opt_lookup_mode:
+ name = match_strdup(&args[0]);
+ if (!name)
+ return -ENOMEM;
+ if (!strcmp(name, "perf")) {
+ f2fs_set_lookup_mode(sbi, LOOKUP_PERF);
+ } else if (!strcmp(name, "compat")) {
+ f2fs_set_lookup_mode(sbi, LOOKUP_COMPAT);
+ } else if (!strcmp(name, "auto")) {
+ f2fs_set_lookup_mode(sbi, LOOKUP_AUTO);
+ } else {
+ kfree(name);
+ return -EINVAL;
+ }
+ kfree(name);
+ break;
+
default:
f2fs_err(sbi, "Unrecognized mount option \"%s\" or missing value",
p);
@@ -1871,9 +1891,9 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root)
if (sbi->sb->s_flags & SB_INLINECRYPT)
seq_puts(seq, ",inlinecrypt");
- if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_DEFAULT)
+ if (f2fs_get_alloc_mode(sbi) == ALLOC_MODE_DEFAULT)
seq_printf(seq, ",alloc_mode=%s", "default");
- else if (F2FS_OPTION(sbi).alloc_mode == ALLOC_MODE_REUSE)
+ else if (f2fs_get_alloc_mode(sbi) == ALLOC_MODE_REUSE)
seq_printf(seq, ",alloc_mode=%s", "reuse");
if (test_opt(sbi, DISABLE_CHECKPOINT))
@@ -1896,6 +1916,14 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root)
if (test_opt(sbi, ATGC))
seq_puts(seq, ",atgc");
+
+ if (f2fs_get_lookup_mode(sbi) == LOOKUP_PERF)
+ seq_show_option(seq, "lookup_mode", "perf");
+ else if (f2fs_get_lookup_mode(sbi) == LOOKUP_COMPAT)
+ seq_show_option(seq, "lookup_mode", "compat");
+ else if (f2fs_get_lookup_mode(sbi) == LOOKUP_AUTO)
+ seq_show_option(seq, "lookup_mode", "auto");
+
return 0;
}
@@ -1910,6 +1938,11 @@ static void default_options(struct f2fs_sb_info *sbi)
F2FS_OPTION(sbi).inline_xattr_size = DEFAULT_INLINE_XATTR_ADDRS;
F2FS_OPTION(sbi).whint_mode = WHINT_MODE_OFF;
F2FS_OPTION(sbi).alloc_mode = ALLOC_MODE_DEFAULT;
+ if (le32_to_cpu(F2FS_RAW_SUPER(sbi)->segment_count_main) <=
+ SMALL_VOLUME_SEGMENTS)
+ f2fs_set_alloc_mode(sbi, ALLOC_MODE_REUSE);
+ else
+ f2fs_set_alloc_mode(sbi, ALLOC_MODE_DEFAULT);
F2FS_OPTION(sbi).fsync_mode = FSYNC_MODE_POSIX;
F2FS_OPTION(sbi).s_resuid = make_kuid(&init_user_ns, F2FS_DEF_RESUID);
F2FS_OPTION(sbi).s_resgid = make_kgid(&init_user_ns, F2FS_DEF_RESGID);
@@ -1945,6 +1978,8 @@ static void default_options(struct f2fs_sb_info *sbi)
#endif
f2fs_build_fault_attr(sbi, 0, 0);
+
+ f2fs_set_lookup_mode(sbi, LOOKUP_PERF);
}
#ifdef CONFIG_QUOTA