Linux EXT4 FS development
 help / color / mirror / Atom feed
* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
From: Theodore Tso @ 2026-04-03 23:11 UTC (permalink / raw)
  To: Andreas Dilger; +Cc: 4fqr, linux-ext4@vger.kernel.org
In-Reply-To: <0778B5AC-16ED-4736-AE64-849541629466@dilger.ca>

On Fri, Apr 03, 2026 at 10:17:38AM -0600, Andreas Dilger wrote:
> 
> I don't see how this exposes any kind of security vulnerability if it
> requires that the image be specially modified in the first place?
> At that point the "attacker" can directly modify the image in any way
> they want, regardless of how e2fsprogs behaves.

After taking a closer look the "vulnerabilities", I understand where
Andreas is coming from.  If the threat model is "the attacker can
modify the file system, then the fact that you can craft the orphan to
"force" the the file system to release an inode isn't interesting ---
because the attacker could just simply overwrite the inode and mark
the blocks as not in use in the block allocation directly.

If there was a way an attempt to pass an inode number which is very
large to release_orphan_inode() could result in a buffer overrun, then
that might be interesting.  But the oh, nooes!  You might be able to
force the file system to release the resize inode is not interesting;
the attacker could just stomp on the resize inode directly.

Should we add some better bounds checking?  Sure, so that we can give
a more user-friendly error message, and reduce the chance of
accidentally making things worse if the file system is corrupted by
chance and metadata checksum is not enabled.  But is it a "security
vulnerability"?  No.

No, if you could actually force a malicious payload to run due to a
stack overrun attack, that would be interesting.  And I was expecting
to find *something* like that in your analysis, only to be
disappointed.

Cheers,

					- Ted

^ permalink raw reply

* Re: [PATCH] e2fsck: large dir rehash fix
From: Theodore Ts'o @ 2026-04-03 21:10 UTC (permalink / raw)
  To: linux-ext4, Alexander Zarochentsev
  Cc: Theodore Ts'o, Andreas Dilger, Artem Blagodarenko,
	Alexander Zarochentsev
In-Reply-To: <20260226201334.3260754-1-alexander.zarochentsev@hpe.com>


On Thu, 26 Feb 2026 20:13:34 +0000, Alexander Zarochentsev wrote:
> Use EXT2_I_SIZE() macro instead of direct access to i_size in
> fill_dir_block() and other functions. W/o the fix, e2fsck -D fails to
> optimise 4GB+ directories reporting "EXT2 directory corrupted".
> 
> Fixing a defect of overwriting the node limit and count by the first dx
> entry in calculate_tree().
> 
> [...]

Applied, thanks!

[1/1] e2fsck: large dir rehash fix
      commit: 0e4de3c7cb8cd1a4a26b0cafa647dd380da28864

Best regards,
-- 
Theodore Ts'o <tytso@mit.edu>

^ permalink raw reply

* Re: [PATCH] libsupport: change get_thread_id return type to unsigned long long
From: Theodore Ts'o @ 2026-04-03 21:10 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: Theodore Ts'o, Ext4 Developers List
In-Reply-To: <20260403151927.GB6192@frogsfrogsfrogs>


On Fri, 03 Apr 2026 08:19:27 -0700, Darrick J. Wong wrote:
> With -Wformat enabled, the e2fsprogs build spews out a lot of warnings
> for fuse2fs:
> 
>  ../../misc/fuse2fs.c: In function ‘__fuse2fs_finish’:
>  ../../misc/fuse2fs.c:152:24: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 3 has type ‘uint64_t’ {aka ‘long unsigned int’} [-Wformat=]
>    152 |                 printf("FUSE2FS (%s): tid=%llu " format, (fuse2fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \
>        |                        ^~~~~~~~~~~~~~~~~~~~~~~~~                              ~~~~~~~~~~~~~~~
>        |                                                                               |
>        |                                                                               uint64_t {aka long unsigned int}
>  ../../misc/fuse2fs.c:556:17: note: in expansion of macro ‘dbg_printf’
>    556 |                 dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
>        |                 ^~~~~~~~~~
> 
> [...]

Applied, thanks!

[1/1] libsupport: change get_thread_id return type to unsigned long long
      commit: 582a5ff290423e6fc11e91ba0534e422c026f018

Best regards,
-- 
Theodore Ts'o <tytso@mit.edu>

^ permalink raw reply

* Re: [PATCH 1/3] libsupport: fix portability issues with the bthread.c
From: Theodore Tso @ 2026-04-03 21:00 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: Ext4 Developers List
In-Reply-To: <20260403151535.GI6254@frogsfrogsfrogs>

On Fri, Apr 03, 2026 at 08:15:35AM -0700, Darrick J. Wong wrote:
> > It's "autoheader".  Whenver you add or remove tests to configure.ac,
> > you need to run "autoconf ; autoheader".  If you don't run
> > "autoheader", then even if the autoconf's feature test enables some
> > new feature test, say HAVE_PR_SET_IO_FLUSHER, the feature won't
> > actually be enabled in the #ifdef.
> 
> Aha!  Thanks for that tip; I'll go fix my dev branch.

Just to give more context, there are two ways that configure scripts
can pass the feature definitions to programs.  One is via the command
line of the compiler.  The problem with that is if you have dozens of
options like -DHAVE_PR_SET_IO_FLUSHER for every single compile, it
bloats the MAKELOG files and makes it hard to read, and eventually you
can run into the command-line length limits.

The other way is via a config.h file which is then #included in each
source file.  But for *that* to work, the config.h.in file needs to
have a template for echo variable that can be #defined.  And that's
what autoheader takes care of for you.  Simpler projects that don't
use a config.h file and pass everything on the compiler's command line
don't need to use autoheader.

For a demonstration, try editing lib/config.h.in to remove the lines:

-/* Define to 1 if PR_SET_IO_FLUSHER is present */
-#undef HAVE_PR_SET_IO_FLUSHER

Then "rm lib/config.h" and run "./config.status", and examine
lib/config.h.  You will see that HAVE_PR_SET_IO_FLUSHER is no longer
defined in the config.h file.  Then run "autoheader", and then re-run
"./config.status", and you will see that HAVE_PR_SET_IO_FLUSHER is
properly defined.

Cheers,

					- Ted

^ permalink raw reply

* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
From: Theodore Tso @ 2026-04-03 18:34 UTC (permalink / raw)
  To: Andreas Dilger; +Cc: 4fqr, linux-ext4@vger.kernel.org
In-Reply-To: <0778B5AC-16ED-4736-AE64-849541629466@dilger.ca>

On Fri, Apr 03, 2026 at 10:17:38AM -0600, Andreas Dilger wrote:
> 
> I don't see how this exposes any kind of security vulnerability if it
> requires that the image be specially modified in the first place?
> At that point the "attacker" can directly modify the image in any way
> they want, regardless of how e2fsprogs behaves.

We can debate whther or not it is a security vulnerability, and if so,
whether it deserves a CVSS corresponding to a low, medium, or high
severity since that's what people who are worrying about FEDRAMP
compliance need to worry about in terms of time to mitigation.

The reason why it's of interest is because we tell people that if they
have a potentially untrustwothy file system image (say, they if they
pick up a USB stick that was thoughtfully dropped off in the parking
lot by the Chinese MSS or Russian KGB agent), they should run e2fsck
on said file system image before they try mounting it.  Asking them to
run e2fsck on the image is only a little bit more likely to work than
telling them to just not pick up the USB stick, or DESTROY IT BY FIRE,
and so the reality is that 99% of all uesrs will just mount the file
system without running fsck --- at which point, when their system is
compromised, we call it a "problem exists between chair and keyboard"
situation.


I don't really care whether we call it a security bug, or a quality of
implementation bug, but that's because I'm not the person responsible
for FEDRAMP compliance at $WORK, and I'm not someone who hands out bug
bounties, or who is trying to keep score of how many security
vulnerabilities that I found in order to demonstrate real world impact
as part of an academic tenure-track evaluation.  :-)

As far as I'm concerned, if e2fsck doesn't (a) detect a file system
corruption, or (b) doesn't fix it after a single e2fsck -y run, or
(c) crashes or results in running a malicious payload as a result of a
specially crafted payload, it's a bug, and bugs should get fixed.

Cheers,

					- Ted

^ permalink raw reply

* Re: [PATCH 2/3] ext4: show inode orphan list detail information
From: Andreas Dilger @ 2026-04-03 16:22 UTC (permalink / raw)
  To: Ye Bin; +Cc: tytso, linux-ext4, jack
In-Reply-To: <20260403082507.1882703-3-yebin@huaweicloud.com>

On Apr 3, 2026, at 02:25, Ye Bin <yebin@huaweicloud.com> wrote:
> 
> 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.

Since the format of this output is still flexible, I would request that
it be formatted in a way that is more easily understood and parsed instead
of just an unformatted list of numbers.

Using YAML combines both human and machine readable properties, without
a lot of overhead, something like:

- ino: INUM, link: NLINK, size: SIZE, blocks: BLOCKS, proj: PROJID, path: "PATH"
- ino: ...
- ino: ...

The PATH should derfinitely be quoted to avoid issues with spaces and
other special characters in the filename.

Cheers, Andreas

> 
> 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
> 


Cheers, Andreas






^ permalink raw reply

* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
From: Andreas Dilger @ 2026-04-03 16:17 UTC (permalink / raw)
  To: 4fqr; +Cc: linux-ext4@vger.kernel.org
In-Reply-To: <DJq7WqGAG9HBnpd-DVD5sVNGDUoSoP2sJ5RAXlucoYhtbqxJXFNUUfhFETwaDeDr6Q8a5xp0hVJb0yIt8EucI-F-PQ28zg8i0Y9HmF8qk5Q=@proton.me>

On Apr 3, 2026, at 05:29, 4fqr <4fqr@proton.me> wrote:
> 
> 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.

I don't see how this exposes any kind of security vulnerability if it
requires that the image be specially modified in the first place?
At that point the "attacker" can directly modify the image in any way
they want, regardless of how e2fsprogs behaves.

It's like saying "the curatins on the inside of my windows do not
protect my privacy if I'm inside the house and I open them up...

Cheers, Andreas

> 
> **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
> <e2fsprogs_audit_4fqr.txt>


Cheers, Andreas






^ permalink raw reply

* [PATCH] libsupport: change get_thread_id return type to unsigned long long
From: Darrick J. Wong @ 2026-04-03 15:19 UTC (permalink / raw)
  To: Theodore Ts'o; +Cc: Ext4 Developers List

From: Darrick J. Wong <djwong@kernel.org>

With -Wformat enabled, the e2fsprogs build spews out a lot of warnings
for fuse2fs:

 ../../misc/fuse2fs.c: In function ‘__fuse2fs_finish’:
 ../../misc/fuse2fs.c:152:24: warning: format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 3 has type ‘uint64_t’ {aka ‘long unsigned int’} [-Wformat=]
   152 |                 printf("FUSE2FS (%s): tid=%llu " format, (fuse2fs)->shortdev, get_thread_id(), ##__VA_ARGS__); \
       |                        ^~~~~~~~~~~~~~~~~~~~~~~~~                              ~~~~~~~~~~~~~~~
       |                                                                               |
       |                                                                               uint64_t {aka long unsigned int}
 ../../misc/fuse2fs.c:556:17: note: in expansion of macro ‘dbg_printf’
   556 |                 dbg_printf(ff, "%s: libfuse ret=%d\n", func, ret);
       |                 ^~~~~~~~~~

Because the linter doesn't like mixing %llu with a uint64_t type.
On some platforms, uint64_t is merely "unsigned long" and whiiiiine.

Since this is a new wrapper function, let's change the return type.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
 lib/support/thread.h |    2 +-
 lib/support/thread.c |    4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lib/support/thread.h b/lib/support/thread.h
index 9a7f5c9db1b7a2..efba1c3b1eec0b 100644
--- a/lib/support/thread.h
+++ b/lib/support/thread.h
@@ -2,4 +2,4 @@
  * thread.h -- header file for thread utilities
  */
 
-uint64_t get_thread_id(void);
+unsigned long long get_thread_id(void);
diff --git a/lib/support/thread.c b/lib/support/thread.c
index a9a10940c0fd3e..651be1cf833860 100644
--- a/lib/support/thread.c
+++ b/lib/support/thread.c
@@ -12,7 +12,7 @@
 
 #include "support/thread.h"
 
-uint64_t get_thread_id(void)
+unsigned long long get_thread_id(void)
 {
 #if defined(HAVE_GETTID)
 	return gettid();
@@ -22,7 +22,7 @@ uint64_t get_thread_id(void)
 	if (pthread_threadid_np(NULL, &tid))
 		return tid;
 #elif defined(HAVE_PTHREAD)
-	return (__u64)(uintptr_t) pthread_self();
+	return (unsigned long long)(uintptr_t) pthread_self();
 #endif
 	return getpid();
 }

^ permalink raw reply related

* Re: [PATCH 1/3] libsupport: fix portability issues with the bthread.c
From: Darrick J. Wong @ 2026-04-03 15:15 UTC (permalink / raw)
  To: Theodore Tso; +Cc: Ext4 Developers List
In-Reply-To: <20260403115313.GA12260@macsyma-wired.lan>

On Fri, Apr 03, 2026 at 07:53:13AM -0400, Theodore Tso wrote:
> On Thu, Apr 02, 2026 at 09:16:24PM -0700, Darrick J. Wong wrote:
> > > -/* 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?
> 
> It's "autoheader".  Whenver you add or remove tests to configure.ac,
> you need to run "autoconf ; autoheader".  If you don't run
> "autoheader", then even if the autoconf's feature test enables some
> new feature test, say HAVE_PR_SET_IO_FLUSHER, the feature won't
> actually be enabled in the #ifdef.

Aha!  Thanks for that tip; I'll go fix my dev branch.

--D

> Cheers,
> 
> 							- Ted

^ permalink raw reply

* Re: [SECURITY] e2fsprogs v1.47.4 Vulnerabilities — Orphan File & Extent Handling
From: Theodore Tso @ 2026-04-03 13:48 UTC (permalink / raw)
  To: 4fqr; +Cc: linux-ext4@vger.kernel.org
In-Reply-To: <DJq7WqGAG9HBnpd-DVD5sVNGDUoSoP2sJ5RAXlucoYhtbqxJXFNUUfhFETwaDeDr6Q8a5xp0hVJb0yIt8EucI-F-PQ28zg8i0Y9HmF8qk5Q=@proton.me>

On Fri, Apr 03, 2026 at 11:29:55AM +0000, 4fqr wrote:
> 
> 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).

You notified me 45 seconds before sending this e-mail to linux-ext4,
which is a public mailing list.  (For future reference, essentially
all lists @vger.kernel.org are public, with the contents available at
https://lore.kernel.org.)

> Patches and coordination discussion will follow once the maintainer
> has reviewed.

Coordination discussion is moot at this point, because you've already
made your findings public.  I'll review them in detail in the next few
days, but it does appear that we are missing some checks in the
orphan_file handling, which whether or not they are exploitable by a
malicious attacker, are real bugs that should be fixed.

Cheers,

					- Ted

^ permalink raw reply

* Re: [PATCH 0/3] show orphan file inode detail info
From: Theodore Tso @ 2026-04-03 13:03 UTC (permalink / raw)
  To: Ye Bin; +Cc: adilger.kernel, linux-ext4, jack
In-Reply-To: <20260403082507.1882703-1-yebin@huaweicloud.com>

On Fri, Apr 03, 2026 at 04:25:04PM +0800, Ye Bin wrote:
> 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.

There are some concens which were noted by Sashiko review, including
races with unmountings, a potential deadlock, and some issues relating
to long pathnames, and a TOCTOU race that might lead to a file system
erroneously being declared corrupted.  PTAL:

https://sashiko.dev/#/patchset/20260403082507.1882703-1-yebin%40huaweicloud.com

Thanks!

						- Ted

^ permalink raw reply

* Re: [PATCH 1/3] ext4: register 'orphan_list' procfs
From: Theodore Tso @ 2026-04-03 12:55 UTC (permalink / raw)
  To: Ye Bin; +Cc: adilger.kernel, linux-ext4, jack
In-Reply-To: <20260403082507.1882703-2-yebin@huaweicloud.com>

On Fri, Apr 03, 2026 at 04:25:05PM +0800, Ye Bin wrote:
> +		proc_create_data("orphan_list", 0444, sbi->s_proc,
> +				 &ext4_orphan_proc_ops, sb);

This should really be mode 0400, especially once the file path is made
available, since otherwise the kernel might end up leaking private
user's information.  Even in a data center use case, in a multi-user
container use case (Docker, Kubernetes, etc.) leaking information
about one user's file names could be a real problem.

      	  	      	    	     - Ted

^ permalink raw reply

* Re: [PATCH 1/3] libsupport: fix portability issues with the bthread.c
From: Theodore Tso @ 2026-04-03 11:53 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: Ext4 Developers List
In-Reply-To: <20260403041624.GD6254@frogsfrogsfrogs>

On Thu, Apr 02, 2026 at 09:16:24PM -0700, Darrick J. Wong wrote:
> > -/* 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?

It's "autoheader".  Whenver you add or remove tests to configure.ac,
you need to run "autoconf ; autoheader".  If you don't run
"autoheader", then even if the autoconf's feature test enables some
new feature test, say HAVE_PR_SET_IO_FLUSHER, the feature won't
actually be enabled in the #ifdef.

Cheers,

							- Ted

^ permalink raw reply

* [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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox