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

