| 0/0 |
2026/03/04 03:42 |
flow |
assessment-kcsan |
10m
Results: map[Benign:true Confident:true Explanation:The data race occurs between `shrink_dentry_list()` (via `d_shrink_del()`) and `__d_lookup()` (via `d_same_name()`).
### Analysis
**Writer Side:**
In `d_shrink_del()`, the `dentry->d_flags` field is modified to clear the `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` bits:
```c
dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
```
This operation is performed while holding `dentry->d_lock`.
**Reader Side:**
In `d_same_name()`, the `parent->d_flags` field is read to check the `DCACHE_OP_COMPARE` bit:
```c
if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
```
This read happens without holding `parent->d_lock`. In the context of `__d_lookup(parent, name)`, the `parent` dentry is the one whose flags are being read.
### Why the race happens
The race occurs when a dentry (the `parent`) was previously on the LRU list and is being processed by `shrink_dentry_list()` (e.g., during a remount or under memory pressure). If another task simultaneously performs a lookup in this directory, it will acquire a reference to the `parent` dentry. When `shrink_dentry_list()` eventually processes this dentry, it notices the non-zero reference count and simply removes it from the shrink list using `d_shrink_del()`. The lookup task, meanwhile, is calling `__d_lookup()` and reading the `parent->d_flags`.
### Why the race is benign
1. **Stable Flag:** The bit being checked by the reader, `DCACHE_OP_COMPARE`, is set when the dentry is initialized (in `d_set_d_op()`) and remains stable for the lifetime of the dentry. It is not modified by `d_shrink_del()`.
2. **Atomic Access:** The `d_flags` field is an `unsigned int` (32 bits). On the reported architecture (x86_64), 32-bit aligned loads and stores are atomic. The reader will see either the value before or after the bits are cleared, but in both cases, the `DCACHE_OP_COMPARE` bit will have the same (correct) value.
3. **No Side Effects:** Even if the compiler were to perform multiple loads (which is unlikely to cause issues here given the bit is stable), the logic in `d_same_name()` would still be safe. If it incorrectly saw `DCACHE_OP_COMPARE` as set, it would call the filesystem's `d_compare` method; if it saw it as cleared, it would use the default `dentry_cmp`. Since the bit doesn't actually change, it will consistently follow one path.
4. **Common Pattern:** Reading `dentry->d_flags` without `d_lock` is a common performance optimization in the dcache, especially in RCU-walk paths (e.g., `__d_lookup_rcu()`).
In conclusion, this is a benign data race on a flags field where the specific bit of interest is not being modified. It does not lead to any memory corruption, crashes, or incorrect logic execution.
Final Verdict: Benign.]
|
| 1/1 |
2026/03/04 03:42 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/45f71b52591409cd48a37f488666afc24544e8d5]
|
| 2/1 |
2026/03/04 03:43 |
action |
kernel-builder |
3m
Results: map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/df6fbd2df1f59b5bfb5c47640fc9b11f93ac662d]
|
| 3/1 |
2026/03/04 03:46 |
action |
codesearch-prepare |
2m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/03/04 03:49 |
agent |
expert |
2m
Model:
Results: map[Benign:true Confident:true]
Instruction:
You are an experienced Linux kernel developer tasked with determining if the given kernel
data race is benign or not. The data race report is from KCSAN tool.
It contains 2 stack traces of the memory accesses that constitute a data race.
A "benign" data races are on a simple int/bool variable or similar field,
and the accesses are not supposed to be protected by any mutual exclusion primitives.
Common examples of such "benign" data races are accesses to various flags fields,
statistics counters, and similar. A "benign" data race does not lead to memory corruption/crash
with a conservative compiler that compiles memory accesses to primitive types
effectively as atomic.
A non-benign (or "harmful" data race) can lead to corruption/crash even with
a conservative compiler that compiles memory accesses to primitive types
effectively as atomic. A common example of a "harmful" data races is race on
a complex container (list/hashmap/etc), where accesses are supposed to be protected
by a mutual exclusion primitive.
Also consider races that happen at the same time with the given one.
If there is no synchronization in between, other memory accesses in the involved threads
race with each other if they access the same memory. For example, if both threads execute:
some_struct->have_elements = true;
list_add(new_node, &some_struct->list_head);
the race on some_struct->have_elements may appear benign, however it also implies there
is a race on some_struct->list_head which is not benign, since the list is not thread-safe.
Take into account that on 32-bit systems 64-bit memory accesses may be split into two accesses,
and thus even with a conservative compiler may not be fully atomic. However, such races may
still be benign depending on how writes are done, and how read data is used.
In the final reply explain why you think the given data race is benign or is harmful.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
In particular, don't make assumptions about the kernel source code,
use codesearch tools to read the actual source code.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The data race report is:
==================================================================
BUG: KCSAN: data-race in __d_lookup / shrink_dentry_list
read-write to 0xffff88811af743c0 of 4 bytes by task 17691 on cpu 1:
d_shrink_del fs/dcache.c:516 [inline]
shrink_dentry_list+0x22d/0x3c0 fs/dcache.c:1166
shrink_dcache_sb+0x15d/0x290 fs/dcache.c:1295
reconfigure_super+0x142/0x590 fs/super.c:1058
do_remount fs/namespace.c:3391 [inline]
path_mount+0xaf7/0xbc0 fs/namespace.c:4151
do_mount fs/namespace.c:4172 [inline]
__do_sys_mount fs/namespace.c:4361 [inline]
__se_sys_mount+0x28c/0x2e0 fs/namespace.c:4338
__x64_sys_mount+0x67/0x80 fs/namespace.c:4338
x64_sys_call+0x2d61/0x3020 arch/x86/include/generated/asm/syscalls_64.h:166
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x370 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
read to 0xffff88811af743c0 of 4 bytes by task 16537 on cpu 0:
d_same_name fs/dcache.c:2220 [inline]
__d_lookup+0x118/0x340 fs/dcache.c:2458
lookup_fast+0x5e/0x320 fs/namei.c:1874
walk_component fs/namei.c:2275 [inline]
link_path_walk+0x51c/0xe30 fs/namei.c:2647
path_parentat fs/namei.c:2851 [inline]
__filename_parentat+0x1a4/0x450 fs/namei.c:2875
filename_parentat fs/namei.c:2893 [inline]
filename_unlinkat+0x99/0x410 fs/namei.c:5509
__do_sys_unlink fs/namei.c:5575 [inline]
__se_sys_unlink+0x2b/0xe0 fs/namei.c:5572
__x64_sys_unlink+0x1f/0x30 fs/namei.c:5572
x64_sys_call+0x2eb6/0x3020 arch/x86/include/generated/asm/syscalls_64.h:88
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x12c/0x370 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
value changed: 0x00140444 -> 0x00100044
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 16537 Comm: syz-executor Tainted: G W syzkaller #0 PREEMPT(full)
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
==================================================================
Reply: The data race occurs between `shrink_dentry_list()` (via `d_shrink_del()`) and `__d_lookup()` (via `d_same_name()`).
### Analysis
**Writer Side:**
In `d_shrink_del()`, the `dentry->d_flags` field is modified to clear the `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` bits:
```c
dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
```
This operation is performed while holding `dentry->d_lock`.
**Reader Side:**
In `d_same_name()`, the `parent->d_flags` field is read to check the `DCACHE_OP_COMPARE` bit:
```c
if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
```
This read happens without holding `parent->d_lock`. In the context of `__d_lookup(parent, name)`, the `parent` dentry is the one whose flags are being read.
### Why the race happens
The race occurs when a dentry (the `parent`) was previously on the LRU list and is being processed by `shrink_dentry_list()` (e.g., during a remount or under memory pressure). If another task simultaneously performs a lookup in this directory, it will acquire a reference to the `parent` dentry. When `shrink_dentry_list()` eventually processes this dentry, it notices the non-zero reference count and simply removes it from the shrink list using `d_shrink_del()`. The lookup task, meanwhile, is calling `__d_lookup()` and reading the `parent->d_flags`.
### Why the race is benign
1. **Stable Flag:** The bit being checked by the reader, `DCACHE_OP_COMPARE`, is set when the dentry is initialized (in `d_set_d_op()`) and remains stable for the lifetime of the dentry. It is not modified by `d_shrink_del()`.
2. **Atomic Access:** The `d_flags` field is an `unsigned int` (32 bits). On the reported architecture (x86_64), 32-bit aligned loads and stores are atomic. The reader will see either the value before or after the bits are cleared, but in both cases, the `DCACHE_OP_COMPARE` bit will have the same (correct) value.
3. **No Side Effects:** Even if the compiler were to perform multiple loads (which is unlikely to cause issues here given the bit is stable), the logic in `d_same_name()` would still be safe. If it incorrectly saw `DCACHE_OP_COMPARE` as set, it would call the filesystem's `d_compare` method; if it saw it as cleared, it would use the default `dentry_cmp`. Since the bit doesn't actually change, it will consistently follow one path.
4. **Common Pattern:** Reading `dentry->d_flags` without `d_lock` is a common performance optimization in the dcache, especially in RCU-walk paths (e.g., `__d_lookup_rcu()`).
In conclusion, this is a benign data race on a flags field where the specific bit of interest is not being modified. It does not lead to any memory corruption, crashes, or incorrect logic execution.
Final Verdict: Benign.
|
| 5/2 |
2026/03/04 03:49 |
llm |
expert |
0m
Model:
Tokens:
input: 4080
output: 119
thoughts: 255
Thoughts: **Analyzing a Data Race in the Dentry Cache**
Okay, so the report indicates a classic data race β *between* `shrink_dentry_list`, specifically where `d_shrink_del` is called, and `__d_lookup` where `d_same_name` is invoked. It looks like the same dentry, at memory location `0xffff88811af743c0`, is being accessed concurrently. The value at that address, which I suspect is a bitfield or set of flags associated with the dentry's state, has shifted from `0x00140444` to `0x00100044`. This is the crux of the issue.
Now, let's trace this back to the code. The report provides the relevant callstacks:
The "writer" (Task 17691) is hitting:
* `d_shrink_del` at `fs/dcache.c:516`
* Which is called by `shrink_dentry_list+0x22d/0x3c0` at `fs/dcache.c:1166`
And the "reader" (Task 16537) is hitting:
* `d_same_name` at `fs/dcache.c:2220`
* Which is called by `__d_lookup+0x118/0x340` at `fs/dcache.c:2458`
I'm going to pull up `fs/dcache.c` and focus on those line numbers. I'll need to understand exactly what these functions are doing with the dentry in question to determine how the concurrent access is resulting in this flag change. This seems like a textbook case of a race condition where the modification of a dentry's internal state during the cache shrinking process interferes with a lookup operation. The crucial part will be identifying the critical section, what synchronization mechanisms (if any) are in place, and where they are failing. We need to determine if there is an issue with locking, atomicity, or if the logic is just inherently flawed.
|
| 6/2 |
2026/03/04 03:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:d_shrink_del]
Results: map[SourceCode: 512: static void d_shrink_del(struct dentry *dentry)
513: {
514: D_FLAG_VERIFY(dentry, DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
515: list_del_init(&dentry->d_lru);
516: dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
517: this_cpu_dec(nr_dentry_unused);
518: }
SourceFile:fs/dcache.c]
|
| 7/2 |
2026/03/04 03:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:d_same_name]
Results: map[SourceCode:2209: /**
2210: * d_same_name - compare dentry name with case-exact name
2211: * @dentry: the negative dentry that was passed to the parent's lookup func
2212: * @parent: parent dentry
2213: * @name: the case-exact name to be associated with the returned dentry
2214: *
2215: * Return: true if names are same, or false
2216: */
2217: bool d_same_name(const struct dentry *dentry, const struct dentry *parent,
2218: const struct qstr *name)
2219: {
2220: if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
2221: if (dentry->d_name.len != name->len)
2222: return false;
2223: return dentry_cmp(dentry, name->name, name->len) == 0;
2224: }
2225: return parent->d_op->d_compare(dentry,
2226: dentry->d_name.len, dentry->d_name.name,
2227: name) == 0;
2228: }
SourceFile:fs/dcache.c]
|
| 8/2 |
2026/03/04 03:49 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:__d_lookup]
Results: map[SourceCode:2402: /**
2403: * __d_lookup - search for a dentry (racy)
2404: * @parent: parent dentry
2405: * @name: qstr of name we wish to find
2406: * Returns: dentry, or NULL
2407: *
2408: * __d_lookup is like d_lookup, however it may (rarely) return a
2409: * false-negative result due to unrelated rename activity.
2410: *
2411: * __d_lookup is slightly faster by avoiding rename_lock read seqlock,
2412: * however it must be used carefully, eg. with a following d_lookup in
2413: * the case of failure.
2414: *
2415: * __d_lookup callers must be commented.
2416: */
2417: struct dentry *__d_lookup(const struct dentry *parent, const struct qstr *name)
2418: {
2419: unsigned int hash = name->hash;
2420: struct hlist_bl_head *b = d_hash(hash);
2421: struct hlist_bl_node *node;
2422: struct dentry *found = NULL;
2423: struct dentry *dentry;
2424:
2425: /*
2426: * Note: There is significant duplication with __d_lookup_rcu which is
2427: * required to prevent single threaded performance regressions
2428: * especially on architectures where smp_rmb (in seqcounts) are costly.
2429: * Keep the two functions in sync.
2430: */
2431:
2432: /*
2433: * The hash list is protected using RCU.
2434: *
2435: * Take d_lock when comparing a candidate dentry, to avoid races
2436: * with d_move().
2437: *
2438: * It is possible that concurrent renames can mess up our list
2439: * walk here and result in missing our dentry, resulting in the
2440: * false-negative result. d_lookup() protects against concurrent
2441: * renames using rename_lock seqlock.
2442: *
2443: * See Documentation/filesystems/path-lookup.txt for more details.
2444: */
2445: rcu_read_lock();
2446:
2447: hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) {
2448:
2449: if (dentry->d_name.hash != hash)
2450: continue;
2451:
2452: spin_lock(&dentry->d_lock);
2453: if (dentry->d_parent != parent)
2454: goto next;
2455: if (d_unhashed(dentry))
2456: goto next;
2457:
2458: if (!d_same_name(dentry, parent, name))
2459: goto next;
2460:
2461: dentry->d_lockref.count++;
2462: found = dentry;
2463: spin_unlock(&dentry->d_lock);
2464: break;
2465: next:
2466: spin_unlock(&dentry->d_lock);
2467: }
2468: rcu_read_unlock();
2469:
2470: return found;
2471: }
SourceFile:fs/dcache.c]
|
| 9/2 |
2026/03/04 03:49 |
llm |
expert |
0m
Model:
Tokens:
input: 6515
output: 41
thoughts: 432
Thoughts: **Analyzing a Data Race in the dentry Flags**
Okay, so I'm looking at a potential data race here involving the `dentry->d_flags` field. The crash report specifically points to a conflict: one thread is writing to this field in `d_shrink_del`, and another is reading from the same memory location (although it's actually accessing `parent->d_flags`) in `d_same_name`. The report clearly shows both memory accesses involve the same *virtual* address: `0xffff88811af743c0`. The critical line is that the `dentry` in `d_shrink_del` and the `parent` in `d_same_name` refer to the same dentry, likely through the `d_lookup` or similar calls.
In `d_shrink_del`, I can see that line `516` clears the `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` flags: `dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);`. The other thread, calling `d_same_name` line `2220` is reading the flag: `if (likely(!(parent->d_flags & DCACHE_OP_COMPARE)))`.
A quick check confirms what I suspected, looking at `__d_lookup`. Line `2458` confirms that `parent` *is* passed to `d_same_name`, and the crash report is right.
Now, I need to figure out the context of `d_shrink_del`. Okay, looking at the implementation of `shrink_dentry_list`, the problematic operation is `d_shrink_del(dentry);` in line `1166`. It's operating on a list of `dentry` structures. So it's shrinking this dentry list, which makes sense.
So, the race is happening during dentry list shrinkage, where one thread is clearing flags on the dentry while another is reading from the same dentry, through the parent pointer. I'll need to dig deeper into the locking in `shrink_dentry_list` and its callers to determine the root cause of this data race. This is likely a synchronization issue, maybe a missed lock or a lock taken in the wrong order.
|
| 10/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:shrink_dentry_list]
Results: map[SourceCode:1155: void shrink_dentry_list(struct list_head *list)
1156: {
1157: while (!list_empty(list)) {
1158: struct dentry *dentry;
1159:
1160: dentry = list_entry(list->prev, struct dentry, d_lru);
1161: spin_lock(&dentry->d_lock);
1162: rcu_read_lock();
1163: if (!lock_for_kill(dentry)) {
1164: bool can_free;
1165: rcu_read_unlock();
1166: d_shrink_del(dentry);
1167: can_free = dentry->d_flags & DCACHE_DENTRY_KILLED;
1168: spin_unlock(&dentry->d_lock);
1169: if (can_free)
1170: dentry_free(dentry);
1171: continue;
1172: }
1173: d_shrink_del(dentry);
1174: shrink_kill(dentry);
1175: }
1176: }
SourceFile:fs/dcache.c]
|
| 11/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 7850
output: 38
thoughts: 774
Thoughts: **Analyzing a Potential Race Condition in the Dentry Lookup Path**
Okay, so I'm digging into this dentry locking issue. I've been looking at how `__d_lookup` interacts with the dentry cache, and I've found something that needs a closer look. I'm focusing on potential race conditions, as always.
Specifically, I'm thinking about the interaction between `__d_lookup` and the dentry shrinking process, especially when a dentry is being shrunk by `shrink_dentry_list`, called by `shrink_dcache_sb` after a remount.
I've got a couple of key observations. First, I see that within `__d_lookup`, the code holds the `dentry->d_lock` but then accesses `parent->d_flags` in the `d_same_name` function *without* holding `parent->d_lock`. That immediately flags a potential issue because `d_flags` is something that can be modified by `shrink_dentry_list`, as `shrink_dentry_list` can modify `d_flags` while modifying a dentry.
Then, I'm trying to figure out if it is safe to read `parent->d_flags` without a lock. I'm thinking that normally, `d_flags` are protected by `d_lock`. It's a fundamental principle of dentry management.
This is where it gets interesting. `__d_lookup` is looking for a child of `parent`. If `parent` is in the process of being shrunk, it implies that it's being evicted from the dcache. However, `__d_lookup` is called with a `parent` dentry that should be valid, and therefore have a reference count (usually held by a caller such as `lookup_fast`).
So, I'm looking at where `__d_lookup` is called: `lookup_fast`. I see that `parent` is derived from `nd->path.dentry`. This `dentry` should have a reference count. That means it should NOT be in the unused list that `shrink_dentry_list` operates on.
However, I remember that `shrink_dcache_sb` calls `shrink_dentry_list`. `shrink_dcache_sb` is called by `reconfigure_super`, which itself is called during remount. A remount could modify the super block in a way that, under specific circumstances, could cause issues with how `__d_lookup` handles `parent`. I need to review `reconfigure_super` in `fs/super.c` to understand the remount process. It feels like there might be a subtle window where a dentry's `d_flags` could be modified concurrently with the lookup. This needs more investigation!
|
| 12/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/super.c IncludeLines:true Name:reconfigure_super]
Results: map[SourceCode:1013: /**
1014: * reconfigure_super - asks filesystem to change superblock parameters
1015: * @fc: The superblock and configuration
1016: *
1017: * Alters the configuration parameters of a live superblock.
1018: */
1019: int reconfigure_super(struct fs_context *fc)
1020: {
1021: struct super_block *sb = fc->root->d_sb;
1022: int retval;
1023: bool remount_ro = false;
1024: bool remount_rw = false;
1025: bool force = fc->sb_flags & SB_FORCE;
1026:
1027: if (fc->sb_flags_mask & ~MS_RMT_MASK)
1028: return -EINVAL;
1029: if (sb->s_writers.frozen != SB_UNFROZEN)
1030: return -EBUSY;
1031:
1032: retval = security_sb_remount(sb, fc->security);
1033: if (retval)
1034: return retval;
1035:
1036: if (fc->sb_flags_mask & SB_RDONLY) {
1037: #ifdef CONFIG_BLOCK
1038: if (!(fc->sb_flags & SB_RDONLY) && sb->s_bdev &&
1039: bdev_read_only(sb->s_bdev))
1040: return -EACCES;
1041: #endif
1042: remount_rw = !(fc->sb_flags & SB_RDONLY) && sb_rdonly(sb);
1043: remount_ro = (fc->sb_flags & SB_RDONLY) && !sb_rdonly(sb);
1044: }
1045:
1046: if (remount_ro) {
1047: if (!hlist_empty(&sb->s_pins)) {
1048: super_unlock_excl(sb);
1049: group_pin_kill(&sb->s_pins);
1050: __super_lock_excl(sb);
1051: if (!sb->s_root)
1052: return 0;
1053: if (sb->s_writers.frozen != SB_UNFROZEN)
1054: return -EBUSY;
1055: remount_ro = !sb_rdonly(sb);
1056: }
1057: }
1058: shrink_dcache_sb(sb);
1059:
1060: /* If we are reconfiguring to RDONLY and current sb is read/write,
1061: * make sure there are no files open for writing.
1062: */
1063: if (remount_ro) {
1064: if (force) {
1065: sb_start_ro_state_change(sb);
1066: } else {
1067: retval = sb_prepare_remount_readonly(sb);
1068: if (retval)
1069: return retval;
1070: }
1071: } else if (remount_rw) {
1072: /*
1073: * Protect filesystem's reconfigure code from writes from
1074: * userspace until reconfigure finishes.
1075: */
1076: sb_start_ro_state_change(sb);
1077: }
1078:
1079: if (fc->ops->reconfigure) {
1080: retval = fc->ops->reconfigure(fc);
1081: if (retval) {
1082: if (!force)
1083: goto cancel_readonly;
1084: /* If forced remount, go ahead despite any errors */
1085: WARN(1, "forced remount of a %s fs returned %i\n",
1086: sb->s_type->name, retval);
1087: }
1088: }
1089:
1090: WRITE_ONCE(sb->s_flags, ((sb->s_flags & ~fc->sb_flags_mask) |
1091: (fc->sb_flags & fc->sb_flags_mask)));
1092: sb_end_ro_state_change(sb);
1093:
1094: /*
1095: * Some filesystems modify their metadata via some other path than the
1096: * bdev buffer cache (eg. use a private mapping, or directories in
1097: * pagecache, etc). Also file data modifications go via their own
1098: * mappings. So If we try to mount readonly then copy the filesystem
1099: * from bdev, we could get stale data, so invalidate it to give a best
1100: * effort at coherency.
1101: */
1102: if (remount_ro && sb->s_bdev)
1103: invalidate_bdev(sb->s_bdev);
1104: return 0;
1105:
1106: cancel_readonly:
1107: sb_end_ro_state_change(sb);
1108: return retval;
1109: }
SourceFile:fs/super.c]
|
| 13/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 10751
output: 41
thoughts: 328
Thoughts: **Dentry Shrinking Conundrum**
Okay, so I'm looking at this `shrink_dcache_sb(sb)` function and trying to understand a potential race condition. My initial thought is it's shrinking all unused dentries for the superblock, which seems straightforward enough.
The core of the problem appears to be a race between two operations. Firstly, `d_shrink_del(dentry)` is clearing `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` flags in `dentry->d_flags`. That's expected during the shrinking process. Secondly, `d_same_name(..., parent, ...)` is reading `parent->d_flags`, likely to determine if it should even consider the operation.
Now the real head-scratcher: if `parent` is being shrunk, its reference count *should* be zero. But, and this is where it gets interesting, `__d_lookup` is called with `parent`, and generally, the caller of `__d_lookup` holds a reference to `parent`! How can that even be possible?
Looking at `lookup_fast`:
```c
1863: struct dentry *parent = nd->path.dentry;
...
1874: dentry = __d_lookup(parent, &nd->last);
```
`nd->path.dentry` *should* have a reference count here. That's a given. It's the whole point of using `nd->path.dentry`, to ensure it is valid until the lookup is done.
So, if `parent` has a reference count, it shouldn't be in the `shrink_dentry_list`. That's logically inconsistent. If it were on the list, a reference count should prevent shrinking.
How is this possible? How can a dentry with a reference count *be* in the `shrink_dentry_list`? That doesn't make any sense. The function `shrink_dcache_sb` eventually calls `select_collect` via `do_shrink_dcache_sb`.
I need to go back and check the code, specifically `shrink_dcache_sb` in `fs/dcache.c`. There's obviously something I'm missing here; some edge case, or subtly, some reordering of operations, that makes this scenario possible. It's a classic concurrency bug, and I need to track down the sequence of events that allows a ref-counted dentry to find its way onto a shrink list.
|
| 14/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:shrink_dcache_sb]
Results: map[SourceCode:1281: /**
1282: * shrink_dcache_sb - shrink dcache for a superblock
1283: * @sb: superblock
1284: *
1285: * Shrink the dcache for the specified super block. This is used to free
1286: * the dcache before unmounting a file system.
1287: */
1288: void shrink_dcache_sb(struct super_block *sb)
1289: {
1290: do {
1291: LIST_HEAD(dispose);
1292:
1293: list_lru_walk(&sb->s_dentry_lru,
1294: dentry_lru_isolate_shrink, &dispose, 1024);
1295: shrink_dentry_list(&dispose);
1296: } while (list_lru_count(&sb->s_dentry_lru) > 0);
1297: }
SourceFile:fs/dcache.c]
|
| 15/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 11964
output: 44
thoughts: 36
Thoughts: Here's my analysis of the provided snippet, as if it were my thought process:
**Diving into the Dentry LRU Isolate/Shrink Process**
Okay, so `list_lru_walk` triggers a call to `dentry_lru_isolate_shrink`. Naturally, my interest is piqued. `dentry_lru_isolate_shrink` is the core of what's happening. I need to understand this shrink process that the `list_lru_walk` function is using. It is vital to determine why the LRU is being walked and what its expected outcome will be. This will be where the magic happens, or where things could go sideways, depending on the implementation! Time to dig in and see what `dentry_lru_isolate_shrink` is doing. I'll focus on the data structures it uses, the conditions under which it isolates dentries, and how the actual shrinking is performed. Gotta trace the logic here.
|
| 16/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:dentry_lru_isolate_shrink]
Results: map[SourceCode:1260: static enum lru_status dentry_lru_isolate_shrink(struct list_head *item,
1261: struct list_lru_one *lru, void *arg)
1262: {
1263: struct list_head *freeable = arg;
1264: struct dentry *dentry = container_of(item, struct dentry, d_lru);
1265:
1266: /*
1267: * we are inverting the lru lock/dentry->d_lock here,
1268: * so use a trylock. If we fail to get the lock, just skip
1269: * it
1270: */
1271: if (!spin_trylock(&dentry->d_lock))
1272: return LRU_SKIP;
1273:
1274: d_lru_shrink_move(lru, dentry, freeable);
1275: spin_unlock(&dentry->d_lock);
1276:
1277: return LRU_REMOVED;
1278: }
SourceFile:fs/dcache.c]
|
| 17/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 12583
output: 39
thoughts: 652
Thoughts: **Analyzing Potential Dentry LRU List Inconsistencies**
Okay, so I'm looking at this `d_lru_shrink_move` function and how it interacts with the LRU list for dentries. It looks like `dentry_lru_isolate_shrink` is called via `list_lru_walk`, which is interesting. The key thing here is the assumption: dentries on the LRU list *should* have a `d_lockref.count` of zero. Makes sense, they're eligible for shrinking/eviction.
But then my mind goes to the `__d_lookup` function. If the `parent` dentry of a lookup is on the LRU list, meaning its reference count is zero, how is that even possible? `__d_lookup(parent, name)` is called, so wouldn't there *have* to be a reference to the `parent`? It's the whole point of the lookup. If I'm doing a lookup *in* a directory, I *must* have a reference to that directory.
Maybe there's a race? Maybe the reference was dropped just before the lookup?
But then I think about `lookup_fast` function which I know is optimized for lookups. `parent = nd->path.dentry`. `nd->path.dentry` *should* be pinned, right? So the reference should be held. This seems like a potential inconsistency I should look into further.
Let me revisit the stack trace of a reader. It leads me to `filename_unlinkat`.
```
read to 0xffff88811af743c0 of 4 bytes by task 16537 on cpu 0:
d_same_name fs/dcache.c:2220 [inline]
__d_lookup+0x118/0x340 fs/dcache.c:2458
lookup_fast+0x5e/0x320 fs/namei.c:1874
walk_component fs/namei.c:2275 [inline]
link_path_walk+0x51c/0xe30 fs/namei.c:2647
...
filename_unlinkat+0x99/0x410 fs/namei.c:5509
```
`filename_unlinkat` calls `filename_parentat`, which returns the parent dentry. It then eventually calls `__do_sys_unlink`. Actually, no, not directly. It is `filename_unlinkat` itself. I need to examine `filename_unlinkat` closely, and will check it out.
|
| 18/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/namei.c IncludeLines:true Name:filename_unlinkat]
Results: map[SourceCode:5492: /*
5493: * Make sure that the actual truncation of the file will occur outside its
5494: * directory's i_rwsem. Truncate can take a long time if there is a lot of
5495: * writeout happening, and we don't want to prevent access to the directory
5496: * while waiting on the I/O.
5497: */
5498: int filename_unlinkat(int dfd, struct filename *name)
5499: {
5500: int error;
5501: struct dentry *dentry;
5502: struct path path;
5503: struct qstr last;
5504: int type;
5505: struct inode *inode;
5506: struct delegated_inode delegated_inode = { };
5507: unsigned int lookup_flags = 0;
5508: retry:
5509: error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
5510: if (error)
5511: return error;
5512:
5513: error = -EISDIR;
5514: if (type != LAST_NORM)
5515: goto exit_path_put;
5516:
5517: error = mnt_want_write(path.mnt);
5518: if (error)
5519: goto exit_path_put;
5520: retry_deleg:
5521: dentry = start_dirop(path.dentry, &last, lookup_flags);
5522: error = PTR_ERR(dentry);
5523: if (IS_ERR(dentry))
5524: goto exit_drop_write;
5525:
5526: /* Why not before? Because we want correct error value */
5527: if (unlikely(last.name[last.len])) {
5528: if (d_is_dir(dentry))
5529: error = -EISDIR;
5530: else
5531: error = -ENOTDIR;
5532: end_dirop(dentry);
5533: goto exit_drop_write;
5534: }
5535: inode = dentry->d_inode;
5536: ihold(inode);
5537: error = security_path_unlink(&path, dentry);
5538: if (error)
5539: goto exit_end_dirop;
5540: error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
5541: dentry, &delegated_inode);
5542: exit_end_dirop:
5543: end_dirop(dentry);
5544: iput(inode); /* truncate the inode here */
5545: if (is_delegated(&delegated_inode)) {
5546: error = break_deleg_wait(&delegated_inode);
5547: if (!error)
5548: goto retry_deleg;
5549: }
5550: exit_drop_write:
5551: mnt_drop_write(path.mnt);
5552: exit_path_put:
5553: path_put(&path);
5554: if (retry_estale(error, lookup_flags)) {
5555: lookup_flags |= LOOKUP_REVAL;
5556: goto retry;
5557: }
5558: return error;
5559: }
SourceFile:fs/namei.c]
|
| 19/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 14890
output: 40
thoughts: 844
Thoughts: **Analyzing a Dentry Lifecycle Issue**
Okay, so I'm looking at this stack trace, and it's pointing me right into the heart of the file system's name resolution process, specifically within the dentry cache. The trace shows a clear path: `filename_unlinkat` calls `__filename_parentat`, which then calls `link_path_walk`, `walk_component`, and ultimately, `lookup_fast`. `lookup_fast` is the crucial spot, where `__d_lookup` is called to search the dentry cache. And here's the kicker: `__d_lookup` uses the current directory's dentry, `nd->path.dentry`, which I know, by definition, must have a positive reference count to be valid.
But here's the problem I'm trying to figure out, and it's where things get interesting and where the bug is. This `parent` dentry, `nd->path.dentry`, is somehow showing up in `shrink_dentry_list`. `shrink_dentry_list` is part of the dentry shrinking process. It's called by `shrink_dcache_sb`, which walks an LRU list of dentries. Dentries only get added to the LRU when their reference count drops to zero, and they're put to sleep by the system.
So, if `parent` is in `shrink_dentry_list`, its reference count *must* have been zero at some point. But if task 16537 is actively using it as `nd->path.dentry`, it should *not* have a zero reference count. The system is in a bad state.
Let me think this through. Is it possible that the dentry was on the LRU and was in the process of being shrunk? Another task then re-looked it up, thus re-incrementing the reference count? That could potentially explain this.
If the dentry is on the "dispose" list (through `shrink_dcache_sb`), it gets the `DCACHE_SHRINK_LIST` flag set. If this flag is set during `dput` it doesn't get added back to the LRU. This is a very important detail.
But if it's on the dispose list, and another task finds it via a cache lookup?
Right! `shrink_dentry_list` is called on dentries that are isolated *from* the LRU. They are removed from the LRU as part of the shrink process. If another task then *finds* this dentry (e.g., via `__d_lookup` on its parent), it *will* increment the reference count. But `shrink_dentry_list` will still attempt to shrink it! The race is becoming clear.
Okay, let me check `shrink_dentry_list` more carefully. It appears `shrink_dentry_list` does the following: it locks the dentry's `d_lock`, then calls `lock_for_kill`. Let's dig into `lock_for_kill`. This might be where the real problem is.
|
| 20/2 |
2026/03/04 03:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/dcache.c IncludeLines:true Name:lock_for_kill]
Results: map[SourceCode: 708: static bool lock_for_kill(struct dentry *dentry)
709: {
710: struct inode *inode = dentry->d_inode;
711:
712: if (unlikely(dentry->d_lockref.count))
713: return false;
714:
715: if (!inode || likely(spin_trylock(&inode->i_lock)))
716: return true;
717:
718: do {
719: spin_unlock(&dentry->d_lock);
720: spin_lock(&inode->i_lock);
721: spin_lock(&dentry->d_lock);
722: if (likely(inode == dentry->d_inode))
723: break;
724: spin_unlock(&inode->i_lock);
725: inode = dentry->d_inode;
726: } while (inode);
727: if (likely(!dentry->d_lockref.count))
728: return true;
729: if (inode)
730: spin_unlock(&inode->i_lock);
731: return false;
732: }
SourceFile:fs/dcache.c]
|
| 21/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 16826
output: 23
thoughts: 498
Thoughts: **Analyzing a Dentry Shrink Race Condition**
Okay, so I'm looking at this `shrink_dentry_list` function and how it interacts with dentry reference counts and the dentry flags. It seems like there's a potential race condition when a dentry is being shrunk, specifically when it's on the `dispose` list. If `dentry->d_lockref.count` isn't zero, `lock_for_kill` returns `false`, meaning the dentry is still in use. In that scenario, `shrink_dentry_list` calls `d_shrink_del(dentry)` and moves on. The `d_shrink_del` function removes the dentry from the shrink lists, like the `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST`, but doesn't actually kill it.
The race condition I see is between a writer (Task 1) and a reader (Task 2). Task 1, running `shrink_dentry_list`, is essentially modifying `parent->d_flags` via the `d_shrink_del` call. Task 2, concurrently running `__d_lookup` and eventually `d_same_name`, is reading `parent->d_flags`.
Specifically, in `d_shrink_del`, it clears `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` from `dentry->d_flags`. Meanwhile, in `d_same_name` there's a `likely` check on `!(parent->d_flags & DCACHE_OP_COMPARE)`. This is where the race happens. The value of `d_flags` changed, and if this happens concurrently it could cause a problem.
I have the before/after values of `dentry->d_flags`, `0x00140444` and `0x00100044` respectively. Doing the bitwise math reveals that `DCACHE_LRU_LIST` and `DCACHE_SHRINK_LIST` are the flags being manipulated.
Now, I need to look up the exact definitions of these `DCACHE_*` constants to fully understand the implications of this race and confirm what assumptions are being broken by this pattern. I need to be sure about the impact of clearing these flags while a lookup is in progress.
|
| 22/2 |
2026/03/04 03:50 |
tool |
grepper |
0m
Args: map[Expression:#define DCACHE_.* 0x]
Results: map[Output:arch/m68k/include/asm/m54xxacr.h-51-#define ICACHE_SIZE 0x4000 /* instruction - 16k */
arch/m68k/include/asm/m54xxacr.h:52:#define DCACHE_SIZE 0x2000 /* data - 8k */
arch/m68k/include/asm/m54xxacr.h-53-
--
arch/m68k/include/asm/m54xxacr.h-56-#define ICACHE_SIZE 0x8000 /* instruction - 32k */
arch/m68k/include/asm/m54xxacr.h:57:#define DCACHE_SIZE 0x8000 /* data - 32k */
arch/m68k/include/asm/m54xxacr.h-58-
--
arch/m68k/include/asm/m54xxacr.h-61-#define ICACHE_SIZE 0x2000 /* instruction - 8k */
arch/m68k/include/asm/m54xxacr.h:62:#define DCACHE_SIZE 0x2000 /* data - 8k */
arch/m68k/include/asm/m54xxacr.h-63-#endif
]
|
| 23/2 |
2026/03/04 03:50 |
llm |
expert |
0m
Model:
Tokens:
input: 18210
output: 23
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Digging Deeper into the dcache**
Well, that initial attempt wasn't particularly fruitful. Time to get my hands dirty and dive into the source. Specifically, I need to understand the details around how the dcache functions. Let's start with `include/linux/dcache.h`. That header file should provide the fundamental data structures and likely the primary function declarations related to the dcache, allowing me to start to piece together what's going on. I'm hoping to find the core definitions and maybe some hints about the key algorithms at play. From there I can extrapolate what is happening.
|
| 24/2 |
2026/03/04 03:51 |
tool |
read-file |
0m
Args: map[File:include/linux/dcache.h]
Results: map[Contents:/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_DCACHE_H
#define __LINUX_DCACHE_H
#include <linux/atomic.h>
#include <linux/list.h>
#include <linux/math.h>
#include <linux/rculist.h>
#include <linux/rculist_bl.h>
#include <linux/spinlock.h>
#include <linux/seqlock.h>
#include <linux/cache.h>
#include <linux/rcupdate.h>
#include <linux/lockref.h>
#include <linux/stringhash.h>
#include <linux/wait.h>
struct path;
struct file;
struct vfsmount;
/*
* linux/include/linux/dcache.h
*
* Dirent cache data structures
*
* (C) Copyright 1997 Thomas Schoebel-Theuer,
* with heavy changes by Linus Torvalds
*/
#define IS_ROOT(x) ((x) == (x)->d_parent)
/* The hash is always the low bits of hash_len */
#ifdef __LITTLE_ENDIAN
#define HASH_LEN_DECLARE u32 hash; u32 len
#define bytemask_from_count(cnt) (~(~0ul << (cnt)*8))
#else
#define HASH_LEN_DECLARE u32 len; u32 hash
#define bytemask_from_count(cnt) (~(~0ul >> (cnt)*8))
#endif
/*
* "quick string" -- eases parameter passing, but more importantly
* saves "metadata" about the string (ie length and the hash).
*
* hash comes first so it snuggles against d_parent in the
* dentry.
*/
struct qstr {
union {
struct {
HASH_LEN_DECLARE;
};
u64 hash_len;
};
const unsigned char *name;
};
#define QSTR_INIT(n,l) { { { .len = l } }, .name = n }
#define QSTR_LEN(n,l) (struct qstr)QSTR_INIT(n,l)
#define QSTR(n) QSTR_LEN(n, strlen(n))
extern const struct qstr empty_name;
extern const struct qstr slash_name;
extern const struct qstr dotdot_name;
/*
* Try to keep struct dentry aligned on 64 byte cachelines (this will
* give reasonable cacheline footprint with larger lines without the
* large memory footprint increase).
*/
#ifdef CONFIG_64BIT
# define DNAME_INLINE_WORDS 5 /* 192 bytes */
#else
# ifdef CONFIG_SMP
# define DNAME_INLINE_WORDS 9 /* 128 bytes */
# else
# define DNAME_INLINE_WORDS 11 /* 128 bytes */
# endif
#endif
#define DNAME_INLINE_LEN (DNAME_INLINE_WORDS*sizeof(unsigned long))
union shortname_store {
unsigned char string[DNAME_INLINE_LEN];
unsigned long words[DNAME_INLINE_WORDS];
};
#define d_lock d_lockref.lock
#define d_iname d_shortname.string
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_spinlock_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
union {
struct qstr __d_name; /* for use ONLY in fs/dcache.c */
const struct qstr d_name;
};
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
union shortname_store d_shortname;
/* --- cacheline 1 boundary (64 bytes) was 32 bytes ago --- */
/* Ref lookup also touches following */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */
/* --- cacheline 2 boundary (128 bytes) --- */
struct lockref d_lockref; /* per-dentry lock and refcount
* keep separate from RCU lookup area if
* possible!
*/
union {
struct list_head d_lru; /* LRU list */
wait_queue_head_t *d_wait; /* in-lookup ones only */
};
struct hlist_node d_sib; /* child of parent list */
struct hlist_head d_children; /* our children */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
struct rcu_head d_rcu;
} d_u;
};
/*
* dentry->d_lock spinlock nesting subclasses:
*
* 0: normal
* 1: nested
*/
enum dentry_d_lock_class
{
DENTRY_D_LOCK_NORMAL, /* implicitly used by plain spin_lock() APIs. */
DENTRY_D_LOCK_NESTED
};
enum d_real_type {
D_REAL_DATA,
D_REAL_METADATA,
};
struct dentry_operations {
int (*d_revalidate)(struct inode *, const struct qstr *,
struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
int (*d_init)(struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(const struct path *, bool);
struct dentry *(*d_real)(struct dentry *, enum d_real_type type);
bool (*d_unalias_trylock)(const struct dentry *);
void (*d_unalias_unlock)(const struct dentry *);
} ____cacheline_aligned;
/*
* Locking rules for dentry_operations callbacks are to be found in
* Documentation/filesystems/locking.rst. Keep it updated!
*
* FUrther descriptions are found in Documentation/filesystems/vfs.rst.
* Keep it updated too!
*/
/* d_flags entries */
enum dentry_flags {
DCACHE_OP_HASH = BIT(0),
DCACHE_OP_COMPARE = BIT(1),
DCACHE_OP_REVALIDATE = BIT(2),
DCACHE_OP_DELETE = BIT(3),
DCACHE_OP_PRUNE = BIT(4),
/*
* This dentry is possibly not currently connected to the dcache tree,
* in which case its parent will either be itself, or will have this
* flag as well. nfsd will not use a dentry with this bit set, but will
* first endeavour to clear the bit either by discovering that it is
* connected, or by performing lookup operations. Any filesystem which
* supports nfsd_operations MUST have a lookup function which, if it
* finds a directory inode with a DCACHE_DISCONNECTED dentry, will
* d_move that dentry into place and return that dentry rather than the
* passed one, typically using d_splice_alias.
*/
DCACHE_DISCONNECTED = BIT(5),
DCACHE_REFERENCED = BIT(6), /* Recently used, don't discard. */
DCACHE_DONTCACHE = BIT(7), /* Purge from memory on final dput() */
DCACHE_CANT_MOUNT = BIT(8),
DCACHE_SHRINK_LIST = BIT(10),
DCACHE_OP_WEAK_REVALIDATE = BIT(11),
/*
* this dentry has been "silly renamed" and has to be deleted on the
* last dput()
*/
DCACHE_NFSFS_RENAMED = BIT(12),
DCACHE_FSNOTIFY_PARENT_WATCHED = BIT(13), /* Parent inode is watched by some fsnotify listener */
DCACHE_DENTRY_KILLED = BIT(14),
DCACHE_MOUNTED = BIT(15), /* is a mountpoint */
DCACHE_NEED_AUTOMOUNT = BIT(16), /* handle automount on this dir */
DCACHE_MANAGE_TRANSIT = BIT(17), /* manage transit from this dirent */
DCACHE_LRU_LIST = BIT(18),
DCACHE_ENTRY_TYPE = (7 << 19), /* bits 19..21 are for storing type: */
DCACHE_MISS_TYPE = (0 << 19), /* Negative dentry */
DCACHE_WHITEOUT_TYPE = (1 << 19), /* Whiteout dentry (stop pathwalk) */
DCACHE_DIRECTORY_TYPE = (2 << 19), /* Normal directory */
DCACHE_AUTODIR_TYPE = (3 << 19), /* Lookupless directory (presumed automount) */
DCACHE_REGULAR_TYPE = (4 << 19), /* Regular file type */
DCACHE_SPECIAL_TYPE = (5 << 19), /* Other file type */
DCACHE_SYMLINK_TYPE = (6 << 19), /* Symlink */
DCACHE_NOKEY_NAME = BIT(22), /* Encrypted name encoded without key */
DCACHE_OP_REAL = BIT(23),
DCACHE_PAR_LOOKUP = BIT(24), /* being looked up (with parent locked shared) */
DCACHE_DENTRY_CURSOR = BIT(25),
DCACHE_NORCU = BIT(26), /* No RCU delay for freeing */
DCACHE_PERSISTENT = BIT(27)
};
#define DCACHE_MANAGED_DENTRY \
(DCACHE_MOUNTED|DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT)
extern seqlock_t rename_lock;
/*
* These are the low-level FS interfaces to the dcache..
*/
extern void d_instantiate(struct dentry *, struct inode *);
extern void d_instantiate_new(struct dentry *, struct inode *);
extern void __d_drop(struct dentry *dentry);
extern void d_drop(struct dentry *dentry);
extern void d_delete(struct dentry *);
/* allocate/de-allocate */
extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
extern struct dentry * d_alloc_anon(struct super_block *);
extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *,
wait_queue_head_t *);
extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
/* weird procfs mess; *NOT* exported */
extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
const struct dentry_operations *);
extern struct dentry * d_add_ci(struct dentry *, struct inode *, struct qstr *);
extern bool d_same_name(const struct dentry *dentry, const struct dentry *parent,
const struct qstr *name);
extern struct dentry *d_find_any_alias(struct inode *inode);
extern struct dentry * d_obtain_alias(struct inode *);
extern struct dentry * d_obtain_root(struct inode *);
extern void shrink_dcache_sb(struct super_block *);
extern void shrink_dcache_parent(struct dentry *);
extern void d_invalidate(struct dentry *);
/* only used at mount-time */
extern struct dentry * d_make_root(struct inode *);
extern void d_mark_tmpfile(struct file *, struct inode *);
extern void d_tmpfile(struct file *, struct inode *);
extern struct dentry *d_find_alias(struct inode *);
extern void d_prune_aliases(struct inode *);
extern void d_dispose_if_unused(struct dentry *, struct list_head *);
extern void shrink_dentry_list(struct list_head *);
extern struct dentry *d_find_alias_rcu(struct inode *);
/* test whether we have any submounts in a subdir tree */
extern int path_has_submounts(const struct path *);
/*
* This adds the entry to the hash queues.
*/
extern void d_rehash(struct dentry *);
extern void d_add(struct dentry *, struct inode *);
/* used for rename() and baskets */
extern void d_move(struct dentry *, struct dentry *);
extern void d_exchange(struct dentry *, struct dentry *);
extern struct dentry *d_ancestor(struct dentry *, struct dentry *);
extern struct dentry *d_lookup(const struct dentry *, const struct qstr *);
static inline unsigned d_count(const struct dentry *dentry)
{
return dentry->d_lockref.count;
}
ino_t d_parent_ino(struct dentry *dentry);
/*
* helper function for dentry_operations.d_dname() members
*/
extern __printf(3, 4)
char *dynamic_dname(char *, int, const char *, ...);
extern char *__d_path(const struct path *, const struct path *, char *, int);
extern char *d_absolute_path(const struct path *, char *, int);
extern char *d_path(const struct path *, char *, int);
extern char *dentry_path_raw(const struct dentry *, char *, int);
extern char *dentry_path(const struct dentry *, char *, int);
/* Allocation counts.. */
/**
* dget_dlock - get a reference to a dentry
* @dentry: dentry to get a reference to
*
* Given a live dentry, increment the reference count and return the dentry.
* Caller must hold @dentry->d_lock. Making sure that dentry is alive is
* caller's resonsibility. There are many conditions sufficient to guarantee
* that; e.g. anything with non-negative refcount is alive, so's anything
* hashed, anything positive, anyone's parent, etc.
*/
static inline struct dentry *dget_dlock(struct dentry *dentry)
{
dentry->d_lockref.count++;
return dentry;
}
/**
* dget - get a reference to a dentry
* @dentry: dentry to get a reference to
*
* Given a dentry or %NULL pointer increment the reference count
* if appropriate and return the dentry. A dentry will not be
* destroyed when it has references. Conversely, a dentry with
* no references can disappear for any number of reasons, starting
* with memory pressure. In other words, that primitive is
* used to clone an existing reference; using it on something with
* zero refcount is a bug.
*
* NOTE: it will spin if @dentry->d_lock is held. From the deadlock
* avoidance point of view it is equivalent to spin_lock()/increment
* refcount/spin_unlock(), so calling it under @dentry->d_lock is
* always a bug; so's calling it under ->d_lock on any of its descendents.
*
*/
static inline struct dentry *dget(struct dentry *dentry)
{
if (dentry)
lockref_get(&dentry->d_lockref);
return dentry;
}
extern struct dentry *dget_parent(struct dentry *dentry);
/**
* d_unhashed - is dentry hashed
* @dentry: entry to check
*
* Returns true if the dentry passed is not currently hashed.
*/
static inline int d_unhashed(const struct dentry *dentry)
{
return hlist_bl_unhashed(&dentry->d_hash);
}
static inline int d_unlinked(const struct dentry *dentry)
{
return d_unhashed(dentry) && !IS_ROOT(dentry);
}
static inline int cant_mount(const struct dentry *dentry)
{
return (dentry->d_flags & DCACHE_CANT_MOUNT);
}
static inline void dont_mount(struct dentry *dentry)
{
spin_lock(&dentry->d_lock);
dentry->d_flags |= DCACHE_CANT_MOUNT;
spin_unlock(&dentry->d_lock);
}
extern void __d_lookup_unhash_wake(struct dentry *dentry);
static inline int d_in_lookup(const struct dentry *dentry)
{
return dentry->d_flags & DCACHE_PAR_LOOKUP;
}
static inline void d_lookup_done(struct dentry *dentry)
{
if (unlikely(d_in_lookup(dentry)))
__d_lookup_unhash_wake(dentry);
}
extern void dput(struct dentry *);
static inline bool d_managed(const struct dentry *dentry)
{
return dentry->d_flags & DCACHE_MANAGED_DENTRY;
}
static inline bool d_mountpoint(const struct dentry *dentry)
{
return dentry->d_flags & DCACHE_MOUNTED;
}
/*
* Directory cache entry type accessor functions.
*/
static inline unsigned __d_entry_type(const struct dentry *dentry)
{
return dentry->d_flags & DCACHE_ENTRY_TYPE;
}
static inline bool d_is_miss(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_MISS_TYPE;
}
static inline bool d_is_whiteout(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_WHITEOUT_TYPE;
}
static inline bool d_can_lookup(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_DIRECTORY_TYPE;
}
static inline bool d_is_autodir(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_AUTODIR_TYPE;
}
static inline bool d_is_dir(const struct dentry *dentry)
{
return d_can_lookup(dentry) || d_is_autodir(dentry);
}
static inline bool d_is_symlink(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_SYMLINK_TYPE;
}
static inline bool d_is_reg(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_REGULAR_TYPE;
}
static inline bool d_is_special(const struct dentry *dentry)
{
return __d_entry_type(dentry) == DCACHE_SPECIAL_TYPE;
}
static inline bool d_is_file(const struct dentry *dentry)
{
return d_is_reg(dentry) || d_is_special(dentry);
}
static inline bool d_is_negative(const struct dentry *dentry)
{
// TODO: check d_is_whiteout(dentry) also.
return d_is_miss(dentry);
}
static inline bool d_flags_negative(unsigned flags)
{
return (flags & DCACHE_ENTRY_TYPE) == DCACHE_MISS_TYPE;
}
static inline bool d_is_positive(const struct dentry *dentry)
{
return !d_is_negative(dentry);
}
/**
* d_really_is_negative - Determine if a dentry is really negative (ignoring fallthroughs)
* @dentry: The dentry in question
*
* Returns true if the dentry represents either an absent name or a name that
* doesn't map to an inode (ie. ->d_inode is NULL). The dentry could represent
* a true miss, a whiteout that isn't represented by a 0,0 chardev or a
* fallthrough marker in an opaque directory.
*
* Note! (1) This should be used *only* by a filesystem to examine its own
* dentries. It should not be used to look at some other filesystem's
* dentries. (2) It should also be used in combination with d_inode() to get
* the inode. (3) The dentry may have something attached to ->d_lower and the
* type field of the flags may be set to something other than miss or whiteout.
*/
static inline bool d_really_is_negative(const struct dentry *dentry)
{
return dentry->d_inode == NULL;
}
/**
* d_really_is_positive - Determine if a dentry is really positive (ignoring fallthroughs)
* @dentry: The dentry in question
*
* Returns true if the dentry represents a name that maps to an inode
* (ie. ->d_inode is not NULL). The dentry might still represent a whiteout if
* that is represented on medium as a 0,0 chardev.
*
* Note! (1) This should be used *only* by a filesystem to examine its own
* dentries. It should not be used to look at some other filesystem's
* dentries. (2) It should also be used in combination with d_inode() to get
* the inode.
*/
static inline bool d_really_is_positive(const struct dentry *dentry)
{
return dentry->d_inode != NULL;
}
static inline int simple_positive(const struct dentry *dentry)
{
return d_really_is_positive(dentry) && !d_unhashed(dentry);
}
unsigned long vfs_pressure_ratio(unsigned long val);
/**
* d_inode - Get the actual inode of this dentry
* @dentry: The dentry to query
*
* This is the helper normal filesystems should use to get at their own inodes
* in their own dentries and ignore the layering superimposed upon them.
*/
static inline struct inode *d_inode(const struct dentry *dentry)
{
return dentry->d_inode;
}
/**
* d_inode_rcu - Get the actual inode of this dentry with READ_ONCE()
* @dentry: The dentry to query
*
* This is the helper normal filesystems should use to get at their own inodes
* in their own dentries and ignore the layering superimposed upon them.
*/
static inline struct inode *d_inode_rcu(const struct dentry *dentry)
{
return READ_ONCE(dentry->d_inode);
}
/**
* d_backing_inode - Get upper or lower inode we should be using
* @upper: The upper layer
*
* This is the helper that should be used to get at the inode that will be used
* if this dentry were to be opened as a file. The inode may be on the upper
* dentry or it may be on a lower dentry pinned by the upper.
*
* Normal filesystems should not use this to access their own inodes.
*/
static inline struct inode *d_backing_inode(const struct dentry *upper)
{
struct inode *inode = upper->d_inode;
return inode;
}
/**
* d_real - Return the real dentry
* @dentry: the dentry to query
* @type: the type of real dentry (data or metadata)
*
* If dentry is on a union/overlay, then return the underlying, real dentry.
* Otherwise return the dentry itself.
*
* See also: Documentation/filesystems/vfs.rst
*/
static inline struct dentry *d_real(struct dentry *dentry, enum d_real_type type)
{
if (unlikely(dentry->d_flags & DCACHE_OP_REAL))
return dentry->d_op->d_real(dentry, type);
else
return dentry;
}
/**
* d_real_inode - Return the real inode hosting the data
* @dentry: The dentry to query
*
* If dentry is on a union/overlay, then return the underlying, real inode.
* Otherwise return d_inode().
*/
static inline struct inode *d_real_inode(const struct dentry *dentry)
{
/* This usage of d_real() results in const dentry */
return d_inode(d_real((struct dentry *) dentry, D_REAL_DATA));
}
struct name_snapshot {
struct qstr name;
union shortname_store inline_name;
};
void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
void release_dentry_name_snapshot(struct name_snapshot *);
static inline struct dentry *d_first_child(const struct dentry *dentry)
{
return hlist_entry_safe(dentry->d_children.first, struct dentry, d_sib);
}
static inline struct dentry *d_next_sibling(const struct dentry *dentry)
{
return hlist_entry_safe(dentry->d_sib.next, struct dentry, d_sib);
}
void set_default_d_op(struct super_block *, const struct dentry_operations *);
struct dentry *d_make_persistent(struct dentry *, struct inode *);
void d_make_discardable(struct dentry *dentry);
#endif /* __LINUX_DCACHE_H */
]
|
| 25/2 |
2026/03/04 03:51 |
llm |
expert |
0m
Model:
Tokens:
input: 24790
output: 25
thoughts: 718
Thoughts: **Analyzing a Potential Data Race in Dentry Flags**
Okay, so I'm looking at a change in `dentry->d_flags` from `0x00140444` to `0x00100044`, and immediately I need to understand what bits have flipped. I'm going through the definitions: `DCACHE_OP_COMPARE` is `BIT(1)`, `DCACHE_SHRINK_LIST` is `BIT(10)`, and `DCACHE_LRU_LIST` is `BIT(18)`.
Let's break down the original value. `0x140444` reveals the following: `BIT(20)` (likely related to `DCACHE_ENTRY_TYPE`), `DCACHE_LRU_LIST`, `DCACHE_SHRINK_LIST`, `DCACHE_REFERENCED`, and `DCACHE_OP_REVALIDATE`.
The new value, `0x100044`, shows that `DCACHE_LRU_LIST` and `DCACHE_SHRINK_LIST` have been cleared. This aligns with what I know about the function `d_shrink_del` which explicitly clears those bits.
Now, I'm checking the context β specifically, the code that's reading `d_flags`:
```c
if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
```
I have to verify that `DCACHE_OP_COMPARE` which is `BIT(1)` isn't affected by the change. And indeed, it's 0 in both the old and new values.
So, the issue is potentially a data race on `d_flags`. One part of the code is clearing bits, and another part is reading a different bit. The crucial question is: is this problematic? This is where the subtleties of concurrency come in.
The classic pitfall is that if the compiler reloads the `parent->d_flags` value in between the check and its use, or if the write to `d_flags` isn't atomic, it *could* be a problem. However, `d_flags` is an `unsigned int` and the write is a simple bitwise AND. On most modern architectures, such operations are atomic if the access is properly aligned.
But KCSAN is reporting it, and that catches my attention. KCSAN is designed to be sensitive and it's picking this up because of the plain access to the dentry flags.
Okay, so I need to step back and think about the lifecycle of `DCACHE_OP_COMPARE`. When is it set? Generally, it's set during dentry creation in either `d_alloc` or `d_set_d_op` and *shouldn't* change during the dentry's lifetime. Therefore, the concern seems to be that if the operation that sets `DCACHE_OP_COMPARE` has not completed before `d_shrink_del` executes, there might be a problem. It looks like it is usually safe, but it's not provable without additional analysis.
|
| 26/2 |
2026/03/04 03:51 |
tool |
codesearch-find-references |
0m
Error:
Args: map[Name:DCACHE_OP_COMPARE]
Results: map[References:<nil> TruncatedOutput:false]
|
| 27/2 |
2026/03/04 03:51 |
llm |
expert |
0m
Model:
Tokens:
input: 26234
output: 21
thoughts: 16
Thoughts: Okay, here's the summary, rendered as your internal thought process:
**Enum Member Analysis**
Right, so I'm looking at this piece of code, and it's flagged as an enum member. Okay, that's the starting point. My immediate thought is, I need to understand where this thing is being *used*. Just seeing the declaration isn't enough; I need context. This could be crucial for debugging, refactoring, or just understanding the overall design. Let's get right to it and hunt down all the references to this specific enum member. That's the key to unlocking its purpose and impact.
|
| 28/2 |
2026/03/04 03:51 |
tool |
grepper |
0m
Args: map[Expression:DCACHE_OP_COMPARE]
Results: map[Output:fs/dcache.c=1889=EXPORT_SYMBOL(d_alloc_name);
--
fs/dcache.c-1891-#define DCACHE_OP_FLAGS \
fs/dcache.c:1892: (DCACHE_OP_HASH | DCACHE_OP_COMPARE | DCACHE_OP_REVALIDATE | \
fs/dcache.c-1893- DCACHE_OP_WEAK_REVALIDATE | DCACHE_OP_DELETE | DCACHE_OP_PRUNE | \
--
fs/dcache.c=1896=static unsigned int d_op_flags(const struct dentry_operations *op)
--
fs/dcache.c-1902- if (op->d_compare)
fs/dcache.c:1903: flags |= DCACHE_OP_COMPARE;
fs/dcache.c-1904- if (op->d_revalidate)
--
fs/dcache.c=2217=bool d_same_name(const struct dentry *dentry, const struct dentry *parent,
--
fs/dcache.c-2219-{
fs/dcache.c:2220: if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
fs/dcache.c-2221- if (dentry->d_name.len != name->len)
--
fs/dcache.c=2229=EXPORT_SYMBOL_GPL(d_same_name);
--
fs/dcache.c-2232- * This is __d_lookup_rcu() when the parent dentry has
fs/dcache.c:2233: * DCACHE_OP_COMPARE, which makes things much nastier.
fs/dcache.c-2234- */
--
fs/dcache.c=2299=struct dentry *__d_lookup_rcu(const struct dentry *parent,
--
fs/dcache.c-2315-
fs/dcache.c:2316: if (unlikely(parent->d_flags & DCACHE_OP_COMPARE))
fs/dcache.c-2317- return __d_lookup_rcu_op_compare(parent, name, seqp);
--
fs/overlayfs/util.c=199=bool ovl_dentry_weird(struct dentry *dentry)
--
fs/overlayfs/util.c-213-
fs/overlayfs/util.c:214: return dentry->d_flags & (DCACHE_OP_HASH | DCACHE_OP_COMPARE);
fs/overlayfs/util.c-215-}
--
include/linux/dcache.h=180=enum dentry_flags {
include/linux/dcache.h-181- DCACHE_OP_HASH = BIT(0),
include/linux/dcache.h:182: DCACHE_OP_COMPARE = BIT(1),
include/linux/dcache.h-183- DCACHE_OP_REVALIDATE = BIT(2),
]
|
| 29/2 |
2026/03/04 03:51 |
llm |
expert |
0m
Model:
Tokens:
input: 27188
output: 24
thoughts: 1166
Thoughts: **Analyzing a Potential Data Race in Dentry Operations**
Okay, so I'm digging into this `dentry` operation and think I've spotted something. It looks like `DCACHE_OP_COMPARE` is set in the `d_op_flags`, which is used during `dentry` initialization via `d_set_d_op`. The crucial thing here is that once a `dentry` is live and has children, the `d_op` (and thus `DCACHE_OP_COMPARE`) *shouldn't* change.
The potential race condition I see is between `d_shrink_del`, which clears `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST`, and `d_same_name`, which reads `d_flags` to check `DCACHE_OP_COMPARE`. My first thought was that since `DCACHE_OP_COMPARE` itself isn't being modified by `d_shrink_del`, and `d_flags` is an unsigned integer, the read in `d_same_name` would always see a consistent value, whether it's the old or new value of the flag.
But then I thought, wait, could "load tearing" or "store tearing" be an issue? On x86_64, 32-bit aligned loads and stores *are* atomic. And, since `d_flags` is `unsigned int` (4 bytes) and at the beginning of `struct dentry`, it's likely aligned. However, the C standard doesn't guarantee this, and KCSAN is flagging it because it's a data race.
I started to dig deeper. I realized that `d_shrink_del` is actually called WITH `dentry->d_lock` held. In `shrink_dentry_list`, the code acquires `spin_lock(&dentry->d_lock)` before calling `d_shrink_del`.
Then, I looked at `__d_lookup`. It holds `dentry->d_lock`, but `d_same_name` reads `parent->d_flags`. Crucially, it doesn't hold `parent->d_lock`. This is a classic data race. One thread modifies a field under a lock, and another thread reads it without that lock.
In many kernel scenarios, if the field is a set of flags and the reader only cares about a flag that isn't changing, it's considered "benign". However, `dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST)` is a *read-modify-write* operation. While the write is safe, because it's done under `parent->d_lock`, the reader `d_same_name` is reading it without the lock. It could potentially see a torn value.
On x86, it's very unlikely. But what about the compiler doing something weird? KCSAN is there to flag those scenarios.
My next thought was to consider if the compiler could affect the `DCACHE_OP_COMPARE` flag. If the compiler implements `dentry->d_flags &= ~mask` as a load, AND, store sequence, and the reader reads in between, then it sees the old value (fine), or after the store (also fine). However, I also realized there's a bigger issue: `d_flags` is used for many things.
I looked at `__d_lookup_rcu`. The code there uses `if (unlikely(parent->d_flags & DCACHE_OP_COMPARE))`. This is also a read without a lock. Actually, many places in `dcache.c` read `d_flags` without `d_lock`, especially in the RCU path. Typically, `READ_ONCE` is used if compiler optimizations need to be bypassed, or if the race is considered harmless.
Now, I need to check if `d_flags` is ever accessed with `READ_ONCE` in that code base.
|
| 30/2 |
2026/03/04 03:51 |
tool |
grepper |
0m
Args: map[Expression:READ_ONCE\(.*d_flags\)]
Results: map[Output:fs/dcache.c=401=static inline void __d_set_inode_and_type(struct dentry *dentry,
--
fs/dcache.c-407- dentry->d_inode = inode;
fs/dcache.c:408: flags = READ_ONCE(dentry->d_flags);
fs/dcache.c-409- flags &= ~DCACHE_ENTRY_TYPE;
--
fs/dcache.c=414=static inline void __d_clear_type_and_inode(struct dentry *dentry)
fs/dcache.c-415-{
fs/dcache.c:416: unsigned flags = READ_ONCE(dentry->d_flags);
fs/dcache.c-417-
--
fs/dcache.c=745=static inline bool retain_dentry(struct dentry *dentry, bool locked)
--
fs/dcache.c-749- smp_rmb();
fs/dcache.c:750: d_flags = READ_ONCE(dentry->d_flags);
fs/dcache.c-751-
--
io_uring/openclose.c=275=int io_install_fixed_fd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
--
io_uring/openclose.c-287-
io_uring/openclose.c:288: flags = READ_ONCE(sqe->install_fd_flags);
io_uring/openclose.c-289- if (flags & ~IORING_FIXED_FD_NO_CLOEXEC)
--
io_uring/uring_cmd.c=184=int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
--
io_uring/uring_cmd.c-191-
io_uring/uring_cmd.c:192: ioucmd->flags = READ_ONCE(sqe->uring_cmd_flags);
io_uring/uring_cmd.c-193- if (ioucmd->flags & ~IORING_URING_CMD_MASK)
]
|
| 31/2 |
2026/03/04 03:51 |
llm |
expert |
0m
Model:
Tokens:
input: 29814
output: 19
thoughts: 2105
Thoughts: **Benign Data Race in dentry Flags Read**
Okay, so I'm looking at this code and there's a potential race condition involving reads of `dentry->d_flags`. Specifically, some parts of the code use `READ_ONCE` for this, while others, like `d_same_name`, don't. The concern revolves around how `DCACHE_OP_COMPARE` is handled in conjunction with `shrink_dentry_list` and `__d_lookup_rcu`.
The core issue is that `DCACHE_OP_COMPARE`'s stability during lookup needs confirmation. The flag is set during dentry creation via `d_set_d_op`, which suggests it should be relatively static. Could it be changed on a live dentry? It's rare and typically protected by a lock, so it's probably fine.
In the context of `shrink_dentry_list`, we're dealing with dentries being evicted. If the dentry being looked up is a `parent`, it means we're looking up a child and the `parent` should be alive. If `parent` is being shrunk, it implies its reference count dropped to zero. But since we're in `__d_lookup`, we have a reference to the `parent`. The only way that would make sense is if the `parent` was *already* in the `shrink_dentry_list` when the reference was taken. In that scenario, `shrink_dentry_list` would see the non-zero refcount and call `d_shrink_del` to remove it from the list.
So, `d_shrink_del` *can* be called on a dentry that's also a `parent` of a dentry that's being looked up. This is where the race exists.
The actual risk hinges on whether the `d_flags` read can return an inconsistent value for `DCACHE_OP_COMPARE`. Because `d_shrink_del` doesn't *modify* `DCACHE_OP_COMPARE`, the only way this could happen is through load/store tearing. On x86_64, aligned 32-bit accesses are atomic, so the entire read would be completed and there would be no torn reads.
However, KCSAN raises concerns about compiler optimizations. But, here, the read only checks a single bit.
If the race is benign, we *should* consider using `READ_ONCE` or marking it as intentional to appease KCSAN and future developers.
Wait, let me look at `d_shrink_del` again...
Ah, yes.
```c
dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
```
This is *not* an atomic update. If another thread *was* concurrently modifying `d_flags` under the lock, `d_shrink_del` holds the `d_lock` too. However, the reader, in functions like `d_same_name`, *doesn't* hold the lock.
If the reader sees a partially updated value, what can happen? On x86, it's a 32-bit store, and there's no "partially updated" value; it's either the old *or* the new value. Crucially, both values will have the same `DCACHE_OP_COMPARE` bit value.
So, on x86, this is still benign.
What about other architectures? Well, it's unlikely for modern 32-bit or 64-bit CPUs if accesses are aligned.
Is `d_flags` aligned? The code says it's the first field of `struct dentry`, and `struct dentry` is cacheline-aligned. Therefore, `d_flags` *is* aligned.
The race *seems* benign.
I should also check if any other flags would be problematic if seen in an inconsistent state, though.
In `d_same_name`, the read of `parent->d_flags` *only* checks `DCACHE_OP_COMPARE`.
In `__d_lookup` checks `d_unhashed`, which uses the `d_hash` list not `d_flags`.
Oh, right, in `d_same_name`:
```c
if (likely(!(parent->d_flags & DCACHE_OP_COMPARE))) {
if (dentry->d_name.len != name->len)
return false;
return dentry_cmp(dentry, name->name, name->len) == 0;
}
return parent->d_op->d_
|
| 32/2 |
2026/03/04 03:51 |
tool |
grepper |
0m
Args: map[Expression:d_flags.*=]
Results: map[Output:
Full output is too long, showing 500 out of 6841 lines.
Use more precise expression if possible.
[Documentation/scheduler/sched-deadline.rst=796=Appendix B. Minimal main()
--
Documentation/scheduler/sched-deadline.rst-879- attr.size = sizeof(attr);
Documentation/scheduler/sched-deadline.rst:880: attr.sched_flags = 0;
Documentation/scheduler/sched-deadline.rst-881- attr.sched_nice = 0;
--
arch/alpha/kernel/signal.c=523=do_work_pending(struct pt_regs *regs, unsigned long thread_flags,
--
arch/alpha/kernel/signal.c-541- local_irq_disable();
arch/alpha/kernel/signal.c:542: thread_flags = read_thread_flags();
arch/alpha/kernel/signal.c-543- } while (thread_flags & _TIF_WORK_MASK);
--
arch/arm/kernel/signal.c=603=do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
--
arch/arm/kernel/signal.c-635- local_irq_disable();
arch/arm/kernel/signal.c:636: thread_flags = read_thread_flags();
arch/arm/kernel/signal.c-637- } while (thread_flags & _TIF_WORK_MASK);
--
arch/arm64/kernel/stacktrace.c=459=static bool dump_backtrace_entry(const struct kunwind_state *state, void *arg)
--
arch/arm64/kernel/stacktrace.c-461- const char *source = state_source_string(state);
arch/arm64/kernel/stacktrace.c:462: union unwind_flags flags = state->flags;
arch/arm64/kernel/stacktrace.c-463- bool has_info = source || flags.all;
--
arch/loongarch/kvm/mmu.c=442=void kvm_arch_commit_memory_region(struct kvm *kvm,
--
arch/loongarch/kvm/mmu.c-447- int needs_flush;
arch/loongarch/kvm/mmu.c:448: u32 old_flags = old ? old->flags : 0;
arch/loongarch/kvm/mmu.c-449- u32 new_flags = new ? new->flags : 0;
--
arch/openrisc/kernel/signal.c=332=do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
--
arch/openrisc/kernel/signal.c-356- local_irq_disable();
arch/openrisc/kernel/signal.c:357: thread_flags = read_thread_flags();
arch/openrisc/kernel/signal.c-358- } while (thread_flags & _TIF_WORK_MASK);
--
arch/powerpc/platforms/powermac/feature.c=299=static long ohare_sleep_state(struct device_node *node, long param, long value)
--
arch/powerpc/platforms/powermac/feature.c-302-
arch/powerpc/platforms/powermac/feature.c:303: if ((pmac_mb.board_flags & PMAC_MB_CAN_SLEEP) == 0)
arch/powerpc/platforms/powermac/feature.c-304- return -EPERM;
--
arch/powerpc/platforms/powermac/feature.c=582=static long heathrow_sleep_state(struct device_node *node, long param,
--
arch/powerpc/platforms/powermac/feature.c-584-{
arch/powerpc/platforms/powermac/feature.c:585: if ((pmac_mb.board_flags & PMAC_MB_CAN_SLEEP) == 0)
arch/powerpc/platforms/powermac/feature.c-586- return -EPERM;
--
arch/powerpc/platforms/powermac/feature.c=1263=core99_firewire_cable_power(struct device_node *node, long param, long value)
--
arch/powerpc/platforms/powermac/feature.c-1268- /* Trick: we allow NULL node */
arch/powerpc/platforms/powermac/feature.c:1269: if ((pmac_mb.board_flags & PMAC_MB_HAS_FW_POWER) == 0)
arch/powerpc/platforms/powermac/feature.c-1270- return -ENODEV;
--
arch/powerpc/platforms/powermac/feature.c=1823=core99_sleep_state(struct device_node *node, long param, long value)
--
arch/powerpc/platforms/powermac/feature.c-1839- }
arch/powerpc/platforms/powermac/feature.c:1840: if ((pmac_mb.board_flags & PMAC_MB_CAN_SLEEP) == 0)
arch/powerpc/platforms/powermac/feature.c-1841- return -EPERM;
--
arch/powerpc/platforms/powermac/feature.c=1856=generic_dev_can_wake(struct device_node *node, long param, long value)
--
arch/powerpc/platforms/powermac/feature.c-1862- if (pmac_mb.board_flags & PMAC_MB_MAY_SLEEP)
arch/powerpc/platforms/powermac/feature.c:1863: pmac_mb.board_flags |= PMAC_MB_CAN_SLEEP;
arch/powerpc/platforms/powermac/feature.c-1864- return 0;
--
arch/powerpc/platforms/powermac/feature.c=2398=static int __init probe_motherboard(void)
--
arch/powerpc/platforms/powermac/feature.c-2534- || strncmp(model, "iBook", 5) == 0))
arch/powerpc/platforms/powermac/feature.c:2535: pmac_mb.board_flags |= PMAC_MB_MOBILE;
arch/powerpc/platforms/powermac/feature.c-2536-
--
arch/powerpc/platforms/powernv/pci-ioda.c=1736=static const struct msi_parent_ops pnv_msi_parent_ops = {
arch/powerpc/platforms/powernv/pci-ioda.c:1737: .required_flags = PNV_PCI_MSI_FLAGS_REQUIRED,
arch/powerpc/platforms/powernv/pci-ioda.c:1738: .supported_flags = PNV_PCI_MSI_FLAGS_SUPPORTED,
arch/powerpc/platforms/powernv/pci-ioda.c-1739- .chip_flags = MSI_CHIP_FLAG_SET_EOI,
--
arch/powerpc/platforms/pseries/msi.c=526=static const struct msi_parent_ops pseries_msi_parent_ops = {
arch/powerpc/platforms/pseries/msi.c:527: .required_flags = PSERIES_PCI_MSI_FLAGS_REQUIRED,
arch/powerpc/platforms/pseries/msi.c:528: .supported_flags = PSERIES_PCI_MSI_FLAGS_SUPPORTED,
arch/powerpc/platforms/pseries/msi.c-529- .chip_flags = MSI_CHIP_FLAG_SET_EOI,
--
arch/powerpc/platforms/pseries/papr_scm.c=834=static int papr_pdsm_smart_inject(struct papr_scm_priv *p,
--
arch/powerpc/platforms/pseries/papr_scm.c-837- int rc;
arch/powerpc/platforms/pseries/papr_scm.c:838: u32 supported_flags = 0;
arch/powerpc/platforms/pseries/papr_scm.c-839- u64 inject_mask = 0, clear_mask = 0;
--
arch/powerpc/platforms/pseries/papr_scm.c-843- if (payload->smart_inject.flags & PDSM_SMART_INJECT_HEALTH_FATAL) {
arch/powerpc/platforms/pseries/papr_scm.c:844: supported_flags |= PDSM_SMART_INJECT_HEALTH_FATAL;
arch/powerpc/platforms/pseries/papr_scm.c-845- if (payload->smart_inject.fatal_enable)
--
arch/powerpc/platforms/pseries/papr_scm.c-851- if (payload->smart_inject.flags & PDSM_SMART_INJECT_BAD_SHUTDOWN) {
arch/powerpc/platforms/pseries/papr_scm.c:852: supported_flags |= PDSM_SMART_INJECT_BAD_SHUTDOWN;
arch/powerpc/platforms/pseries/papr_scm.c-853- if (payload->smart_inject.unsafe_shutdown_enable)
--
arch/s390/kvm/vsie.c=313=static int shadow_crycb(struct kvm_vcpu *vcpu, struct vsie_page *vsie_page)
--
arch/s390/kvm/vsie.c-355- (ECB3_AES | ECB3_DEA);
arch/s390/kvm/vsie.c:356: ecd_flags = scb_o->ecd & vcpu->arch.sie_block->ecd &
arch/s390/kvm/vsie.c-357- (ECD_ECC | ECD_HMAC);
--
arch/s390/pci/pci_irq.c=495=static struct msi_parent_ops zpci_msi_parent_ops = {
arch/s390/pci/pci_irq.c:496: .supported_flags = MSI_GENERIC_FLAGS_MASK |
arch/s390/pci/pci_irq.c-497- MSI_FLAG_PCI_MSIX |
arch/s390/pci/pci_irq.c-498- MSI_FLAG_MULTI_PCI_MSI,
arch/s390/pci/pci_irq.c:499: .required_flags = MSI_FLAG_USE_DEF_DOM_OPS |
arch/s390/pci/pci_irq.c-500- MSI_FLAG_USE_DEF_CHIP_OPS,
--
arch/s390/pci/pci_irq.c=504=int zpci_create_parent_msi_domain(struct zpci_bus *zbus)
--
arch/s390/pci/pci_irq.c-519- if (irq_delivery == FLOATING)
arch/s390/pci/pci_irq.c:520: zpci_msi_parent_ops.required_flags |= MSI_FLAG_NO_AFFINITY;
arch/s390/pci/pci_irq.c-521-
--
arch/um/drivers/virt-pci.c=414=static const struct msi_parent_ops um_pci_msi_parent_ops = {
arch/um/drivers/virt-pci.c:415: .required_flags = UM_PCI_MSI_FLAGS_REQUIRED,
arch/um/drivers/virt-pci.c:416: .supported_flags = UM_PCI_MSI_FLAGS_SUPPORTED,
arch/um/drivers/virt-pci.c-417- .bus_select_token = DOMAIN_BUS_NEXUS,
--
arch/um/kernel/process.c=84=void interrupt_end(void)
--
arch/um/kernel/process.c-88-
arch/um/kernel/process.c:89: thread_flags = read_thread_flags();
arch/um/kernel/process.c-90- while (thread_flags & _TIF_WORK_MASK) {
--
arch/um/kernel/process.c-96- resume_user_mode_work(regs);
arch/um/kernel/process.c:97: thread_flags = read_thread_flags();
arch/um/kernel/process.c-98- }
--
arch/x86/boot/cpuflags.c=68=void get_cpuflags(void)
--
arch/x86/boot/cpuflags.c-75- return;
arch/x86/boot/cpuflags.c:76: loaded_flags = true;
arch/x86/boot/cpuflags.c-77-
--
arch/x86/boot/startup/sme.c=209=static void __init __sme_map_range(struct sme_populate_pgd_data *ppd,
--
arch/x86/boot/startup/sme.c-213-
arch/x86/boot/startup/sme.c:214: ppd->pmd_flags = pmd_flags;
arch/x86/boot/startup/sme.c-215- ppd->pte_flags = pte_flags;
--
arch/x86/hyperv/irqdomain.c=322=static struct msi_parent_ops hv_msi_parent_ops = {
arch/x86/hyperv/irqdomain.c:323: .supported_flags = HV_MSI_FLAGS_SUPPORTED,
arch/x86/hyperv/irqdomain.c:324: .required_flags = HV_MSI_FLAGS_REQUIRED,
arch/x86/hyperv/irqdomain.c-325- .bus_select_token = DOMAIN_BUS_NEXUS,
--
arch/x86/include/asm/pgtable.h=183=static inline bool pmd_shstk(pmd_t pmd)
--
arch/x86/include/asm/pgtable.h-185- return cpu_feature_enabled(X86_FEATURE_SHSTK) &&
arch/x86/include/asm/pgtable.h:186: (pmd_flags(pmd) & (_PAGE_RW | _PAGE_DIRTY | _PAGE_PSE)) ==
arch/x86/include/asm/pgtable.h-187- (_PAGE_DIRTY | _PAGE_PSE);
--
arch/x86/include/asm/pgtable.h=206=static inline bool pud_shstk(pud_t pud)
--
arch/x86/include/asm/pgtable.h-208- return cpu_feature_enabled(X86_FEATURE_SHSTK) &&
arch/x86/include/asm/pgtable.h:209: (pud_flags(pud) & (_PAGE_RW | _PAGE_DIRTY | _PAGE_PSE)) ==
arch/x86/include/asm/pgtable.h-210- (_PAGE_DIRTY | _PAGE_PSE);
--
arch/x86/include/asm/pgtable.h=1033=static inline int pmd_bad(pmd_t pmd)
arch/x86/include/asm/pgtable.h-1034-{
arch/x86/include/asm/pgtable.h:1035: return (pmd_flags(pmd) & ~(_PAGE_USER | _PAGE_ACCESSED)) !=
arch/x86/include/asm/pgtable.h-1036- (_KERNPG_TABLE & ~_PAGE_ACCESSED);
--
arch/x86/include/asm/pgtable.h=1072=static inline int pud_bad(pud_t pud)
arch/x86/include/asm/pgtable.h-1073-{
arch/x86/include/asm/pgtable.h:1074: return (pud_flags(pud) & ~(_KERNPG_TABLE | _PAGE_USER)) != 0;
arch/x86/include/asm/pgtable.h-1075-}
--
arch/x86/include/asm/pgtable.h=1100=static inline int p4d_bad(p4d_t p4d)
--
arch/x86/include/asm/pgtable.h-1106-
arch/x86/include/asm/pgtable.h:1107: return (p4d_flags(p4d) & ~ignore_flags) != 0;
arch/x86/include/asm/pgtable.h-1108-}
--
arch/x86/include/asm/pgtable.h=1143=static inline int pgd_bad(pgd_t pgd)
--
arch/x86/include/asm/pgtable.h-1152-
arch/x86/include/asm/pgtable.h:1153: return (pgd_flags(pgd) & ~ignore_flags) != _KERNPG_TABLE;
arch/x86/include/asm/pgtable.h-1154-}
--
arch/x86/kernel/apic/msi.c=259=static const struct msi_parent_ops x86_vector_msi_parent_ops = {
arch/x86/kernel/apic/msi.c:260: .supported_flags = X86_VECTOR_MSI_FLAGS_SUPPORTED,
arch/x86/kernel/apic/msi.c-261- .init_dev_msi_info = x86_init_dev_msi_info,
--
arch/x86/kernel/head64.c=51=SYM_PIC_ALIAS(next_early_pgt);
arch/x86/kernel/head64.c:52:pmdval_t early_pmd_flags = __PAGE_KERNEL_LARGE & ~(_PAGE_GLOBAL | _PAGE_NX);
arch/x86/kernel/head64.c-53-
--
arch/x86/kernel/kprobes/core.c=817=save_previous_kprobe(struct kprobe_ctlblk *kcb)
--
arch/x86/kernel/kprobes/core.c-820- kcb->prev_kprobe.status = kcb->kprobe_status;
arch/x86/kernel/kprobes/core.c:821: kcb->prev_kprobe.old_flags = kcb->kprobe_old_flags;
arch/x86/kernel/kprobes/core.c:822: kcb->prev_kprobe.saved_flags = kcb->kprobe_saved_flags;
arch/x86/kernel/kprobes/core.c-823-}
--
arch/x86/kernel/kprobes/core.c=826=restore_previous_kprobe(struct kprobe_ctlblk *kcb)
--
arch/x86/kernel/kprobes/core.c-829- kcb->kprobe_status = kcb->prev_kprobe.status;
arch/x86/kernel/kprobes/core.c:830: kcb->kprobe_old_flags = kcb->prev_kprobe.old_flags;
arch/x86/kernel/kprobes/core.c:831: kcb->kprobe_saved_flags = kcb->prev_kprobe.saved_flags;
arch/x86/kernel/kprobes/core.c-832-}
--
arch/x86/kernel/kprobes/core.c=835=set_current_kprobe(struct kprobe *p, struct pt_regs *regs,
--
arch/x86/kernel/kprobes/core.c-838- __this_cpu_write(current_kprobe, p);
arch/x86/kernel/kprobes/core.c:839: kcb->kprobe_saved_flags = kcb->kprobe_old_flags
arch/x86/kernel/kprobes/core.c-840- = (regs->flags & X86_EFLAGS_IF);
--
arch/x86/kernel/pvclock.c-18-
arch/x86/kernel/pvclock.c:19:static u8 valid_flags __read_mostly = 0;
arch/x86/kernel/pvclock.c-20-static struct pvclock_vsyscall_time_info *pvti_cpu0_va __read_mostly;
--
arch/x86/kernel/pvclock.c=22=void pvclock_set_flags(u8 flags)
arch/x86/kernel/pvclock.c-23-{
arch/x86/kernel/pvclock.c:24: valid_flags = flags;
arch/x86/kernel/pvclock.c-25-}
--
arch/x86/kvm/x86.c=13590=static void kvm_mmu_slot_apply_flags(struct kvm *kvm,
--
arch/x86/kvm/x86.c-13594-{
arch/x86/kvm/x86.c:13595: u32 old_flags = old ? old->flags : 0;
arch/x86/kvm/x86.c-13596- u32 new_flags = new ? new->flags : 0;
--
arch/x86/kvm/xen.c=1362=int kvm_xen_hvm_config(struct kvm *kvm, struct kvm_xen_hvm_config *xhc)
--
arch/x86/kvm/xen.c-1364- /* Only some feature flags need to be *enabled* by userspace */
arch/x86/kvm/xen.c:1365: u32 permitted_flags = KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL |
arch/x86/kvm/xen.c-1366- KVM_XEN_HVM_CONFIG_EVTCHN_SEND |
--
arch/x86/kvm/xen.c-1397-
arch/x86/kvm/xen.c:1398: old_flags = kvm->arch.xen.hvm_config.flags;
arch/x86/kvm/xen.c-1399- memcpy(&kvm->arch.xen.hvm_config, xhc, sizeof(*xhc));
--
arch/x86/mm/kmmio.c=236=int kmmio_handler(struct pt_regs *regs, unsigned long addr)
--
arch/x86/mm/kmmio.c-298- ctx->probe = get_kmmio_probe(page_base);
arch/x86/mm/kmmio.c:299: ctx->saved_flags = (regs->flags & (X86_EFLAGS_TF | X86_EFLAGS_IF));
arch/x86/mm/kmmio.c-300- ctx->addr = page_base;
--
arch/x86/mm/mem_encrypt_amd.c=156=static void __init __sme_early_map_unmap_mem(void *vaddr, unsigned long size,
--
arch/x86/mm/mem_encrypt_amd.c-162- /* Use early_pmd_flags but remove the encryption mask */
arch/x86/mm/mem_encrypt_amd.c:163: pmd_flags = __sme_clr(early_pmd_flags);
arch/x86/mm/mem_encrypt_amd.c-164-
--
arch/x86/mm/mem_encrypt_amd.c=477=void __init sme_early_init(void)
--
arch/x86/mm/mem_encrypt_amd.c-481-
arch/x86/mm/mem_encrypt_amd.c:482: early_pmd_flags = __sme_set(early_pmd_flags);
arch/x86/mm/mem_encrypt_amd.c-483-
--
arch/x86/mm/pat/memtype.c=142=static inline void set_page_memtype(struct page *pg,
--
arch/x86/mm/pat/memtype.c-164-
arch/x86/mm/pat/memtype.c:165: old_flags = READ_ONCE(pg->flags.f);
arch/x86/mm/pat/memtype.c-166- do {
--
arch/x86/mm/pat/set_memory.c=1315=static int collapse_pud_page(pud_t *pud, unsigned long addr,
--
arch/x86/mm/pat/set_memory.c-1345- return 0;
arch/x86/mm/pat/set_memory.c:1346: if (pmd_flags(entry) != pmd_flags(first))
arch/x86/mm/pat/set_memory.c-1347- return 0;
--
block/bfq-iosched.c=6236=static void bfq_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,
--
block/bfq-iosched.c-6284- */
block/bfq-iosched.c:6285: cmd_flags = rq->cmd_flags;
block/bfq-iosched.c-6286- spin_unlock_irq(&bfqd->lock);
--
block/blk-flush.c=148=static void blk_flush_complete_seq(struct request *rq,
--
block/blk-flush.c-157- rq->flush.seq |= seq;
block/blk-flush.c:158: cmd_flags = rq->cmd_flags;
block/blk-flush.c-159-
--
block/blk-flush.c=276=static void blk_kick_flush(struct request_queue *q, struct blk_flush_queue *fq,
--
block/blk-flush.c-317-
block/blk-flush.c:318: flush_rq->cmd_flags = REQ_OP_FLUSH | REQ_PREFLUSH;
block/blk-flush.c:319: flush_rq->cmd_flags |= (flags & REQ_DRV) | (flags & REQ_FAILFAST_MASK);
block/blk-flush.c-320- flush_rq->rq_flags |= RQF_FLUSH_SEQ;
--
block/blk-flush.c=384=bool blk_insert_flush(struct request *rq)
--
block/blk-flush.c-410- */
block/blk-flush.c:411: rq->cmd_flags &= ~REQ_PREFLUSH;
block/blk-flush.c-412- if (!supports_fua)
block/blk-flush.c:413: rq->cmd_flags &= ~REQ_FUA;
block/blk-flush.c-414-
--
block/blk-flush.c-419- */
block/blk-flush.c:420: rq->cmd_flags |= REQ_SYNC;
block/blk-flush.c-421-
--
block/blk-integrity.c=58=int blk_get_meta_cap(struct block_device *bdev, unsigned int cmd,
--
block/blk-integrity.c-72- if (bi->flags & BLK_INTEGRITY_DEVICE_CAPABLE)
block/blk-integrity.c:73: meta_cap.lbmd_flags |= LBMD_PI_CAP_INTEGRITY;
block/blk-integrity.c-74- if (bi->flags & BLK_INTEGRITY_REF_TAG)
block/blk-integrity.c:75: meta_cap.lbmd_flags |= LBMD_PI_CAP_REFTAG;
block/blk-integrity.c-76- meta_cap.lbmd_interval = 1 << bi->interval_exp;
--
block/blk-integrity.c=123=int blk_rq_integrity_map_user(struct request *rq, void __user *ubuf,
--
block/blk-integrity.c-134- rq->nr_integrity_segments = blk_rq_count_integrity_sg(rq->q, rq->bio);
block/blk-integrity.c:135: rq->cmd_flags |= REQ_INTEGRITY;
block/blk-integrity.c-136- return 0;
--
block/blk-merge.c=707=static inline void blk_update_mixed_merge(struct request *req,
--
block/blk-merge.c-714- if (front_merge) {
block/blk-merge.c:715: req->cmd_flags &= ~REQ_FAILFAST_MASK;
block/blk-merge.c:716: req->cmd_flags |= bio->bi_opf & REQ_FAILFAST_MASK;
block/blk-merge.c-717- }
--
block/blk-merge.c=743=static bool blk_atomic_write_mergeable_rq_bio(struct request *rq,
--
block/blk-merge.c-745-{
block/blk-merge.c:746: return (rq->cmd_flags & REQ_ATOMIC) == (bio->bi_opf & REQ_ATOMIC);
block/blk-merge.c-747-}
--
block/blk-merge.c=749=static bool blk_atomic_write_mergeable_rqs(struct request *rq,
--
block/blk-merge.c-751-{
block/blk-merge.c:752: return (rq->cmd_flags & REQ_ATOMIC) == (next->cmd_flags & REQ_ATOMIC);
block/blk-merge.c-753-}
--
block/blk-merge.c=777=static struct request *attempt_merge(struct request_queue *q,
--
block/blk-merge.c-822- if (((req->rq_flags | next->rq_flags) & RQF_MIXED_MERGE) ||
block/blk-merge.c:823: (req->cmd_flags & REQ_FAILFAST_MASK) !=
block/blk-merge.c-824- (next->cmd_flags & REQ_FAILFAST_MASK)) {
--
block/blk-merge.c=944=enum bio_merge_status bio_attempt_back_merge(struct request *req,
--
block/blk-merge.c-954-
block/blk-merge.c:955: if ((req->cmd_flags & REQ_FAILFAST_MASK) != ff)
block/blk-merge.c-956- blk_rq_set_mixed_merge(req);
--
block/blk-merge.c=975=static enum bio_merge_status bio_attempt_front_merge(struct request *req,
--
block/blk-merge.c-993-
block/blk-merge.c:994: if ((req->cmd_flags & REQ_FAILFAST_MASK) != ff)
block/blk-merge.c-995- blk_rq_set_mixed_merge(req);
--
block/blk-mq-debugfs.c=263=int __blk_mq_debugfs_rq_show(struct seq_file *m, struct request *rq)
--
block/blk-mq-debugfs.c-276- seq_printf(m, "%s", op_str);
block/blk-mq-debugfs.c:277: seq_puts(m, ", .cmd_flags=");
block/blk-mq-debugfs.c-278- blk_flags_show(m, (__force unsigned int)(rq->cmd_flags & ~REQ_OP_MASK),
--
block/blk-mq.c=410=static struct request *blk_mq_rq_ctx_init(struct blk_mq_alloc_data *data,
--
block/blk-mq.c-420- rq->mq_hctx = hctx;
block/blk-mq.c:421: rq->cmd_flags = data->cmd_flags;
block/blk-mq.c-422-
--
block/blk-mq.c=501=static void blk_mq_limit_depth(struct blk_mq_alloc_data *data)
--
block/blk-mq.c-520- */
block/blk-mq.c:521: if ((data->cmd_flags & REQ_OP_MASK) == REQ_OP_FLUSH ||
block/blk-mq.c-522- blk_op_is_passthrough(data->cmd_flags))
--
block/blk-mq.c=597=static struct request *blk_mq_rq_cache_fill(struct request_queue *q,
--
block/blk-mq.c-605- .shallow_depth = 0,
block/blk-mq.c:606: .cmd_flags = opf,
block/blk-mq.c-607- .rq_flags = 0,
--
block/blk-mq.c=626=static struct request *blk_mq_alloc_cached_request(struct request_queue *q,
--
block/blk-mq.c-648- return NULL;
block/blk-mq.c:649: if (op_is_flush(rq->cmd_flags) != op_is_flush(opf))
block/blk-mq.c-650- return NULL;
--
block/blk-mq.c-655-
block/blk-mq.c:656: rq->cmd_flags = opf;
block/blk-mq.c-657- INIT_LIST_HEAD(&rq->queuelist);
--
block/blk-mq.c=661=struct request *blk_mq_alloc_request(struct request_queue *q, blk_opf_t opf,
--
block/blk-mq.c-671- .shallow_depth = 0,
block/blk-mq.c:672: .cmd_flags = opf,
block/blk-mq.c-673- .rq_flags = 0,
--
block/blk-mq.c=700=struct request *blk_mq_alloc_request_hctx(struct request_queue *q,
--
block/blk-mq.c-706- .shallow_depth = 0,
block/blk-mq.c:707: .cmd_flags = opf,
block/blk-mq.c-708- .rq_flags = 0,
--
block/blk-mq.c=954=bool blk_update_request(struct request *req, blk_status_t error,
--
block/blk-mq.c-1046- if (req->rq_flags & RQF_MIXED_MERGE) {
block/blk-mq.c:1047: req->cmd_flags &= ~REQ_FAILFAST_MASK;
block/blk-mq.c:1048: req->cmd_flags |= req->bio->bi_opf & REQ_FAILFAST_MASK;
block/blk-mq.c-1049- }
--
block/blk-mq.c=2685=static void blk_mq_bio_to_request(struct request *rq, struct bio *bio,
--
block/blk-mq.c-2690- if (bio->bi_opf & REQ_RAHEAD)
block/blk-mq.c:2691: rq->cmd_flags |= REQ_FAILFAST_MASK;
block/blk-mq.c-2692-
--
block/blk-mq.c=3046=static struct request *blk_mq_get_new_requests(struct request_queue *q,
--
block/blk-mq.c-3053- .shallow_depth = 0,
block/blk-mq.c:3054: .cmd_flags = bio->bi_opf,
block/blk-mq.c-3055- .rq_flags = 0,
--
block/blk-mq.c=3080=static struct request *blk_mq_peek_cached_request(struct blk_plug *plug,
--
block/blk-mq.c-3093- return NULL;
block/blk-mq.c:3094: if (op_is_flush(rq->cmd_flags) != op_is_flush(opf))
block/blk-mq.c-3095- return NULL;
--
block/blk-mq.c=3099=static void blk_mq_use_cached_rq(struct request *rq, struct blk_plug *plug,
--
block/blk-mq.c-3112- blk_mq_rq_time_init(rq, blk_time_get_ns());
block/blk-mq.c:3113: rq->cmd_flags = bio->bi_opf;
block/blk-mq.c-3114- INIT_LIST_HEAD(&rq->queuelist);
--
block/blk.h=465=static inline void req_set_nomerge(struct request_queue *q, struct request *req)
block/blk.h-466-{
block/blk.h:467: req->cmd_flags |= REQ_NOMERGE;
block/blk.h-468- if (req == q->last_merge)
--
drivers/acpi/acpica/dsfield.c=260=acpi_ds_get_field_names(struct acpi_create_field_info *info,
--
drivers/acpi/acpica/dsfield.c-319-
drivers/acpi/acpica/dsfield.c:320: info->field_flags = (u8)
drivers/acpi/acpica/dsfield.c-321- ((info->
--
drivers/acpi/acpica/dsfield.c=473=acpi_ds_create_field(union acpi_parse_object *op,
--
drivers/acpi/acpica/dsfield.c-510- arg = arg->common.next;
drivers/acpi/acpica/dsfield.c:511: info.field_flags = (u8) arg->common.value.integer;
drivers/acpi/acpica/dsfield.c-512- info.attribute = 0;
--
drivers/acpi/acpica/dsfield.c=673=acpi_ds_create_bank_field(union acpi_parse_object *op,
--
drivers/acpi/acpica/dsfield.c-728- arg = arg->common.next;
drivers/acpi/acpica/dsfield.c:729: info.field_flags = (u8) arg->common.value.integer;
drivers/acpi/acpica/dsfield.c-730-
--
drivers/acpi/acpica/dsfield.c=766=acpi_ds_create_index_field(union acpi_parse_object *op,
--
drivers/acpi/acpica/dsfield.c-806- arg = arg->common.next;
drivers/acpi/acpica/dsfield.c:807: info.field_flags = (u8) arg->common.value.integer;
drivers/acpi/acpica/dsfield.c-808-
--
drivers/acpi/acpica/dsopcode.c=75=acpi_ds_init_buffer_field(u16 aml_opcode,
--
drivers/acpi/acpica/dsopcode.c-125-
drivers/acpi/acpica/dsopcode.c:126: field_flags = AML_FIELD_ACCESS_BYTE;
drivers/acpi/acpica/dsopcode.c-127- bit_offset = offset;
--
drivers/acpi/acpica/dsopcode.c-145- bit_count = 1;
drivers/acpi/acpica/dsopcode.c:146: field_flags = AML_FIELD_ACCESS_BYTE;
drivers/acpi/acpica/dsopcode.c-147- break;
--
drivers/acpi/acpica/dsopcode.c-154- bit_count = 8;
drivers/acpi/acpica/dsopcode.c:155: field_flags = AML_FIELD_ACCESS_BYTE;
drivers/acpi/acpica/dsopcode.c-156- break;
--
drivers/acpi/acpica/dsopcode.c-163- bit_count = 16;
drivers/acpi/acpica/dsopcode.c:164: field_flags = AML_FIELD_ACCESS_WORD;
drivers/acpi/acpica/dsopcode.c-165- break;
--
drivers/acpi/acpica/dsopcode.c-172- bit_count = 32;
drivers/acpi/acpica/dsopcode.c:173: field_flags = AML_FIELD_ACCESS_DWORD;
drivers/acpi/acpica/dsopcode.c-174- break;
--
drivers/acpi/acpica/dsopcode.c-181- bit_count = 64;
drivers/acpi/acpica/dsopcode.c:182: field_flags = AML_FIELD_ACCESS_QWORD;
drivers/acpi/acpica/dsopcode.c-183- break;
--
drivers/acpi/acpica/dsopcode.c-208- * Initialize areas of the field object that are common to all fields
drivers/acpi/acpica/dsopcode.c:209: * For field_flags, use LOCK_RULE = 0 (NO_LOCK),
drivers/acpi/acpica/dsopcode.c-210- * UPDATE_RULE = 0 (UPDATE_PRESERVE)
--
drivers/acpi/acpica/excreate.c=409=acpi_ex_create_method(u8 * aml_start,
--
drivers/acpi/acpica/excreate.c-436- */
drivers/acpi/acpica/excreate.c:437: method_flags = (u8)operand[1]->integer.value;
drivers/acpi/acpica/excreate.c-438- obj_desc->method.param_count = (u8)
--
drivers/acpi/acpica/exprep.c=293=acpi_ex_prep_common_field_object(union acpi_operand_object *obj_desc,
--
drivers/acpi/acpica/exprep.c-308- */
drivers/acpi/acpica/exprep.c:309: obj_desc->common_field.field_flags = field_flags;
drivers/acpi/acpica/exprep.c-310- obj_desc->common_field.attribute = field_attribute;
--
drivers/acpi/acpica/nsxfname.c=475=acpi_status acpi_install_method(u8 *buffer)
--
drivers/acpi/acpica/nsxfname.c-516-
drivers/acpi/acpica/nsxfname.c:517: method_flags = *parser_state.aml++;
drivers/acpi/acpica/nsxfname.c-518- aml_start = parser_state.aml;
--
]
]
|
| 33/2 |
2026/03/04 03:51 |
llm |
expert |
0m
Model:
Tokens:
input: 44213
output: 22
thoughts: 16
Thoughts: Okay, here's my summarized thought process, assuming I'm an expert and this is part of a larger debugging or investigation:
**Narrowing the Focus: Too Broad a Net**
Ugh, the initial search results are swamping me. It's just a sea of information right now. I need to zero in on the relevant area. Thinking logically, the issue seems related to file system caching and the dentry cache, so I'll restrict my search scope. The filename `fs/dcache.c` should pinpoint the code I'm really interested in, that's where the core caching logic is most likely to reside. This will save me a ton of time.
|
| 34/2 |
2026/03/04 03:51 |
tool |
grepper |
0m
Args: map[Expression:dentry->d_flags.*=]
Results: map[Output:fs/autofs/autofs_i.h=193=static inline void __managed_dentry_set_managed(struct dentry *dentry)
fs/autofs/autofs_i.h-194-{
fs/autofs/autofs_i.h:195: dentry->d_flags |= (DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT);
fs/autofs/autofs_i.h-196-}
--
fs/autofs/autofs_i.h=205=static inline void __managed_dentry_clear_managed(struct dentry *dentry)
fs/autofs/autofs_i.h-206-{
fs/autofs/autofs_i.h:207: dentry->d_flags &= ~(DCACHE_NEED_AUTOMOUNT|DCACHE_MANAGE_TRANSIT);
fs/autofs/autofs_i.h-208-}
--
fs/dcache.c=490=static void d_lru_add(struct dentry *dentry)
--
fs/dcache.c-492- D_FLAG_VERIFY(dentry, 0);
fs/dcache.c:493: dentry->d_flags |= DCACHE_LRU_LIST;
fs/dcache.c-494- this_cpu_inc(nr_dentry_unused);
--
fs/dcache.c=501=static void d_lru_del(struct dentry *dentry)
--
fs/dcache.c-503- D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
fs/dcache.c:504: dentry->d_flags &= ~DCACHE_LRU_LIST;
fs/dcache.c-505- this_cpu_dec(nr_dentry_unused);
--
fs/dcache.c=512=static void d_shrink_del(struct dentry *dentry)
--
fs/dcache.c-515- list_del_init(&dentry->d_lru);
fs/dcache.c:516: dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);
fs/dcache.c-517- this_cpu_dec(nr_dentry_unused);
--
fs/dcache.c=520=static void d_shrink_add(struct dentry *dentry, struct list_head *list)
--
fs/dcache.c-523- list_add(&dentry->d_lru, list);
fs/dcache.c:524: dentry->d_flags |= DCACHE_SHRINK_LIST | DCACHE_LRU_LIST;
fs/dcache.c-525- this_cpu_inc(nr_dentry_unused);
--
fs/dcache.c=534=static void d_lru_isolate(struct list_lru_one *lru, struct dentry *dentry)
--
fs/dcache.c-536- D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
fs/dcache.c:537: dentry->d_flags &= ~DCACHE_LRU_LIST;
fs/dcache.c-538- this_cpu_dec(nr_dentry_unused);
--
fs/dcache.c=544=static void d_lru_shrink_move(struct list_lru_one *lru, struct dentry *dentry,
--
fs/dcache.c-547- D_FLAG_VERIFY(dentry, DCACHE_LRU_LIST);
fs/dcache.c:548: dentry->d_flags |= DCACHE_SHRINK_LIST;
fs/dcache.c-549- if (d_is_negative(dentry))
--
fs/dcache.c=608=static inline void dentry_unlist(struct dentry *dentry)
--
fs/dcache.c-614- */
fs/dcache.c:615: dentry->d_flags |= DCACHE_DENTRY_KILLED;
fs/dcache.c-616- if (unlikely(hlist_unhashed(&dentry->d_sib)))
--
fs/dcache.c=745=static inline bool retain_dentry(struct dentry *dentry, bool locked)
--
fs/dcache.c-782- return false;
fs/dcache.c:783: dentry->d_flags |= DCACHE_REFERENCED;
fs/dcache.c-784- }
--
fs/dcache.c=932=void d_make_discardable(struct dentry *dentry)
--
fs/dcache.c-935- WARN_ON(!(dentry->d_flags & DCACHE_PERSISTENT));
fs/dcache.c:936: dentry->d_flags &= ~DCACHE_PERSISTENT;
fs/dcache.c-937- dentry->d_lockref.count--;
--
fs/dcache.c=1179=static enum lru_status dentry_lru_isolate(struct list_head *item,
--
fs/dcache.c-1205- if (dentry->d_flags & DCACHE_REFERENCED) {
fs/dcache.c:1206: dentry->d_flags &= ~DCACHE_REFERENCED;
fs/dcache.c-1207- spin_unlock(&dentry->d_lock);
--
fs/dcache.c=1469=int d_set_mounted(struct dentry *dentry)
--
fs/dcache.c-1486- if (!d_mountpoint(dentry)) {
fs/dcache.c:1487: dentry->d_flags |= DCACHE_MOUNTED;
fs/dcache.c-1488- ret = 0;
--
fs/dcache.c=1548=static enum d_walk_ret select_collect_umount(void *_data, struct dentry *dentry)
--
fs/dcache.c-1550- if (dentry->d_flags & DCACHE_PERSISTENT) {
fs/dcache.c:1551: dentry->d_flags &= ~DCACHE_PERSISTENT;
fs/dcache.c-1552- dentry->d_lockref.count--;
--
fs/dcache.c=1734=static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
--
fs/dcache.c-1777-
fs/dcache.c:1778: dentry->d_flags = 0;
fs/dcache.c-1779- lockref_init(&dentry->d_lockref);
--
fs/dcache.c-1784- dentry->d_op = sb->__s_d_op;
fs/dcache.c:1785: dentry->d_flags = sb->s_d_flags;
fs/dcache.c-1786- dentry->d_fsdata = NULL;
--
fs/dcache.c=1841=struct dentry *d_alloc_cursor(struct dentry * parent)
--
fs/dcache.c-1844- if (dentry) {
fs/dcache.c:1845: dentry->d_flags |= DCACHE_DENTRY_CURSOR;
fs/dcache.c-1846- dentry->d_parent = dget(parent);
--
fs/dcache.c=1866=struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
--
fs/dcache.c-1872- if (likely(dentry)) {
fs/dcache.c:1873: dentry->d_flags |= DCACHE_NORCU;
fs/dcache.c-1874- /* d_op_flags(&anon_ops) is 0 */
--
fs/dcache.c=1918=static void d_set_d_op(struct dentry *dentry, const struct dentry_operations *op)
--
fs/dcache.c-1924- if (flags)
fs/dcache.c:1925: dentry->d_flags |= flags;
fs/dcache.c-1926-}
--
fs/dcache.c=2517=void d_delete(struct dentry * dentry)
--
fs/dcache.c-2528- __d_drop(dentry);
fs/dcache.c:2529: dentry->d_flags &= ~DCACHE_CANT_MOUNT;
fs/dcache.c-2530- dentry_unlink_inode(dentry);
--
fs/dcache.c=2718=static wait_queue_head_t *__d_lookup_unhash(struct dentry *dentry)
--
fs/dcache.c-2726- hlist_bl_lock(b);
fs/dcache.c:2727: dentry->d_flags &= ~DCACHE_PAR_LOOKUP;
fs/dcache.c-2728- __hlist_bl_del(&dentry->d_u.d_in_lookup_hash);
--
fs/dcache.c=2796=struct dentry *d_make_persistent(struct dentry *dentry, struct inode *inode)
--
fs/dcache.c-2803- __d_instantiate(dentry, inode);
fs/dcache.c:2804: dentry->d_flags |= DCACHE_PERSISTENT;
fs/dcache.c-2805- dget_dlock(dentry);
--
fs/exportfs/expfs.c=91=static void clear_disconnected(struct dentry *dentry)
--
fs/exportfs/expfs.c-99- spin_lock(&dentry->d_lock);
fs/exportfs/expfs.c:100: dentry->d_flags &= ~DCACHE_DISCONNECTED;
fs/exportfs/expfs.c-101- spin_unlock(&dentry->d_lock);
--
fs/fuse/dir.c=160=static void fuse_dentry_tree_work(struct work_struct *work)
--
fs/fuse/dir.c-178- /* If dentry is still referenced, let next dput release it */
fs/fuse/dir.c:179: fd->dentry->d_flags |= DCACHE_OP_DELETE;
fs/fuse/dir.c-180- spin_unlock(&fd->dentry->d_lock);
--
fs/fuse/dir.c=250=static void fuse_dentry_settime(struct dentry *dentry, u64 time)
--
fs/fuse/dir.c-261- if (!delete)
fs/fuse/dir.c:262: dentry->d_flags &= ~DCACHE_OP_DELETE;
fs/fuse/dir.c-263- else
fs/fuse/dir.c:264: dentry->d_flags |= DCACHE_OP_DELETE;
fs/fuse/dir.c-265- spin_unlock(&dentry->d_lock);
--
fs/libfs.c=70=struct dentry *simple_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
--
fs/libfs.c-75- spin_lock(&dentry->d_lock);
fs/libfs.c:76: dentry->d_flags |= DCACHE_DONTCACHE;
fs/libfs.c-77- spin_unlock(&dentry->d_lock);
--
fs/namespace.c=922=static void maybe_free_mountpoint(struct mountpoint *mp, struct list_head *list)
--
fs/namespace.c-926- spin_lock(&dentry->d_lock);
fs/namespace.c:927: dentry->d_flags &= ~DCACHE_MOUNTED;
fs/namespace.c-928- spin_unlock(&dentry->d_lock);
--
fs/nfs/unlink.c=172=nfs_async_unlink(struct dentry *dentry, const struct qstr *name)
--
fs/nfs/unlink.c-193- goto out_unlock;
fs/nfs/unlink.c:194: dentry->d_flags |= DCACHE_NFSFS_RENAMED;
fs/nfs/unlink.c-195- devname_garbage = dentry->d_fsdata;
--
fs/nfs/unlink.c=225=nfs_complete_unlink(struct dentry *dentry, struct inode *inode)
--
fs/nfs/unlink.c-229- spin_lock(&dentry->d_lock);
fs/nfs/unlink.c:230: dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
fs/nfs/unlink.c-231- data = dentry->d_fsdata;
--
fs/nfs/unlink.c=243=nfs_cancel_async_unlink(struct dentry *dentry)
--
fs/nfs/unlink.c-248-
fs/nfs/unlink.c:249: dentry->d_flags &= ~DCACHE_NFSFS_RENAMED;
fs/nfs/unlink.c-250- dentry->d_fsdata = NULL;
--
fs/notify/fsnotify.c=103=static void fsnotify_clear_child_dentry_flag(struct inode *pinode,
--
fs/notify/fsnotify.c-112- if (!fsnotify_inode_watches_children(pinode))
fs/notify/fsnotify.c:113: dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
fs/notify/fsnotify.c-114- spin_unlock(&dentry->d_lock);
--
fs/overlayfs/util.c=166=void ovl_dentry_update_reval(struct dentry *dentry, struct dentry *realdentry)
--
fs/overlayfs/util.c-171- spin_lock(&dentry->d_lock);
fs/overlayfs/util.c:172: dentry->d_flags |= realdentry->d_flags & OVL_D_REVALIDATE;
fs/overlayfs/util.c-173- spin_unlock(&dentry->d_lock);
--
fs/overlayfs/util.c=182=void ovl_dentry_init_flags(struct dentry *dentry, struct dentry *upperdentry,
--
fs/overlayfs/util.c-193- spin_lock(&dentry->d_lock);
fs/overlayfs/util.c:194: dentry->d_flags &= ~mask;
fs/overlayfs/util.c:195: dentry->d_flags |= flags & mask;
fs/overlayfs/util.c-196- spin_unlock(&dentry->d_lock);
--
include/linux/dcache.h=379=static inline void dont_mount(struct dentry *dentry)
--
include/linux/dcache.h-381- spin_lock(&dentry->d_lock);
include/linux/dcache.h:382: dentry->d_flags |= DCACHE_CANT_MOUNT;
include/linux/dcache.h-383- spin_unlock(&dentry->d_lock);
--
include/linux/fscrypt.h=268=static inline void fscrypt_handle_d_move(struct dentry *dentry)
--
include/linux/fscrypt.h-274- if (dentry->d_flags & DCACHE_NOKEY_NAME) {
include/linux/fscrypt.h:275: dentry->d_flags &= ~DCACHE_NOKEY_NAME;
include/linux/fscrypt.h-276-
--
include/linux/fscrypt.h-281- if (dentry->d_op->d_revalidate == fscrypt_d_revalidate)
include/linux/fscrypt.h:282: dentry->d_flags &= ~DCACHE_OP_REVALIDATE;
include/linux/fscrypt.h-283- }
--
include/linux/fscrypt.h=315=static inline void fscrypt_prepare_dentry(struct dentry *dentry,
--
include/linux/fscrypt.h-327- spin_lock(&dentry->d_lock);
include/linux/fscrypt.h:328: dentry->d_flags |= DCACHE_NOKEY_NAME;
include/linux/fscrypt.h-329- spin_unlock(&dentry->d_lock);
--
include/linux/fscrypt.h-338- spin_lock(&dentry->d_lock);
include/linux/fscrypt.h:339: dentry->d_flags &= ~DCACHE_OP_REVALIDATE;
include/linux/fscrypt.h-340- spin_unlock(&dentry->d_lock);
--
include/linux/fsnotify_backend.h=690=static inline void fsnotify_update_flags(struct dentry *dentry)
--
include/linux/fsnotify_backend.h-701- if (fsnotify_inode_watches_children(dentry->d_parent->d_inode))
include/linux/fsnotify_backend.h:702: dentry->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;
include/linux/fsnotify_backend.h-703- else
include/linux/fsnotify_backend.h:704: dentry->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;
include/linux/fsnotify_backend.h-705-}
]
|
| 35/2 |
2026/03/04 03:52 |
llm |
expert |
0m
Model:
Tokens:
input: 48991
output: 19
thoughts: 2578
Thoughts: **Dentry Flag Data Race Analysis**
Alright, let's break down this `d_flags` data race issue. The core concern revolves around modifications to `d_flags` primarily happening under `d_lock`, with exceptions during initialization (`__d_alloc`) and potentially some filesystem-specific code. We see `d_shrink_del` holding `d_lock` while manipulating `d_flags`.
The critical observation here is that readers like `d_same_name` access `parent->d_flags` without holding the lock. This seems permissible because `DCACHE_OP_COMPARE` is, in principle, stable. However, if `DCACHE_OP_COMPARE` were to change dynamically, it'd become a problem. Fortunately, it's generally only modified during initialization via `d_set_d_op`. The `fscrypt` modification of `DCACHE_OP_REVALIDATE` is a red herring; we're concerned about `DCACHE_OP_COMPARE`. We need to verify that nothing else modifies `DCACHE_OP_COMPARE` on a live dentry, and a grep through the output doesn't show any such instance.
So, the race is specifically on `d_flags`, but the bit being read (`DCACHE_OP_COMPARE`) is stable. That makes it a benign race. Of course, `d_shrink_del` also clears `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST`, but the reader is checking `DCACHE_OP_COMPARE`, not these bits.
Let's examine the value change. `0x00140444` to `0x00100044`. The reader checks `!(parent->d_flags & DCACHE_OP_COMPARE)`. The value of `DCACHE_OP_COMPARE` is `0x2`. In both values, `0x2` is *not* set. Thus the check is true in both cases. The reader proceeds to `dentry_cmp`. If the reader had seen `0x2` set, it would've called `parent->d_op->d_compare`. Since `0x2` is *not* being modified, a torn read is irrelevant unless the entire word is corrupted (which doesn't happen with aligned 32-bit writes on x86). Even if the value were torn, `parent->d_op->d_compare` would probably still work if `d_op` is still valid.
Therefore, the race on `d_flags` appears benign. This type of pattern, where flags are read without a lock for performance, is common in the dcache, especially in RCU paths. I should check if there are other similar reports or if this has been discussed before. KCSAN often marks dcache reports as benign.
Let's verify that `d_flags` is indeed a 32-bit unsigned integer, `unsigned int d_flags;`. Good. Also, `dentry->d_flags &= ~(DCACHE_SHRINK_LIST | DCACHE_LRU_LIST);` only affects bits 10 and 18. The compiler should use a 32-bit store for a 32-bit variable.
Okay, let's trace through the scenario with `d_shrink_del`. `d_shrink_del` is called from `shrink_dentry_list`, and the `dentry` there is the one being shrunk. From `__d_lookup`, the `parent` is the dentry being shrunk. In `__d_lookup`, `spin_lock(&dentry->d_lock)` is called. The race is on `parent->d_flags`. `shrink_dentry_list` is shrinking `parent`. If `parent` is being shrunk, its reference count must be 0, but `__d_lookup` requires a reference to `parent`. So the caller must have a reference, and `parent` shouldn't be in the shrink list. This, however, is removed from LRU and added to the 'dispose' list for shrinking. `__d_lookup` has been called.
So, here's the scenario: 1. `parent`'s refcount drops to 0, added to LRU. 2. `shrink_dcache_sb` starts, moves `parent` from LRU to the `dispose` list, and `parent->d_flags |= DCACHE_SHRINK_LIST`. 3. A lookup starts and finds `parent`, increasing the refcount. 4. That lookup calls `__d_lookup(parent, name)`. 5. `__d_lookup` calls `d_
|
| 36/2 |
2026/03/04 03:52 |
tool |
set-results |
0m
Args: map[Benign:true Confident:true]
Results: map[Benign:true Confident:true]
|
| 37/2 |
2026/03/04 03:52 |
llm |
expert |
0m
Model:
Tokens:
input: 52630
output: 769
thoughts: 262
Thoughts: **My Assessment of the Data Race**
Okay, I've finished digging into this data race, and I've come to a conclusion: it's benign. The root cause lies in how the `d_shrink_del` function interacts with the `dentry->d_flags` field, specifically when working within the `dentry->d_lock` context. The issue arises because `d_same_name` reads the `parent->d_flags` field without holding its associated lock. In this particular scenario, the `parent` is the very dentry being processed by `shrink_dentry_list`.
What's happening? `d_shrink_del` is clearing `DCACHE_SHRINK_LIST` and `DCACHE_LRU_LIST` bits when it modifies `dentry->d_flags`. Meanwhile, the `d_same_name` reader is only concerned with the `DCACHE_OP_COMPARE` bit.
Here's the key: `DCACHE_OP_COMPARE` is stable for as long as a dentry is alive. It isn't modified by `d_shrink_del`. Thus, even though there's a race, the read in `d_same_name` will always see a consistent, valid state for that bit β because it's essentially immutable once set. The worst-case scenario might be an outdated value, but not an inconsistent one.
On top of that, we have to consider hardware. The access is to an aligned 32-bit integer, and that operation is atomic at the hardware level on x86_64, which is the architecture in question. Therefore, even with a race, there's no way this is going to lead to memory corruption or a crash.
Essentially, while technically a race condition exists, itβs not exploitable. Itβs harmless. We can close this out.
|