/* Show process information. Copyright (C) 1995 Free Software Foundation, Inc. Written by Miles Bader 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 #include #include #include #include #include #include #include #include #include "error.h" #include "common.h" /* ---------------------------------------------------------------- */ static void usage(status) int status; { if (status != 0) fprintf(stderr, "Try `%s --help' for more information.\n", program_invocation_short_name); else { printf("Usage: %s [OPTION...] [PID...]\n", program_invocation_short_name); printf("\ \n\ -a, --all-users List other users' processes\n\ --fmt=FMT Use the output-format FMT\n\ FMT may be `default', `user', `vmem', `long',\n\ `jobc', `full', `hurd', `hurd-long',\n\ or a custom format-string\n\ -d List all processes except process group leaders\n\ -e, --all List all processes\n\ -f Use the `full' output-format\n\ -g Include session leaders\n\ -H, --no-header Don't print a descriptive header line\n\ -j Use the `jobc' output-format\n\ -l Use the `long' output-format\n\ --login[=LID] Add the processes from the login collection LID\n\ (which defaults that of the current process)\n\ -M, --no-msg-port Don't show info that uses a process's msg port\n\ -n, --nominal-fields Don't elide fields containing `uninteresting' data\n\ -oUSER, --owner=USER Show processes owned by USER\n\ -pPID, --pid=PID List the process PID\n\ --pgrp=PGRP List processes in the process group PGRP\n\ -P, --no-parent Include processes without parents\n\ -Q, --all-fields Don't elide unusable fields (normally if there's\n\ some reason ps can't print a field for any\n\ process, it's removed from the output entirely)\n\ -r, --reverse Reverse the order of any sort\n\ --session[=SID] Add the processes from the session SID (which\n\ defaults to the sid of the current process)\n\ --sort=FIELD Sort the output with respect to FIELD\n\ -s, --threads Show the threads for each process\n\ -tTTY, --tty=TTY Only show processes who's controlling tty is TTY\n\ -u Use the `user' output-format\n\ -v Use the `vmem' output-format\n\ -w (ignored)\n\ -x Include orphaned processes\n\ \n\ The USER, LID, PID, PGRP, and SID arguments may also be comma separated lists.\n\ The System V options -u and -g may be accessed with -o and --pgrp.\n\ "); } exit(status); } int parse_enum(char *arg, char **choices, char *kind, bool allow_mismatches) { int arglen = strlen(arg); char **p = choices; int partial_match = -1; while (*p != NULL) if (strcmp(*p, arg) == 0) return p - choices; else { if (strncmp(*p, arg, arglen) == 0) if (partial_match >= 0) { fprintf(stderr, "%s: ambiguous %s", arg, kind); usage(1); } else partial_match = p - choices; p++; } if (partial_match >= 0 || allow_mismatches) return partial_match; else { fprintf(stderr, "%s: Invalid %s", arg, kind); usage(1); return 0; } } /* ---------------------------------------------------------------- */ #define FILTER_OWNER 0x01 #define FILTER_NSESSLDR 0x02 #define FILTER_CTTY 0x04 #define FILTER_UNORPHANED 0x08 #define FILTER_PARENTED 0x10 enum procsets { PROCSET_ALL, PROCSET_SESSION, PROCSET_LOGIN }; char *procset_names[] = {"all", "session", "login", 0}; /* Long options without corresponding short ones. -1 is EOF. */ #define OPT_LOGIN -2 #define OPT_SESSION -3 #define OPT_SORT -4 #define OPT_FMT -5 #define OPT_HELP -6 #define OPT_PGRP -7 #define SHORT_OPTIONS "adefgHjlMno:p:PQrst::uvwx" static struct option options[] = { {"all", no_argument, 0, 'e'}, {"all-users", no_argument, 0, 'a'}, {"all-fields", no_argument, 0, 'Q'}, {"fmt", required_argument, 0, OPT_FMT}, {"help", no_argument, 0, OPT_HELP}, {"login", optional_argument, 0, OPT_LOGIN}, {"lid", optional_argument, 0, OPT_LOGIN}, {"no-header", no_argument, 0, 'H'}, {"no-msg-port", no_argument, 0, 'M'}, {"no-squash", no_argument, 0, 'Q'}, {"no-parent", no_argument, 0, 'P'}, {"nominal-fields", no_argument, 0, 'n'}, {"owner", required_argument, 0, 'o'}, {"pgrp", required_argument, 0, OPT_PGRP}, {"session", optional_argument, 0, OPT_SESSION}, {"sid", optional_argument, 0, OPT_SESSION}, {"threads", no_argument, 0, 's'}, {"tty", optional_argument, 0, 't'}, {"reverse", no_argument, 0, 'r'}, {"sort", required_argument, 0, OPT_SORT}, {0, 0, 0, 0} }; /* The names of various predefined output formats. */ char *fmt_names[] = {"default", "user", "vmem", "long", "jobc", "full", "hurd", "hurd-long",0}; /* How each of those formats should be sorted; */ char *fmt_sortkeys[] = {"pid", "%cpu", "%mem", "pid", "pid", "pid", "pid", "pid"}; /* and whether the sort should be backwards or forwards; */ bool fmt_sortrev[] = {FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE}; /* and the actual format strings. */ char *fmts[] = { /* default */ "~PID ~TH# ~TT=tty ~SC=susp ~STAT=state ~TIME ~COMMAND=args", /* user (-u) */ "~USER ~PID ~TH# ~%CPU ~%MEM ~SZ=vsize ~RSS=rsize ~TT=tty ~SC=susp ~STAT=state ~COMMAND=args", /* vmem (-v) */ "~PID ~TH# ~STAT=state ~SL=sleep ~PAGEIN=pgins ~FAULTS=pgflts ~COWFLT=cowflts ~ZFILLS ~SIZE=vsize ~RSS=rsize ~%CPU ~%MEM ~COMMAND=args", /* long (-l) */ "~UID ~PID ~TH# ~PPID ~PRI ~NI=bpri ~TH=nth ~MSGI=msgin ~MSGO=msgout ~SZ=vsize ~RSS=rsize ~SC=susp ~STAT=state ~TT=tty ~TIME ~COMMAND=args", /* jobc (-j) */ "~USER ~PID ~TH# ~PPID ~PGRP ~SESS ~LCOLL ~SC=susp ~STAT=state ~TT=tty ~TIME ~COMMAND=args", /* full (-f) (from sysv) */ "~-USER ~PID ~PPID ~TTY ~TIME ~COMMAND=args", /* hurd */ "~PID ~Th# ~UID ~NTh ~VMem=vsize ~RSS=rsize ~User=utime ~System=stime ~Args", /* hurd-long */ "~PID ~Th# ~UID ~PPID ~PGRP ~Sess ~NTh ~VMem=vsize ~RSS=rsize ~%CPU ~User=utime ~System=stime ~Args" }; /* ---------------------------------------------------------------- */ /* For each string in the comma-separated list in ARG, call ADD_FN; if ARG is empty and DEFAULT_ADD_FN isn't NULL, then call DEFAULT_ADD_FN instead. */ static void _parse_strlist (char *arg, void (*add_fn)(char *str), void (*default_add_fn)(), char *type_name) { if (arg) while (isspace(*arg)) arg++; if (arg == NULL || *arg == '\0') if (default_add_fn) (*default_add_fn)(); else error(7, 0, "No %s specified", type_name); else { char *end = arg; void mark_end() { *end++ = '\0'; while (isspace(*end)) end++; } void parse_element() { if (*arg == '\0') error(7, 0, "Empty element in %s list", type_name); (*add_fn)(arg); arg = end; } while (*end != '\0') switch (*end) { case ' ': case '\t': mark_end(); if (*end == ',') mark_end(); parse_element(); break; case ',': mark_end(); parse_element(); break; default: end++; } parse_element(); } } /* ---------------------------------------------------------------- */ /* For each string in the comma-separated list in ARG, call ADD_FN; if ARG is empty and DEFAULT_FN isn't NULL, then call ADD_FN on the resutl of calling DEFAULT_FN instead, otherwise signal an error. */ static void parse_strlist (char *arg, void (*add_fn)(char *str), char *(*default_fn)(), char *type_name) { void default_str_add() { (*add_fn)((*default_fn)()); } _parse_strlist(arg, add_fn, default_str_add, type_name); } /* For each numeric string in the comma-separated list in ARG, call ADD_FN; if ARG is empty and DEFAULT_FN isn't NULL, then call DEF_FN to get a number, and call ADD_FN on that, otherwise signal an error. If any member of the list isn't a number, and LOOKUP_FN isn't NULL, then it is called to return an integer for the string. LOOKUP_FN should signal an error itself it there's some problem parsing the string. */ static void parse_numlist (char *arg, void (*add_fn)(unsigned num), int (*default_fn)(), int (*lookup_fn)(char *str), char *type_name) { void default_num_add() { (*add_fn)((*default_fn)()); } void add_num_str(char *str) { char *p; for (p = str; *p != '\0'; p++) if (!isdigit(*p)) { (*add_fn)((*lookup_fn)(str)); return; } (*add_fn)(atoi(str)); } _parse_strlist(arg, add_num_str, default_num_add, type_name); } /* ---------------------------------------------------------------- */ static process_t proc_server; /* Returns our session id. */ static pid_t current_sid() { pid_t sid; error_t err = proc_getsid(proc_server, getpid(), &sid); if (err) error(2, err, "Couldn't get current session id"); return sid; } /* Returns our login collection id. */ static pid_t current_lid() { pid_t lid; error_t err = proc_getloginid(proc_server, getpid(), &lid); if (err) error(2, err, "Couldn't get current login collection") ; return lid; } /* Returns the UID for the user called NAME. */ static int lookup_user(char *name) { struct passwd *pw = getpwnam(name); if (pw == NULL) error(2, 0, "%s: Unknown user", name); return pw->pw_uid; } /* ---------------------------------------------------------------- */ void main(int argc, char *argv[]) { int opt; error_t err; unsigned num_uids = 0, uids_alloced = 1; uid_t *uids = malloc(sizeof(uid_t) * num_uids); unsigned num_tty_names = 0, tty_names_alloced = 1; char **tty_names = malloc(sizeof(char *) * num_tty_names); proc_stat_list_t procset; ps_context_t context; ps_fmt_t fmt; char *fmt_string = "default", *sort_key_name = NULL; unsigned filter_mask = FILTER_OWNER | FILTER_NSESSLDR | FILTER_UNORPHANED | FILTER_PARENTED; bool sort_reverse = FALSE, print_heading = TRUE; bool squash_bogus_fields = TRUE, squash_nominal_fields = TRUE; bool show_threads = FALSE, no_msg_port = FALSE; /* Add a specific process to be printed out. */ void add_pid (unsigned pid) { err = proc_stat_list_add_pid(procset, pid); if (err) error(3, err, "%d: Can't add process id", pid); /* If explicit processes are specified, we probably don't want to filter them out later. This implicit turning off of filtering might be confusing in the case where a login-collection or session is specified along with some pids, but it's probably not worth worrying about. */ filter_mask = 0; } /* Print out all process from the given session. */ void add_sid(unsigned sid) { err = proc_stat_list_add_session(procset, sid); if (err) error(2, err, "Couldn't add session"); } /* Print out all process from the given login collection. */ void add_lid(unsigned lid) { error_t err = proc_stat_list_add_login_coll(procset, lid); if (err) error(2, err, "Couldn't add login collection"); } /* Print out all process from the given process group. */ void add_pgrp(unsigned pgrp) { error_t err = proc_stat_list_add_pgrp(procset, pgrp); if (err) error(2, err, "Couldn't add process group"); } /* Add a user who's processes should be printed out. */ void add_uid (unsigned uid) { if (uids_alloced == num_uids) { uids_alloced *= 2; uids = realloc(uids, uids_alloced * sizeof(int)); if (uids == NULL) error(8, ENOMEM, "Can't allocate uid list"); } uids[num_uids++] = uid; } /* Returns TRUE if PS is owned by any of the users in UIDS. */ bool proc_stat_has_owner(struct proc_stat *ps) { unsigned i; uid_t uid = proc_stat_info(ps)->owner; for (i = 0; i < num_uids; i++) if (uids[i] == uid) return TRUE; return FALSE; } /* Add TTY_NAME to the list for which processes with those controlling terminals will be printed. */ void add_tty_name (char *tty_name) { if (tty_names_alloced == num_tty_names) { tty_names_alloced *= 2; tty_names = realloc(tty_names, tty_names_alloced * sizeof(int)); if (tty_names == NULL) error(8, ENOMEM, "Can't allocate tty_name list"); } tty_names[num_tty_names++] = tty_name; } bool proc_stat_has_ctty(struct proc_stat *ps) { if (proc_stat_has(ps, PSTAT_TTY)) /* Only match processes whose tty we can figure out. */ { ps_tty_t tty = proc_stat_tty(ps); if (tty) { unsigned i; char *name = ps_tty_name(tty); char *short_name = ps_tty_short_name(tty); for (i = 0; i < num_tty_names; i++) if ((name && strcmp (tty_names[i], name) == 0) || (short_name && strcmp (tty_names[i], short_name) == 0)) return TRUE; } } return FALSE; } /* Returns the name of the current controlling terminal. */ static char *current_tty_name() { error_t err; ps_tty_t 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); } proc_server = getproc(); err = ps_context_create(proc_server, &context); if (err) error(1, err, "ps_context_create"); err = proc_stat_list_create(context, &procset); if (err) error(1, err, "proc_stat_list_create"); /* Parse our options. */ while ((opt = getopt_long(argc, argv, "-" SHORT_OPTIONS, options, 0)) != EOF) switch (opt) { case 1: /* Non-option argument. */ if (!isdigit(*optarg)) /* Old-fashioned `ps' syntax takes options without the leading dash. Prepend a dash and feed back to getopt. */ { size_t len = strlen (optarg) + 1; argv[--optind] = alloca (1 + len); argv[optind][0] = '-'; memcpy (&argv[optind][1], optarg, len); break; } /* Otherwise, fall through and treat the arg as a process id. */ case 'p': parse_numlist(optarg, add_pid, NULL, NULL, "PID"); break; case 'a': filter_mask &= ~(FILTER_OWNER | FILTER_NSESSLDR); break; case 'd': filter_mask &= ~(FILTER_OWNER | FILTER_UNORPHANED); break; case 'e': filter_mask = 0; break; case 'g': filter_mask &= ~FILTER_NSESSLDR; 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 's': show_threads = TRUE; break; case OPT_FMT: fmt_string = optarg; break; case OPT_SORT: sort_key_name = optarg; break; case 'r': sort_reverse = TRUE; break; case 't': parse_strlist(optarg, add_tty_name, current_tty_name, "tty"); break; case 'o': parse_numlist(optarg, add_uid, NULL, lookup_user, "user"); break; case OPT_SESSION: parse_numlist(optarg, add_sid, current_sid, NULL, "session id"); break; case OPT_LOGIN: parse_numlist(optarg, add_lid, current_lid, NULL, "login collection"); break; case OPT_PGRP: parse_numlist(optarg, add_pgrp, NULL, NULL, "process group"); break; case 'w': /* Ignored; just here to make BSD users less unhappy. */ break; case OPT_HELP: usage(0); default: usage(1); } while (argv[optind] != NULL) parse_numlist(argv[optind++], add_pid, NULL, NULL, "PID"); if (num_uids == 0 && (filter_mask & FILTER_OWNER)) add_uid(getuid()); { int fmt_index = parse_enum(fmt_string, fmt_names, "format type", 1); if (fmt_index >= 0) { fmt_string = fmts[fmt_index]; if (sort_key_name == NULL) { sort_key_name = fmt_sortkeys[fmt_index]; sort_reverse = fmt_sortrev[fmt_index]; } } } if (proc_stat_list_num_procs(procset) == 0) { err = proc_stat_list_add_all(procset); if (err) error(2, err, "Couldn't get process list"); } if (no_msg_port) proc_stat_list_set_flags(procset, PSTAT_NO_MSGPORT); /* Filter out any processes that we don't want to show. */ if (num_uids > 0) proc_stat_list_filter1(procset, proc_stat_has_owner, PSTAT_INFO, 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_NSESSLDR) proc_stat_list_filter(procset, &ps_not_sess_leader_filter, FALSE); if (filter_mask & FILTER_UNORPHANED) proc_stat_list_filter(procset, &ps_unorphaned_filter, FALSE); if (filter_mask & FILTER_PARENTED) proc_stat_list_filter(procset, &ps_parent_filter, FALSE); err = ps_fmt_create(fmt_string, ps_std_fmt_specs, &fmt); if (err) error(4, err, "Can't create output format"); if (show_threads) proc_stat_list_add_threads(procset); if (sort_key_name != NULL) /* Sort on the given field; we look in both the user-named fields and the system named fields for the name given, as the user may only know the printed title and not the official name. */ { ps_fmt_spec_t sort_key = NULL; unsigned nfields = ps_fmt_num_fields(fmt); ps_fmt_field_t field = ps_fmt_fields(fmt); /* first, look at the actual printed titles in the current format */ while (nfields-- > 0) if (strcasecmp(sort_key_name, ps_fmt_field_title(field)) == 0) { sort_key = ps_fmt_field_fmt_spec(field); break; } else field++; /* Then, if not there, look at the actual ps_fmt_spec names (this way the user can sort on a key that's not actually displayed). */ if (sort_key == NULL) { sort_key = find_ps_fmt_spec(sort_key_name, ps_std_fmt_specs); if (sort_key == NULL) error(3, 0, "%s: bad sort key", sort_key_name); } err = proc_stat_list_sort(procset, sort_key, sort_reverse); if (err) /* Give an error message, but don't exit. */ error(0, err, "Couldn't sort processes"); } if (squash_bogus_fields) /* Remove any fields that we can't print anyway (because of system bugs/protection violations?). */ { ps_flags_t bogus_flags = ps_fmt_needs(fmt); err = proc_stat_list_find_bogus_flags(procset, &bogus_flags); if (err) error(0, err, "Couldn't remove bogus fields"); else ps_fmt_squash_flags (fmt, bogus_flags); } if (squash_nominal_fields) /* Remove any fields that contain only `uninteresting' information. */ { bool nominal (ps_fmt_spec_t spec) { return proc_stat_list_spec_nominal (procset, spec); } ps_fmt_squash (fmt, nominal); } if (print_heading) if (proc_stat_list_num_procs(procset) > 0) { err = ps_fmt_write_titles(fmt, stdout, NULL); if (err) error(0, err, "Can't print titles"); putc('\n', stdout); } else error(0, 0, "No applicable processes"); err = proc_stat_list_fmt(procset, fmt, stdout, NULL); if (err) error(5, err, "Couldn't output process status"); exit(0); }