Expose a task subdirectory in each process directory and fill it with numeric thread index entries backed by libps thread proc_stats.
Thread nodes keep the containing process alive through the procfs parent reference chain. For now, only show the files that make sense for threads: stat and status use thread state plus process data from the containing process. Signed-off-by: Bradley Morgan <[email protected]> --- procfs/Makefile | 4 +- procfs/process.c | 220 +++++++++++++++++++++++++++++++++++++---------- procfs/process.h | 4 + procfs/taskdir.c | 115 +++++++++++++++++++++++++ procfs/taskdir.h | 21 +++++ 5 files changed, 316 insertions(+), 48 deletions(-) create mode 100644 procfs/taskdir.c create mode 100644 procfs/taskdir.h diff --git a/procfs/Makefile b/procfs/Makefile index d32328d2..1bb0b4b9 100644 --- a/procfs/Makefile +++ b/procfs/Makefile @@ -21,8 +21,8 @@ makemode := server target = procfs -SRCS = procfs.c netfs.c procfs_dir.c process.c proclist.c rootdir.c dircat.c main.c mach_debugUser.c default_pagerUser.c pfinetUser.c -LCLHDRS = dircat.h main.h process.h procfs.h procfs_dir.h proclist.h rootdir.h +SRCS = procfs.c netfs.c procfs_dir.c process.c proclist.c rootdir.c taskdir.c dircat.c main.c mach_debugUser.c default_pagerUser.c pfinetUser.c +LCLHDRS = dircat.h main.h process.h procfs.h procfs_dir.h proclist.h rootdir.h taskdir.h OBJS = $(SRCS:.c=.o) HURDLIBS = netfs fshelp iohelp ps ports ihash shouldbeinlibc diff --git a/procfs/process.c b/procfs/process.c index 3170b775..e24f5d33 100644 --- a/procfs/process.c +++ b/procfs/process.c @@ -28,6 +28,7 @@ #include "procfs_dir.h" #include "process.h" #include "main.h" +#include "taskdir.h" /* This module implements the process directories and the files they contain. A libps proc_stat structure is created for each process @@ -92,6 +93,33 @@ static int args_filename_length (const char *name) return strchrnul (name, ' ') - name; } +static void +get_code_range (struct proc_stat *ps, vm_address_t *start_code, + vm_address_t *end_code) +{ + process_t p; + error_t err; + + *start_code = 1; /* 0 would make killall5.c consider it a kernel process, + thus use 1 as default. */ + *end_code = 1; + + err = proc_pid2proc (ps->context->server, ps->pid, &p); + if (! err) + { + boolean_t essential = 0; + proc_is_important (p, &essential); + if (essential) + *start_code = *end_code = 0; /* To make killall5.c consider it a + kernel process that is to be left + alone. */ + else + proc_get_code (p, start_code, end_code); + + mach_port_deallocate (mach_task_self (), p); + } +} + /* Actual content generators */ static ssize_t @@ -216,20 +244,17 @@ process_file_gc_maps (struct proc_stat *ps, char **contents) } static ssize_t -process_file_gc_stat (struct proc_stat *ps, char **contents) +process_file_gc_stat_common (struct proc_stat *ps, struct proc_stat *task_ps, + int pid, int num_threads, char **contents) { - struct procinfo *pi = proc_stat_proc_info (ps); - task_basic_info_t tbi = proc_stat_task_basic_info (ps); + struct procinfo *pi = proc_stat_proc_info (task_ps); + task_basic_info_t tbi = proc_stat_task_basic_info (task_ps); thread_basic_info_t thbi = proc_stat_thread_basic_info (ps); thread_sched_info_t thsi = proc_stat_thread_sched_info (ps); - const char *fn = args_filename (proc_stat_args (ps)); + const char *fn = args_filename (proc_stat_args (task_ps)); - vm_address_t start_code = 1; /* 0 would make killall5.c consider it - a kernel process, thus use 1 as - default. */ - vm_address_t end_code = 1; - process_t p; - error_t err = proc_pid2proc (ps->context->server, ps->pid, &p); + vm_address_t start_code; + vm_address_t end_code; unsigned last_processor; @@ -239,19 +264,7 @@ process_file_gc_stat (struct proc_stat *ps, char **contents) last_processor = 0; #endif - if (! err) - { - boolean_t essential = 0; - proc_is_important (p, &essential); - if (essential) - start_code = end_code = 0; /* To make killall5.c consider it a - kernel process that is to be - left alone. */ - else - proc_get_code (p, &start_code, &end_code); - - mach_port_deallocate (mach_task_self (), p); - } + get_code_range (task_ps, &start_code, &end_code); /* See proc(5) for more information about the contents of each field for the Linux procfs. */ @@ -275,7 +288,7 @@ process_file_gc_stat (struct proc_stat *ps, char **contents) "%u %u " /* RT priority and policy */ "%llu " /* aggregated block I/O delay */ "\n", - proc_stat_pid (ps), args_filename_length (fn), fn, state_char (ps), + pid, args_filename_length (fn), fn, state_char (ps), pi->ppid, pi->pgrp, pi->session, 0, 0, /* no such thing as a major:minor for ctty */ 0, /* no such thing as CLONE_* flags on Hurd */ @@ -285,7 +298,7 @@ process_file_gc_stat (struct proc_stat *ps, char **contents) 0L, 0L, /* cumulative time for children */ MACH_PRIORITY_TO_NICE(thbi->base_priority) + 20, MACH_PRIORITY_TO_NICE(thbi->base_priority), - pi->nthreads, 0L, + num_threads, 0L, timeval_jiffies (tbi->creation_time), /* FIXME: ... since boot */ (long unsigned) tbi->virtual_size, (long unsigned) tbi->resident_size / PAGE_SIZE, 0L, @@ -301,6 +314,22 @@ process_file_gc_stat (struct proc_stat *ps, char **contents) 0LL); } +static ssize_t +process_file_gc_stat (struct proc_stat *ps, char **contents) +{ + return process_file_gc_stat_common (ps, ps, proc_stat_pid (ps), + proc_stat_proc_info (ps)->nthreads, + contents); +} + +static ssize_t +process_file_gc_thread_stat (struct proc_stat *ps, char **contents) +{ + return process_file_gc_stat_common (ps, proc_stat_thread_origin (ps), + proc_stat_thread_index (ps), 1, + contents); +} + static ssize_t process_file_gc_statm (struct proc_stat *ps, char **contents) { @@ -313,10 +342,11 @@ process_file_gc_statm (struct proc_stat *ps, char **contents) } static ssize_t -process_file_gc_status (struct proc_stat *ps, char **contents) +process_file_gc_status_common (struct proc_stat *ps, struct proc_stat *task_ps, + int tgid, int pid, char **contents) { - task_basic_info_t tbi = proc_stat_task_basic_info (ps); - const char *fn = args_filename (proc_stat_args (ps)); + task_basic_info_t tbi = proc_stat_task_basic_info (task_ps); + const char *fn = args_filename (proc_stat_args (task_ps)); return asprintf (contents, "Name:\t%.*s\n" @@ -332,18 +362,34 @@ process_file_gc_status (struct proc_stat *ps, char **contents) "Threads:\t%u\n", args_filename_length (fn), fn, state_string (ps), - proc_stat_pid (ps), /* XXX will need more work for threads */ - proc_stat_pid (ps), - proc_stat_proc_info (ps)->ppid, - proc_stat_owner_uid (ps), - proc_stat_owner_uid (ps), - proc_stat_owner_uid (ps), - proc_stat_owner_uid (ps), + tgid, + pid, + proc_stat_proc_info (task_ps)->ppid, + proc_stat_owner_uid (task_ps), + proc_stat_owner_uid (task_ps), + proc_stat_owner_uid (task_ps), + proc_stat_owner_uid (task_ps), tbi->virtual_size / 1024, tbi->virtual_size / 1024, tbi->resident_size / 1024, tbi->resident_size / 1024, - proc_stat_num_threads (ps)); + proc_stat_num_threads (task_ps)); +} + +static ssize_t +process_file_gc_status (struct proc_stat *ps, char **contents) +{ + return process_file_gc_status_common (ps, ps, proc_stat_pid (ps), + proc_stat_pid (ps), contents); +} + +static ssize_t +process_file_gc_thread_status (struct proc_stat *ps, char **contents) +{ + struct proc_stat *task_ps = proc_stat_thread_origin (ps); + + return process_file_gc_status_common (ps, task_ps, proc_stat_pid (task_ps), + proc_stat_thread_index (ps), contents); } @@ -357,11 +403,20 @@ struct process_file_desc /* The proc_stat information required to get the contents of this file. */ ps_flags_t needs; + /* The proc_stat information required for thread nodes. */ + ps_flags_t thread_needs; + + /* The containing process information required for thread nodes. */ + ps_flags_t thread_origin_needs; + /* Content generator to use for this file. Once we have acquired the necessary information, there can be only memory allocation errors, hence this simplified signature. */ ssize_t (*get_contents) (struct proc_stat *ps, char **contents); + /* Content generator to use for thread nodes. */ + ssize_t (*thread_get_contents) (struct proc_stat *ps, char **contents); + /* The cmdline and environ contents don't need any cleaning since they point directly into the proc_stat structure. */ int no_cleanup; @@ -381,17 +436,42 @@ static error_t process_file_get_contents (void *hook, char **contents, ssize_t *contents_len) { struct process_file_node *file = hook; + struct proc_stat *ps = file->ps; + struct proc_stat *origin = NULL; + ps_flags_t needs = file->desc->needs; + ssize_t (*get_contents) (struct proc_stat *, char **) = + file->desc->get_contents; error_t err; + if (proc_stat_is_thread (ps)) + { + if (! file->desc->thread_get_contents) + return EIO; + + needs = file->desc->thread_needs; + get_contents = file->desc->thread_get_contents; + origin = proc_stat_thread_origin (ps); + } + /* Fetch the required information. */ - err = proc_stat_set_flags (file->ps, file->desc->needs); + err = proc_stat_set_flags (ps, needs); if (err) return EIO; - if ((proc_stat_flags (file->ps) & file->desc->needs) != file->desc->needs) + if ((proc_stat_flags (ps) & needs) != needs) return EIO; + if (origin && file->desc->thread_origin_needs) + { + err = proc_stat_set_flags (origin, file->desc->thread_origin_needs); + if (err) + return EIO; + if ((proc_stat_flags (origin) & file->desc->thread_origin_needs) + != file->desc->thread_origin_needs) + return EIO; + } + /* Call the actual content generator (see the definitions below). */ - *contents_len = file->desc->get_contents (file->ps, contents); + *contents_len = get_contents (ps, contents); return 0; } @@ -413,6 +493,7 @@ process_file_make_node (void *dir_hook, const void *entry_hook) .cleanup = free, }; struct process_file_node *f; + struct proc_stat *owner_ps; struct node *np; f = malloc (sizeof *f); @@ -426,7 +507,9 @@ process_file_make_node (void *dir_hook, const void *entry_hook) if (! np) return NULL; - procfs_node_chown (np, proc_stat_owner_uid (f->ps)); + owner_ps = proc_stat_is_thread (f->ps) + ? proc_stat_thread_origin (f->ps) : f->ps; + procfs_node_chown (np, proc_stat_owner_uid (owner_ps)); if (f->desc->mode) procfs_node_chmod (np, f->desc->mode); @@ -451,6 +534,20 @@ process_stat_make_node (void *dir_hook, const void *entry_hook) return np; } +static int +process_file_exists (void *dir_hook, const void *entry_hook) +{ + const struct process_file_desc *desc = entry_hook; + + return ! proc_stat_is_thread (dir_hook) || desc->thread_get_contents; +} + +static int +process_task_exists (void *dir_hook, const void *entry_hook) +{ + return ! proc_stat_is_thread (dir_hook); +} + /* Implementation of the process directory per se. */ @@ -491,13 +588,25 @@ static struct procfs_dir_entry entries[] = { .mode = 0400, }, }, + { + .name = "task", + .ops = { + .make_node = taskdir_make_node, + .exists = process_task_exists, + }, + }, { .name = "stat", .hook = & (struct process_file_desc) { .get_contents = process_file_gc_stat, + .thread_get_contents = process_file_gc_thread_stat, .needs = PSTAT_PID | PSTAT_ARGS | PSTAT_STATE | PSTAT_PROC_INFO | PSTAT_TASK | PSTAT_TASK_BASIC | PSTAT_THREAD_BASIC | PSTAT_THREAD_SCHED | PSTAT_THREAD_WAIT, + .thread_needs = PSTAT_THREAD | PSTAT_STATE | PSTAT_THREAD_BASIC + | PSTAT_THREAD_SCHED | PSTAT_THREAD_WAIT, + .thread_origin_needs = PSTAT_PID | PSTAT_ARGS | PSTAT_PROC_INFO + | PSTAT_TASK_BASIC, }, .ops = { .make_node = process_stat_make_node, @@ -514,25 +623,46 @@ static struct procfs_dir_entry entries[] = { .name = "status", .hook = & (struct process_file_desc) { .get_contents = process_file_gc_status, + .thread_get_contents = process_file_gc_thread_status, .needs = PSTAT_PID | PSTAT_ARGS | PSTAT_STATE | PSTAT_PROC_INFO | PSTAT_TASK_BASIC | PSTAT_OWNER_UID | PSTAT_NUM_THREADS, + .thread_needs = PSTAT_THREAD | PSTAT_STATE | PSTAT_THREAD_BASIC, + .thread_origin_needs = PSTAT_PID | PSTAT_ARGS | PSTAT_PROC_INFO + | PSTAT_TASK_BASIC | PSTAT_OWNER_UID | PSTAT_NUM_THREADS, }, }, {} }; -error_t -process_lookup_pid (struct ps_context *pc, pid_t pid, struct node **np) +struct node * +process_make_node (struct proc_stat *ps) { static const struct procfs_dir_ops dir_ops = { .entries = entries, .cleanup = (void (*)(void *)) _proc_stat_free, .entry_ops = { .make_node = process_file_make_node, + .exists = process_file_exists, }, }; - struct proc_stat *ps; + struct proc_stat *owner_ps = proc_stat_is_thread (ps) + ? proc_stat_thread_origin (ps) : ps; + struct node *np; int owner; + + np = procfs_dir_make_node (&dir_ops, ps); + if (! np) + return NULL; + + owner = proc_stat_owner_uid (owner_ps); + procfs_node_chown (np, owner >= 0 ? owner : opt_anon_owner); + return np; +} + +error_t +process_lookup_pid (struct ps_context *pc, pid_t pid, struct node **np) +{ + struct proc_stat *ps; error_t err; err = _proc_stat_create (pid, pc, &ps); @@ -548,11 +678,9 @@ process_lookup_pid (struct ps_context *pc, pid_t pid, struct node **np) return EIO; } - *np = procfs_dir_make_node (&dir_ops, ps); + *np = process_make_node (ps); if (! *np) return ENOMEM; - owner = proc_stat_owner_uid (ps); - procfs_node_chown (*np, owner >= 0 ? owner : opt_anon_owner); return 0; } diff --git a/procfs/process.h b/procfs/process.h index b230a281..b3c41359 100644 --- a/procfs/process.h +++ b/procfs/process.h @@ -25,3 +25,7 @@ error_t process_lookup_pid (struct ps_context *pc, pid_t pid, struct node **np); +/* Create a node for a process or thread proc_stat. The returned node + consumes PS. */ +struct node * +process_make_node (struct proc_stat *ps); diff --git a/procfs/taskdir.c b/procfs/taskdir.c new file mode 100644 index 00000000..677d2404 --- /dev/null +++ b/procfs/taskdir.c @@ -0,0 +1,115 @@ +/* Hurd /proc filesystem, implementation of process thread directories. + 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. + + The GNU Hurd 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ps.h> +#include "procfs.h" +#include "process.h" +#include "main.h" + +#define THREAD_INDEX_STR_SIZE (3 * sizeof (unsigned int) + 1) + +static error_t +taskdir_get_contents (void *hook, char **contents, ssize_t *contents_len) +{ + static const char dot_dotdot[] = ".\0.."; + struct proc_stat *ps = hook; + error_t err; + unsigned int i; + size_t pos; + + err = proc_stat_set_flags (ps, PSTAT_NUM_THREADS); + if (err) + return err; + if (! (proc_stat_flags (ps) & PSTAT_NUM_THREADS)) + return EIO; + + *contents = malloc (sizeof dot_dotdot + + proc_stat_num_threads (ps) * THREAD_INDEX_STR_SIZE); + if (! *contents) + return ENOMEM; + + memcpy (*contents, dot_dotdot, sizeof dot_dotdot); + pos = sizeof dot_dotdot; + + for (i = 0; i < proc_stat_num_threads (ps); i++) + { + int n = sprintf (*contents + pos, "%u", i); + assert_backtrace (n >= 0); + pos += n + 1; + } + + *contents_len = pos; + return 0; +} + +static error_t +taskdir_lookup (void *hook, const char *name, struct node **np) +{ + struct proc_stat *ps = hook; + struct proc_stat *thread_ps; + unsigned long index; + char *endp; + error_t err; + + if (name[0] == '\0' || (name[0] == '0' && name[1] != '\0')) + return ENOENT; + + errno = 0; + index = strtoul (name, &endp, 10); + if (errno || *endp || index > UINT_MAX) + return ENOENT; + + err = proc_stat_thread_create (ps, index, &thread_ps); + if (err == EINVAL) + return ENOENT; + if (err) + return err; + + *np = process_make_node (thread_ps); + if (! *np) + return ENOMEM; + + return 0; +} + +struct node * +taskdir_make_node (void *dir_hook, const void *entry_hook) +{ + static const struct procfs_node_ops ops = { + .get_contents = taskdir_get_contents, + .lookup = taskdir_lookup, + .cleanup_contents = procfs_cleanup_contents_with_free, + }; + struct proc_stat *ps = dir_hook; + struct node *np; + int owner; + + np = procfs_make_node (&ops, ps); + if (! np) + return NULL; + + owner = proc_stat_owner_uid (ps); + procfs_node_chown (np, owner >= 0 ? owner : opt_anon_owner); + return np; +} diff --git a/procfs/taskdir.h b/procfs/taskdir.h new file mode 100644 index 00000000..0c5ff47f --- /dev/null +++ b/procfs/taskdir.h @@ -0,0 +1,21 @@ +/* Hurd /proc filesystem, implementation of process thread directories. + 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. + + The GNU Hurd 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +struct node * +taskdir_make_node (void *dir_hook, const void *entry_hook); -- 2.53.0
