* [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
From: 4fqr @ 2026-04-03 11:29 UTC (permalink / raw)
To: linux-ext4@vger.kernel.org
[-- Attachment #1.1: Type: text/plain, Size: 1349 bytes --]
linux-ext4@vger.kernel.org,
I'm disclosing three security vulnerabilities in e2fsprogs v1.47.4 affecting orphan file inode processing and extent tree validation. This follows responsible disclosure notification to the maintainer (Theodore Ts'o).
**Vulnerability Overview:**
F1 (CRITICAL): process_orphan_block() lacks inode range validation before calling release_orphan_inode(), allowing arbitrary inode destruction via crafted orphan file blocks.
F2 (HIGH): pass1 dispatch chain aliases s_orphan_file_inum with reserved inodes (5–10), bypassing reserved inode guards and enabling mode corruption on critical system inodes like the resize inode.
F3 (MEDIUM): ext2fs_extent_fix_parents() contains an unsigned underflow where blk64_t subtraction truncates to __u32, corrupting parent extent length metadata.
**Technical Details:**
All three are exploitable via crafted .img files processed by e2fsck -y with no special privileges. Detailed technical report with code locations, attack scenarios, and exact fixes is attached.
**Timeline:**
- Primary maintainer contact: Theodore Ts'o (tytso@mit.edu)
- 90-day embargo from maintainer acknowledgment
- Kernel security team notified concurrently
Patches and coordination discussion will follow once the maintainer has reviewed.
Thanks,
4fqr
4fqr@proton.me
Attachment: e2fsprogs_audit_4fqr.txt
[-- Attachment #1.2: Type: text/html, Size: 2449 bytes --]
[-- Attachment #2: e2fsprogs_audit_4fqr.txt --]
[-- Type: text/plain, Size: 30030 bytes --]
================================================================================
e2fsprogs â SECURITY AUDIT REPORT
Target: v1.47.4 | Date: 2026-04-03
================================================================================
Auditor : 4fqr
Scope : Full source tree â emphasis on e2fsck, libext2fs, orphan handling,
extent tree code, and any path reachable by a malicious disk image.
Method : Static analysis + manual code review. No fuzzing performed.
Threat : Attacker supplies a crafted ext4 filesystem image.
Victim runs e2fsck -y on it (USB attach, cloud disk, VM image).
================================================================================
EXECUTIVE SUMMARY
================================================================================
Three confirmed vulnerabilities were found. Two are directly triggerable by
a crafted filesystem image processed by e2fsck. One creates a destructive
chain attack when combined with the other.
The root cause in each case is the same class of mistake: on-disk values are
trusted as valid indices or inode numbers without the same range-checks that
are applied in older, parallel code paths.
None of these require kernel exploitation, memory corruption primitives, or
special privileges. A crafted .img file + "e2fsck -y image.img" is enough.
Severity summary:
ââââââ¬ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¬âââââââââââ
â # â Title â Severity â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F1 â Orphan file blocks: no inode range check before â CRITICAL â
â â release_orphan_inode() â â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F2 â s_orphan_file_inum aliases reserved inodes in pass1 â HIGH â
â â dispatch; triggers destructive chain via resize inode â â
ââââââ¼ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¼âââââââââââ¤
â F3 â ext2fs_extent_fix_parents(): __u32 += blk64_t â MEDIUM â
â â unsigned underflow corrupts parent extent length â â
ââââââ´ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ´âââââââââââ
================================================================================
BACKGROUND â HOW ORPHAN PROCESSING WORKS
================================================================================
When ext4 needs to track inodes that must be cleaned up on next mount/fsck
(e.g. unlinked files still open), it uses one of two mechanisms:
LEGACY â a singly-linked list threaded through s_last_orphan â
inode.i_dtime â inode.i_dtime â ... â 0
NEW â an "orphan file": a dedicated hidden inode (s_orphan_file_inum)
whose data blocks each contain an array of u32 inode numbers.
Indicated by feature flags orphan_file + orphan_present.
e2fsck processes both at startup in release_orphan_inodes() (super.c:509),
before Pass 1 runs â meaning before any inode bitmap, block bitmap, or inode
table has been validated against the actual filesystem state.
The function that does the actual work for both paths is:
release_orphan_inode(ctx, &ino, block_buf) [super.c:317]
It reads the inode, frees its blocks, and â if i_links_count == 0 â marks
the inode itself as free in the inode allocation bitmap.
================================================================================
FINDING F1 â CRITICAL
Missing inode range validation in process_orphan_block()
================================================================================
File : e2fsck/super.c
Lines : 420 â 427 (vulnerable path)
548 â 552 (the guard that exists for the legacy path, but NOT here)
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:420
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
bdata = (__u32 *)pd->buf;
for (j = 0; j < inodes_per_ob; j++) {
if (!bdata[j])
continue;
ino = ext2fs_le32_to_cpu(bdata[j]); /* raw disk value */
if (release_orphan_inode(ctx, &ino, pd->block_buf)) /* NO CHECK */
goto return_abort;
bdata[j] = 0;
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE GUARD THAT PROTECTS THE LEGACY PATH (but is absent here) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:548
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
/* Traditional orphan list: head inode is validated */
if (ino && ((ino < EXT2_FIRST_INODE(fs->super)) ||
(ino > fs->super->s_inodes_count))) {
fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_HEAD_INODE, &pctx);
goto err_qctx;
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â WHAT release_orphan_inode() DOES WITHOUT A VALID INODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
super.c:317
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
static int release_orphan_inode(e2fsck_t ctx, ext2_ino_t *ino, ...)
{
e2fsck_read_inode_full(ctx, *ino, ...); /* reads inode from disk */
next_ino = inode.i_dtime;
if (next_ino &&
((next_ino < EXT2_FIRST_INODE(fs->super)) || ...)) /* NEXT is
{ return 1; } checked,
not *ino */
if (release_inode_blocks(ctx, *ino, &inode, ...)) /* frees */
return 1; /* blocks */
if (!inode.i_links_count) {
ext2fs_inode_alloc_stats2(fs, *ino, -1, ...); /* marks inode */
ctx->free_inodes++; /* as FREE in */
ext2fs_set_dtime(fs, ...); /* the bitmap */
}
e2fsck_write_inode_full(ctx, *ino, ...); /* writes back */
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Note carefully: inside release_orphan_inode, the range check at lines
334â339 validates NEXT (i_dtime chain), not *ino itself. The current
inode is never validated.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â ATTACK SCENARIO â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Craft an ext4 image with:
1. Feature flags: orphan_file + orphan_present set in the superblock.
Do NOT set metadata_csum â the block checksum is then skipped.
(If metadata_csum is desired, set s_checksum_seed to any known value
and compute the matching CRC32c â the attacker controls the seed.)
2. A valid inode for s_orphan_file_inum (any regular non-reserved inode).
Its data block(s) are attacker-controlled.
3. The data block(s) filled with target inode numbers as little-endian
u32 values. Useful targets with i_links_count == 0:
- inode 7 (EXT2_RESIZE_INO) â journal/block bitmaps freed
- inode 9 (exclude bitmap inode)
- inode 10 (journal backup inode)
Useful targets with i_links_count > 0 (blocks released, inode kept):
- Any inode whose blocks you want freed (data destruction)
4. Do NOT set EXT2_ERROR_FS in s_state â that flag causes
release_orphan_inodes() to short-circuit before reaching this code.
Result: e2fsck -y reads the image, enters release_orphan_inodes(), calls
process_orphan_file(), reaches process_orphan_block(), and invokes
release_inode_blocks() + ext2fs_inode_alloc_stats2() on each target inode
â all before Pass 1 has validated a single bitmap.
Impact: targeted inodes have their blocks freed and are marked available
for reuse. The filesystem is silently corrupted. With EXT2_RESIZE_INO as
a target, the block group descriptor tables are freed.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Add the same guard that the legacy path has, immediately before the call
to release_orphan_inode() in process_orphan_block():
ino = ext2fs_le32_to_cpu(bdata[j]);
+ if (ino < EXT2_FIRST_INODE(fs->super) ||
+ ino > fs->super->s_inodes_count) {
+ fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx);
+ goto return_abort;
+ }
if (release_orphan_inode(ctx, &ino, pd->block_buf))
================================================================================
FINDING F2 â HIGH
s_orphan_file_inum aliases reserved inodes in pass1 dispatch chain
================================================================================
File : e2fsck/pass1.c
Lines : 1725 â 1879 (the dispatch chain)
1853 â 1866 (the orphan-file branch â fires too early)
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE DISPATCH CHAIN IN PASS 1 â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
pass1.c evaluates the following else-if ladder for every inode:
1725: if (ino == EXT2_BAD_INO) â inode 1 â protected
1773: elif (ino == EXT2_ROOT_INO) â inode 2 â protected
1800: elif (ino == EXT2_JOURNAL_INO) â inode 8 â protected
1826: elif (quota_inum_is_reserved(fs, ino)) â inodes 3, 4 â protected
1853: elif (ino == s_orphan_file_inum) â ORPHAN FILE BRANCH
1879: elif (ino < EXT2_FIRST_INODE(fs->super))â catches 5,6,7,9,10
If s_orphan_file_inum is set to 5, 6, 7, 9, or 10 in the superblock,
the orphan-file branch at line 1853 fires BEFORE the generic reserved-inode
guard at line 1879. Those reserved inodes are never properly handled.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
pass1.c:1853
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
} else if (ino == fs->super->s_orphan_file_inum) {
ext2fs_mark_inode_bitmap2(ctx->inode_used_map, ino);
if (ext2fs_has_feature_orphan_file(fs->super)) {
if (!LINUX_S_ISREG(inode->i_mode) && /* mode check */
fix_problem(ctx, PR_1_ORPHAN_FILE_BAD_MODE, &pctx)) {
inode->i_mode = LINUX_S_IFREG; /* WRITES BACK */
e2fsck_write_inode(ctx, ino, inode, "pass1");
}
check_blocks(ctx, &pctx, block_buf, NULL);
FINISH_INODE_LOOP(ctx, ino, &pctx, failed_csum);
continue; /* skips all reserved-inode validation */
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
With -y, fix_problem() returns 1 unconditionally. For any reserved inode
that is not a regular file (resize inode has S_IFREG, others have mode=0),
e2fsck would write LINUX_S_IFREG into that inode's i_mode field on disk.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â THE DESTRUCTIVE CHAIN: s_orphan_file_inum = 7 (EXT2_RESIZE_INO) â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
The resize inode (inode 7) is used for online filesystem expansion. Its
data blocks are the per-group BLOCK BITMAP IMAGES â one block per group,
containing bit-for-bit maps of allocated blocks.
When process_orphan_file() runs on inode 7 (in release_orphan_inodes,
before Pass 1), it iterates every data block of inode 7 and calls
process_orphan_block() on each:
/* process_orphan_block reads the block as an array of u32 inode#s */
bdata = (__u32 *)pd->buf; /* resize inode block data */
for (j = 0; j < inodes_per_ob; j++) {
ino = ext2fs_le32_to_cpu(bdata[j]); /* bitmap words as inode#s */
release_orphan_inode(ctx, &ino, ...);
}
A per-group block bitmap for a dense filesystem is full of 0xFFFFFFFF
words â inode# 4294967295 > s_inodes_count, harmless. But for a filesystem
that is moderately allocated, the bitmaps contain words like 0x0000003F or
0x000003FF â small inode numbers that ARE valid inodes. Those inodes get
their blocks released and are marked free.
On a semi-sparse crafted filesystem, the attacker can arrange exactly which
words appear in the resize inode's blocks by controlling block allocation
density, giving precise control over which inodes get wiped.
This chain requires BOTH F1 and F2:
F2 â process_orphan_file() is called on the resize inode's blocks
F1 â each word in those blocks passes unchecked into release_orphan_inode
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Validate s_orphan_file_inum at superblock-check time and in the pass1
dispatch chain. Two changes:
(a) In e2fsck/super.c, when opening the orphan file, guard the inode number:
orphan_inum = fs->super->s_orphan_file_inum;
+ if (orphan_inum < EXT2_FIRST_INODE(fs->super) ||
+ orphan_inum > fs->super->s_inodes_count) {
+ /* fix_problem / clear orphan_file feature */
+ }
(b) In e2fsck/pass1.c, move the orphan-file else-if AFTER the
ino < EXT2_FIRST_INODE guard (line 1879), or add an explicit
lower-bound check:
} else if (ino == fs->super->s_orphan_file_inum
+ && ino >= EXT2_FIRST_INODE(fs->super)) {
================================================================================
FINDING F3 â MEDIUM
ext2fs_extent_fix_parents(): __u32 += blk64_t unsigned underflow
================================================================================
File : lib/ext2fs/extent.c
Line : 816
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â VULNERABLE CODE â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
extent.c:800
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
/* modified node's start block */
start = extent.e_lblk; /* blk64_t: leaf's logical block */
...
while (handle->level > 0 &&
(path->left == path->entries - 1)) {
ext2fs_extent_get(handle, EXT2_EXTENT_UP, &extent); /* go to parent*/
if (extent.e_lblk == start)
break;
path = handle->path + handle->level;
extent.e_len += (extent.e_lblk - start); /* LINE 816 */
extent.e_lblk = start;
ext2fs_extent_replace(handle, 0, &extent);
}
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Type breakdown:
extent.e_len is __u32 (32-bit unsigned)
extent.e_lblk is blk64_t (64-bit unsigned)
start is blk64_t (64-bit unsigned)
The function is designed for the case where a leaf's start block was moved
EARLIER (smaller) â the parent index entry must extend to cover it:
extent.e_lblk > start â subtraction positive â e_len grows. Correct.
But if a crafted extent tree has a parent index entry whose e_lblk is
LESS than the current leaf's e_lblk (a B-tree invariant violation that is
not rejected on read), then:
extent.e_lblk < start
â (extent.e_lblk - start) as blk64_t wraps to ~0ULL - delta + 1
â += on __u32 truncates that to a small garbage value
â extent_replace() writes the corrupted length back to disk
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â REACHABILITY â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ext2fs_extent_fix_parents() is called by:
- ext2fs_extent_insert() used throughout e2fsck and libext2fs
- ext2fs_extent_set_bmap() used during extent tree rebuild
- rewrite_extent_replay() e2fsck/extents.c â called in Pass 1E
on every inode flagged for rebuild
A crafted inode with an extent tree where a leaf's e_lblk < its parent
index's e_lblk will reach this path during Pass 1E. The attacker does not
need to corrupt the leaf-writing path â the malformed parent-child
relationship in the on-disk image is sufficient.
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â FIX â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
Guard against the underflow with an explicit check before the arithmetic:
if (extent.e_lblk == start)
break;
path = handle->path + handle->level;
+ if (extent.e_lblk < start) {
+ /* parent starts after child â malformed tree; bail */
+ retval = EXT2_ET_EXTENT_INVALID_LENGTH;
+ goto done;
+ }
extent.e_len += (extent.e_lblk - start);
================================================================================
FINDINGS REVIEW
================================================================================
The preliminary scan raised several additional issues. After careful review
of the actual source, here is the verdict on each:
ââââââââââââââââââââââââââââââââââââ¬âââââââââââââââââââââââââââââââââââââââ
â Claim â Verdict â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extents.c:96 â blk64_t overflow â NOT A BUG. Both __u32 values are â
â in extent merge accumulation â promoted to blk64_t before addition. â
â â The (1ULL<<32) check is correct. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â icount.c:220 â sprintf overflow â NOT A BUG. Allocation at line 216 â
â with long tdb_dir path â is strlen(tdb_dir)+64; format string â
â â produces at most +53 bytes. Safe. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â dir_iterate.c â infinite loop â NOT EXPLOITABLE. rec_len < 8 check â
â on rec_len = 0 or 1 â at line 84 returns 0 immediately. â
â â Zero-length entries abort cleanly. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extents.c:241 â loop re-entry â BENIGN by design. The ex--; i-- â
â use-after-free in UNINIT split â pattern re-processes the same array â
â â slot (e_len reduced each pass). The â
â â e_len==0 guard at line 232 exits. â
ââââââââââââââââââââââââââââââââââââ¼âââââââââââââââââââââââââââââââââââââââ¤
â extent.c:1607 â save_length â NEEDS FUZZING. Context unclear from â
â minus underflow in split_node â static analysis alone. Recommend â
â â targeted AFL++ run on this function. â
ââââââââââââââââââââââââââââââââââââ´âââââââââââââââââââââââââââââââââââââââ
================================================================================
ATTACK SURFACE NOTES
================================================================================
WHY THIS MATTERS MORE THAN TYPICAL PARSER BUGS
e2fsck is routinely run automatically:
- systemd-fsck triggers it on dirty ext4 partitions at boot
- Desktop OS automounters call it on USB drives
- Cloud providers run it during disk attach / snapshot restore
- CI pipelines often call "e2fsck -y" to clean up test images
In all of these contexts, the filesystem image is the attack input and
e2fsck runs as root. Filesystem-level bugs here can silently destroy
data, corrupt kernel metadata, or (with further chaining) achieve
privilege escalation via inode bitmap manipulation.
THE TIMING OF ORPHAN PROCESSING IS CRITICAL
release_orphan_inodes() is called at the very start of e2fsck, from
check_super_block() â before Pass 1, before bitmaps are validated,
before any consistency has been established. The attacker's inode
destructions happen against an unvalidated filesystem state.
CHECKSUMS DO NOT PROTECT YOU HERE
Finding F1 is exploitable both with and without metadata_csum:
- Without: checksum check is skipped entirely.
- With: attacker sets s_checksum_seed to any value, computes
CRC32c(seed || ino || gen || blk || buf) correctly.
The seed lives in the same superblock the attacker crafts.
Checksums here protect against accidental corruption, not adversarial input.
================================================================================
QUICK REFERENCE â EXACT DIFF LOCATIONS
================================================================================
F1 e2fsck/super.c line 424 add range check before
release_orphan_inode() call
F2a e2fsck/super.c ~line 446 validate s_orphan_file_inum
>= EXT2_FIRST_INODE on entry
to process_orphan_file()
F2b e2fsck/pass1.c line 1853 add lower-bound guard to the
s_orphan_file_inum elif branch
F3 lib/ext2fs/extent.c line 816 guard against extent.e_lblk < start
before the += arithmetic
================================================================================
END OF REPORT
================================================================================
^ permalink raw reply
* [PATCH 3/3] ext4: show orphan file inode detail info
From: Ye Bin @ 2026-04-03 8:25 UTC (permalink / raw)
To: tytso, adilger.kernel, linux-ext4; +Cc: jack
In-Reply-To: <20260403082507.1882703-1-yebin@huaweicloud.com>
From: Ye Bin <yebin10@huawei.com>
Support show inode information in orphan file.
Signed-off-by: Ye Bin <yebin10@huawei.com>
---
fs/ext4/orphan.c | 115 ++++++++++++++++++++++++++++++++++++++---------
1 file changed, 94 insertions(+), 21 deletions(-)
diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index 272de32d1a47..c01887fb496b 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -663,6 +663,11 @@ int ext4_orphan_file_empty(struct super_block *sb)
struct ext4_proc_orphan {
struct ext4_inode_info cursor;
bool print_title;
+ struct ext4_orphan_info *oi;
+ int inodes_per_ob;
+ int block_idx;
+ int offset;
+ bool orphan_file;
};
static inline bool ext4_is_cursor(struct ext4_inode_info *inode)
@@ -683,24 +688,65 @@ static struct inode *ext4_list_next(struct list_head *head, struct list_head *p)
return NULL;
}
+static struct inode *ext4_orphan_file_next(struct ext4_proc_orphan *s,
+ struct super_block *sb)
+{
+ struct inode *inode;
+ struct ext4_orphan_info *oi = s->oi;
+
+ for (; s->block_idx < oi->of_blocks; s->block_idx++) {
+ __le32 *bdata = (__le32 *)(oi->of_binfo[s->block_idx].ob_bh->b_data);
+
+ if (atomic_read(&oi->of_binfo[s->block_idx].ob_free_entries) ==
+ s->inodes_per_ob) {
+ s->offset = 0;
+ continue;
+ }
+ for (; s->offset < s->inodes_per_ob; s->offset++) {
+ if (!bdata[s->offset])
+ continue;
+ inode = ext4_iget(sb, le32_to_cpu(bdata[s->offset]),
+ EXT4_IGET_NORMAL);
+ if (IS_ERR(inode))
+ continue;
+ s->offset++;
+ return inode;
+ }
+ s->offset = 0;
+ }
+
+ return NULL;
+}
+
static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
{
struct ext4_proc_orphan *s = seq->private;
struct super_block *sb = pde_data(file_inode(seq->file));
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct list_head *prev;
+ void *ret;
- mutex_lock(&sbi->s_orphan_lock);
+ if (!s->orphan_file) {
+ mutex_lock(&sbi->s_orphan_lock);
+ if (!*pos)
+ prev = &sbi->s_orphan;
+ else
+ prev = &s->cursor.i_orphan;
- if (!*pos) {
- prev = &sbi->s_orphan;
- } else {
- prev = &s->cursor.i_orphan;
- if (list_empty(prev))
+ if (!list_empty(prev)) {
+ ret = ext4_list_next(&sbi->s_orphan, prev);
+ if (ret)
+ return ret;
+ }
+
+ if (!s->oi)
return NULL;
+ list_del_init(&s->cursor.i_orphan);
+ mutex_unlock(&sbi->s_orphan_lock);
+ s->orphan_file = true;
}
- return ext4_list_next(&sbi->s_orphan, prev);
+ return ext4_orphan_file_next(s, sb);
}
static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
@@ -708,10 +754,26 @@ static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
struct super_block *sb = pde_data(file_inode(seq->file));
struct ext4_sb_info *sbi = EXT4_SB(sb);
struct inode *inode = v;
+ struct ext4_proc_orphan *s = seq->private;
+ void *ret;
++*pos;
- return ext4_list_next(&sbi->s_orphan, &EXT4_I(inode)->i_orphan);
+ if (!s->orphan_file) {
+ ret = ext4_list_next(&sbi->s_orphan, &EXT4_I(inode)->i_orphan);
+ if (ret)
+ return ret;
+ if (!s->oi)
+ return NULL;
+ list_del_init(&s->cursor.i_orphan);
+ mutex_unlock(&sbi->s_orphan_lock);
+ s->orphan_file = true;
+ v = NULL;
+ }
+
+ iput(v);
+
+ return ext4_orphan_file_next(s, sb);
}
static void ext4_show_filename(struct seq_file *seq, struct inode *inode)
@@ -758,12 +820,16 @@ static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
struct inode *inode = v;
struct ext4_proc_orphan *s = seq->private;
- if (inode)
- list_move_tail(&s->cursor.i_orphan, &EXT4_I(inode)->i_orphan);
- else
- list_del_init(&s->cursor.i_orphan);
+ if (!s->orphan_file) {
+ if (inode)
+ list_move_tail(&s->cursor.i_orphan, &EXT4_I(inode)->i_orphan);
+ else
+ list_del_init(&s->cursor.i_orphan);
- mutex_unlock(&sbi->s_orphan_lock);
+ mutex_unlock(&sbi->s_orphan_lock);
+ } else {
+ iput(v);
+ }
}
const struct seq_operations ext4_orphan_seq_ops = {
@@ -777,16 +843,21 @@ static int ext4_seq_orphan_open(struct inode *inode, struct file *file)
{
int rc;
struct seq_file *m;
- struct ext4_proc_orphan *private;
+ struct ext4_proc_orphan *s;
rc = seq_open_private(file, &ext4_orphan_seq_ops,
sizeof(struct ext4_proc_orphan));
if (!rc) {
+ struct super_block *sb = pde_data(file_inode(file));
m = file->private_data;
- private = m->private;
- INIT_LIST_HEAD(&private->cursor.i_orphan);
- private->cursor.vfs_inode.i_ino = 0;
- private->print_title = true;
+ s = m->private;
+ INIT_LIST_HEAD(&s->cursor.i_orphan);
+ s->cursor.vfs_inode.i_ino = 0;
+ s->print_title = true;
+ if (ext4_has_feature_orphan_file(sb)) {
+ s->oi = &EXT4_SB(sb)->s_orphan_info;
+ s->inodes_per_ob = ext4_inodes_per_orphan_block(sb);
+ }
}
return rc;
@@ -798,9 +869,11 @@ static int ext4_seq_orphan_release(struct inode *inode, struct file *file)
struct ext4_proc_orphan *s = seq->private;
struct ext4_sb_info *sbi = EXT4_SB(pde_data(inode));
- mutex_lock(&sbi->s_orphan_lock);
- list_del(&s->cursor.i_orphan);
- mutex_unlock(&sbi->s_orphan_lock);
+ if (!s->orphan_file) {
+ mutex_lock(&sbi->s_orphan_lock);
+ list_del(&s->cursor.i_orphan);
+ mutex_unlock(&sbi->s_orphan_lock);
+ }
return seq_release_private(inode, file);
}
--
2.34.1
^ permalink raw reply related
* [PATCH 1/3] ext4: register 'orphan_list' procfs
From: Ye Bin @ 2026-04-03 8:25 UTC (permalink / raw)
To: tytso, adilger.kernel, linux-ext4; +Cc: jack
In-Reply-To: <20260403082507.1882703-1-yebin@huaweicloud.com>
From: Ye Bin <yebin10@huawei.com>
This patch register '/proc/fs/ext4/XXX/orphan_list' procfs for show inode
orphan list about EXT4 file system.
In actual production environments, there may be inconsistencies in df/du,
sometimes due to kernel occupation, making it difficult to find such files,
and it is also difficult to operate in the current network environment. So
add "orphan_list" procfs to quickly query files that have been deleted but
are occupied.
Signed-off-by: Ye Bin <yebin10@huawei.com>
---
fs/ext4/ext4.h | 1 +
fs/ext4/orphan.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++
fs/ext4/sysfs.c | 2 ++
3 files changed, 73 insertions(+)
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 876597f8331d..fa38a8380fed 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3873,6 +3873,7 @@ extern void ext4_stop_mmpd(struct ext4_sb_info *sbi);
extern const struct fsverity_operations ext4_verityops;
/* orphan.c */
+extern const struct proc_ops ext4_orphan_proc_ops;
extern int ext4_orphan_add(handle_t *, struct inode *);
extern int ext4_orphan_del(handle_t *, struct inode *);
extern void ext4_orphan_cleanup(struct super_block *sb,
diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index 64ea47624233..1d231aeaf282 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -4,6 +4,8 @@
#include <linux/fs.h>
#include <linux/quotaops.h>
#include <linux/buffer_head.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
#include "ext4.h"
#include "ext4_jbd2.h"
@@ -657,3 +659,71 @@ int ext4_orphan_file_empty(struct super_block *sb)
return 0;
return 1;
}
+
+struct ext4_proc_orphan {
+ struct ext4_inode_info cursor;
+};
+
+static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ return NULL;
+}
+
+static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ return NULL;
+}
+
+static int ext4_orphan_seq_show(struct seq_file *seq, void *v)
+{
+ return 0;
+}
+
+static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+const struct seq_operations ext4_orphan_seq_ops = {
+ .start = ext4_orphan_seq_start,
+ .next = ext4_orphan_seq_next,
+ .stop = ext4_orphan_seq_stop,
+ .show = ext4_orphan_seq_show,
+};
+
+static int ext4_seq_orphan_open(struct inode *inode, struct file *file)
+{
+ int rc;
+ struct seq_file *m;
+ struct ext4_proc_orphan *private;
+
+ rc = seq_open_private(file, &ext4_orphan_seq_ops,
+ sizeof(struct ext4_proc_orphan));
+ if (!rc) {
+ m = file->private_data;
+ private = m->private;
+ INIT_LIST_HEAD(&private->cursor.i_orphan);
+ private->cursor.vfs_inode.i_ino = 0;
+ }
+
+ return rc;
+}
+
+static int ext4_seq_orphan_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = file->private_data;
+ struct ext4_proc_orphan *s = seq->private;
+ struct ext4_sb_info *sbi = EXT4_SB(pde_data(inode));
+
+ mutex_lock(&sbi->s_orphan_lock);
+ list_del(&s->cursor.i_orphan);
+ mutex_unlock(&sbi->s_orphan_lock);
+
+ return seq_release_private(inode, file);
+}
+
+const struct proc_ops ext4_orphan_proc_ops = {
+ .proc_open = ext4_seq_orphan_open,
+ .proc_read = seq_read,
+ .proc_lseek = seq_lseek,
+ .proc_release = ext4_seq_orphan_release,
+};
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index b87d7bdab06a..324eb6b5900e 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -634,6 +634,8 @@ int ext4_register_sysfs(struct super_block *sb)
ext4_seq_mb_stats_show, sb);
proc_create_seq_data("mb_structs_summary", 0444, sbi->s_proc,
&ext4_mb_seq_structs_summary_ops, sb);
+ proc_create_data("orphan_list", 0444, sbi->s_proc,
+ &ext4_orphan_proc_ops, sb);
}
return 0;
}
--
2.34.1
^ permalink raw reply related
* [PATCH 0/3] show orphan file inode detail info
From: Ye Bin @ 2026-04-03 8:25 UTC (permalink / raw)
To: tytso, adilger.kernel, linux-ext4; +Cc: jack
From: Ye Bin <yebin10@huawei.com>
In actual production environments, the issue of inconsistency between
df and du is frequently encountered. In many cases, the cause of the
problem can be identified through the use of lsof. However, when
overlayfs is combined with project quota configuration, the issue becomes
more complex and troublesome to diagnose. First, to determine the project
ID, one needs to obtain orphaned nodes using `fsck.ext4 -fn /dev/xx`, and
then retrieve file information through `debugfs`. However, the file names
cannot always be obtained, and it is often unclear which files they are.
To identify which files these are, one would need to use crash for online
debugging or use kprobe to gather information incrementally. However, some
customers in production environments do not agree to upload any tools, and
online debugging might impact the business. There are also scenarios where
files are opened in kernel mode, which do not generate file descriptors(fds),
making it impossible to identify which files were deleted but still have
references through lsof. This patchset adds a procfs interface to query
information about orphaned nodes, which can assist in the analysis and
localization of such issues.
Ye Bin (3):
ext4: register 'orphan_list' procfs
ext4: show inode orphan list detail information
ext4: show orphan file inode detail info
fs/ext4/ext4.h | 1 +
fs/ext4/orphan.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++
fs/ext4/sysfs.c | 2 +
3 files changed, 230 insertions(+)
--
2.34.1
^ permalink raw reply
* [PATCH 2/3] ext4: show inode orphan list detail information
From: Ye Bin @ 2026-04-03 8:25 UTC (permalink / raw)
To: tytso, adilger.kernel, linux-ext4; +Cc: jack
In-Reply-To: <20260403082507.1882703-1-yebin@huaweicloud.com>
From: Ye Bin <yebin10@huawei.com>
Some inodes added to the orphan list are due to truncation, while others
are due to deletion.Therefore, we printed the information of inode as
follows: inode number/i_nlink/i_size/i_blocks/projid/file path. By using
this information, it is possible to quickly identify files that have been
deleted but are still being referenced.
Signed-off-by: Ye Bin <yebin10@huawei.com>
---
fs/ext4/orphan.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 86 insertions(+), 2 deletions(-)
diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index 1d231aeaf282..272de32d1a47 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -662,25 +662,108 @@ int ext4_orphan_file_empty(struct super_block *sb)
struct ext4_proc_orphan {
struct ext4_inode_info cursor;
+ bool print_title;
};
-static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+static inline bool ext4_is_cursor(struct ext4_inode_info *inode)
+{
+ return (inode->vfs_inode.i_ino == 0);
+}
+
+static struct inode *ext4_list_next(struct list_head *head, struct list_head *p)
{
+ struct ext4_inode_info *inode;
+
+ list_for_each_continue(p, head) {
+ inode = list_entry(p, typeof(*inode), i_orphan);
+ if (!ext4_is_cursor(inode))
+ return &inode->vfs_inode;
+ }
+
return NULL;
}
+static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ struct ext4_proc_orphan *s = seq->private;
+ struct super_block *sb = pde_data(file_inode(seq->file));
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct list_head *prev;
+
+ mutex_lock(&sbi->s_orphan_lock);
+
+ if (!*pos) {
+ prev = &sbi->s_orphan;
+ } else {
+ prev = &s->cursor.i_orphan;
+ if (list_empty(prev))
+ return NULL;
+ }
+
+ return ext4_list_next(&sbi->s_orphan, prev);
+}
+
static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
- return NULL;
+ struct super_block *sb = pde_data(file_inode(seq->file));
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode = v;
+
+ ++*pos;
+
+ return ext4_list_next(&sbi->s_orphan, &EXT4_I(inode)->i_orphan);
+}
+
+static void ext4_show_filename(struct seq_file *seq, struct inode *inode)
+{
+ struct dentry *dentry;
+
+ dentry = d_find_alias(inode);
+ if (!dentry)
+ dentry = d_find_any_alias(inode);
+
+ if (dentry)
+ seq_dentry(seq, dentry, "\t\n\\");
+ else
+ seq_puts(seq, "unknown");
+
+ dput(dentry);
+ seq_putc(seq, '\n');
}
static int ext4_orphan_seq_show(struct seq_file *seq, void *v)
{
+ struct inode *inode = v;
+ struct ext4_proc_orphan *s = seq->private;
+
+ if (s->print_title) {
+ seq_puts(seq, "INO\tNLINK\tSIZE\tBLOCKS\tPROJID\tPATH\n");
+ s->print_title = false;
+ }
+
+ seq_printf(seq, "%llu\t%u\t%llu\t%llu\t%u\t",
+ inode->i_ino, inode->i_nlink,
+ i_size_read(inode), inode->i_blocks,
+ __kprojid_val(EXT4_I(inode)->i_projid));
+
+ ext4_show_filename(seq, inode);
+
return 0;
}
static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
{
+ struct super_block *sb = pde_data(file_inode(seq->file));
+ struct ext4_sb_info *sbi = EXT4_SB(sb);
+ struct inode *inode = v;
+ struct ext4_proc_orphan *s = seq->private;
+
+ if (inode)
+ list_move_tail(&s->cursor.i_orphan, &EXT4_I(inode)->i_orphan);
+ else
+ list_del_init(&s->cursor.i_orphan);
+
+ mutex_unlock(&sbi->s_orphan_lock);
}
const struct seq_operations ext4_orphan_seq_ops = {
@@ -703,6 +786,7 @@ static int ext4_seq_orphan_open(struct inode *inode, struct file *file)
private = m->private;
INIT_LIST_HEAD(&private->cursor.i_orphan);
private->cursor.vfs_inode.i_ino = 0;
+ private->print_title = true;
}
return rc;
--
2.34.1
^ permalink raw reply related
* (no subject)
From: liming wu @ 2026-04-03 6:48 UTC (permalink / raw)
To: linux-ext4
linux-ext4+unsubscribe@vger.kernel.org
^ permalink raw reply
* Re: [PATCH 3/3] fuse2fs: fix build failure on systems which don't define EUCLEAN
From: Darrick J. Wong @ 2026-04-03 4:18 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: Ext4 Developers List
In-Reply-To: <20260403040328.2385083-4-tytso@mit.edu>
On Fri, Apr 03, 2026 at 12:03:28AM -0400, Theodore Ts'o wrote:
> MacOS doesn't have EUCLEAN, so we use EIO as the closest error code.
> But then we need to avoid a compile error caused by a duplicate case
> labels of EUCLEAN and EIO.
>
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Looks good to me,
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> ---
> misc/fuse2fs.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
> index dfbc98636..94e289fab 100644
> --- a/misc/fuse2fs.c
> +++ b/misc/fuse2fs.c
> @@ -5870,7 +5870,9 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
> #ifdef EILSEQ
> case EILSEQ:
> #endif
> +#if EUCLEAN != EIO
> case EUCLEAN:
> +#endif
> /* these errnos usually denote corruption or persistence fail */
> is_err = 1;
> ret = -err;
> --
> 2.51.0
>
^ permalink raw reply
* Re: [PATCH 2/3] libsupport: add a portable get_thread_id() function
From: Darrick J. Wong @ 2026-04-03 4:17 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: Ext4 Developers List
In-Reply-To: <20260403040328.2385083-3-tytso@mit.edu>
On Fri, Apr 03, 2026 at 12:03:27AM -0400, Theodore Ts'o wrote:
> The gettid() system call is only available on Linux. So create a new
> function, get_thread_id() which implements a number of different ways
> of providing a thread id as an integer.
>
> Use get_thread_id() instead of gettid() in fuse2fs.
>
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
> ---
> configure | 12 ++++++++++++
> configure.ac | 2 ++
> lib/config.h.in | 6 ++++++
> lib/support/Makefile.in | 13 +++++++++++--
> lib/support/thread.c | 36 ++++++++++++++++++++++++++++++++++++
> lib/support/thread.h | 5 +++++
> misc/fuse2fs.c | 3 ++-
> 7 files changed, 74 insertions(+), 3 deletions(-)
> create mode 100644 lib/support/thread.c
> create mode 100644 lib/support/thread.h
>
> diff --git a/configure b/configure
> index b9a82dcec..b04b31aff 100755
> --- a/configure
> +++ b/configure
> @@ -13749,6 +13749,12 @@ if test "x$ac_cv_func_getrusage" = xyes
> then :
> printf "%s\n" "#define HAVE_GETRUSAGE 1" >>confdefs.h
>
> +fi
> +ac_fn_c_check_func "$LINENO" "gettid" "ac_cv_func_gettid"
> +if test "x$ac_cv_func_gettid" = xyes
> +then :
> + printf "%s\n" "#define HAVE_GETTID 1" >>confdefs.h
> +
> fi
> ac_fn_c_check_func "$LINENO" "jrand48" "ac_cv_func_jrand48"
> if test "x$ac_cv_func_jrand48" = xyes
> @@ -13893,6 +13899,12 @@ if test "x$ac_cv_func_pthread_setname_np" = xyes
> then :
> printf "%s\n" "#define HAVE_PTHREAD_SETNAME_NP 1" >>confdefs.h
>
> +fi
> +ac_fn_c_check_func "$LINENO" "pthread_threadid_np" "ac_cv_func_pthread_threadid_np"
> +if test "x$ac_cv_func_pthread_threadid_np" = xyes
> +then :
> + printf "%s\n" "#define HAVE_PTHREAD_THREADID_NP 1" >>confdefs.h
> +
> fi
> ac_fn_c_check_func "$LINENO" "qsort_r" "ac_cv_func_qsort_r"
> if test "x$ac_cv_func_qsort_r" = xyes
> diff --git a/configure.ac b/configure.ac
> index 2473879fd..4921f81f7 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1246,6 +1246,7 @@ AC_CHECK_FUNCS(m4_flatten([
> getrandom
> getrlimit
> getrusage
> + gettid
> jrand48
> keyctl
> llistxattr
> @@ -1270,6 +1271,7 @@ AC_CHECK_FUNCS(m4_flatten([
> pread64
> pwrite64
> pthread_setname_np
> + pthread_threadid_np
> qsort_r
> secure_getenv
> setmntent
> diff --git a/lib/config.h.in b/lib/config.h.in
> index c6cbced5f..f129abfe7 100644
> --- a/lib/config.h.in
> +++ b/lib/config.h.in
> @@ -181,6 +181,9 @@
> /* Define if the GNU gettext() function is already present or preinstalled. */
> #undef HAVE_GETTEXT
>
> +/* Define to 1 if you have the 'gettid' function. */
> +#undef HAVE_GETTID
> +
> /* Define to 1 if you have the GNU-style 'qsort_r' function. */
> #undef HAVE_GNU_QSORT_R
>
> @@ -331,6 +334,9 @@
> /* Define to 1 if you have the 'pthread_setname_np' function. */
> #undef HAVE_PTHREAD_SETNAME_NP
>
> +/* Define to 1 if you have the 'pthread_threadid_np' function. */
> +#undef HAVE_PTHREAD_THREADID_NP
> +
> /* Define to 1 if you have the 'pwrite' function. */
> #undef HAVE_PWRITE
>
> diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
> index 6383816fd..9aac9cf00 100644
> --- a/lib/support/Makefile.in
> +++ b/lib/support/Makefile.in
> @@ -25,6 +25,7 @@ OBJS= bthread.o \
> quotaio.o \
> quotaio_v2.o \
> quotaio_tree.o \
> + thread.o \
> dict.o \
> devname.o
>
> @@ -41,6 +42,7 @@ SRCS= $(srcdir)/argv_parse.c \
> $(srcdir)/quotaio.c \
> $(srcdir)/quotaio_tree.c \
> $(srcdir)/quotaio_v2.c \
> + $(srcdir)/thread.c \
> $(srcdir)/dict.c \
> $(srcdir)/devname.c
>
> @@ -81,10 +83,15 @@ test_cstring: $(srcdir)/cstring.c
> $(Q) $(CC) -o test_cstring -DDEBUG_PROGRAM $(srcdir)/cstring.c \
> $(ALL_CFLAGS)
>
> +test_thread: $(srcdir)/thread.c
> + $(E) " CC $@"
> + $(Q) $(CC) -o test_thread -DDEBUG_PROGRAM $(srcdir)/thread.c \
> + $(ALL_CFLAGS)
> +
> clean::
> $(RM) -f \#* *.s *.o *.a *~ *.bak core profiled/* \
> ../libsupport.a ../libsupport_p.a $(SMANPAGES) \
> - prof_err.c prof_err.h test_profile test_cstring
> + prof_err.c prof_err.h test_profile test_cstring test_thread
>
> #fullcheck check:: tst_uuid
> # LD_LIBRARY_PATH=$(LIB) DYLD_LIBRARY_PATH=$(LIB) ./tst_uuid
> @@ -111,7 +118,7 @@ $(OBJS):
> argv_parse.o: $(srcdir)/argv_parse.c $(top_builddir)/lib/config.h \
> $(top_builddir)/lib/dirpaths.h $(srcdir)/argv_parse.h
> bthread.o: $(srcdir)/bthread.c $(top_builddir)/lib/config.h \
> - $(srcdir)/bthread.h
> + $(top_builddir)/lib/dirpaths.h $(srcdir)/bthread.h
> cstring.o: $(srcdir)/cstring.c $(top_builddir)/lib/config.h \
> $(top_builddir)/lib/dirpaths.h $(srcdir)/cstring.h
> mkquota.o: $(srcdir)/mkquota.c $(top_builddir)/lib/config.h \
> @@ -183,6 +190,8 @@ quotaio_v2.o: $(srcdir)/quotaio_v2.c $(top_builddir)/lib/config.h \
> $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
> $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h \
> $(srcdir)/quotaio_tree.h
> +thread.o: $(srcdir)/thread.c $(top_builddir)/lib/config.h \
> + $(top_builddir)/lib/dirpaths.h $(srcdir)/thread.h
> dict.o: $(srcdir)/dict.c $(top_builddir)/lib/config.h \
> $(top_builddir)/lib/dirpaths.h $(srcdir)/dict.h
> devname.o: $(srcdir)/devname.c $(top_builddir)/lib/config.h \
> diff --git a/lib/support/thread.c b/lib/support/thread.c
> new file mode 100644
> index 000000000..a9a10940c
> --- /dev/null
> +++ b/lib/support/thread.c
> @@ -0,0 +1,36 @@
> +/*
> + * thread.c - utility functions for Posix threads
Should these new files have an explicit license specification?
Other than that,
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> + */
> +
> +#include "config.h"
> +#ifdef HAVE_PTHREAD
> +#include <pthread.h>
> +#endif
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <unistd.h>
> +
> +#include "support/thread.h"
> +
> +uint64_t get_thread_id(void)
> +{
> +#if defined(HAVE_GETTID)
> + return gettid();
> +#elif defined(HAVE_PTHREAD_THREADID_NP)
> + uint64_t tid;
> +
> + if (pthread_threadid_np(NULL, &tid))
> + return tid;
> +#elif defined(HAVE_PTHREAD)
> + return (__u64)(uintptr_t) pthread_self();
> +#endif
> + return getpid();
> +}
> +
> +#ifdef DEBUG_PROGRAM
> +int main(int argc, char **argv)
> +{
> + printf("Thread id: %llu\n", get_thread_id());
> + return 0;
> +}
> +#endif
> diff --git a/lib/support/thread.h b/lib/support/thread.h
> new file mode 100644
> index 000000000..9a7f5c9db
> --- /dev/null
> +++ b/lib/support/thread.h
> @@ -0,0 +1,5 @@
> +/*
> + * thread.h -- header file for thread utilities
> + */
> +
> +uint64_t get_thread_id(void);
> diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
> index 0b43ec0fb..dfbc98636 100644
> --- a/misc/fuse2fs.c
> +++ b/misc/fuse2fs.c
> @@ -48,6 +48,7 @@
> #include "ext2fs/ext2_fs.h"
> #include "ext2fs/ext2fsP.h"
> #include "support/bthread.h"
> +#include "support/thread.h"
> #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
> # define FUSE_PLATFORM_OPTS ""
> #else
> @@ -148,7 +149,7 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
>
> #define dbg_printf(fuse2fs, format, ...) \
> while ((fuse2fs)->debug) { \
> - printf("FUSE2FS (%s): tid=%d " format, (fuse2fs)->shortdev, gettid(), ##__VA_ARGS__); \
> + printf("FUSE2FS (%s): tid=%llu " format, (fuse2fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \
> fflush(stdout); \
> break; \
> }
> --
> 2.51.0
>
^ permalink raw reply
* Re: [PATCH 1/3] libsupport: fix portability issues with the bthread.c
From: Darrick J. Wong @ 2026-04-03 4:16 UTC (permalink / raw)
To: Theodore Ts'o; +Cc: Ext4 Developers List
In-Reply-To: <20260403040328.2385083-2-tytso@mit.edu>
On Fri, Apr 03, 2026 at 12:03:26AM -0400, Theodore Ts'o wrote:
> The function pthread_setname_np() is non-portable; that's what the
> "np" means. In particular, on Mac systems, the function takes only a
> single argument, while on most other systems which have the function,
> it takes two arguments.
>
> Also fix a problem where a 1-bit signed integer can only accept values
> of 0 or -1. Change it to be a 1-bit unsigned integer, which can
> accept values of 0 or 1. Clang will issue a warning if 1-bit signed
> integer are used incorrectly, and fail with -Werror.
>
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>
> ---
> configure | 6 ++++++
> configure.ac | 1 +
> lib/config.h.in | 24 +++++++++++++++---------
> lib/support/bthread.c | 12 ++++++++++--
> 4 files changed, 32 insertions(+), 11 deletions(-)
>
> diff --git a/configure b/configure
> index 4da5439fe..b9a82dcec 100755
> --- a/configure
> +++ b/configure
> @@ -13887,6 +13887,12 @@ if test "x$ac_cv_func_pwrite64" = xyes
> then :
> printf "%s\n" "#define HAVE_PWRITE64 1" >>confdefs.h
>
> +fi
> +ac_fn_c_check_func "$LINENO" "pthread_setname_np" "ac_cv_func_pthread_setname_np"
> +if test "x$ac_cv_func_pthread_setname_np" = xyes
> +then :
> + printf "%s\n" "#define HAVE_PTHREAD_SETNAME_NP 1" >>confdefs.h
> +
> fi
> ac_fn_c_check_func "$LINENO" "qsort_r" "ac_cv_func_qsort_r"
> if test "x$ac_cv_func_qsort_r" = xyes
> diff --git a/configure.ac b/configure.ac
> index ecef9df39..2473879fd 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -1269,6 +1269,7 @@ AC_CHECK_FUNCS(m4_flatten([
> pwrite
> pread64
> pwrite64
> + pthread_setname_np
> qsort_r
> secure_getenv
> setmntent
> diff --git a/lib/config.h.in b/lib/config.h.in
> index 8ea7ec2b1..c6cbced5f 100644
> --- a/lib/config.h.in
> +++ b/lib/config.h.in
> @@ -46,9 +46,6 @@
> /* Define to the version of FUSE to use */
> #undef FUSE_USE_VERSION
>
> -/* Define to 1 if fuse supports cache_readdir */
> -#undef HAVE_FUSE_CACHE_READDIR
Huh, there's a lot of churn in this file. Do you have a magic script
somewhere that regenerates config.h.in?
I don't see any problems with this file's changes, but I could also not
scatter junk everywhere :)
> -
> /* Define to 1 if you have the 'add_key' function. */
> #undef HAVE_ADD_KEY
>
> @@ -73,9 +70,6 @@
> /* Define to 1 if you have the BSD-style 'qsort_r' function. */
> #undef HAVE_BSD_QSORT_R
>
> -/* Define to 1 if PR_SET_IO_FLUSHER is present */
> -#undef HAVE_PR_SET_IO_FLUSHER
> -
> /* Define to 1 if you have the Mac OS X function
> CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
> #undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES
> @@ -87,6 +81,9 @@
> /* Define to 1 if you have the 'chflags' function. */
> #undef HAVE_CHFLAGS
>
> +/* Define to 1 if CLOCK_MONOTONIC is present */
> +#undef HAVE_CLOCK_MONOTONIC
> +
> /* Define if the GNU dcgettext() function is already present or preinstalled.
> */
> #undef HAVE_DCGETTEXT
> @@ -136,9 +133,15 @@
> /* Define to 1 if you have the 'fsync' function. */
> #undef HAVE_FSYNC
>
> +/* Define to 1 if FS_IOC_READ_VERITY_METADATA ioctl is available */
> +#undef HAVE_FS_IOC_READ_VERITY_METADATA
> +
> /* Define to 1 if you have the 'ftruncate64' function. */
> #undef HAVE_FTRUNCATE64
>
> +/* Define to 1 if fuse supports cache_readdir */
> +#undef HAVE_FUSE_CACHE_READDIR
> +
> /* Define to 1 if you have the <fuse.h> header file. */
> #undef HAVE_FUSE_H
>
> @@ -313,6 +316,9 @@
> /* Define to 1 if you have the 'pread64' function. */
> #undef HAVE_PREAD64
>
> +/* Define to 1 if PR_SET_IO_FLUSHER is present */
> +#undef HAVE_PR_SET_IO_FLUSHER
> +
> /* Define if you have POSIX threads libraries and header files. */
> #undef HAVE_PTHREAD
>
> @@ -322,6 +328,9 @@
> /* Have PTHREAD_PRIO_INHERIT. */
> #undef HAVE_PTHREAD_PRIO_INHERIT
>
> +/* Define to 1 if you have the 'pthread_setname_np' function. */
> +#undef HAVE_PTHREAD_SETNAME_NP
> +
> /* Define to 1 if you have the 'pwrite' function. */
> #undef HAVE_PWRITE
>
> @@ -699,7 +708,4 @@
> /* Define to 1 on platforms where this makes time_t a 64-bit type. */
> #undef __MINGW_USE_VC2005_COMPAT
>
> -/* Define to 1 if CLOCK_MONOTONIC is present */
> -#undef HAVE_CLOCK_MONOTONIC
> -
> #include <dirpaths.h>
> diff --git a/lib/support/bthread.c b/lib/support/bthread.c
> index 936ca0f0f..87eeb1b3d 100644
> --- a/lib/support/bthread.c
> +++ b/lib/support/bthread.c
> @@ -9,6 +9,7 @@
> * %End-Header%
> */
> #include "config.h"
> +#ifdef HAVE_PTHREAD
Hmmm if we don't have pthreads, then shouldn't bthread.h also exclude
all the function declarations ifndef HAVE_PTHREAD?
> #include <stdlib.h>
> #include <errno.h>
> #include <pthread.h>
> @@ -33,7 +34,7 @@ struct bthread {
> bthread_fn_t fn;
> void *data;
> unsigned int period; /* seconds */
> - int can_join:1;
> + unsigned int can_join:1;
That works, though I suppose you could change it to bool.
--D
> };
>
> /* Wait for a signal or for the periodic interval */
> @@ -101,7 +102,13 @@ int bthread_create(const char *name, bthread_fn_t fn, void *data,
> if (error)
> goto out_cond;
>
> +#ifdef HAVE_PTHREAD_SETNAME_NP
> +#ifdef __APPLE__
> + pthread_setname_np(name);
> +#else
> pthread_setname_np(bt->thread, name);
> +#endif
> +#endif
>
> *btp = bt;
> return 0;
> @@ -178,7 +185,7 @@ int bthread_cancel(struct bthread *bt)
> /* Ask the thread to cancel itself and wait for it */
> void bthread_stop(struct bthread *bt)
> {
> - int need_join = 0;
> + unsigned int need_join = 0;
>
> pthread_mutex_lock(&bt->lock);
> switch (bt->state) {
> @@ -199,3 +206,4 @@ void bthread_stop(struct bthread *bt)
> if (need_join)
> pthread_join(bt->thread, NULL);
> }
> +#endif /* HAVE_PTHREAD */
> --
> 2.51.0
>
^ permalink raw reply
* [PATCH 1/3] libsupport: fix portability issues with the bthread.c
From: Theodore Ts'o @ 2026-04-03 4:03 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Darrick J. Wong, Theodore Ts'o
In-Reply-To: <20260403040328.2385083-1-tytso@mit.edu>
The function pthread_setname_np() is non-portable; that's what the
"np" means. In particular, on Mac systems, the function takes only a
single argument, while on most other systems which have the function,
it takes two arguments.
Also fix a problem where a 1-bit signed integer can only accept values
of 0 or -1. Change it to be a 1-bit unsigned integer, which can
accept values of 0 or 1. Clang will issue a warning if 1-bit signed
integer are used incorrectly, and fail with -Werror.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
configure | 6 ++++++
configure.ac | 1 +
lib/config.h.in | 24 +++++++++++++++---------
lib/support/bthread.c | 12 ++++++++++--
4 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/configure b/configure
index 4da5439fe..b9a82dcec 100755
--- a/configure
+++ b/configure
@@ -13887,6 +13887,12 @@ if test "x$ac_cv_func_pwrite64" = xyes
then :
printf "%s\n" "#define HAVE_PWRITE64 1" >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "pthread_setname_np" "ac_cv_func_pthread_setname_np"
+if test "x$ac_cv_func_pthread_setname_np" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_SETNAME_NP 1" >>confdefs.h
+
fi
ac_fn_c_check_func "$LINENO" "qsort_r" "ac_cv_func_qsort_r"
if test "x$ac_cv_func_qsort_r" = xyes
diff --git a/configure.ac b/configure.ac
index ecef9df39..2473879fd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1269,6 +1269,7 @@ AC_CHECK_FUNCS(m4_flatten([
pwrite
pread64
pwrite64
+ pthread_setname_np
qsort_r
secure_getenv
setmntent
diff --git a/lib/config.h.in b/lib/config.h.in
index 8ea7ec2b1..c6cbced5f 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -46,9 +46,6 @@
/* Define to the version of FUSE to use */
#undef FUSE_USE_VERSION
-/* Define to 1 if fuse supports cache_readdir */
-#undef HAVE_FUSE_CACHE_READDIR
-
/* Define to 1 if you have the 'add_key' function. */
#undef HAVE_ADD_KEY
@@ -73,9 +70,6 @@
/* Define to 1 if you have the BSD-style 'qsort_r' function. */
#undef HAVE_BSD_QSORT_R
-/* Define to 1 if PR_SET_IO_FLUSHER is present */
-#undef HAVE_PR_SET_IO_FLUSHER
-
/* Define to 1 if you have the Mac OS X function
CFLocaleCopyPreferredLanguages in the CoreFoundation framework. */
#undef HAVE_CFLOCALECOPYPREFERREDLANGUAGES
@@ -87,6 +81,9 @@
/* Define to 1 if you have the 'chflags' function. */
#undef HAVE_CHFLAGS
+/* Define to 1 if CLOCK_MONOTONIC is present */
+#undef HAVE_CLOCK_MONOTONIC
+
/* Define if the GNU dcgettext() function is already present or preinstalled.
*/
#undef HAVE_DCGETTEXT
@@ -136,9 +133,15 @@
/* Define to 1 if you have the 'fsync' function. */
#undef HAVE_FSYNC
+/* Define to 1 if FS_IOC_READ_VERITY_METADATA ioctl is available */
+#undef HAVE_FS_IOC_READ_VERITY_METADATA
+
/* Define to 1 if you have the 'ftruncate64' function. */
#undef HAVE_FTRUNCATE64
+/* Define to 1 if fuse supports cache_readdir */
+#undef HAVE_FUSE_CACHE_READDIR
+
/* Define to 1 if you have the <fuse.h> header file. */
#undef HAVE_FUSE_H
@@ -313,6 +316,9 @@
/* Define to 1 if you have the 'pread64' function. */
#undef HAVE_PREAD64
+/* Define to 1 if PR_SET_IO_FLUSHER is present */
+#undef HAVE_PR_SET_IO_FLUSHER
+
/* Define if you have POSIX threads libraries and header files. */
#undef HAVE_PTHREAD
@@ -322,6 +328,9 @@
/* Have PTHREAD_PRIO_INHERIT. */
#undef HAVE_PTHREAD_PRIO_INHERIT
+/* Define to 1 if you have the 'pthread_setname_np' function. */
+#undef HAVE_PTHREAD_SETNAME_NP
+
/* Define to 1 if you have the 'pwrite' function. */
#undef HAVE_PWRITE
@@ -699,7 +708,4 @@
/* Define to 1 on platforms where this makes time_t a 64-bit type. */
#undef __MINGW_USE_VC2005_COMPAT
-/* Define to 1 if CLOCK_MONOTONIC is present */
-#undef HAVE_CLOCK_MONOTONIC
-
#include <dirpaths.h>
diff --git a/lib/support/bthread.c b/lib/support/bthread.c
index 936ca0f0f..87eeb1b3d 100644
--- a/lib/support/bthread.c
+++ b/lib/support/bthread.c
@@ -9,6 +9,7 @@
* %End-Header%
*/
#include "config.h"
+#ifdef HAVE_PTHREAD
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
@@ -33,7 +34,7 @@ struct bthread {
bthread_fn_t fn;
void *data;
unsigned int period; /* seconds */
- int can_join:1;
+ unsigned int can_join:1;
};
/* Wait for a signal or for the periodic interval */
@@ -101,7 +102,13 @@ int bthread_create(const char *name, bthread_fn_t fn, void *data,
if (error)
goto out_cond;
+#ifdef HAVE_PTHREAD_SETNAME_NP
+#ifdef __APPLE__
+ pthread_setname_np(name);
+#else
pthread_setname_np(bt->thread, name);
+#endif
+#endif
*btp = bt;
return 0;
@@ -178,7 +185,7 @@ int bthread_cancel(struct bthread *bt)
/* Ask the thread to cancel itself and wait for it */
void bthread_stop(struct bthread *bt)
{
- int need_join = 0;
+ unsigned int need_join = 0;
pthread_mutex_lock(&bt->lock);
switch (bt->state) {
@@ -199,3 +206,4 @@ void bthread_stop(struct bthread *bt)
if (need_join)
pthread_join(bt->thread, NULL);
}
+#endif /* HAVE_PTHREAD */
--
2.51.0
^ permalink raw reply related
* [PATCH 2/3] libsupport: add a portable get_thread_id() function
From: Theodore Ts'o @ 2026-04-03 4:03 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Darrick J. Wong, Theodore Ts'o
In-Reply-To: <20260403040328.2385083-1-tytso@mit.edu>
The gettid() system call is only available on Linux. So create a new
function, get_thread_id() which implements a number of different ways
of providing a thread id as an integer.
Use get_thread_id() instead of gettid() in fuse2fs.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
configure | 12 ++++++++++++
configure.ac | 2 ++
lib/config.h.in | 6 ++++++
lib/support/Makefile.in | 13 +++++++++++--
lib/support/thread.c | 36 ++++++++++++++++++++++++++++++++++++
lib/support/thread.h | 5 +++++
misc/fuse2fs.c | 3 ++-
7 files changed, 74 insertions(+), 3 deletions(-)
create mode 100644 lib/support/thread.c
create mode 100644 lib/support/thread.h
diff --git a/configure b/configure
index b9a82dcec..b04b31aff 100755
--- a/configure
+++ b/configure
@@ -13749,6 +13749,12 @@ if test "x$ac_cv_func_getrusage" = xyes
then :
printf "%s\n" "#define HAVE_GETRUSAGE 1" >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "gettid" "ac_cv_func_gettid"
+if test "x$ac_cv_func_gettid" = xyes
+then :
+ printf "%s\n" "#define HAVE_GETTID 1" >>confdefs.h
+
fi
ac_fn_c_check_func "$LINENO" "jrand48" "ac_cv_func_jrand48"
if test "x$ac_cv_func_jrand48" = xyes
@@ -13893,6 +13899,12 @@ if test "x$ac_cv_func_pthread_setname_np" = xyes
then :
printf "%s\n" "#define HAVE_PTHREAD_SETNAME_NP 1" >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "pthread_threadid_np" "ac_cv_func_pthread_threadid_np"
+if test "x$ac_cv_func_pthread_threadid_np" = xyes
+then :
+ printf "%s\n" "#define HAVE_PTHREAD_THREADID_NP 1" >>confdefs.h
+
fi
ac_fn_c_check_func "$LINENO" "qsort_r" "ac_cv_func_qsort_r"
if test "x$ac_cv_func_qsort_r" = xyes
diff --git a/configure.ac b/configure.ac
index 2473879fd..4921f81f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1246,6 +1246,7 @@ AC_CHECK_FUNCS(m4_flatten([
getrandom
getrlimit
getrusage
+ gettid
jrand48
keyctl
llistxattr
@@ -1270,6 +1271,7 @@ AC_CHECK_FUNCS(m4_flatten([
pread64
pwrite64
pthread_setname_np
+ pthread_threadid_np
qsort_r
secure_getenv
setmntent
diff --git a/lib/config.h.in b/lib/config.h.in
index c6cbced5f..f129abfe7 100644
--- a/lib/config.h.in
+++ b/lib/config.h.in
@@ -181,6 +181,9 @@
/* Define if the GNU gettext() function is already present or preinstalled. */
#undef HAVE_GETTEXT
+/* Define to 1 if you have the 'gettid' function. */
+#undef HAVE_GETTID
+
/* Define to 1 if you have the GNU-style 'qsort_r' function. */
#undef HAVE_GNU_QSORT_R
@@ -331,6 +334,9 @@
/* Define to 1 if you have the 'pthread_setname_np' function. */
#undef HAVE_PTHREAD_SETNAME_NP
+/* Define to 1 if you have the 'pthread_threadid_np' function. */
+#undef HAVE_PTHREAD_THREADID_NP
+
/* Define to 1 if you have the 'pwrite' function. */
#undef HAVE_PWRITE
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
index 6383816fd..9aac9cf00 100644
--- a/lib/support/Makefile.in
+++ b/lib/support/Makefile.in
@@ -25,6 +25,7 @@ OBJS= bthread.o \
quotaio.o \
quotaio_v2.o \
quotaio_tree.o \
+ thread.o \
dict.o \
devname.o
@@ -41,6 +42,7 @@ SRCS= $(srcdir)/argv_parse.c \
$(srcdir)/quotaio.c \
$(srcdir)/quotaio_tree.c \
$(srcdir)/quotaio_v2.c \
+ $(srcdir)/thread.c \
$(srcdir)/dict.c \
$(srcdir)/devname.c
@@ -81,10 +83,15 @@ test_cstring: $(srcdir)/cstring.c
$(Q) $(CC) -o test_cstring -DDEBUG_PROGRAM $(srcdir)/cstring.c \
$(ALL_CFLAGS)
+test_thread: $(srcdir)/thread.c
+ $(E) " CC $@"
+ $(Q) $(CC) -o test_thread -DDEBUG_PROGRAM $(srcdir)/thread.c \
+ $(ALL_CFLAGS)
+
clean::
$(RM) -f \#* *.s *.o *.a *~ *.bak core profiled/* \
../libsupport.a ../libsupport_p.a $(SMANPAGES) \
- prof_err.c prof_err.h test_profile test_cstring
+ prof_err.c prof_err.h test_profile test_cstring test_thread
#fullcheck check:: tst_uuid
# LD_LIBRARY_PATH=$(LIB) DYLD_LIBRARY_PATH=$(LIB) ./tst_uuid
@@ -111,7 +118,7 @@ $(OBJS):
argv_parse.o: $(srcdir)/argv_parse.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/argv_parse.h
bthread.o: $(srcdir)/bthread.c $(top_builddir)/lib/config.h \
- $(srcdir)/bthread.h
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/bthread.h
cstring.o: $(srcdir)/cstring.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/cstring.h
mkquota.o: $(srcdir)/mkquota.c $(top_builddir)/lib/config.h \
@@ -183,6 +190,8 @@ quotaio_v2.o: $(srcdir)/quotaio_v2.c $(top_builddir)/lib/config.h \
$(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/hashmap.h \
$(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h \
$(srcdir)/quotaio_tree.h
+thread.o: $(srcdir)/thread.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/thread.h
dict.o: $(srcdir)/dict.c $(top_builddir)/lib/config.h \
$(top_builddir)/lib/dirpaths.h $(srcdir)/dict.h
devname.o: $(srcdir)/devname.c $(top_builddir)/lib/config.h \
diff --git a/lib/support/thread.c b/lib/support/thread.c
new file mode 100644
index 000000000..a9a10940c
--- /dev/null
+++ b/lib/support/thread.c
@@ -0,0 +1,36 @@
+/*
+ * thread.c - utility functions for Posix threads
+ */
+
+#include "config.h"
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "support/thread.h"
+
+uint64_t get_thread_id(void)
+{
+#if defined(HAVE_GETTID)
+ return gettid();
+#elif defined(HAVE_PTHREAD_THREADID_NP)
+ uint64_t tid;
+
+ if (pthread_threadid_np(NULL, &tid))
+ return tid;
+#elif defined(HAVE_PTHREAD)
+ return (__u64)(uintptr_t) pthread_self();
+#endif
+ return getpid();
+}
+
+#ifdef DEBUG_PROGRAM
+int main(int argc, char **argv)
+{
+ printf("Thread id: %llu\n", get_thread_id());
+ return 0;
+}
+#endif
diff --git a/lib/support/thread.h b/lib/support/thread.h
new file mode 100644
index 000000000..9a7f5c9db
--- /dev/null
+++ b/lib/support/thread.h
@@ -0,0 +1,5 @@
+/*
+ * thread.h -- header file for thread utilities
+ */
+
+uint64_t get_thread_id(void);
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index 0b43ec0fb..dfbc98636 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -48,6 +48,7 @@
#include "ext2fs/ext2_fs.h"
#include "ext2fs/ext2fsP.h"
#include "support/bthread.h"
+#include "support/thread.h"
#if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 0)
# define FUSE_PLATFORM_OPTS ""
#else
@@ -148,7 +149,7 @@ static inline uint64_t round_down(uint64_t b, unsigned int align)
#define dbg_printf(fuse2fs, format, ...) \
while ((fuse2fs)->debug) { \
- printf("FUSE2FS (%s): tid=%d " format, (fuse2fs)->shortdev, gettid(), ##__VA_ARGS__); \
+ printf("FUSE2FS (%s): tid=%llu " format, (fuse2fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \
fflush(stdout); \
break; \
}
--
2.51.0
^ permalink raw reply related
* [PATCH -e2fsprogs 0/3] Fix portability issues on MacOS
From: Theodore Ts'o @ 2026-04-03 4:03 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Darrick J. Wong, Theodore Ts'o
The recent fuse2fs changes introduced some portability issues; fix
them so that e2fsprogs can build on MacOS and create a fuse2fs binary
that works on MacOS 26.3.1 using MacFuse.
Note: the f_opt_extent test is failing on Github Actions when testing
on MacOS. All of the tests are passing clean up my MacOS laptop. So
I'm not sure where the github action failure is coming from.
Theodore Ts'o (3):
libsupport: fix portability issues with the bthread.c
libsupport: add a portable get_thread_id() function
fuse2fs: fix build failure on systems which don't define EUCLEAN
configure | 18 ++++++++++++++++++
configure.ac | 3 +++
lib/config.h.in | 30 +++++++++++++++++++++---------
lib/support/Makefile.in | 13 +++++++++++--
lib/support/bthread.c | 12 ++++++++++--
lib/support/thread.c | 36 ++++++++++++++++++++++++++++++++++++
lib/support/thread.h | 5 +++++
misc/fuse2fs.c | 5 ++++-
8 files changed, 108 insertions(+), 14 deletions(-)
create mode 100644 lib/support/thread.c
create mode 100644 lib/support/thread.h
--
2.51.0
^ permalink raw reply
* [PATCH 3/3] fuse2fs: fix build failure on systems which don't define EUCLEAN
From: Theodore Ts'o @ 2026-04-03 4:03 UTC (permalink / raw)
To: Ext4 Developers List; +Cc: Darrick J. Wong, Theodore Ts'o
In-Reply-To: <20260403040328.2385083-1-tytso@mit.edu>
MacOS doesn't have EUCLEAN, so we use EIO as the closest error code.
But then we need to avoid a compile error caused by a duplicate case
labels of EUCLEAN and EIO.
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
---
misc/fuse2fs.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c
index dfbc98636..94e289fab 100644
--- a/misc/fuse2fs.c
+++ b/misc/fuse2fs.c
@@ -5870,7 +5870,9 @@ static int __translate_error(ext2_filsys fs, ext2_ino_t ino, errcode_t err,
#ifdef EILSEQ
case EILSEQ:
#endif
+#if EUCLEAN != EIO
case EUCLEAN:
+#endif
/* these errnos usually denote corruption or persistence fail */
is_err = 1;
ret = -err;
--
2.51.0
^ permalink raw reply related
* Re: [PATCH 8/8] arch: use rest_of_page() macro where appropriate
From: Paul Walmsley @ 2026-04-02 21:58 UTC (permalink / raw)
To: Yury Norov
Cc: Andrew Morton, David S. Miller, Michael S. Tsirkin,
Theodore Ts'o, Albert Ou, Alexander Duyck, Alexander Gordeev,
Alexander Viro, Alexandra Winter, Andreas Dilger, Andrew Lunn,
Anna Schumaker, Anton Yakovlev, Arnaldo Carvalho de Melo,
Aswin Karuvally, Borislav Petkov, Carlos Maiolino,
Catalin Marinas, Chao Yu, Christian Borntraeger,
Christian Brauner, Claudio Imbrenda, Dave Hansen, David Airlie,
Dominique Martinet, Dongsheng Yang, Eric Dumazet,
Eric Van Hensbergen, Heiko Carstens, Herbert Xu, Ingo Molnar,
Jaegeuk Kim, Jakub Kicinski, Jani Nikula, Janosch Frank,
Jaroslav Kysela, Jens Axboe, Joonas Lahtinen, Latchesar Ionkov,
Linus Walleij, Madhavan Srinivasan, Mark Brown, Michael Ellerman,
Miklos Szeredi, Namhyung Kim, Palmer Dabbelt, Paolo Abeni,
Paolo Bonzini, Paul Walmsley, Peter Zijlstra, Rodrigo Vivi,
Sean Christopherson, Simona Vetter, Takashi Iwai, Thomas Gleixner,
Trond Myklebust, Tvrtko Ursulin, Vasily Gorbik, Will Deacon,
Yury Norov, Zheng Gu, linux-kernel, x86, linux-arm-kernel,
linuxppc-dev, linux-riscv, kvm, linux-s390, linux-block,
intel-gfx, dri-devel, dm-devel, netdev, linux-spi, linux-ext4,
linux-f2fs-devel, linux-fsdevel, linux-xfs, linux-nfs,
linux-crypto, linux-mm, linux-perf-users, v9fs, virtualization,
linux-sound
In-Reply-To: <20260304012717.201797-9-ynorov@nvidia.com>
On Tue, 3 Mar 2026, Yury Norov wrote:
> Switch arch code to using the macro. No functional changes intended.
>
> Signed-off-by: Yury Norov <ynorov@nvidia.com>
Acked-by: Paul Walmsley <pjw@kernel.org> # arch/riscv
- Paul
^ permalink raw reply
* Re: [PATCH v6 03/22] fsverity: generate and store zero-block hash
From: Andrey Albershteyn @ 2026-04-02 14:47 UTC (permalink / raw)
To: Eric Biggers
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, djwong
In-Reply-To: <20260401222717.GH2466@quark>
On 2026-04-01 15:27:17, Eric Biggers wrote:
> On Tue, Mar 31, 2026 at 11:28:04PM +0200, Andrey Albershteyn wrote:
> > Compute the hash of one filesystem block's worth of zeros. A filesystem
> > implementation can decide to elide merkle tree blocks containing only
> > this hash and synthesize the contents at read time.
> >
> > Let's pretend that there's a file containing six data blocks and whose
> > merkle tree looks roughly like this:
> >
> > root
> > +--leaf0
> > | +--data0
> > | +--data1
> > | `--data2
> > `--leaf1
> > +--data3
> > +--data4
> > `--data5
> >
> > If data[0-2] are sparse holes, then leaf0 will contain a repeating
> > sequence of @zero_digest. Therefore, leaf0 need not be written to disk
> > because its contents can be synthesized.
> >
> > A subsequent xfs patch will use this to reduce the size of the merkle
> > tree when dealing with sparse gold master disk images and the like.
> >
> > Add a helper to pre-fill folio with hashes of empty blocks. This will be
> > used by iomap to synthesize blocks full of zero hashes on the fly.
> >
> > Signed-off-by: Darrick J. Wong <djwong@kernel.org>
> > Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> > ---
> > fs/verity/fsverity_private.h | 3 +++
> > fs/verity/open.c | 3 +++
> > fs/verity/pagecache.c | 22 ++++++++++++++++++++++
> > include/linux/fsverity.h | 8 ++++++++
> > 4 files changed, 36 insertions(+)
>
> Acked-by: Eric Biggers <ebiggers@kernel.org>
>
> The example given in the commit message is a bit misleading, though.
> Usually there are actually 128 hashes per block, and a block of hashes
> covers 512 KiB. So this optimization applies only where there is a hole
> in the file's data of size (at least) 512 KiB, aligned to the same
> amount.
>
> It's also worth noting that this optimization is being done only for the
> first level. The levels above that are still being stored. So, this
> doesn't really enable e.g. exabyte sized sparse regions, as a block will
> still be stored for each 64 MiB (instead of every 512 KiB).
>
> I'm okay with this if you want to do this, but I just want to make sure
> its limitations are well-understood.
Sure, I will fix the example and add a note that this is only for
data level.
I think for higher levels it could be later added transparently.
Old tree blocks will be read by iomap if exist, and for new format
block will be generated.
--
- Andrey
>
> > + /* the hash of a merkle block-sized buffer of zeroes */
> > + u8 zero_digest[FS_VERITY_MAX_DIGEST_SIZE];
>
> "the hash of an all-zeroes block" would be clearer. This is the hash
> from fsverity_hash_block() which includes the optional salt, not the
> hash from fsverity_hash_buffer() which does not include the salt.
>
> > +/**
> > + * fsverity_fill_zerohash() - fill folio with hashes of zero data block
> > + * @folio: folio to fill
> > + * @poff: offset in the folio to start
> > + * @plen: length of the range to fill with hashes
>
> Maybe go with (len, offset) for consistency with
> fsverity_verify_blocks(). (I assume the "p" prefix stands for "page",
> which is misleading since this works with a folio.)
>
> > +void fsverity_fill_zerohash(struct folio *folio, size_t poff, size_t plen,
> > + struct fsverity_info *vi)
> > +{
> > + size_t offset = poff;
> > +
> > + WARN_ON_ONCE(!IS_ALIGNED(poff, vi->tree_params.digest_size));
> > + WARN_ON_ONCE(!IS_ALIGNED(plen, vi->tree_params.digest_size));
> > +
> > + for (; offset < (poff + plen); offset += vi->tree_params.digest_size)
> > + memcpy_to_folio(folio, offset, vi->tree_params.zero_digest,
> > + vi->tree_params.digest_size);
>
> This could be done more efficiently, especially on HIGHMEM. Probably
> fine for now though, especially since the intersection of anyone wanting
> XFS && fsverity && HIGHMEM is likely to be extremely small.
>
> - Eric
>
^ permalink raw reply
* Re: [PATCH v6 02/22] fsverity: expose ensure_fsverity_info()
From: Andrey Albershteyn @ 2026-04-02 14:02 UTC (permalink / raw)
To: Eric Biggers
Cc: Andrey Albershteyn, linux-xfs, fsverity, linux-fsdevel, hch,
linux-ext4, linux-f2fs-devel, linux-btrfs, djwong
In-Reply-To: <20260401220241.GG2466@quark>
On 2026-04-01 15:02:41, Eric Biggers wrote:
> On Tue, Mar 31, 2026 at 11:28:03PM +0200, Andrey Albershteyn wrote:
> > This function will be used by XFS's scrub to force fsverity activation,
> > therefore, to read fsverity context.
> >
> > Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
> > Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
> > ---
>
> Acked-by: Eric Biggers <ebiggers@kernel.org>
>
> > +/**
> > + * fsverity_ensure_verity_info() - create verity info if it's not in memory yet
> > + * @inode: the inode for which verity info should be created
> > + *
> > + * Ensure this inode has verity info attached to it. Read fsverity descriptor
> > + * and creates verity based on that. Inodes opened outside of
> > + * file_operations->open will not have any verity info attached. This
> > + * info is required for any fsverity related operations.
> > + *
> > + * Return: 0 on success, -errno on failure
> > + */
> > +int fsverity_ensure_verity_info(struct inode *inode);
>
> As Christoph mentioned, fs/verity/ uses the convention of the kerneldoc
> for functions being above the function definition.
>
> I think the comment could also be clearer:
>
> > create verity info if it's not in memory yet
>
> Maybe "cache verity info if it's not already cached", to avoid potential
> confusion with enabling fsverity on the file.
>
> > Ensure this inode has verity info attached to it.
>
> Maybe add: "It's assumed the inode already has fsverity enabled."
>
> > Inodes opened outside of file_operations->open will not have any
> > verity info attached. This info is required for any fsverity
> > related operations.
>
> The first sentence could be misinterpreted as saying that this function
> won't do anything in that case. The second sentence isn't clear what
> counts as "any fsverity related operation". Also "opened" doesn't seem
> like the right word to use when talking about a filesystem-internal read
> that occurs without a file descriptor having been opened.
>
> Maybe replace with:
>
> * This needs to be called at least once before any of the inode's data
> * can be verified (and thus read at all) or the inode's fsverity digest
> * retrieved. fsverity_file_open() calls this already, which handles
> * normal file accesses. If a filesystem does any internal (i.e. not
> * associated with a file descriptor) reads of the file's data or
> * fsverity digest, it must call this explicitly before doing so.
>
> By the way, should there be a patch that converts
> ovl_ensure_verity_loaded() to use this?
sure, I will add a patch on top replacing ovl's function
--
- Andrey
^ permalink raw reply
* Re: [GIT PULL 7/9] fuse2fs: better tracking of writable state
From: Theodore Tso @ 2026-04-02 14:00 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: linux-ext4
In-Reply-To: <177334916629.3254574.9721669086227705727.stg-ugh@frogsfrogsfrogs>
On Thu, Mar 12, 2026 at 02:01:13PM -0700, Darrick J. Wong wrote:
> Hi Ted,
>
> Please pull this branch with changes for ext4.
>
> As usual, I did a test-merge with the main upstream branch as of a few
> minutes ago, and didn't see any conflicts. Please let me know if you
> encounter any problems.
>
> The following changes since commit fe8281316fd37133eb78474040affabb3f060e24:
>
> fuse2fs: record thread id in debug trace data (2026-03-08 19:14:06 -0700)
Thanks, merged into the next branch.
- Ted
^ permalink raw reply
* Re: [GIT PULL 6/9] fuse2fs: improve operation tracing
From: Theodore Tso @ 2026-04-02 13:59 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: linux-ext4
In-Reply-To: <177334916604.3254574.11968947765826043346.stg-ugh@frogsfrogsfrogs>
On Thu, Mar 12, 2026 at 02:00:58PM -0700, Darrick J. Wong wrote:
> Hi Ted,
>
> Please pull this branch with changes for ext4.
>
> As usual, I did a test-merge with the main upstream branch as of a few
> minutes ago, and didn't see any conflicts. Please let me know if you
> encounter any problems.
>
> The following changes since commit 9476b1377d887872f96d7144a81d2107cff3df1f:
>
> fuse2fs: adjust OOM killer score if possible (2026-03-08 19:14:05 -0700)
Thanks, merged into the next branch.
- Ted
^ permalink raw reply
* Re: [GIT PULL 5/9] fuse2fs: refactor mount code
From: Theodore Tso @ 2026-04-02 13:55 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: linux-ext4
In-Reply-To: <177334916576.3254574.12995451033800002505.stg-ugh@frogsfrogsfrogs>
On Thu, Mar 12, 2026 at 02:00:42PM -0700, Darrick J. Wong wrote:
> Hi Ted,
>
> Please pull this branch with changes for ext4.
>
> As usual, I did a test-merge with the main upstream branch as of a few
> minutes ago, and didn't see any conflicts. Please let me know if you
> encounter any problems.
>
> The following changes since commit 5d3394ff8d6f2e120be1343c1504831d9602a3b4:
>
> fuse2fs: hoist unmount code from main (2026-03-08 19:14:05 -0700)
Thanks, merged into the next branch.
- Ted
^ permalink raw reply
* Re: [GIT PULL 4/9] fuse2fs: refactor unmount code
From: Theodore Tso @ 2026-04-02 13:51 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: linux-ext4
In-Reply-To: <177334916548.3254574.4332031441968487356.stg-ugh@frogsfrogsfrogs>
On Thu, Mar 12, 2026 at 02:00:26PM -0700, Darrick J. Wong wrote:
> Hi Ted,
>
> Please pull this branch with changes for ext4.
>
> As usual, I did a test-merge with the main upstream branch as of a few
> minutes ago, and didn't see any conflicts. Please let me know if you
> encounter any problems.
>
> The following changes since commit f5c46155c79cccd46e047d732063c7f21db35c8e:
>
> fuse2fs: collect runtime of various operations (2026-03-08 19:14:05 -0700)
Thanks, merged.
- Ted
^ permalink raw reply
* Re: [GIT PULL 3/9] fuse2fs: clean up operation startup
From: Theodore Tso @ 2026-04-02 13:48 UTC (permalink / raw)
To: Darrick J. Wong; +Cc: linux-ext4
In-Reply-To: <177334916519.3254574.10658825847200071665.stg-ugh@frogsfrogsfrogs>
On Thu, Mar 12, 2026 at 02:00:11PM -0700, Darrick J. Wong wrote:
>
> As usual, I did a test-merge with the main upstream branch as of a few
> minutes ago, and didn't see any conflicts. Please let me know if you
> encounter any problems.
>
> The following changes since commit a4fc4817e3072cb19f481da24d4520671a174fff:
>
> fuse2fs: implement MMP updates (2026-03-08 19:14:04 -0700)
Thanks, merged. (BTW, I noticed that the previous pull request
resulted in a large number of regression test failures triggered by
improving libext2fs's dir_index support. I should have noticed it
earlier, but I had skipped running "make -j24 check" after merging the
previous pull request, which is my bad.)
- Ted
^ permalink raw reply
* Re: [PATCH v2 3/3] ext4: derive f_fsid from block device to avoid collisions
From: Anand Jain @ 2026-04-02 7:33 UTC (permalink / raw)
To: Theodore Tso
Cc: Andreas Dilger, Darrick J. Wong, Anand Jain, linux-ext4,
linux-btrfs, linux-xfs, hch
In-Reply-To: <20260325125952.GB2107@macsyma.local>
On 25/3/26 20:59, Theodore Tso wrote:
> On Wed, Mar 25, 2026 at 06:59:32PM +0800, Anand Jain wrote:
>>
>> IMO, sb->s_uuid (as used by overlayfs)
>> Represents a filesystem UUID that is persistent.
>> It is derived from on-disk metadata.
>>
>> statfs()->f_fsid is..
>> A kind of runtime filesystem identifier used to distinguish mounted
>> filesystems within a running system.
>> It may be stable across reboots or device removal and reinsertion,
>> but this is not guaranteed. It may change if the device dev_t changes.
>
> I always worry about "it might be stable, but it might not; ¯\_(ツ)_/¯"
>
> The problem with that is that people might starting using this
> kinda-of-guarantee-but-maybe-not in scripts or in programs, and then
> when people try to run that script or program on a different system,
> or on a different file system, things goes *boom*.
>
> So if we want to say that it is stable so long as dev_t and the file
> system the same, that's a well defined semantic.
>
Yeah, agreed. Avoid misuse. Document that f_fsid is stable as long
as dev_t and the underlying filesystem identity don't change.
> If it's that it has no guarantees whatsoever; cloud change across
> reboots; could change across remounts, then maybe it should just be a
> global mount sequence number that starts with a random number at boot.
> So you can use it to distinguish between different mounted file
> systems, but that's *all* you can do with the thing. That would also
> be a well defined semantic.
>
Per-mount random value (or global mount sequence) is also a
well-defined semantic, but it comes with trade-offs: we lose
consistency across mount cycles and need to carry per-mount
state.
IMHO, it's better to stick with a deterministic id:
f_fsid = f(dev_t, fsid)
predictable and aligned with XFS/btrfs and avoids additional state.
Bottom line, it fixes the cloned filesystem case without regressing
the existing semantics.
^ permalink raw reply
* Re: [PATCH v4 09/13] ext4: ensure zeroed partial blocks are persisted in SYNC mode
From: Zhang Yi @ 2026-04-02 1:21 UTC (permalink / raw)
To: Jan Kara
Cc: linux-ext4, linux-fsdevel, linux-kernel, tytso, adilger.kernel,
ojaswin, ritesh.list, libaokun, yi.zhang, yizhang089, yangerkun,
yukuai
In-Reply-To: <42toomq4fa64kbice5yl4spjsw4em2qcq7kvkkas6254iij3u2@r6ekulbdog3i>
On 4/2/2026 1:06 AM, Jan Kara wrote:
> On Fri 27-03-26 18:29:35, Zhang Yi wrote:
>> From: Zhang Yi <yi.zhang@huawei.com>
>>
>> In ext4_zero_range() and ext4_punch_hole(), when operating in SYNC mode
>> and zeroing a partial block, only data=journal modes guarantee that the
>> zeroed data is synchronously persisted after the operation completes.
>> For data=ordered/writeback mode and non-journal modes, this guarantee is
>> missing.
>>
>> Introduce a partial_zero parameter to explicitly trigger writeback for
>> all scenarios where a partial block is zeroed, ensuring the zeroed data
>> is durably persisted.
>>
>> Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
>> ---
>> fs/ext4/ext4.h | 2 +-
>> fs/ext4/extents.c | 9 ++++++++-
>> fs/ext4/inode.c | 19 ++++++++++++++-----
>> 3 files changed, 23 insertions(+), 7 deletions(-)
>>
>> diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
>> index 859ae05339ad..bfe86479a83c 100644
>> --- a/fs/ext4/ext4.h
>> +++ b/fs/ext4/ext4.h
>> @@ -3101,7 +3101,7 @@ extern int ext4_meta_trans_blocks(struct inode *inode, int lblocks,
>> int pextents);
>> extern int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end);
>> extern int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart,
>> - loff_t length);
>> + loff_t length, bool *did_zero);
>> extern vm_fault_t ext4_page_mkwrite(struct vm_fault *vmf);
>> extern qsize_t *ext4_get_reserved_space(struct inode *inode);
>> extern int ext4_get_projid(struct inode *inode, kprojid_t *projid);
>> diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
>> index 002b1ec8cee2..16386f499138 100644
>> --- a/fs/ext4/extents.c
>> +++ b/fs/ext4/extents.c
>> @@ -4673,6 +4673,7 @@ static long ext4_zero_range(struct file *file, loff_t offset,
>> loff_t align_start, align_end, new_size = 0;
>> loff_t end = offset + len;
>> unsigned int blocksize = i_blocksize(inode);
>> + bool partial_zeroed = false;
>> int ret, flags;
>>
>> trace_ext4_zero_range(inode, offset, len, mode);
>> @@ -4728,9 +4729,15 @@ static long ext4_zero_range(struct file *file, loff_t offset,
>> return ret;
>>
>> /* Zero out partial block at the edges of the range */
>> - ret = ext4_zero_partial_blocks(inode, offset, len);
>> + ret = ext4_zero_partial_blocks(inode, offset, len, &partial_zeroed);
>> if (ret)
>> return ret;
>> + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) {
>> + ret = filemap_write_and_wait_range(inode->i_mapping, offset,
>> + end - 1);
>> + if (ret)
>> + return ret;
>> + }
>
> Shouldn't we handle this somewhat below, where we do:
>
> if (file->f_flags & O_SYNC)
> ext4_handle_sync(handle);
>
> It would be logical to keep these together... Similarly for
> ext4_punch_hole() below. An yes, the check when calling ext4_handle_sync()
> should be extended with an IS_SYNC() check, which is a preexisting bug.
>
> Honza
Yeah, right, I've done this in the next patch. Thank you for your review!
Cheers,
Yi.
>
>>
>> handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
>> if (IS_ERR(handle)) {
>> diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
>> index 8aa4369e3150..b934ad86a96d 100644
>> --- a/fs/ext4/inode.c
>> +++ b/fs/ext4/inode.c
>> @@ -4227,7 +4227,8 @@ int ext4_block_zero_eof(struct inode *inode, loff_t from, loff_t end)
>> return 0;
>> }
>>
>> -int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length)
>> +int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length,
>> + bool *did_zero)
>> {
>> struct super_block *sb = inode->i_sb;
>> unsigned partial_start, partial_end;
>> @@ -4244,20 +4245,21 @@ int ext4_zero_partial_blocks(struct inode *inode, loff_t lstart, loff_t length)
>> /* Handle partial zero within the single block */
>> if (start == end &&
>> (partial_start || (partial_end != sb->s_blocksize - 1))) {
>> - err = ext4_block_zero_range(inode, lstart, length, NULL, NULL);
>> + err = ext4_block_zero_range(inode, lstart, length, did_zero,
>> + NULL);
>> return err;
>> }
>> /* Handle partial zero out on the start of the range */
>> if (partial_start) {
>> err = ext4_block_zero_range(inode, lstart, sb->s_blocksize,
>> - NULL, NULL);
>> + did_zero, NULL);
>> if (err)
>> return err;
>> }
>> /* Handle partial zero out on the end of the range */
>> if (partial_end != sb->s_blocksize - 1)
>> err = ext4_block_zero_range(inode, byte_end - partial_end,
>> - partial_end + 1, NULL, NULL);
>> + partial_end + 1, did_zero, NULL);
>> return err;
>> }
>>
>> @@ -4406,6 +4408,7 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)
>> loff_t end = offset + length;
>> handle_t *handle;
>> unsigned int credits;
>> + bool partial_zeroed = false;
>> int ret;
>>
>> trace_ext4_punch_hole(inode, offset, length, 0);
>> @@ -4441,9 +4444,15 @@ int ext4_punch_hole(struct file *file, loff_t offset, loff_t length)
>> if (ret)
>> return ret;
>>
>> - ret = ext4_zero_partial_blocks(inode, offset, length);
>> + ret = ext4_zero_partial_blocks(inode, offset, length, &partial_zeroed);
>> if (ret)
>> return ret;
>> + if (((file->f_flags & O_SYNC) || IS_SYNC(inode)) && partial_zeroed) {
>> + ret = filemap_write_and_wait_range(inode->i_mapping, offset,
>> + end - 1);
>> + if (ret)
>> + return ret;
>> + }
>>
>> if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
>> credits = ext4_chunk_trans_extent(inode, 0);
>> --
>> 2.52.0
>>
^ permalink raw reply
* Re: [PATCH] ext4: fix missing brelse() in ext4_xattr_inode_dec_ref_all()
From: Ritesh Harjani @ 2026-04-02 1:04 UTC (permalink / raw)
To: skoyama.kernel, linux-ext4; +Cc: Sohei Koyama
In-Reply-To: <20260330174248.71268-1-skoyama@ddn.com>
skoyama.kernel@gmail.com writes:
> From: Sohei Koyama <skoyama@ddn.com>
>
> The commit c8e008b60492 ("ext4: ignore xattrs past end")
> introduced a refcount leak in when block_csum is false.
>
> ext4_xattr_inode_dec_ref_all() calls ext4_get_inode_loc() to
> get iloc.bh, but never releases it with brelse().
>
> Fixes: c8e008b60492 ("ext4: ignore xattrs past end")
Nice catch, indeed we need to decrement the refcount for bh there.
Since this patch made into stable releases, so I guess it's good to
Cc:stable too.
Feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
^ permalink raw reply
* Re: [PATCH v5 3/5] ext4: fix the error handling process in extents_kunit_init).
From: Ritesh Harjani @ 2026-04-02 0:21 UTC (permalink / raw)
To: Ye Bin, tytso, adilger.kernel, linux-ext4; +Cc: jack
In-Reply-To: <20260330133035.287842-4-yebin@huaweicloud.com>
Ye Bin <yebin@huaweicloud.com> writes:
> From: Ye Bin <yebin10@huawei.com>
>
> The error processing in extents_kunit_init() is improper, causing
> resource leakage.
> Reconstruct the error handling process to prevent potential resource
> leaks
>
> Fixes: cb1e0c1d1fad ("ext4: kunit tests for extent splitting and conversion")
> Signed-off-by: Ye Bin <yebin10@huawei.com>
> ---
> fs/ext4/extents-test.c | 50 +++++++++++++++++++++++++++++-------------
> 1 file changed, 35 insertions(+), 15 deletions(-)
Looks better now. Thanks for taking care of it.
Feel free to add:
Reviewed-by: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox