[RFC PATCH 10/24] pidfs: publish future pidfd files
From: Li Chen
Date: Thu Jul 16 2026 - 11:24:06 EST
Publish an attached future pidfd by transferring pidfs attributes and
forwarding process-exit wakeups to poll registrations made while the
file was taskless.
Make numeric pidfd opens and file-handle opens wait across the interval
between task visibility and producer publication. Internal PIDFD_STALE
callers remain nonblocking so task creation cannot wait on itself.
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Li Chen <me@linux.beauty>
---
fs/pidfs.c | 127 +++++++++++++++++++++++++++++++++++++++++-
include/linux/pidfs.h | 5 ++
2 files changed, 130 insertions(+), 2 deletions(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 36ee4f210f73f..d62235e01b351 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -116,7 +116,10 @@ struct pidfs_future_file {
const struct pidfs_future_file_ops *ops;
struct pidfs_attr *attr;
struct pid *pid;
+ wait_queue_head_t wait_pidfd;
+ wait_queue_entry_t pid_wait;
u64 ino;
+ bool pid_wait_attached;
};
static struct pidfs_node *pidfs_inode_node(const struct inode *inode)
@@ -137,6 +140,12 @@ pidfs_future_file(const struct inode *inode)
return container_of(node, struct pidfs_future_file, node);
}
+static bool pidfs_future_file_terminal(struct pidfs_future_file *future)
+{
+ return future && future->ops->is_terminal &&
+ future->ops->is_terminal(future->data);
+}
+
void *pidfs_future_file_data(const struct file *file,
const struct pidfs_future_file_ops *ops)
{
@@ -169,6 +178,21 @@ static struct pid *pidfs_inode_pid(const struct inode *inode)
return pid;
}
+static int pidfs_wait_for_published_pid(struct inode *inode)
+{
+ struct pidfs_future_file *future = pidfs_future_file(inode);
+ int status = -ESRCH;
+ int ret;
+
+ if (!future)
+ return 0;
+
+ ret = wait_event_killable(future->wait_pidfd,
+ (status = PTR_ERR_OR_ZERO(pidfs_inode_pid(inode))) !=
+ -ESRCH || pidfs_future_file_terminal(future));
+ return ret ? ret : status;
+}
+
#if BITS_PER_LONG == 32
DEFINE_SPINLOCK(pidfs_ino_lock);
@@ -370,10 +394,22 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
*/
static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
{
+ struct pidfs_future_file *future;
struct task_struct *task;
__poll_t poll_flags = 0;
- struct pid *pid = pidfd_pid(file);
+ struct pid *pid;
+ future = pidfs_future_file(file_inode(file));
+ pid = pidfd_pid(file);
+ if (IS_ERR(pid) && PTR_ERR(pid) == -ESRCH && future) {
+ /* Close the publication race after registering the future wait. */
+ poll_wait(file, &future->wait_pidfd, pts);
+ pid = pidfd_pid(file);
+ }
+
+ if (IS_ERR(pid) && PTR_ERR(pid) == -ESRCH &&
+ pidfs_future_file_terminal(future))
+ return EPOLLERR | EPOLLHUP;
if (IS_ERR(pid))
return PTR_ERR(pid) == -ESRCH ? 0 : EPOLLHUP;
@@ -927,6 +963,8 @@ static void pidfs_evict_inode(struct inode *inode)
return;
future = container_of(node, struct pidfs_future_file, node);
+ if (future->pid_wait_attached)
+ remove_wait_queue(&future->pid->wait_pidfd, &future->pid_wait);
if (future->ops->release)
future->ops->release(future->data);
if (future->attr)
@@ -934,6 +972,16 @@ static void pidfs_evict_inode(struct inode *inode)
kfree(future);
}
+static int pidfs_future_pid_wake(wait_queue_entry_t *wait,
+ unsigned int mode, int sync, void *key)
+{
+ struct pidfs_future_file *future;
+
+ future = container_of(wait, struct pidfs_future_file, pid_wait);
+ wake_up_all(&future->wait_pidfd);
+ return 0;
+}
+
static const struct super_operations pidfs_sops = {
.drop_inode = inode_just_drop,
.evict_inode = pidfs_evict_inode,
@@ -1068,12 +1116,16 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
static struct file *pidfs_export_open(const struct path *path, unsigned int oflags)
{
struct file *file;
+ int ret;
/*
* Clear O_LARGEFILE as open_by_handle_at() forces it and raise
* O_RDWR as pidfds always are.
*/
oflags &= ~O_LARGEFILE;
+ ret = pidfs_wait_for_published_pid(d_inode(path->dentry));
+ if (ret)
+ return ERR_PTR(ret);
file = dentry_open(path, oflags | O_RDWR, current_cred());
/* do_dentry_open() strips O_EXCL, which encodes PIDFD_THREAD. */
if (!IS_ERR(file))
@@ -1289,6 +1341,17 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path);
if (ret < 0)
return ERR_PTR(ret);
+ /*
+ * A future dentry can be stashed before copy_process() makes its task
+ * visible. Non-stale callers have already observed the task, so do not
+ * return that inode until its producer has published the process state.
+ * PIDFD_STALE is used by pre-task internal callers and must not wait here.
+ */
+ if (!(flags & PIDFD_STALE)) {
+ ret = pidfs_wait_for_published_pid(d_inode(path.dentry));
+ if (ret)
+ return ERR_PTR(ret);
+ }
VFS_WARN_ON_ONCE(!pid->attr);
@@ -1317,7 +1380,8 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
* associate one preallocated pid with
* pidfs_future_file_set_pid(), publish its pidfs metadata with
* pidfs_future_file_publish_pid(), make @ops->get_pid() resolve that pid, and
- * finally wake waiters with pidfs_future_file_notify().
+ * finally wake waiters with pidfs_future_file_notify(). A producer that
+ * becomes terminal without publishing must also notify its waiters.
*
* A task associated with the future pid must not become runnable before
* publication metadata and exit-wakeup forwarding are installed.
@@ -1378,6 +1442,7 @@ struct file *pidfs_alloc_future_file(const char *name, void *data,
future->data = data;
future->ops = ops;
future->ino = ino;
+ init_waitqueue_head(&future->wait_pidfd);
inode->i_private = &future->node;
return file;
}
@@ -1422,6 +1487,60 @@ int pidfs_future_file_set_pid(struct file *file, struct pid *pid)
return 0;
}
+/**
+ * pidfs_future_file_publish_pid - publish pidfs metadata for a future pid
+ * @file: future pidfs file
+ * @pid: pid previously associated with @file
+ *
+ * This transfers the future inode attributes to @pid and forwards pid exit
+ * wakeups. After it returns, the producer must publish the state used by
+ * @ops->get_pid() before calling pidfs_future_file_notify().
+ */
+void pidfs_future_file_publish_pid(struct file *file, struct pid *pid)
+{
+ struct pidfs_future_file *future = pidfs_future_file(file_inode(file));
+ struct pidfs_attr *unused = NULL;
+
+ if (WARN_ON_ONCE(!future || READ_ONCE(future->pid) != pid))
+ return;
+
+ scoped_guard(spinlock_irq, &pid->wait_pidfd.lock) {
+ if (WARN_ON_ONCE(pid->attr == PIDFS_PID_DEAD))
+ return;
+ if (!pid->attr) {
+ pid->attr = future->attr;
+ future->attr = NULL;
+ } else {
+ unused = future->attr;
+ future->attr = NULL;
+ }
+ }
+ if (unused)
+ kmem_cache_free(pidfs_attr_cachep, unused);
+
+ init_waitqueue_func_entry(&future->pid_wait, pidfs_future_pid_wake);
+ add_wait_queue(&pid->wait_pidfd, &future->pid_wait);
+ future->pid_wait_attached = true;
+}
+
+/**
+ * pidfs_future_file_notify - wake future-pidfd state waiters
+ * @file: future pidfs file
+ *
+ * For publication, the producer must call this only after @ops->get_pid() can
+ * return the live pid. The publication must use release ordering paired with
+ * the producer's acquire-side lookup. A taskless terminal producer may call
+ * this after making @ops->is_terminal() return true.
+ */
+void pidfs_future_file_notify(struct file *file)
+{
+ struct pidfs_future_file *future = pidfs_future_file(file_inode(file));
+
+ if (WARN_ON_ONCE(!future))
+ return;
+ wake_up_all(&future->wait_pidfd);
+}
+
/**
* pidfs_future_file_clear_pid - undo an unpublished future-pid association
* @file: future pidfs file
@@ -1444,6 +1563,10 @@ void pidfs_future_file_clear_pid(struct file *file, struct pid *pid)
if (WARN_ON_ONCE(!future || READ_ONCE(future->pid) != pid))
return;
+ if (future->pid_wait_attached) {
+ remove_wait_queue(&pid->wait_pidfd, &future->pid_wait);
+ future->pid_wait_attached = false;
+ }
WARN_ON_ONCE(cmpxchg(&pid->stashed, dentry, NULL) != dentry);
dentry->d_fsdata = NULL;
WARN_ON_ONCE(pid->attr);
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index 828e96f769ba1..e4df7f7748487 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -13,11 +13,14 @@ struct pid;
* @get_pid: Return the published, borrowed, non-NULL process identity, or an
* error pointer while the producer is still taskless. The producer must
* keep the returned pid alive for the future inode lifetime.
+ * @is_terminal: Return true when a taskless producer can no longer publish a
+ * process. Optional; a producer without terminal failures may leave it NULL.
* @release: Optionally release producer-owned data when the pidfs inode is
* evicted. If provided, this is called at most once.
*/
struct pidfs_future_file_ops {
struct pid *(*get_pid)(void *data);
+ bool (*is_terminal)(void *data);
void (*release)(void *data);
};
@@ -29,6 +32,8 @@ void *pidfs_future_file_data(const struct file *file,
const struct pidfs_future_file_ops *ops);
u64 pidfs_future_file_ino(const struct file *file);
int pidfs_future_file_set_pid(struct file *file, struct pid *pid);
+void pidfs_future_file_publish_pid(struct file *file, struct pid *pid);
+void pidfs_future_file_notify(struct file *file);
void pidfs_future_file_clear_pid(struct file *file, struct pid *pid);
void __init pidfs_init(void);
void pidfs_prepare_pid(struct pid *pid);
--
2.52.0