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 <[email protected]>
---
 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


Reply via email to