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>
This commit is contained in:
Daniel Lee
2025-08-02 12:15:28 -07:00
committed by Paul Lawrence
parent 1ee9b67d67
commit 34d1a222d4
5 changed files with 114 additions and 7 deletions

View File

@@ -309,7 +309,25 @@ 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
===============

View File

@@ -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 @@ start_find_entry:
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;

View File

@@ -4553,6 +4553,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 */

View File

@@ -2621,7 +2621,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;

View File

@@ -156,6 +156,7 @@ enum {
Opt_nogc_merge,
Opt_memory_mode,
Opt_age_extent_cache,
Opt_lookup_mode,
Opt_err,
};
@@ -233,6 +234,7 @@ static match_table_t f2fs_tokens = {
{Opt_nogc_merge, "nogc_merge"},
{Opt_memory_mode, "memory=%s"},
{Opt_age_extent_cache, "age_extent_cache"},
{Opt_lookup_mode, "lookup_mode=%s"},
{Opt_err, NULL},
};
@@ -954,9 +956,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;
@@ -1171,6 +1173,23 @@ static int parse_options(struct super_block *sb, char *options, bool is_remount)
}
kfree(name);
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);
@@ -1893,9 +1912,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))
@@ -1924,6 +1943,13 @@ static int f2fs_show_options(struct seq_file *seq, struct dentry *root)
else if (F2FS_OPTION(sbi).memory_mode == MEMORY_MODE_LOW)
seq_printf(seq, ",memory=%s", "low");
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;
}
@@ -1946,6 +1972,11 @@ static void default_options(struct f2fs_sb_info *sbi, bool remount)
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);
@@ -1977,6 +2008,8 @@ static void default_options(struct f2fs_sb_info *sbi, bool remount)
#endif
f2fs_build_fault_attr(sbi, 0, 0);
f2fs_set_lookup_mode(sbi, LOOKUP_PERF);
}
#ifdef CONFIG_QUOTA