diff options
Diffstat (limited to 'libps/procstat.c')
-rw-r--r-- | libps/procstat.c | 1137 |
1 files changed, 1137 insertions, 0 deletions
diff --git a/libps/procstat.c b/libps/procstat.c new file mode 100644 index 00000000..46058e07 --- /dev/null +++ b/libps/procstat.c @@ -0,0 +1,1137 @@ +/* The proc_stat type, which holds information about a hurd process. + + Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This program 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <hurd.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "ps.h" +#include "common.h" + +#include "ps_msg.h" + +/* ---------------------------------------------------------------- */ + +/* These directly correspond to the bits in a state, starting from 0. See + ps.h for an explanation of what each of these means. */ +char *proc_stat_state_tags = "TZRHDSIN<u+slfmpoxwg"; + +/* ---------------------------------------------------------------- */ + +/* The type of the per-thread data returned by proc_getprocinfo. */ +typedef typeof (((struct procinfo *)0)->threadinfos[0]) threadinfo_data_t; +typedef threadinfo_data_t *threadinfo_t; + +/* Return the PSTAT_STATE_ bits describing the state of an individual thread, + from that thread's thread_basic_info_t struct */ +static int +thread_state (thread_basic_info_t bi) +{ + int state = 0; + + switch (bi->run_state) + { + case TH_STATE_RUNNING: + state |= PSTAT_STATE_T_RUN; + break; + case TH_STATE_UNINTERRUPTIBLE: + state |= PSTAT_STATE_T_WAIT; + break; + case TH_STATE_HALTED: + state |= PSTAT_STATE_T_HALT; + break; + case TH_STATE_STOPPED: + state |= PSTAT_STATE_T_HALT | PSTAT_STATE_T_UNCLEAN; + break; + case TH_STATE_WAITING: + /* Act like unix: waits of less than 20 seconds means a process is + `sleeping' and >= 20 seconds means it's `idle' */ + state |= bi->sleep_time < 20 ? PSTAT_STATE_T_SLEEP : PSTAT_STATE_T_IDLE; + break; + } + + if (bi->base_priority < 12) + state |= PSTAT_STATE_T_NASTY; + else if (bi->base_priority > 12) + state |= PSTAT_STATE_T_NICE; + + return state; +} + +/* ---------------------------------------------------------------- */ + +/* The set of things we get from procinfo that are per-thread. */ +#define PSTAT_PROCINFO_THREAD \ + (PSTAT_THREAD_BASIC | PSTAT_THREAD_SCHED | PSTAT_THREAD_WAIT) + +/* The set of things we get from procinfo that are per-task, and thread dependent. */ +#define PSTAT_PROCINFO_TASK_THREAD_DEP \ + (PSTAT_PROCINFO_THREAD | PSTAT_NUM_THREADS | PSTAT_THREAD_WAITS) + +/* The set of things we get from procinfo that are per-task (note that this + includes thread fields, because tasks use them for thread summaries). */ +#define PSTAT_PROCINFO_TASK \ + (PSTAT_PROCINFO_TASK_THREAD_DEP | PSTAT_PROC_INFO | PSTAT_TASK_BASIC) + +/* The set of PSTAT_ flags that we get using proc_getprocinfo. */ +#define PSTAT_PROCINFO PSTAT_PROCINFO_TASK + +/* The set of things in PSTAT_PROCINFO that we will not attempt to refetch on + subsequent getprocinfo calls. */ +#define PSTAT_PROCINFO_MERGE PSTAT_TASK_BASIC +#define PSTAT_PROCINFO_REFETCH (PSTAT_PROCINFO - PSTAT_PROCINFO_MERGE) + +/* Fetches process information from the set in PSTAT_PROCINFO, returning it + in PI & PI_SIZE. NEED is the information, and HAVE is the what we already + have. */ +static error_t +fetch_procinfo (process_t server, pid_t pid, + ps_flags_t need, ps_flags_t *have, + struct procinfo **pi, size_t *pi_size, + char **waits, size_t *waits_len) +{ + int pi_flags = 0; + + if ((need & PSTAT_TASK_BASIC) && !(*have & PSTAT_TASK_BASIC)) + pi_flags |= PI_FETCH_TASKINFO; + if ((need & PSTAT_NUM_THREADS) && !(*have & PSTAT_NUM_THREADS)) + pi_flags |= PI_FETCH_THREADS; + if ((need & PSTAT_THREAD_BASIC) && !(*have & PSTAT_THREAD_BASIC)) + pi_flags |= PI_FETCH_THREAD_BASIC | PI_FETCH_THREADS; + if ((need & PSTAT_THREAD_SCHED) && !(*have & PSTAT_THREAD_SCHED)) + pi_flags |= PI_FETCH_THREAD_SCHED | PI_FETCH_THREADS; + if ((need & PSTAT_THREAD_WAITS) && !(*have & PSTAT_THREAD_WAITS)) + pi_flags |= PI_FETCH_THREAD_WAITS | PI_FETCH_THREADS; + + if (pi_flags || ((need & PSTAT_PROC_INFO) && !(*have & PSTAT_PROC_INFO))) + { + error_t err; + + *pi_size /= sizeof (int); /* getprocinfo takes an array of ints. */ + err = proc_getprocinfo (server, pid, &pi_flags, + (procinfo_t *)pi, pi_size, waits, waits_len); + *pi_size *= sizeof (int); + + if (! err) + /* Update *HAVE to reflect what we've successfully fetched. */ + { + *have |= PSTAT_PROC_INFO; + if (pi_flags & PI_FETCH_TASKINFO) + *have |= PSTAT_TASK_BASIC; + if (pi_flags & PI_FETCH_THREADS) + *have |= PSTAT_NUM_THREADS; + if (pi_flags & PI_FETCH_THREAD_BASIC) + *have |= PSTAT_THREAD_BASIC; + if (pi_flags & PI_FETCH_THREAD_SCHED) + *have |= PSTAT_THREAD_SCHED; + if (pi_flags & PI_FETCH_THREAD_WAITS) + *have |= PSTAT_THREAD_WAITS; + } + return err; + } + else + return 0; +} + +/* The size of the initial buffer malloced to try and avoid getting + vm_alloced memory for the procinfo structure returned by getprocinfo. + Here we just give enough for four threads. */ +#define PROCINFO_MALLOC_SIZE \ + (sizeof (struct procinfo) + 4 * sizeof (threadinfo_data_t)) + +#define WAITS_MALLOC_SIZE 128 + +/* Fetches process information from the set in PSTAT_PROCINFO, returning it + in PI & PI_SIZE, and if *PI_SIZE is non-zero, merges the new information + with what was in *PI, and deallocates *PI. NEED is the information, and + *HAVE is the what we already have (which will be updated). */ +static ps_flags_t +merge_procinfo (struct proc_stat *ps, ps_flags_t need, ps_flags_t have) +{ + error_t err; + struct procinfo *new_pi, old_pi_hdr; + size_t new_pi_size; + char *new_waits = 0; + size_t new_waits_len = 0; + /* We always re-fetch any thread-specific info, as the set of threads could + have changed since the last time we did this, and we can't tell. */ + ps_flags_t really_need = need | (have & PSTAT_PROCINFO_REFETCH); + ps_flags_t really_have = have & ~PSTAT_PROCINFO_REFETCH; + + /* Give NEW_PI, the default buffer to receive procinfo data, some storage. */ + if (have & PSTAT_PROCINFO) + /* We already have some procinfo stuff, so try to reuse its storage, + first saving the old values. We know that below we'll never try to + merge anything beyond the static struct procinfo header, so just save + that. */ + old_pi_hdr = *ps->proc_info; + else + /* The first time we're getting procinfo stuff. Malloc a block that's + probably big enough for everything. */ + { + ps->proc_info = malloc (PROCINFO_MALLOC_SIZE); + ps->proc_info_size = PROCINFO_MALLOC_SIZE; + ps->proc_info_vm_alloced = 0; + if (! ps->proc_info) + return ENOMEM; + } + new_pi = ps->proc_info; + new_pi_size = ps->proc_info_size; + + if (really_need & PSTAT_THREAD_WAITS) + /* We're going to get thread waits info, so make some storage for it too.*/ + { + if (! (have & PSTAT_THREAD_WAITS)) + { + ps->thread_waits = malloc (WAITS_MALLOC_SIZE); + ps->thread_waits_len = WAITS_MALLOC_SIZE; + ps->thread_waits_vm_alloced = 0; + } + new_waits = ps->thread_waits; + new_waits_len = ps->thread_waits_len; + } + + err = fetch_procinfo (ps->context->server, ps->pid, really_need, &really_have, + &new_pi, &new_pi_size, + &new_waits, &new_waits_len); + if (err) + /* Just keep what we had before. If that was nothing, we have to free + the first-time storage we malloced. */ + { + if (! (have & PSTAT_PROCINFO)) + free (new_pi); + if ((really_need & PSTAT_THREAD_WAITS) && !(have & PSTAT_THREAD_WAITS)) + free (new_waits); + return have; + } + + /* There was old information, try merging it. */ + if (have & PSTAT_TASK_BASIC) + /* Task info. */ + bcopy (&old_pi_hdr.taskinfo, &new_pi->taskinfo, + sizeof (struct task_basic_info)); + /* That's it for now. */ + + if (new_pi != ps->proc_info) + /* We got new memory vm_alloced by the getprocinfo, discard the old. */ + { + if (ps->proc_info_vm_alloced) + munmap (ps->proc_info, ps->proc_info_size); + else + free (ps->proc_info); + ps->proc_info = new_pi; + ps->proc_info_size = new_pi_size; + ps->proc_info_vm_alloced = 1; + } + + if (really_need & PSTAT_THREAD_WAITS) + /* We were trying to get thread waits.... */ + { + if (! (really_have & PSTAT_THREAD_WAITS)) + /* Failed to do so. Make sure we deallocate memory for it. */ + new_waits = 0; + if (new_waits != ps->thread_waits) + /* We got new memory vm_alloced by the getprocinfo, discard the old. */ + { + if (ps->thread_waits_vm_alloced) + munmap (ps->thread_waits, ps->thread_waits_len); + else + free (ps->thread_waits); + ps->thread_waits = new_waits; + ps->thread_waits_len = new_waits_len; + ps->thread_waits_vm_alloced = 1; + } + } + + /* Return what thread info we have -- this may *decrease*, as all + previously fetched thread-info is out-of-date now, so we have to make do + with whatever we've fetched this time. */ + return really_have; +} + +/* Returns FLAGS augmented with any other flags that are necessary + preconditions to setting them. */ +static ps_flags_t +add_preconditions (ps_flags_t flags, struct ps_context *context) +{ + /* Implement any inter-flag dependencies: if the new flags in FLAGS depend on + some other set of flags to be set, make sure those are also in FLAGS. */ + + if ((flags & PSTAT_USER_MASK) + && context->user_hooks && context->user_hooks->dependencies) + /* There are some user flags needing to be set... See what they need. */ + flags |= (*context->user_hooks->dependencies) (flags & PSTAT_USER_MASK); + + if (flags & PSTAT_TTY) + flags |= PSTAT_CTTYID; + if (flags & PSTAT_STATE) + flags |= PSTAT_PROC_INFO | PSTAT_THREAD_BASIC; + if (flags & PSTAT_OWNER) + flags |= PSTAT_OWNER_UID; + if (flags & PSTAT_OWNER_UID) + flags |= PSTAT_PROC_INFO; + if (flags & PSTAT_SUSPEND_COUNT) + /* We just request the resources require for both the thread and task + versions, as the extraneous info won't be possible to aquire anyway. */ + flags |= PSTAT_TASK_BASIC | PSTAT_THREAD_BASIC; + if (flags & (PSTAT_CTTYID | PSTAT_CWDIR | PSTAT_AUTH | PSTAT_UMASK) + && !(flags & PSTAT_NO_MSGPORT)) + { + flags |= PSTAT_MSGPORT; + flags |= PSTAT_TASK; /* for authentication */ + } + if (flags & PSTAT_TASK_EVENTS) + flags |= PSTAT_TASK; + + return flags; +} + +/* Those flags that should be set before calling should_suppress_msgport. */ +#define PSTAT_TEST_MSGPORT \ + (PSTAT_NUM_THREADS | PSTAT_SUSPEND_COUNT | PSTAT_THREAD_BASIC) + +/* Those flags that need the msg port, perhaps implicitly. */ +#define PSTAT_USES_MSGPORT \ + (PSTAT_MSGPORT | PSTAT_THREAD_WAIT | PSTAT_THREAD_WAITS) + +/* Return true when there's some condition indicating that we shouldn't use + PS's msg port. For this routine to work correctly, PS's flags should + contain as many flags in PSTAT_TEST_MSGPORT as possible. */ +static int +should_suppress_msgport (struct proc_stat *ps) +{ + ps_flags_t have = ps->flags; + + if ((have & PSTAT_SUSPEND_COUNT) && ps->suspend_count != 0) + /* Task is suspended. */ + return TRUE; + + if ((have & PSTAT_THREAD_BASIC) && ps->thread_basic_info->suspend_count != 0) + /* All threads are suspended. */ + return TRUE; + + if ((have & PSTAT_NUM_THREADS) && ps->num_threads == 0) + /* No threads (some bug could cause the procserver still to think there's + a msgport). */ + return TRUE; + + return FALSE; +} + +/* Returns FLAGS with PSTAT_MSGPORT turned off and PSTAT_NO_MSGPORT on. */ +#define SUPPRESS_MSGPORT_FLAGS(flags) \ + (((flags) & ~PSTAT_USES_MSGPORT) | PSTAT_NO_MSGPORT) + +/* ---------------------------------------------------------------- */ + +/* Returns a new malloced struct thread_basic_info containing a summary of + all the thread basic info in PI. Sizes and cumulative times are summed, + delta time are averaged. The run_states are added by having running + thread take precedence over waiting ones, and if there are any other + incompatible states, simply using a bogus value of -1. */ +static struct thread_basic_info * +summarize_thread_basic_info (struct procinfo *pi) +{ + int i; + unsigned num_threads = 0, num_run_threads = 0; + thread_basic_info_t tbi = malloc (sizeof (struct thread_basic_info)); + int run_base_priority = 0, run_cur_priority = 0; + int total_base_priority = 0, total_cur_priority = 0; + + if (!tbi) + return 0; + + bzero (tbi, sizeof *tbi); + + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died + && ! (pi->threadinfos[i].pis_bi.flags & TH_FLAGS_IDLE)) + { + thread_basic_info_t bi = &pi->threadinfos[i].pis_bi; + int thread_run_state = bi->run_state; + + /* Construct some aggregate run-state for the process: besides the + defined thread run_states, we use 0 to mean no threads, and -1 + to mean two threads have conflicting run_stats. */ + + if (tbi->run_state == 0) + /* No prior state, just copy this thread's. */ + tbi->run_state = thread_run_state; + else if (tbi->run_state == TH_STATE_RUNNING + || thread_run_state == TH_STATE_RUNNING) + /* If any thread is running, mark the process as running. */ + tbi->run_state = TH_STATE_RUNNING; + else if (tbi->run_state != bi->run_state) + /* Otherwise there are two conflicting non-running states, so + just give up and say we don't know. */ + tbi->run_state = -1; + + tbi->cpu_usage += bi->cpu_usage; + tbi->sleep_time += bi->sleep_time; + + /* The aggregate suspend count is the minimum of all threads. */ + if (i == 0 || tbi->suspend_count > bi->suspend_count) + tbi->suspend_count = bi->suspend_count; + + tbi->user_time.seconds += bi->user_time.seconds; + tbi->user_time.microseconds += bi->user_time.microseconds; + tbi->system_time.seconds += bi->system_time.seconds; + tbi->system_time.microseconds += bi->system_time.microseconds; + + if (tbi->run_state == TH_STATE_RUNNING) + { + run_base_priority += bi->base_priority; + run_cur_priority += bi->base_priority; + num_run_threads++; + } + else + { + total_base_priority += bi->base_priority; + total_cur_priority += bi->base_priority; + } + + num_threads++; + } + + if (num_threads > 0) + { + tbi->sleep_time /= num_threads; + if (num_run_threads > 0) + { + tbi->base_priority = run_base_priority / num_run_threads; + tbi->cur_priority = run_cur_priority / num_run_threads; + } + else + { + tbi->base_priority = total_base_priority / num_threads; + tbi->cur_priority = total_cur_priority / num_threads; + } + } + + tbi->user_time.seconds += tbi->user_time.microseconds / 1000000; + tbi->user_time.microseconds %= 1000000; + tbi->system_time.seconds += tbi->system_time.microseconds / 1000000; + tbi->system_time.microseconds %= 1000000; + + return tbi; +} + +/* Returns a new malloced struct thread_sched_info containing a summary of + all the thread scheduling info in PI. The prioritys are an average of the + thread priorities. */ +static struct thread_sched_info * +summarize_thread_sched_info (struct procinfo *pi) +{ + int i; + unsigned num_threads = 0; + thread_sched_info_t tsi = malloc (sizeof (struct thread_sched_info)); + + if (!tsi) + return 0; + + bzero (tsi, sizeof *tsi); + + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died + && ! (pi->threadinfos[i].pis_bi.flags & TH_FLAGS_IDLE)) + { + thread_sched_info_t si = &pi->threadinfos[i].pis_si; + tsi->base_priority += si->base_priority; + tsi->cur_priority += si->cur_priority; + tsi->max_priority += si->max_priority; + tsi->depress_priority += si->depress_priority; + num_threads++; + } + + if (num_threads > 0) + { + tsi->base_priority /= num_threads; + tsi->cur_priority /= num_threads; + tsi->max_priority /= num_threads; + tsi->depress_priority /= num_threads; + } + + return tsi; +} + +/* Returns the union of the state bits for all the threads in PI. */ +static int +summarize_thread_states (struct procinfo *pi) +{ + int i; + int state = 0; + + /* The union of all thread state bits... */ + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died + && ! (pi->threadinfos[i].pis_bi.flags & TH_FLAGS_IDLE)) + state |= thread_state (&pi->threadinfos[i].pis_bi); + + return state; +} + +/* Returns what's blocking the first blocked thread in PI in WAIT and RPC. */ +static void +summarize_thread_waits (struct procinfo *pi, char *waits, size_t waits_len, + char **wait, int *rpc) +{ + int i; + char *next_wait = waits; + + *wait = 0; /* Defaults */ + *rpc = 0; + + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died) + { + if (next_wait > waits + waits_len) + break; + else + { + int left = waits + waits_len - next_wait; + + if (pi->threadinfos[i].pis_bi.flags & TH_FLAGS_IDLE) + ; /* kernel idle thread; ignore */ + else if (strncmp (next_wait, "msgport", left) == 0 + || strncmp (next_wait, "itimer", left) == 0) + ; /* libc internal threads; ignore. */ + else if (*wait) + /* There are multiple user threads. Punt. */ + { + *wait = "*"; + *rpc = 0; + break; + } + else + { + *wait = next_wait; + *rpc = pi->threadinfos[i].rpc_block; + } + + /* Advance NEXT_WAIT to the next wait string. */ + next_wait += strnlen (next_wait, left) + 1; + } + } +} + +/* Returns the number of threads in PI that aren't marked dead. */ +static unsigned +count_threads (struct procinfo *pi, ps_flags_t have) +{ + if (have & (PSTAT_PROCINFO_TASK_THREAD_DEP & ~PSTAT_NUM_THREADS)) + /* If we have thread info besides the number of threads, then the + threadinfos structures in PI are valid (we use the died bit). */ + { + int i; + unsigned num_threads = 0; + + /* The union of all thread state bits... */ + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died) + num_threads++; + + return num_threads; + } + else + /* Otherwise just use the number proc gave us. */ + return pi->nthreads; +} + +/* Returns the threadinfo for the INDEX'th thread from PI that isn't marked + dead. */ +threadinfo_t +get_thread_info (struct procinfo *pi, unsigned index) +{ + int i; + + /* The union of all thread state bits... */ + for (i = 0; i < pi->nthreads; i++) + if (! pi->threadinfos[i].died && index-- == 0) + return &pi->threadinfos[i]; + + return 0; +} + +/* Returns a pointer to the Nth entry in the '\0'-separated vector of strings + in ARGZ & ARGZ_LEN. Note that we don't have to do the bit with only + counting non-dead threads like get_thread_info does, because the + thread_waits string vector only contains entries for live threads. */ +char * +get_thread_wait (char *waits, size_t waits_len, unsigned n) +{ + char *wait = waits; + while (n-- && wait) + if (wait >= waits + waits_len) + wait = 0; + else + wait += strnlen (wait, waits + waits_len - wait) + 1; + return wait; +} + +/* Returns a malloced block of memory SIZE bytes long, containing a copy of + SRC. */ +static void * +clone (void *src, size_t size) +{ + void *dst = malloc (size); + if (dst) + bcopy (src, dst, size); + return dst; +} + +/* Add the information specified by NEED to PS which we can get with + proc_getprocinfo. */ +static ps_flags_t +set_procinfo_flags (struct proc_stat *ps, ps_flags_t need, ps_flags_t have) +{ + if (have & PSTAT_PID) + { + struct procinfo *pi; + ps_flags_t had = have; + + if (! (have & PSTAT_PROCINFO)) + /* Never got any before; zero out our pointers. */ + { + ps->proc_info = 0; + ps->proc_info_size = 0; + ps->thread_waits = 0; + ps->thread_waits_len = 0; + } + + if ((need & PSTAT_THREAD_WAIT) && !(need & PSTAT_THREAD_WAITS)) + /* We need thread wait information only for summarization. This is + expensive and pointless for lots of threads, so try to avoid it + in that case. */ + { + if (! (have & PSTAT_NUM_THREADS)) + /* We've don't know how many threads there are yet; find out. */ + { + have = merge_procinfo (ps, PSTAT_NUM_THREADS, have); + if (have & PSTAT_NUM_THREADS) + ps->num_threads = count_threads (ps->proc_info, have); + } + if ((have & PSTAT_NUM_THREADS) && ps->num_threads <= 3) + /* Perhaps only 1 user thread -- thread-wait info may be + meaningful! */ + need |= PSTAT_THREAD_WAITS; + } + + have = merge_procinfo (ps, need, have); + pi = ps->proc_info; + + /* Update dependent fields. We redo these even if we've already + gotten them, as the information will be newer. */ + if (have & PSTAT_TASK_BASIC) + ps->task_basic_info = &pi->taskinfo; + if (have & PSTAT_NUM_THREADS) + ps->num_threads = count_threads (pi, have); + if (had & PSTAT_THREAD_BASIC) + free (ps->thread_basic_info); + if (have & PSTAT_THREAD_BASIC) + ps->thread_basic_info = summarize_thread_basic_info (pi); + if (had & PSTAT_THREAD_SCHED) + free (ps->thread_sched_info); + if (have & PSTAT_THREAD_SCHED) + ps->thread_sched_info = summarize_thread_sched_info (pi); + if (have & PSTAT_THREAD_WAITS) + /* Thread-waits info can be used to generate thread-wait info. */ + { + summarize_thread_waits (pi, + ps->thread_waits, ps->thread_waits_len, + &ps->thread_wait, &ps->thread_rpc); + have |= PSTAT_THREAD_WAIT; + } + else if (!(have & PSTAT_NO_MSGPORT) + && (have & PSTAT_NUM_THREADS) && ps->num_threads > 3) + /* More than 3 threads (1 user thread + libc signal thread + + possible itimer thread) always results in this value for the + process's thread_wait field. For fewer threads, we should + have fetched thread_waits info and hit the previous case. */ + { + ps->thread_wait = "*"; + ps->thread_rpc = 0; + have |= PSTAT_THREAD_WAIT; + } + } + else + /* For a thread, we get use the proc_info from the containing process. */ + { + struct proc_stat *origin = ps->thread_origin; + /* Fetch for the containing process basically the same information we + want for the thread, but it also needs all the thread wait info. */ + ps_flags_t oflags = + (need & PSTAT_PROCINFO_THREAD) + | ((need & PSTAT_THREAD_WAIT) ? PSTAT_THREAD_WAITS : 0); + + proc_stat_set_flags (origin, oflags); + oflags = origin->flags; + + if (oflags & PSTAT_PROCINFO_THREAD) + /* Got some threadinfo at least. */ + { + threadinfo_t ti = + get_thread_info (origin->proc_info, ps->thread_index); + + /* Now copy out the information for this particular thread from the + ORIGIN's list of thread information. */ + + need &= ~have; + + if ((need & PSTAT_THREAD_BASIC) && (oflags & PSTAT_THREAD_BASIC) + && (ps->thread_basic_info = + clone (&ti->pis_bi, sizeof (struct thread_basic_info)))) + have |= PSTAT_THREAD_BASIC; + + if ((need & PSTAT_THREAD_SCHED) && (oflags & PSTAT_THREAD_SCHED) + && (ps->thread_sched_info = + clone (&ti->pis_si, sizeof (struct thread_sched_info)))) + have |= PSTAT_THREAD_SCHED; + + if ((need & PSTAT_THREAD_WAIT) && (oflags & PSTAT_THREAD_WAITS)) + { + ps->thread_wait = + get_thread_wait (origin->thread_waits, + origin->thread_waits_len, + ps->thread_index); + if (ps->thread_wait) + { + ps->thread_rpc = ti->rpc_block; + have |= PSTAT_THREAD_WAIT; + } + } + } + + /* Mark things that don't apply to threads (note that we don't do the + analogous thing for tasks above, as tasks do have thread fields + containing summary information for all their threads). */ + ps->inapp |= need & ~have & PSTAT_PROCINFO & ~PSTAT_PROCINFO_THREAD; + } + + return have; +} + +/* Add FLAGS to PS's flags, fetching information as necessary to validate + the corresponding fields in PS. Afterwards you must still check the flags + field before using new fields, as something might have failed. Returns + a system error code if a fatal error occurred, or 0 if none. */ +error_t +proc_stat_set_flags (struct proc_stat *ps, ps_flags_t flags) +{ + ps_flags_t have = ps->flags; /* flags set in ps */ + ps_flags_t need; /* flags not set in ps, but desired to be */ + ps_flags_t no_msgport_flags; /* a version of FLAGS for use when we can't + use the msg port. */ + ps_flags_t test_msgport_flags; /* Flags needed to test for msgport + validity, including any preconditions. */ + process_t server = ps_context_server (ps->context); + + /* Turn off use of the msg port if we decide somewhere along the way that + it's hosed. */ + void suppress_msgport () + { + /* Turn off those things that were only good given the msg port. */ + need &= ~(flags & ~no_msgport_flags); + have = SUPPRESS_MSGPORT_FLAGS (have); + } + + flags &= ~ps->failed; /* Don't try to get things we can't. */ + + /* Propagate PSTAT_NO_MSGPORT. */ + if (flags & PSTAT_NO_MSGPORT) + have = SUPPRESS_MSGPORT_FLAGS (have); + if (have & PSTAT_NO_MSGPORT) + flags = SUPPRESS_MSGPORT_FLAGS (flags); + + no_msgport_flags = + add_preconditions (SUPPRESS_MSGPORT_FLAGS (flags), ps->context); + flags = add_preconditions (flags, ps->context); + + if (flags & PSTAT_USES_MSGPORT) + /* Add in some values that we can use to determine whether the msgport + shouldn't be used. */ + { + test_msgport_flags = add_preconditions (PSTAT_TEST_MSGPORT, ps->context); + flags |= test_msgport_flags; + } + else + test_msgport_flags = 0; + + need = flags & ~have & ~ps->failed; + + /* Returns true if (1) FLAGS is in NEED, and (2) the appropriate + preconditions PRECOND are available; if only (1) is true, FLAG is added + to the INAPP set if appropiate (to distinguish it from an error), and + returns false. */ +#define NEED(flag, precond) \ + ({ \ + ps_flags_t __flag = (flag), _precond = (precond); \ + int val; \ + if (! (__flag & need)) \ + val = 0; \ + else if ((_precond & have) == _precond) \ + val = 1; \ + else \ + { \ + val = 0; \ + if (_precond & ps->inapp) \ + ps->inapp |= __flag; \ + } \ + val; \ + }) + + /* MGET: If we're trying to set FLAG, and the preconditions PRECOND are set + in the flags already, then eval CALL and set ERR to the result. + If the resulting ERR is 0 add FLAG to the set of valid flags. ERR is + returned. */ +#define MGET(flag, precond, call) \ + ({ \ + error_t err; \ + ps_flags_t _flag = (flag); \ + if (NEED (_flag, precond)) \ + { \ + err = (call); \ + if (!err) \ + have |= _flag; \ + } \ + else \ + err = 0; \ + err; \ + }) + + /* A version of MGET specifically for the msg port, that turns off the msg + port if a call to it times out. It also implies a precondition of + PSTAT_MSGPORT. */ +#define MP_MGET(flag, precond, call) \ + ({ error_t err = MGET (flag, (precond) | PSTAT_MSGPORT, call); \ + if (err == EMACH_RCV_TIMED_OUT) suppress_msgport (); \ + err; \ + }) + + if (need & ~have & test_msgport_flags & PSTAT_PROCINFO) + /* Pre-fetch information returned by set_procinfo_flags that we need for + msgport validity testing; if we need other procinfo stuff, we get that + later. */ + have = set_procinfo_flags (ps, need & ~have & test_msgport_flags, have); + + if (NEED (PSTAT_SUSPEND_COUNT, + ((have & PSTAT_PID) ? PSTAT_TASK_BASIC : PSTAT_THREAD_BASIC))) + { + if (have & PSTAT_PID) + ps->suspend_count = ps->task_basic_info->suspend_count; + else + ps->suspend_count = ps->thread_basic_info->suspend_count; + have |= PSTAT_SUSPEND_COUNT; + } + + ps->flags = have; /* should_suppress_msgport looks at them. */ + if (should_suppress_msgport (ps)) + suppress_msgport (); + + /* the process's struct procinfo as returned by proc_getprocinfo. */ + if (need & ~have & PSTAT_PROCINFO) + have = set_procinfo_flags (ps, need, have); + + /* The process's libc msg port (see <hurd/msg.defs>). */ + MGET(PSTAT_MSGPORT, PSTAT_PID, proc_getmsgport (server, ps->pid, &ps->msgport)); + /* The process's process port. */ + MGET(PSTAT_PROCESS, PSTAT_PID, proc_pid2proc (server, ps->pid, &ps->process)); + /* The process's task port. */ + MGET(PSTAT_TASK, PSTAT_PID, proc_pid2task (server, ps->pid, &ps->task)); + + /* VM statistics for the task. See <mach/task_info.h>. */ + if (NEED (PSTAT_TASK_EVENTS, PSTAT_TASK)) + { + ps->task_events_info = &ps->task_events_info_buf; + ps->task_events_info_size = TASK_EVENTS_INFO_COUNT; + if (task_info (ps->task, TASK_EVENTS_INFO, + (task_info_t)ps->task_events_info, + &ps->task_events_info_size) + == 0) + have |= PSTAT_TASK_EVENTS; + } + + /* PSTAT_STATE_ bits for the process and all its threads. */ + if ((need & PSTAT_STATE) && (have & (PSTAT_PROC_INFO | PSTAT_THREAD_BASIC))) + { + ps->state = 0; + + if (have & PSTAT_THREAD_BASIC) + { + /* Thread states. */ + if (have & PSTAT_THREAD) + ps->state |= thread_state (ps->thread_basic_info); + else + /* For a process, we use the thread list instead of + PS->thread_basic_info because it contains more information. */ + ps->state |= summarize_thread_states (ps->proc_info); + } + + if (have & PSTAT_PROC_INFO) + /* Process state. */ + { + int pi_flags = ps->proc_info->state; + if (pi_flags & PI_STOPPED) + ps->state |= PSTAT_STATE_P_STOP; + if (pi_flags & PI_ZOMBIE) + ps->state |= PSTAT_STATE_P_ZOMBIE; + if (pi_flags & PI_SESSLD) + ps->state |= PSTAT_STATE_P_SESSLDR; + if (pi_flags & PI_LOGINLD) + ps->state |= PSTAT_STATE_P_LOGINLDR; + if (!(pi_flags & PI_EXECED)) + ps->state |= PSTAT_STATE_P_FORKED; + if (pi_flags & PI_NOMSG) + ps->state |= PSTAT_STATE_P_NOMSG; + if (pi_flags & PI_NOPARENT) + ps->state |= PSTAT_STATE_P_NOPARENT; + if (pi_flags & PI_ORPHAN) + ps->state |= PSTAT_STATE_P_ORPHAN; + if (pi_flags & PI_TRACED) + ps->state |= PSTAT_STATE_P_TRACE; + if (pi_flags & PI_WAITING) + ps->state |= PSTAT_STATE_P_WAIT; + if (pi_flags & PI_GETMSG) + ps->state |= PSTAT_STATE_P_GETMSG; + } + + have |= PSTAT_STATE; + } + + /* The process's exec arguments */ + if (NEED (PSTAT_ARGS, PSTAT_PID)) + { + char *buf = malloc (100); + ps->args_len = 100; + ps->args = buf; + if (ps->args) + { + if (proc_getprocargs (server, ps->pid, &ps->args, &ps->args_len)) + free (buf); + else + { + have |= PSTAT_ARGS; + ps->args_vm_alloced = (ps->args != buf); + if (ps->args_vm_alloced) + free (buf); + } + } + } + + /* The process's exec environment */ + if (NEED (PSTAT_ENV, PSTAT_PID)) + { + char *buf = malloc (100); + ps->env_len = 100; + ps->env = buf; + if (ps->env) + { + if (proc_getprocenv (server, ps->pid, &ps->env, &ps->env_len)) + free (buf); + else + { + have |= PSTAT_ENV; + ps->env_vm_alloced = (ps->env != buf); + if (ps->env_vm_alloced) + free (buf); + } + } + } + + /* The ctty id port; note that this is just a magic cookie; + we use it to fetch a port to the actual terminal -- it's not useful for + much else. */ + MP_MGET (PSTAT_CTTYID, PSTAT_TASK, + ps_msg_get_init_port (ps->msgport, ps->task, + INIT_PORT_CTTYID, &ps->cttyid)); + + /* A port to the process's current working directory. */ + MP_MGET (PSTAT_CWDIR, PSTAT_TASK, + ps_msg_get_init_port (ps->msgport, ps->task, + INIT_PORT_CWDIR, &ps->cwdir)); + + /* The process's auth port, which we can use to determine who the process + is authenticated as. */ + MP_MGET (PSTAT_AUTH, PSTAT_TASK, + ps_msg_get_init_port (ps->msgport, ps->task, INIT_PORT_AUTH, + &ps->auth)); + + /* The process's umask, which controls which protection bits won't be set + when creating a file. */ + MP_MGET (PSTAT_UMASK, PSTAT_TASK, + ps_msg_get_init_int (ps->msgport, ps->task, INIT_UMASK, + &ps->umask)); + + if (NEED (PSTAT_OWNER_UID, PSTAT_PROC_INFO)) + { + if (ps->proc_info->state & PI_NOTOWNED) + ps->owner_uid = -1; + else + ps->owner_uid = ps->proc_info->owner; + have |= PSTAT_OWNER_UID; + } + + /* A ps_user object for the process's owner. */ + if (NEED (PSTAT_OWNER, PSTAT_OWNER_UID)) + { + if (ps->owner_uid < 0) + { + ps->owner = 0; + have |= PSTAT_OWNER; + } + else if (! ps_context_find_user (ps->context, ps->owner_uid, &ps->owner)) + have |= PSTAT_OWNER; + } + + /* A ps_tty for the process's controlling terminal, or NULL if it + doesn't have one. */ + if (NEED (PSTAT_TTY, PSTAT_CTTYID)) + if (ps_context_find_tty_by_cttyid (ps->context, ps->cttyid, &ps->tty) == 0) + have |= PSTAT_TTY; + + /* Update PS's flag state. We haven't tried user flags yet, so don't mark + them as having failed. We do this before checking user bits so that the + user fetch hook sees PS in a consistent state. */ + ps->failed |= (need & ~PSTAT_USER_MASK) & ~have; + ps->flags = have; + + need &= ~have; + if (need && ps->context->user_hooks && ps->context->user_hooks->fetch) + /* There is some user state we need to fetch. */ + { + have |= (*ps->context->user_hooks->fetch) (ps, need, have); + /* Update the flag state again having tried the user bits. We allow + the user hook to turn on non-user bits, in which case we remove them + from the failed set; the user hook may know some way of getting the + info that we don't. */ + ps->failed = (ps->failed | need) & ~have; + ps->flags = have; + } + + return 0; +} + +/* ---------------------------------------------------------------- */ +/* Discard PS and any resources it holds. */ +void +_proc_stat_free (ps) + struct proc_stat *ps; +{ + if (ps->context->user_hooks && ps->context->user_hooks->cleanup) + /* Free any user state. */ + (*ps->context->user_hooks->cleanup) (ps); + + /* Free the mach port PORT if FLAG is set in PS's flags. */ +#define MFREEPORT(flag, port) \ + ((ps->flags & (flag)) \ + ? mach_port_deallocate(mach_task_self (), (ps->port)) : 0) + + /* If FLAG is set in PS's flags, then if VM_ALLOCED is zero, free the malloced + field MEM in PS; othrewise, vm_deallocate MEM, consisting of SIZE + elements of type ELTYPE, *unless* MEM == SBUF, which usually means + that MEM points to a static buffer somewhere instead of vm_alloc'd + memory. */ +#define MFREEMEM(flag, mem, size, vm_alloced, sbuf, eltype) \ + (((ps->flags & (flag)) && ps->mem != sbuf) \ + ? (vm_alloced ? (VMFREE(ps->mem, size * sizeof (eltype))) : free (ps->mem)) : 0) + + /* free PS's ports */ + MFREEPORT (PSTAT_PROCESS, process); + MFREEPORT (PSTAT_TASK, task); + MFREEPORT (PSTAT_MSGPORT, msgport); + MFREEPORT (PSTAT_CTTYID, cttyid); + MFREEPORT (PSTAT_CWDIR, cwdir); + MFREEPORT (PSTAT_AUTH, auth); + + /* free any allocated memory pointed to by PS */ + MFREEMEM (PSTAT_PROCINFO, proc_info, ps->proc_info_size, + ps->proc_info_vm_alloced, 0, char); + MFREEMEM (PSTAT_THREAD_BASIC, thread_basic_info, 0, 0, 0, 0); + MFREEMEM (PSTAT_THREAD_SCHED, thread_sched_info, 0, 0, 0, 0); + MFREEMEM (PSTAT_ARGS, args, ps->args_len, ps->args_vm_alloced, 0, char); + MFREEMEM (PSTAT_ENV, env, ps->env_len, ps->env_vm_alloced, 0, char); + MFREEMEM (PSTAT_TASK_EVENTS, task_events_info, ps->task_events_info_size, + 0, &ps->task_events_info_buf, char); + + FREE (ps); +} + +/* Returns in PS a new proc_stat for the process PID at the process context + CONTEXT. If a memory allocation error occurs, ENOMEM is returned, + otherwise 0. */ +error_t +_proc_stat_create (pid_t pid, struct ps_context *context, struct proc_stat **ps) +{ + *ps = NEW (struct proc_stat); + if (*ps == NULL) + return ENOMEM; + + (*ps)->pid = pid; + (*ps)->flags = PSTAT_PID; + (*ps)->failed = 0; + (*ps)->inapp = PSTAT_THREAD; + (*ps)->context = context; + (*ps)->hook = 0; + + return 0; +} + +/* ---------------------------------------------------------------- */ + +/* Returns in THREAD_PS a proc_stat for the Nth thread in the proc_stat + PS (N should be between 0 and the number of threads in the process). The + resulting proc_stat isn't fully functional -- most flags can't be set in + it. It also contains a pointer to PS, so PS shouldn't be freed without + also freeing THREAD_PS. If N was out of range, EINVAL is returned. If a + memory allocation error occured, ENOMEM is returned. Otherwise, 0 is + returned. */ +error_t +proc_stat_thread_create (struct proc_stat *ps, unsigned index, struct proc_stat **thread_ps) +{ + error_t err = proc_stat_set_flags (ps, PSTAT_NUM_THREADS); + + if (err) + return err; + else if (index >= ps->num_threads) + return EINVAL; + else + { + struct proc_stat *tps = NEW (struct proc_stat); + + if (tps == NULL) + return ENOMEM; + + /* A value of -1 for the PID indicates that this is a thread. */ + tps->pid = -1; + tps->flags = PSTAT_THREAD; + tps->failed = 0; + tps->inapp = PSTAT_PID; + + tps->thread_origin = ps; + tps->thread_index = index; + + tps->context = ps->context; + + *thread_ps = tps; + + return 0; + } +} |