diff options
Diffstat (limited to 'utils/ps.c')
-rw-r--r-- | utils/ps.c | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/utils/ps.c b/utils/ps.c new file mode 100644 index 00000000..3dba399e --- /dev/null +++ b/utils/ps.c @@ -0,0 +1,442 @@ +/* Show process information. + + Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.org> + + 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 <ctype.h> +#include <unistd.h> +#include <argp.h> +#include <argz.h> +#include <idvec.h> +#include <ps.h> +#include <error.h> +#include <version.h> + +#include "psout.h" +#include "parse.h" +#include "pids.h" + +const char *argp_program_version = STANDARD_HURD_VERSION (ps); + +#define OA OPTION_ARG_OPTIONAL + +static const struct argp_option options[] = +{ + {0,0,0,0, "Output format selection:", 1}, + {"format", 'F', "FMT", 0, "Use the output-format FMT; FMT may be" + " `default', `user', `vmem', `long'," + " `jobc', `full', `hurd', `hurd-long'," + " or a custom format-string"}, + {"posix-format",'o', "FMT", 0, "Use the posix-style output-format FMT"}, + {0, 'f', 0, 0, "Use the `full' output-format"}, + {0, 'j', 0, 0, "Use the `jobc' output-format"}, + {0, 'l', 0, 0, "Use the `long' output-format"}, + {0, 'u', 0, 0, "Use the `user' output-format"}, + {0, 'v', 0, 0, "Use the `vmem' output-format"}, + + {0,0,0,0, "Process filtering (by default, other users'" + " processes, threads, and process-group leaders are not shown):", 2}, + {"all-users", 'a', 0, 0, "List other users' processes"}, + {0, 'd', 0, 0, "List all processes except process group" + " leaders"}, + {"all", 'e', 0, 0, "List all processes"}, + {0, 'A', 0, OPTION_ALIAS}, /* Posix option meaning -e */ + {0, 'g', 0, 0, "Include session and login leaders"}, + {"owner", 'U', "USER", 0, "Show only processes owned by USER"}, + {"not-owner", 'O', "USER", 0, "Show only processes not owned by USER"}, + {"no-parent", 'P', 0, 0, "Include processes without parents"}, + {"threads", 'T', 0, 0, "Show the threads for each process"}, + {"tty", 't', "TTY", OA, "Only show processes with controlling" + " terminal TTY"}, + {0, 'x', 0, 0, "Include orphaned processes"}, + + {0,0,0,0, "Elision of output fields:", 4}, + {"no-msg-port",'M', 0, 0, "Don't show info that uses a process's" + " msg port"}, + {"nominal-fields",'n', 0, 0, "Don't elide fields containing" + " `uninteresting' data"}, + {"all-fields", 'Q', 0, 0, "Don't elide unusable fields (normally" + " if there's some reason ps can't print" + " a field for any process, it's removed" + " from the output entirely)"}, + + {0,0,0,0, "Output attributes:"}, + {"no-header", 'H', 0, 0, "Don't print a descriptive header line"}, + {"reverse", 'r', 0, 0, "Reverse the order of any sort"}, + {"sort", 's', "FIELD",0, "Sort the output with respect to FIELD," + " backwards if FIELD is prefixed by `-'"}, + {"top", 'h', "ENTRIES", OA, "Show the top ENTRIES processes" + " (default 10), or if ENTRIES is" + " negative, the bottom -ENTRIES"}, + {"head", 0, 0, OPTION_ALIAS}, + {"bottom", 'b', "ENTRIES", OA, "Show the bottom ENTRIES processes" + " (default 10)"}, + {"tail", 0, 0, OPTION_ALIAS}, + {"width", 'w', "WIDTH",OA, "If WIDTH is given, try to format the" + " output for WIDTH columns, otherwise," + " remove the default limit"}, + {0, 0} +}; + +static const char doc[] = +"Show information about processes PID... (default all `interesting' processes)" +"\vThe USER, LID, PID, PGRP, and SID arguments may also be comma separated" +" lists. The System V options -u and -g may be accessed with -O and -G."; + +#define FILTER_OWNER 0x01 +#define FILTER_NOT_LEADER 0x02 +#define FILTER_CTTY 0x04 +#define FILTER_UNORPHANED 0x08 +#define FILTER_PARENTED 0x10 + +/* A particular predefined output format. */ +struct output_fmt +{ + const char *name; + const char *sort_key; /* How this format should be sorted. */ + const char *fmt; /* The format string. */ +}; + +/* The predefined output formats. */ +struct output_fmt output_fmts[] = +{ + { "default", "pid", + "%^%?user %pid %th %tt %sc %stat %time %command" }, + { "user", "-cpu", + "%^%user %pid %th %cpu %mem %sz %rss %tt %sc %stat %start %time %command" }, + { "vmem", "-mem", + "%^%pid %th %stat %sl %pgins %pgflts %cowflts %zfills %sz %rss %cpu %mem %command" + }, + { "long", "pid", + "%^%uid %pid %th %ppid %pri %ni %nth %msgi %msgo %sz %rss %sc %wait %stat %tt %time %command" }, + { "jobc", "pid", + "%^%user %pid %th %ppid %pgrp %sess %lcoll %sc %stat %tt %time %command" }, + { "full", "pid", + "%^%-user %pid %ppid %tty %time %command" }, + { "hurd", "pid", + "%pid %th %uid %nth %{vsize:Vmem} %rss %{utime:User} %{stime:System} %args" + }, + { "hurd-long", "pid", + "%pid %th %uid %ppid %pgrp %sess %nth %{vsize:Vmem} %rss %cpu %{utime:User} %{stime:System} %args" + } +}; + +/* Augment the standard specs with our own abbrevs. */ +static const struct ps_fmt_spec +spec_abbrevs[] = { + {"TT=tty"}, {"SC=susp"}, {"Stat=state"}, {"Command=args"}, {"SL=sleep"}, + {"NTH=nth", "TH"}, {"NI=bpri"}, {"SZ=vsize"}, {"RSS=rsize"}, + {"MsgI=msgin"}, {"MsgO=msgout"}, + {0} +}; +static struct ps_fmt_specs ps_specs = + { spec_abbrevs, &ps_std_fmt_specs }; + +/* Returns the UID for the user called NAME. */ +static int +lookup_user (const char *name, struct argp_state *state) +{ + struct passwd *pw = getpwnam(name); + if (pw == NULL) + argp_failure (state, 2, 0, "%s: Unknown user", name); + return pw->pw_uid; +} + +int +main(int argc, char *argv[]) +{ + error_t err; + /* A buffer used for rewriting old-style ps command line arguments that + need a dash prepended for the parser to understand them. It gets + realloced for each successive arg that needs it, on the assumption that + args don't get parsed multiple times. */ + char *arg_hack_buf = 0; + struct idvec *only_uids = make_idvec (), *not_uids = make_idvec (); + char *tty_names = 0; + unsigned num_tty_names = 0; + struct proc_stat_list *procset; + struct ps_context *context; + const char *fmt_string = "default", *sort_key_name = NULL; + unsigned filter_mask = + FILTER_OWNER | FILTER_NOT_LEADER | FILTER_UNORPHANED | FILTER_PARENTED; + int sort_reverse = FALSE, print_heading = TRUE; + int squash_bogus_fields = TRUE, squash_nominal_fields = TRUE; + int show_threads = FALSE, no_msg_port = FALSE; + int output_width = -1; /* Desired max output size. */ + int show_non_hurd_procs = 1; /* Show non-hurd processes. */ + int posix_fmt = 0; /* Use a posix_fmt-style format string. */ + int top = 0; /* Number of entries to output. */ + pid_t *pids = 0; /* User-specified pids. */ + size_t num_pids = 0; + struct pids_argp_params pids_argp_params = { &pids, &num_pids, 1 }; + + /* Add a user who's processes should be printed out. */ + error_t add_uid (uid_t uid, struct argp_state *state) + { + error_t err = idvec_add (only_uids, uid); + if (err) + argp_failure (state, 23, err, "Can't add uid"); + return err; + } + /* Add a user who's processes should not be printed out. */ + error_t add_not_uid (uid_t uid, struct argp_state *state) + { + error_t err = idvec_add (not_uids, uid); + if (err) + argp_failure (state, 23, err, "Can't add uid"); + return err; + } + /* Returns TRUE if PS is owned by any of the users in ONLY_UIDS, and none + in NOT_UIDS. */ + int proc_stat_owner_ok(struct proc_stat *ps) + { + int uid = proc_stat_owner_uid (ps); + if (only_uids->num > 0 && !idvec_contains (only_uids, uid)) + return 0; + if (not_uids->num > 0 && idvec_contains (not_uids, uid)) + return 0; + return 1; + } + + /* Add TTY_NAME to the list for which processes with those controlling + terminals will be printed. */ + error_t add_tty_name (const char *tty_name, struct argp_state *state) + { + error_t err = argz_add (&tty_names, &num_tty_names, tty_name); + if (err) + argp_failure (state, 8, err, "%s: Can't add tty", tty_name); + return err; + } + int proc_stat_has_ctty(struct proc_stat *ps) + { + if (proc_stat_has(ps, PSTAT_TTY)) + /* Only match processes whose tty we can figure out. */ + { + struct ps_tty *tty = proc_stat_tty (ps); + if (tty) + { + char *try = 0; + const char *name = ps_tty_name (tty); + const char *short_name = ps_tty_short_name(tty); + + while ((try = argz_next (tty_names, num_tty_names, try))) + if ((name && strcmp (try, name) == 0) + || (short_name && strcmp (try, short_name) == 0)) + return TRUE; + } + } + return FALSE; + } + + /* Returns the name of the current controlling terminal. */ + static const char *current_tty_name() + { + error_t err; + struct ps_tty *tty; + mach_port_t cttyid = getcttyid(); + + if (cttyid == MACH_PORT_NULL) + error(2, 0, "No controlling terminal"); + + err = ps_context_find_tty_by_cttyid (context, cttyid, &tty); + if (err) + error(2, err, "Can't get controlling terminal"); + + return ps_tty_name (tty); + } + + error_t parse_opt (int key, char *arg, struct argp_state *state) + { + switch (key) + { + case ARGP_KEY_ARG: /* Non-option argument. */ + if (!isdigit (*arg) && !state->quoted) + /* Old-fashioned `ps' syntax takes options without the leading + dash. Prepend a dash and feed back to getopt. */ + { + size_t len = strlen (arg) + 1; + arg_hack_buf = realloc (arg_hack_buf, 1 + len); + state->argv[--state->next] = arg_hack_buf; + state->argv[state->next][0] = '-'; + memcpy (&state->argv[state->next][1], arg, len); + break; + } + else + /* Let PIDS_ARGP handle it. */ + return ARGP_ERR_UNKNOWN; + + case 'a': filter_mask &= ~FILTER_OWNER; break; + case 'd': filter_mask &= ~(FILTER_OWNER | FILTER_UNORPHANED); break; + case 'e': case 'A': filter_mask = 0; break; + case 'g': filter_mask &= ~FILTER_NOT_LEADER; break; + case 'x': filter_mask &= ~FILTER_UNORPHANED; break; + case 'P': filter_mask &= ~FILTER_PARENTED; break; + case 'f': fmt_string = "full"; break; + case 'u': fmt_string = "user"; break; + case 'v': fmt_string = "vmem"; break; + case 'j': fmt_string = "jobc"; break; + case 'l': fmt_string = "long"; break; + case 'M': no_msg_port = TRUE; break; + case 'H': print_heading = FALSE; break; + case 'Q': squash_bogus_fields = squash_nominal_fields = FALSE; break; + case 'n': squash_nominal_fields = FALSE; break; + case 'T': show_threads = TRUE; break; + case 's': sort_key_name = arg; break; + case 'r': sort_reverse = TRUE; break; + case 'h': top = arg ? atoi (arg) : 10; break; + case 'b': top = -(arg ? atoi (arg) : 10); break; + case 'F': fmt_string = arg; posix_fmt = 0; break; + case 'o': fmt_string = arg; posix_fmt = 1; break; + + case 'w': + output_width = arg ? atoi (arg) : 0; /* 0 means `unlimited'. */ + break; + + case 't': + return parse_strlist (arg, add_tty_name, current_tty_name, "tty", state); + case 'U': + return parse_numlist (arg, add_uid, NULL, lookup_user, "user", state); + case 'O': + return parse_numlist (arg, add_not_uid, NULL, lookup_user, "user", state); + + case ARGP_KEY_INIT: + /* Initialize inputs for child parsers. */ + state->child_inputs[0] = &pids_argp_params; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + + struct argp_child argp_kids[] = + { { &pids_argp, 0, + "Process selection (before filtering; default is all processes):", 3}, + {0} }; + struct argp argp = { options, parse_opt, 0, doc, argp_kids }; + + err = ps_context_create (getproc (), &context); + if (err) + error(1, err, "ps_context_create"); + + /* Parse our command line. This shouldn't ever return an error. */ + argp_parse (&argp, argc, argv, 0, 0, 0); + + /* Select an explicit format string if FMT_STRING is a format name. */ + { + const char *fmt_name (unsigned n) + { + return + n >= (sizeof output_fmts / sizeof *output_fmts) + ? 0 + : output_fmts[n].name; + } + int fmt_index = parse_enum (fmt_string, fmt_name, "format type", 1, 0); + if (fmt_index >= 0) + { + fmt_string = output_fmts[fmt_index].fmt; + if (sort_key_name == NULL) + sort_key_name = output_fmts[fmt_index].sort_key; + } + } + + err = proc_stat_list_create(context, &procset); + if (err) + error(1, err, "proc_stat_list_create"); + + if (num_pids == 0) + /* No explicit processes specified. */ + { + err = proc_stat_list_add_all (procset, 0, 0); + + /* Try to avoid showing non-hurd processes if this isn't a native-booted + hurd system (because there would be lots of them). Here we use a + simple heuristic: Is the 5th process a hurd process (1-4 are + typically: proc server, init, kernel, boot default pager (maybe); the + last two are know not to be hurd processes)? */ + if (procset->num_procs > 4) + { + struct proc_stat *ps = procset->proc_stats[4]; + if (proc_stat_set_flags (ps, PSTAT_STATE) == 0 + && (ps->flags & PSTAT_STATE)) + show_non_hurd_procs = !(ps->state & PSTAT_STATE_P_NOPARENT); + else + /* Something is fucked, we can't get the state bits for PS. + Default to showing everything. */ + show_non_hurd_procs = 1; + } + } + else + /* User-specified processes. */ + { + err = proc_stat_list_add_pids (procset, pids, num_pids, 0); + filter_mask = 0; /* Don't mess with them. */ + } + + if (err) + error(2, err, "Can't get process list"); + + if (no_msg_port) + proc_stat_list_set_flags(procset, PSTAT_NO_MSGPORT); + + if (only_uids->num == 0 && (filter_mask & FILTER_OWNER)) + /* Restrict the output to only our own processes. */ + { + int uid = getuid (); + if (uid >= 0) + add_uid (uid, 0); + else + filter_mask &= ~FILTER_OWNER; /* Must be an anonymous process. */ + } + + /* Filter out any processes that we don't want to show. */ + if (only_uids->num || not_uids->num) + proc_stat_list_filter1 (procset, proc_stat_owner_ok, + PSTAT_OWNER_UID, FALSE); + if (num_tty_names > 0) + { + /* We set the PSTAT_TTY flag separately so that our filter function + can look at any procs that fail to set it. */ + proc_stat_list_set_flags(procset, PSTAT_TTY); + proc_stat_list_filter1(procset, proc_stat_has_ctty, 0, FALSE); + } + if (filter_mask & FILTER_NOT_LEADER) + proc_stat_list_filter (procset, &ps_not_leader_filter, FALSE); + if (filter_mask & FILTER_UNORPHANED) + proc_stat_list_filter (procset, &ps_unorphaned_filter, FALSE); + if (!show_non_hurd_procs && (filter_mask & FILTER_PARENTED)) + proc_stat_list_filter (procset, &ps_parent_filter, FALSE); + + if (show_threads) + proc_stat_list_add_threads(procset); + + proc_stat_list_filter (procset, &ps_alive_filter, FALSE); + + psout (procset, fmt_string, posix_fmt, &ps_specs, + sort_key_name, sort_reverse, + output_width, print_heading, + squash_bogus_fields, squash_nominal_fields, top); + + return 0; +} |