Hello,
Thanks for working on it, that'll clean quite a few warnings that scare
people :)
Milos Nikic, le mer. 16 sept. 2026 04:47:31 -0700, a ecrit:
> Even if file system itself doesn't fix it, fsck understands this
> implementation of orphan list and will gracefully clean it up first
> time it sees it.
Does it do it even when the check count was not reached?
Is it perhaps the duty of ext2fs to process it on filesystem mount?
Samuel
> It works by utilizing superblock's s_last_orphan as the head of the
> singly linked list. If s_last_orphan is 0 the list is empty and there
> are no orphans. Otherwise it points to the last element added to that
> list. That element is the node that needs to be deleted, so by
> convention its i_dtime is altered to point to the next element and so on.
> ---
> ext2fs/Makefile | 2 +-
> ext2fs/ext2fs.c | 2 +
> ext2fs/ext2fs.h | 7 ++
> ext2fs/inode.c | 22 ++--
> ext2fs/orphan.c | 215 ++++++++++++++++++++++++++++++++++++++++
> libdiskfs/Makefile | 2 +-
> libdiskfs/dir-link.c | 4 +
> libdiskfs/dir-rename.c | 2 +
> libdiskfs/dir-renamed.c | 4 +
> libdiskfs/dir-rmdir.c | 2 +
> libdiskfs/dir-unlink.c | 5 +-
> libdiskfs/diskfs.h | 14 +++
> libdiskfs/node-drop.c | 6 +-
> libdiskfs/orphan.c | 40 ++++++++
> 14 files changed, 316 insertions(+), 11 deletions(-)
> create mode 100644 ext2fs/orphan.c
> create mode 100644 libdiskfs/orphan.c
>
> diff --git a/ext2fs/Makefile b/ext2fs/Makefile
> index a2b0f1eef..3a8f1ada0 100644
> --- a/ext2fs/Makefile
> +++ b/ext2fs/Makefile
> @@ -22,7 +22,7 @@ makemode := server
> target = ext2fs
> SRCS = balloc.c dir.c ext2fs.c getblk.c hyper.c ialloc.c \
> inode.c pager.c pokel.c truncate.c storeinfo.c msg.c xinl.c \
> - xattr.c journal.c
> + xattr.c journal.c orphan.c
> OBJS = $(SRCS:.c=.o)
> HURDLIBS = diskfs pager iohelp fshelp store ports ihash shouldbeinlibc
> LDLIBS = -lpthread $(and $(HAVE_LIBBZ2),-lbz2) $(and $(HAVE_LIBZ),-lz)
> diff --git a/ext2fs/ext2fs.c b/ext2fs/ext2fs.c
> index 984df0448..5b996370e 100644
> --- a/ext2fs/ext2fs.c
> +++ b/ext2fs/ext2fs.c
> @@ -271,6 +271,8 @@ main (int argc, char **argv)
> fprintf (stderr, "ext2fs: journaling enabled on %s\n",
> diskfs_disk_name);
> JRNL_LOG_DEBUG ("Global Journal Initialized at %p", ext2_journal);
> diskfs_nput(jnode);
> + /* Recover any orphan inodes left from a previous unclean shutdown. */
> + ext2_recover_orphan_list ();
> }
> }
> else
> diff --git a/ext2fs/ext2fs.h b/ext2fs/ext2fs.h
> index 0975457d1..65946dd9e 100644
> --- a/ext2fs/ext2fs.h
> +++ b/ext2fs/ext2fs.h
> @@ -179,6 +179,10 @@ struct disknode
> partially allocated. */
> int last_page_partially_writable;
>
> + /* True if this inode is on the ext3 orphan list (nlink=0 but still
> + open). The i_dtime field is used as the next pointer. */
> + int on_orphan_list;
> +
> /* Index to start a directory lookup at. */
> int dir_idx;
> };
> @@ -490,6 +494,9 @@ _dino_deref (struct ext2_inode *inode)
>
> /* Write all active disknodes into the inode pager. */
> void write_all_disknodes (void);
> +
> +/* Recover (clean up) the orphan inode list at mount time. */
> +void ext2_recover_orphan_list (void);
>
> /* ---------------------------------------------------------------- */
>
> diff --git a/ext2fs/inode.c b/ext2fs/inode.c
> index 5fa5165e6..46df08251 100644
> --- a/ext2fs/inode.c
> +++ b/ext2fs/inode.c
> @@ -488,14 +488,22 @@ write_node (struct node *np)
> info->i_flags |= EXT2_IMMUTABLE_FL;
> di->i_flags = htole32 (info->i_flags);
>
> - if (st->st_mode == 0)
> - /* Set dtime non-zero to indicate a deleted file.
> - We don't clear i_size, i_blocks, and i_translator in this case,
> - to give "undeletion" utilities a chance. */
> - di->i_dtime = htole32 (di->i_mtime);
> - else
> + /* The i_dtime field is actively used as a linked list pointer
> + by the orphan list machinery, which updates the disk cache
> + Do not overwrite it for orphans. */
> + if (!diskfs_node_disknode (np)->on_orphan_list)
> + {
> + if (st->st_mode == 0)
> + /* Set dtime non-zero to indicate a deleted file. */
> + di->i_dtime = htole32 (di->i_mtime);
> + else
> + di->i_dtime = htole32 (0);
> + }
> +
> + /* We don't clear i_size, i_blocks, and i_translator if mode is 0,
> + to give "undeletion" utilities a chance. */
> + if (st->st_mode != 0)
> {
> - di->i_dtime = htole32 (0);
> di->i_size = htole32 (st->st_size);
> if (sizeof (off_t) >= 8 && !S_ISDIR (st->st_mode))
> /* 64bit file size */
> diff --git a/ext2fs/orphan.c b/ext2fs/orphan.c
> new file mode 100644
> index 000000000..bdd0eb90b
> --- /dev/null
> +++ b/ext2fs/orphan.c
> @@ -0,0 +1,215 @@
> +/* Ext3-style Orphan Inode List implementation for ext2fs.
> + When a file is unlinked (nlink=0) but still held open by a process,
> + the inode is added to the orphan list (anchored at s_last_orphan in
> + the superblock). Each orphaned inode uses its i_dtime field as a
> + "next" pointer in the singly-linked list. On mount, the list is
> + traversed and each orphan is cleaned up (truncated and freed).
> +
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + Written by Milos Nikic.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 2, or (at your option)
> + any later version.
> +
> + This program is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +#include "ext2fs.h"
> +#include "journal.h"
> +#include <pthread.h>
> +
> +/* Dedicated mutex to protect the Ext3 orphan linked list and
> s_last_orphan.*/
> +static pthread_mutex_t orphan_lock = PTHREAD_MUTEX_INITIALIZER;
> +
> +/* Add inode NP to the orphan list. */
> +void
> +diskfs_orphan_add (struct node *np)
> +{
> + ino_t inum = np->cache_id;
> + struct ext2_inode *di;
> + diskfs_transaction_t *txn = NULL;
> +
> + if (!ext2_journal)
> + return;
> +
> + assert_backtrace (!diskfs_readonly);
> + assert_backtrace (np->dn_stat.st_nlink == 0);
> +
> + if (diskfs_node_disknode (np)->on_orphan_list)
> + return;
> +
> + ext2_debug ("adding inode %lu to orphan list", (unsigned long) inum);
> +
> + txn = diskfs_journal_start_transaction ();
> +
> + pthread_mutex_lock (&orphan_lock);
> +
> + di = dino_ref (inum);
> +
> + /* Set i_dtime to the current s_last_orphan (the old list head). */
> + di->i_dtime = sblock->s_last_orphan;
> + di->i_links_count = 0;
> +
> + /* Update the superblock to point to this inode as the new list head. */
> + sblock->s_last_orphan = htole32 (inum);
> + sblock_dirty = 1;
> +
> + if (txn)
> + {
> + journal_dirty_block (txn, boffs_block (bptr_offs (di)));
> +
> + /* Sync our private sblock to the Mach disk cache so the journal
> captures it */
> + memcpy (boffs_ptr (SBLOCK_OFFS), sblock, SBLOCK_SIZE);
> + journal_dirty_block (txn, boffs_block (SBLOCK_OFFS));
> + }
> +
> + dino_deref (di);
> +
> + diskfs_node_disknode (np)->on_orphan_list = 1;
> + pthread_mutex_unlock (&orphan_lock);
> +
> + if (txn)
> + diskfs_journal_stop_transaction (txn);
> + else
> + alloc_sync (np);
> +}
> +
> +/* Remove inode NP from the orphan list. */
> +void
> +diskfs_orphan_del (struct node *np)
> +{
> + if (!ext2_journal)
> + return;
> +
> + ino_t inum = np->cache_id;
> + __u32 prev_orphan;
> + diskfs_transaction_t *txn = NULL;
> +
> + if (!diskfs_node_disknode (np)->on_orphan_list)
> + return;
> +
> + ext2_debug ("removing inode %lu from orphan list", (unsigned long)inum);
> +
> + txn = diskfs_journal_start_transaction ();
> +
> + pthread_mutex_lock (&orphan_lock);
> +
> + struct ext2_inode *my_di = dino_ref (inum);
> + __u32 my_next = le32toh (my_di->i_dtime);
> +
> + /* Scrub our own i_dtime. If we don't, e2fsck's Pass 1 will see the
> leftover
> + inode number (< s_inodes_count), mistake it for a stale linked-list
> + pointer, and declare the inode a refugee of a corrupted orphan list! */
> + my_di->i_dtime = 0;
> +
> + if (txn)
> + journal_dirty_block (txn, boffs_block (bptr_offs (my_di)));
> +
> + dino_deref (my_di);
> +
> + prev_orphan = le32toh (sblock->s_last_orphan);
> +
> + if (prev_orphan == inum)
> + {
> + sblock->s_last_orphan = htole32 (my_next);
> + sblock_dirty = 1;
> +
> + if (txn)
> + {
> + memcpy (boffs_ptr (SBLOCK_OFFS), sblock, SBLOCK_SIZE);
> + journal_dirty_block (txn, boffs_block (SBLOCK_OFFS));
> + }
> + }
> + else
> + {
> + /* Walk the list to find the predecessor. */
> + while (prev_orphan != 0)
> + {
> + struct ext2_inode *prev_di = dino_ref (prev_orphan);
> + __u32 next = le32toh (prev_di->i_dtime);
> +
> + if (next == inum)
> + {
> + /* Found the predecessor. Update its i_dtime to skip over us.
> */
> + prev_di->i_dtime = htole32 (my_next);
> +
> + if (txn)
> + journal_dirty_block (txn, boffs_block (bptr_offs (prev_di)));
> +
> + dino_deref (prev_di);
> + break;
> + }
> + dino_deref (prev_di);
> + prev_orphan = next;
> + }
> + }
> +
> + diskfs_node_disknode (np)->on_orphan_list = 0;
> +
> + pthread_mutex_unlock (&orphan_lock);
> +
> + if (txn)
> + diskfs_journal_stop_transaction (txn);
> + else
> + alloc_sync (np);
> +}
> +
> +/* Recover (clean up) the orphan list at mount time. */
> +void
> +ext2_recover_orphan_list (void)
> +{
> + if (diskfs_readonly)
> + {
> + ext2_warning ("orphan inodes on readonly fs; leaving for fsck");
> + return;
> + }
> +
> + ino_t inum;
> + __u32 next_orphan;
> + int count = 0;
> + struct ext2_inode *di;
> + struct node *np = NULL;
> + error_t err;
> +
> + next_orphan = le32toh (sblock->s_last_orphan);
> +
> + if (next_orphan == 0)
> + return;
> +
> + ext2_warning ("recovering orphan inode list (head=%u)", next_orphan);
> +
> + while (next_orphan != 0)
> + {
> + inum = next_orphan;
> +
> + /* Grab the next pointer directly from the disk structure */
> + di = dino_ref (inum);
> + next_orphan = le32toh (di->i_dtime);
> + dino_deref (di);
> +
> + err = diskfs_cached_lookup (inum, &np);
> + if (err || !np)
> + {
> + ext2_warning ("cannot look up orphan inode %lu: %s",
> + (unsigned long)inum, strerror (err));
> + continue;
> + }
> + diskfs_node_disknode (np)->on_orphan_list = 1;
> +
> + diskfs_nput (np);
> + count++;
> + }
> +
> + if (count > 0)
> + ext2_warning ("recovered %d orphan inode(s)", count);
> +}
> diff --git a/libdiskfs/Makefile b/libdiskfs/Makefile
> index 2b5a4a3b9..341532466 100644
> --- a/libdiskfs/Makefile
> +++ b/libdiskfs/Makefile
> @@ -52,7 +52,7 @@ OTHERSRCS = conch-fetch.c conch-set.c dir-clear.c
> dir-init.c dir-renamed.c \
> remount.c console.c disk-pager.c \
> name-cache.c direnter.c dirrewrite.c dirremove.c lookup.c dead-name.c \
> validate-mode.c validate-group.c validate-author.c validate-flags.c \
> - validate-rdev.c validate-owner.c priv.c get-source.c journal.c
> + validate-rdev.c validate-owner.c priv.c get-source.c journal.c orphan.c
> SRCS = $(OTHERSRCS) $(FSSRCS) $(IOSRCS) $(FSYSSRCS) $(IFSOCKSRCS)
> installhdrs = diskfs.h diskfs-pager.h
>
> diff --git a/libdiskfs/dir-link.c b/libdiskfs/dir-link.c
> index ec3c0a3d8..608ff2183 100644
> --- a/libdiskfs/dir-link.c
> +++ b/libdiskfs/dir-link.c
> @@ -121,6 +121,8 @@ diskfs_S_dir_link (struct protid *dircred,
> {
> /* Deallocate link on TNP */
> tnp->dn_stat.st_nlink--;
> + if (tnp->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (tnp);
> tnp->dn_set_ctime = 1;
> diskfs_node_update (tnp, diskfs_synchronous);
> }
> @@ -132,6 +134,8 @@ diskfs_S_dir_link (struct protid *dircred,
> if (err)
> {
> np->dn_stat.st_nlink--;
> + if (np->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (np);
> np->dn_set_ctime = 1;
> diskfs_node_update (np, diskfs_synchronous);
> }
> diff --git a/libdiskfs/dir-rename.c b/libdiskfs/dir-rename.c
> index 939d0b6ae..e85ef45bb 100644
> --- a/libdiskfs/dir-rename.c
> +++ b/libdiskfs/dir-rename.c
> @@ -240,6 +240,8 @@ diskfs_S_dir_rename (struct protid *fromcred,
> diskfs_node_update (fdp, diskfs_synchronous);
>
> fnp->dn_stat.st_nlink--;
> + if (fnp->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (fnp);
> fnp->dn_set_ctime = 1;
>
> diskfs_node_update (fnp, diskfs_synchronous);
> diff --git a/libdiskfs/dir-renamed.c b/libdiskfs/dir-renamed.c
> index 97487ce53..8fcaffdba 100644
> --- a/libdiskfs/dir-renamed.c
> +++ b/libdiskfs/dir-renamed.c
> @@ -212,6 +212,8 @@ diskfs_rename_dir (struct node *fdp, struct node *fnp,
> const char *fromname,
> if (!err)
> {
> tnp->dn_stat.st_nlink--;
> + if (tnp->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (tnp);
> tnp->dn_set_ctime = 1;
> }
> diskfs_clear_directory (tnp, tdp, tocred);
> @@ -251,6 +253,8 @@ diskfs_rename_dir (struct node *fdp, struct node *fnp,
> const char *fromname,
> diskfs_dirremove (fdp, fnp, fromname, ds);
> ds = 0;
> fnp->dn_stat.st_nlink--;
> + if (fnp->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (fnp);
> fnp->dn_set_ctime = 1;
> diskfs_file_update (fdp, diskfs_synchronous);
> diskfs_node_update (fnp, diskfs_synchronous);
> diff --git a/libdiskfs/dir-rmdir.c b/libdiskfs/dir-rmdir.c
> index de288aae1..82c20a7ea 100644
> --- a/libdiskfs/dir-rmdir.c
> +++ b/libdiskfs/dir-rmdir.c
> @@ -90,6 +90,8 @@ diskfs_S_dir_rmdir (struct protid *dircred,
> if (!error)
> {
> np->dn_stat.st_nlink--;
> + if (np->dn_stat.st_nlink == 0)
> + diskfs_orphan_add (np);
> np->dn_set_ctime = 1;
> diskfs_clear_directory (np, dnp, dircred);
> diskfs_file_update (np, diskfs_synchronous);
> diff --git a/libdiskfs/dir-unlink.c b/libdiskfs/dir-unlink.c
> index 4ceaec4b9..39eaf4bf7 100644
> --- a/libdiskfs/dir-unlink.c
> +++ b/libdiskfs/dir-unlink.c
> @@ -82,7 +82,10 @@ diskfs_S_dir_unlink (struct protid *dircred,
> diskfs_node_update (np, diskfs_synchronous);
>
> if (np->dn_stat.st_nlink == 0)
> - fshelp_fetch_control (&np->transbox, &control);
> + {
> + diskfs_orphan_add (np);
> + fshelp_fetch_control (&np->transbox, &control);
> + }
>
> /* This check is necessary because we might get here on an error while
> checking the mode on something which happens to be `.'. */
> diff --git a/libdiskfs/diskfs.h b/libdiskfs/diskfs.h
> index d8dac1293..7c86cf324 100644
> --- a/libdiskfs/diskfs.h
> +++ b/libdiskfs/diskfs.h
> @@ -584,6 +584,20 @@ int diskfs_journal_needs_sync (diskfs_transaction_t
> *txn);
> The default definition does nothing. */
> void diskfs_journal_shutdown (void);
>
> +/* Orphan Inode List hooks.
> + These are called by libdiskfs when a file is unlinked (nlink drops to
> + 0) but still held open, and when the inode is finally freed.
> + Filesystems with an ext3-style Orphan List (e.g. ext2fs) should
> + override the weak default implementations. */
> +
> +/* Add inode NP to the orphan list. Called when nlink drops to 0 while
> + the node is still held open (has hard references). NP must be locked. */
> +void diskfs_orphan_add (struct node *np);
> +
> +/* Remove inode NP from the orphan list. Called when the inode is about
> + to be permanently freed in diskfs_drop_node. NP must be locked. */
> +void diskfs_orphan_del (struct node *np);
> +
> /* The user must define this function. Sync the info in NP->dn_stat
> and any associated format-specific information to disk. If WAIT is true,
> then return only after the physicial media has been completely updated. */
> diff --git a/libdiskfs/node-drop.c b/libdiskfs/node-drop.c
> index a12c29ad0..bb2d38e26 100644
> --- a/libdiskfs/node-drop.c
> +++ b/libdiskfs/node-drop.c
> @@ -43,7 +43,7 @@ diskfs_drop_node (struct node *np)
> /* XXX: if the filesystem is readonly, we cannot remove the files with no
> link
> but e.g. memory mapping still in memory. This notably happens when
> upgrading packages without restarting the corresponding processes. Fsck
> - will have to fix them. */
> + will have to fix them or the orphan list, if implemented. */
> if (np->dn_stat.st_nlink == 0 && !diskfs_readonly)
> {
> diskfs_check_readonly ();
> @@ -85,9 +85,13 @@ diskfs_drop_node (struct node *np)
> np->dn_stat.st_rdev = 0;
> np->dn_set_ctime = np->dn_set_atime = 1;
> diskfs_node_update (np, diskfs_synchronous);
> + diskfs_orphan_del (np);
> diskfs_free_node (np, savemode);
> }
> else
> + /* Here we don't remove the node from the orphan list
> + so that on the next restart file system has the
> + opportunity to deal with it before fsck. */
> diskfs_node_update (np, diskfs_synchronous);
>
> fshelp_drop_transbox (&np->transbox);
> diff --git a/libdiskfs/orphan.c b/libdiskfs/orphan.c
> new file mode 100644
> index 000000000..fed512de3
> --- /dev/null
> +++ b/libdiskfs/orphan.c
> @@ -0,0 +1,40 @@
> +/* Default orphan list hooks for libdiskfs.
> + Provides weak no-op implementations of the orphan list functions.
> + Filesystems with an ext3-style orphan list (e.g. ext2fs) override these.
> +
> + Written by Milos Nikic.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> +
> + This file is part of the GNU Hurd.
> +
> + The GNU Hurd is free software; you can redistribute it and/or modify
> + it under the terms of the GNU General Public License as published by
> + the Free Software Foundation; either version 2, or (at your option)
> + any later version.
> +
> + This program is distributed in the hope that it will be useful, but
> + WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + General Public License for more details.
> +
> + You should have received a copy of the GNU General Public License
> + along with this program; if not, write to the Free Software
> + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */
> +
> +#include "diskfs.h"
> +
> +/* Add inode NP to the orphan list. Called when nlink drops to 0
> + while the node is still held open (has hard references).
> + NP must be locked. The default implementation does nothing. */
> +void __attribute__((weak)) diskfs_orphan_add (struct node *np)
> +{
> + /* Do nothing */
> +}
> +
> +/* Remove inode NP from the orphan list. Called when the inode is
> + about to be permanently freed in diskfs_drop_node.
> + NP must be locked. The default implementation does nothing. */
> +void __attribute__((weak)) diskfs_orphan_del (struct node *np)
> +{
> + /* Do nothing */
> +}
> --
> 2.55.0
>
--
Samuel
PS> Salut ! J'ai un sujet de philo à vous soumettre : "Suffit-il
PS> d'observer pour connaître" Idées + plan Merçi
Oui, ya qu'a t'observer pour connaître le fait que tu es une feignasse.
-+- FF in: Guide du Neuneu d'Usenet - Neuneu fait de la philo -+-