ps.c revision 129600
1/*-
2 * Copyright (c) 1990, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 4. Neither the name of the University nor the names of its contributors
14 *    may be used to endorse or promote products derived from this software
15 *    without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 * ------+---------+---------+-------- + --------+---------+---------+---------*
29 * Copyright (c) 2004  - Garance Alistair Drosehn <gad@FreeBSD.org>.
30 * All rights reserved.
31 *
32 * Significant modifications made to bring `ps' options somewhat closer
33 * to the standard for `ps' as described in SingleUnixSpec-v3.
34 * ------+---------+---------+-------- + --------+---------+---------+---------*
35 */
36
37#ifndef lint
38static const char copyright[] =
39"@(#) Copyright (c) 1990, 1993, 1994\n\
40	The Regents of the University of California.  All rights reserved.\n";
41#endif /* not lint */
42
43#if 0
44#ifndef lint
45static char sccsid[] = "@(#)ps.c	8.4 (Berkeley) 4/2/94";
46#endif /* not lint */
47#endif
48
49#include <sys/cdefs.h>
50__FBSDID("$FreeBSD: head/bin/ps/ps.c 129600 2004-05-22 23:13:58Z gad $");
51
52#include <sys/param.h>
53#include <sys/proc.h>
54#include <sys/user.h>
55#include <sys/stat.h>
56#include <sys/ioctl.h>
57#include <sys/sysctl.h>
58
59#include <ctype.h>
60#include <err.h>
61#include <errno.h>
62#include <fcntl.h>
63#include <grp.h>
64#include <kvm.h>
65#include <limits.h>
66#include <locale.h>
67#include <paths.h>
68#include <pwd.h>
69#include <stdio.h>
70#include <stdlib.h>
71#include <string.h>
72#include <unistd.h>
73
74#include "ps.h"
75
76#define	W_SEP	" \t"		/* "Whitespace" list separators */
77#define	T_SEP	","		/* "Terminate-element" list separators */
78
79#ifdef LAZY_PS
80#define	DEF_UREAD	0
81#define	OPT_LAZY_f	"f"
82#else
83#define	DEF_UREAD	1	/* Always do the more-expensive read. */
84#define	OPT_LAZY_f		/* I.e., the `-f' option is not added. */
85#endif
86
87int	 cflag;			/* -c */
88int	 eval;			/* Exit value */
89time_t	 now;			/* Current time(3) value */
90int	 rawcpu;		/* -C */
91int	 sumrusage;		/* -S */
92int	 termwidth;		/* Width of the screen (0 == infinity). */
93int	 totwidth;		/* Calculated-width of requested variables. */
94
95struct varent *vhead;
96
97static int	 forceuread = DEF_UREAD; /* Do extra work to get u-area. */
98static kvm_t	*kd;
99static KINFO	*kinfo;
100static int	 needcomm;	/* -o "command" */
101static int	 needenv;	/* -e */
102static int	 needuser;	/* -o "user" */
103static int	 optfatal;	/* Fatal error parsing some list-option. */
104
105static enum sort { DEFAULT, SORTMEM, SORTCPU } sortby = DEFAULT;
106
107struct listinfo;
108typedef	int	addelem_rtn(struct listinfo *_inf, const char *_elem);
109
110struct listinfo {
111	int		 count;
112	int		 maxcount;
113	int		 elemsize;
114	addelem_rtn	*addelem;
115	const char	*lname;
116	union {
117		gid_t	*gids;
118		pid_t	*pids;
119		dev_t	*ttys;
120		uid_t	*uids;
121		void	*ptr;
122	} l;
123};
124
125static int	 addelem_gid(struct listinfo *, const char *);
126static int	 addelem_pid(struct listinfo *, const char *);
127static int	 addelem_tty(struct listinfo *, const char *);
128static int	 addelem_uid(struct listinfo *, const char *);
129static void	 add_list(struct listinfo *, const char *);
130static void	 dynsizevars(KINFO *);
131static void	*expand_list(struct listinfo *);
132static const char *
133		 fmt(char **(*)(kvm_t *, const struct kinfo_proc *, int),
134		    KINFO *, char *, int);
135static void	 free_list(struct listinfo *);
136static void	 init_list(struct listinfo *, addelem_rtn, int, const char *);
137static char	*kludge_oldps_options(char *);
138static int	 pscomp(const void *, const void *);
139static void	 saveuser(KINFO *);
140static void	 scanvars(void);
141static void	 sizevars(void);
142static void	 usage(void);
143
144static char dfmt[] = "pid,tt,state,time,command";
145static char jfmt[] = "user,pid,ppid,pgid,jobc,state,tt,time,command";
146static char lfmt[] = "uid,pid,ppid,cpu,pri,nice,vsz,rss,mwchan,state,"
147			"tt,time,command";
148static char   o1[] = "pid";
149static char   o2[] = "tt,state,time,command";
150static char ufmt[] = "user,pid,%cpu,%mem,vsz,rss,tt,state,start,time,command";
151static char vfmt[] = "pid,state,time,sl,re,pagein,vsz,rss,lim,tsiz,"
152			"%cpu,%mem,command";
153static char Zfmt[] = "label";
154
155#define	PS_ARGS	"AaCce" OPT_LAZY_f "G:gHhjLlM:mN:O:o:p:rSTt:U:uvwXxZ"
156
157int
158main(int argc, char *argv[])
159{
160	struct listinfo gidlist, pgrplist, pidlist;
161	struct listinfo ruidlist, sesslist, ttylist, uidlist;
162	struct kinfo_proc *kp;
163	struct varent *vent;
164	struct winsize ws;
165	const char *cp, *nlistf, *memf;
166	char *cols;
167	int all, ch, dropgid, elem, flag, _fmt, i, lineno;
168	int nentries, nocludge, nkept, nselectors;
169	int prtheader, showthreads, wflag, what, xkeep, xkeep_implied;
170	char errbuf[_POSIX2_LINE_MAX];
171
172	(void) setlocale(LC_ALL, "");
173	time(&now);			/* Used by routines in print.c. */
174
175	if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0')
176		termwidth = atoi(cols);
177	else if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
178	     ioctl(STDERR_FILENO, TIOCGWINSZ, (char *)&ws) == -1 &&
179	     ioctl(STDIN_FILENO,  TIOCGWINSZ, (char *)&ws) == -1) ||
180	     ws.ws_col == 0)
181		termwidth = 79;
182	else
183		termwidth = ws.ws_col - 1;
184
185	/*
186	 * Don't apply a kludge if the first argument is an option taking an
187	 * argument
188	 */
189	if (argc > 1) {
190		nocludge = 0;
191		if (argv[1][0] == '-') {
192			for (cp = PS_ARGS; *cp != '\0'; cp++) {
193				if (*cp != ':')
194					continue;
195				if (*(cp - 1) == argv[1][1]) {
196					nocludge = 1;
197					break;
198				}
199			}
200		}
201		if (nocludge == 0)
202			argv[1] = kludge_oldps_options(argv[1]);
203	}
204
205	all = dropgid = _fmt = nselectors = optfatal = 0;
206	prtheader = showthreads = wflag = xkeep_implied = 0;
207	xkeep = -1;			/* Neither -x nor -X. */
208	init_list(&gidlist, addelem_gid, sizeof(gid_t), "group");
209	init_list(&pgrplist, addelem_pid, sizeof(pid_t), "process group");
210	init_list(&pidlist, addelem_pid, sizeof(pid_t), "process id");
211	init_list(&ruidlist, addelem_uid, sizeof(uid_t), "ruser");
212	init_list(&sesslist, addelem_pid, sizeof(pid_t), "session id");
213	init_list(&ttylist, addelem_tty, sizeof(dev_t), "tty");
214	init_list(&uidlist, addelem_uid, sizeof(uid_t), "user");
215	memf = nlistf = _PATH_DEVNULL;
216	while ((ch = getopt(argc, argv, PS_ARGS)) != -1)
217		switch((char)ch) {
218		case 'A':
219			/*
220			 * Exactly the same as `-ax'.   This has been
221			 * added for compatability with SUSv3, but for
222			 * now it will not be described in the man page.
223			 */
224			nselectors++;
225			all = xkeep = 1;
226			break;
227		case 'a':
228			nselectors++;
229			all = 1;
230			break;
231		case 'C':
232			rawcpu = 1;
233			break;
234		case 'c':
235			cflag = 1;
236			break;
237		case 'e':			/* XXX set ufmt */
238			needenv = 1;
239			break;
240#ifdef LAZY_PS
241		case 'f':
242			if (getuid() == 0 || getgid() == 0)
243				forceuread = 1;
244			break;
245#endif
246		case 'G':
247			add_list(&gidlist, optarg);
248			xkeep_implied = 1;
249			nselectors++;
250			break;
251		case 'g':
252#if 0
253			/*-
254			 * XXX - This SUSv3 behavior is still under debate
255			 *	since it conflicts with the (undocumented)
256			 *	`-g' option.  So we skip it for now.
257			 */
258			add_list(&pgrplist, optarg);
259			xkeep_implied = 1;
260			nselectors++;
261			break;
262#else
263			/* The historical BSD-ish (from SunOS) behavior. */
264			break;			/* no-op */
265#endif
266		case 'H':
267			showthreads = KERN_PROC_INC_THREAD;
268			break;
269		case 'h':
270			prtheader = ws.ws_row > 5 ? ws.ws_row : 22;
271			break;
272		case 'j':
273			parsefmt(jfmt, 0);
274			_fmt = 1;
275			jfmt[0] = '\0';
276			break;
277		case 'L':
278			showkey();
279			exit(0);
280		case 'l':
281			parsefmt(lfmt, 0);
282			_fmt = 1;
283			lfmt[0] = '\0';
284			break;
285		case 'M':
286			memf = optarg;
287			dropgid = 1;
288			break;
289		case 'm':
290			sortby = SORTMEM;
291			break;
292		case 'N':
293			nlistf = optarg;
294			dropgid = 1;
295			break;
296		case 'O':
297			parsefmt(o1, 1);
298			parsefmt(optarg, 1);
299			parsefmt(o2, 1);
300			o1[0] = o2[0] = '\0';
301			_fmt = 1;
302			break;
303		case 'o':
304			parsefmt(optarg, 1);
305			_fmt = 1;
306			break;
307		case 'p':
308			add_list(&pidlist, optarg);
309			/*
310			 * Note: `-p' does not *set* xkeep, but any values
311			 * from pidlist are checked before xkeep is.  That
312			 * way they are always matched, even if the user
313			 * specifies `-X'.
314			 */
315			nselectors++;
316			break;
317#if 0
318		case 'R':
319			/*-
320			 * XXX - This un-standard option is still under
321			 *	debate.  This is what SUSv3 defines as
322			 *	the `-U' option, and while it would be
323			 *	nice to have, it could cause even more
324			 *	confusion to implement it as `-R'.
325			 */
326			add_list(&ruidlist, optarg);
327			xkeep_implied = 1;
328			nselectors++;
329			break;
330#endif
331		case 'r':
332			sortby = SORTCPU;
333			break;
334		case 'S':
335			sumrusage = 1;
336			break;
337#if 0
338		case 's':
339			/*-
340			 * XXX - This non-standard option is still under
341			 *	debate.  This *is* supported on Solaris,
342			 *	Linux, and IRIX, but conflicts with `-s'
343			 *	on NetBSD and maybe some older BSD's.
344			 */
345			add_list(&sesslist, optarg);
346			xkeep_implied = 1;
347			nselectors++;
348			break;
349#endif
350		case 'T':
351			if ((optarg = ttyname(STDIN_FILENO)) == NULL)
352				errx(1, "stdin: not a terminal");
353			/* FALLTHROUGH */
354		case 't':
355			add_list(&ttylist, optarg);
356			xkeep_implied = 1;
357			nselectors++;
358			break;
359		case 'U':
360			/* This is what SUSv3 defines as the `-u' option. */
361			add_list(&uidlist, optarg);
362			xkeep_implied = 1;
363			nselectors++;
364			break;
365		case 'u':
366			parsefmt(ufmt, 0);
367			sortby = SORTCPU;
368			_fmt = 1;
369			ufmt[0] = '\0';
370			break;
371		case 'v':
372			parsefmt(vfmt, 0);
373			sortby = SORTMEM;
374			_fmt = 1;
375			vfmt[0] = '\0';
376			break;
377		case 'w':
378			if (wflag)
379				termwidth = UNLIMITED;
380			else if (termwidth < 131)
381				termwidth = 131;
382			wflag++;
383			break;
384		case 'X':
385			/*
386			 * Note that `-X' and `-x' are not standard "selector"
387			 * options. For most selector-options, we check *all*
388			 * processes to see if any are matched by the given
389			 * value(s).  After we have a set of all the matched
390			 * processes, then `-X' and `-x' govern whether we
391			 * modify that *matched* set for processes which do
392			 * not have a controlling terminal.  `-X' causes
393			 * those processes to be deleted from the matched
394			 * set, while `-x' causes them to be kept.
395			 */
396			xkeep = 0;
397			break;
398		case 'x':
399			xkeep = 1;
400			break;
401		case 'Z':
402			parsefmt(Zfmt, 0);
403			Zfmt[0] = '\0';
404			break;
405		case '?':
406		default:
407			usage();
408		}
409	argc -= optind;
410	argv += optind;
411	if (optfatal)
412		exit(1);		/* Error messages already printed. */
413	if (xkeep < 0)			/* Neither -X nor -x was specified. */
414		xkeep = xkeep_implied;
415
416#define	BACKWARD_COMPATIBILITY
417#ifdef	BACKWARD_COMPATIBILITY
418	if (*argv) {
419		nlistf = *argv;
420		if (*++argv)
421			memf = *argv;
422	}
423#endif
424	/*
425	 * Discard setgid privileges if not the running kernel so that bad
426	 * guys can't print interesting stuff from kernel memory.
427	 */
428	if (dropgid) {
429		setgid(getgid());
430		setuid(getuid());
431	}
432
433	kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf);
434	if (kd == 0)
435		errx(1, "%s", errbuf);
436
437	if (!_fmt)
438		parsefmt(dfmt, 0);
439
440	if (nselectors == 0) {
441		uidlist.l.ptr = malloc(sizeof(uid_t));
442		if (uidlist.l.ptr == NULL)
443			errx(1, "malloc failed");
444		nselectors = 1;
445		uidlist.count = uidlist.maxcount = 1;
446		*uidlist.l.uids = getuid();
447	}
448
449	/*
450	 * scan requested variables, noting what structures are needed,
451	 * and adjusting header widths as appropriate.
452	 */
453	scanvars();
454
455	/*
456	 * Get process list.  If the user requested just one selector-
457	 * option, then kvm_getprocs can be asked to return just those
458	 * processes.  Otherwise, have it return all processes, and
459	 * then this routine will search that full list and select the
460	 * processes which match any of the user's selector-options.
461	 */
462	what = showthreads != 0 ? KERN_PROC_ALL : KERN_PROC_PROC;
463	flag = 0;
464	if (nselectors == 1) {
465		if (gidlist.count == 1) {
466			what = KERN_PROC_RGID | showthreads;
467			flag = *gidlist.l.gids;
468			nselectors = 0;
469		} else if (pgrplist.count == 1) {
470			what = KERN_PROC_PGRP | showthreads;
471			flag = *pgrplist.l.pids;
472			nselectors = 0;
473		} else if (pidlist.count == 1) {
474			what = KERN_PROC_PID | showthreads;
475			flag = *pidlist.l.pids;
476			nselectors = 0;
477		} else if (ruidlist.count == 1) {
478			what = KERN_PROC_RUID | showthreads;
479			flag = *ruidlist.l.uids;
480			nselectors = 0;
481		} else if (sesslist.count == 1) {
482			what = KERN_PROC_SESSION | showthreads;
483			flag = *sesslist.l.pids;
484			nselectors = 0;
485		} else if (ttylist.count == 1) {
486			what = KERN_PROC_TTY | showthreads;
487			flag = *ttylist.l.ttys;
488			nselectors = 0;
489		} else if (uidlist.count == 1) {
490			what = KERN_PROC_UID | showthreads;
491			flag = *uidlist.l.uids;
492			nselectors = 0;
493		} else if (all) {
494			/* No need for this routine to select processes. */
495			nselectors = 0;
496		}
497	}
498
499	/*
500	 * select procs
501	 */
502	nentries = -1;
503	kp = kvm_getprocs(kd, what, flag, &nentries);
504	if ((kp == NULL && nentries > 0) || (kp != NULL && nentries < 0))
505		errx(1, "%s", kvm_geterr(kd));
506	nkept = 0;
507	if (nentries > 0) {
508		if ((kinfo = malloc(nentries * sizeof(*kinfo))) == NULL)
509			errx(1, "malloc failed");
510		for (i = nentries; --i >= 0; ++kp) {
511			/*
512			 * If the user specified multiple selection-criteria,
513			 * then keep any process matched by the inclusive OR
514			 * of all the selection-criteria given.
515			 */
516			if (pidlist.count > 0) {
517				for (elem = 0; elem < pidlist.count; elem++)
518					if (kp->ki_pid == pidlist.l.pids[elem])
519						goto keepit;
520			}
521			/*
522			 * Note that we had to process pidlist before
523			 * filtering out processes which do not have
524			 * a controlling terminal.
525			 */
526			if (xkeep == 0) {
527				if ((kp->ki_tdev == NODEV ||
528				    (kp->ki_flag & P_CONTROLT) == 0))
529					continue;
530			}
531			if (nselectors == 0)
532				goto keepit;
533			if (gidlist.count > 0) {
534				for (elem = 0; elem < gidlist.count; elem++)
535					if (kp->ki_rgid == gidlist.l.gids[elem])
536						goto keepit;
537			}
538			if (pgrplist.count > 0) {
539				for (elem = 0; elem < pgrplist.count; elem++)
540					if (kp->ki_pgid ==
541					    pgrplist.l.pids[elem])
542						goto keepit;
543			}
544			if (ruidlist.count > 0) {
545				for (elem = 0; elem < ruidlist.count; elem++)
546					if (kp->ki_ruid ==
547					    ruidlist.l.uids[elem])
548						goto keepit;
549			}
550			if (sesslist.count > 0) {
551				for (elem = 0; elem < sesslist.count; elem++)
552					if (kp->ki_sid == sesslist.l.pids[elem])
553						goto keepit;
554			}
555			if (ttylist.count > 0) {
556				for (elem = 0; elem < ttylist.count; elem++)
557					if (kp->ki_tdev == ttylist.l.ttys[elem])
558						goto keepit;
559			}
560			if (uidlist.count > 0) {
561				for (elem = 0; elem < uidlist.count; elem++)
562					if (kp->ki_uid == uidlist.l.uids[elem])
563						goto keepit;
564			}
565			/*
566			 * This process did not match any of the user's
567			 * selector-options, so skip the process.
568			 */
569			continue;
570
571		keepit:
572			kinfo[nkept].ki_p = kp;
573			if (needuser)
574				saveuser(&kinfo[nkept]);
575			dynsizevars(&kinfo[nkept]);
576			nkept++;
577		}
578	}
579
580	sizevars();
581
582	/*
583	 * print header
584	 */
585	printheader();
586	if (nkept == 0)
587		exit(1);
588
589	/*
590	 * sort proc list
591	 */
592	qsort(kinfo, nkept, sizeof(KINFO), pscomp);
593	/*
594	 * For each process, call each variable output function.
595	 */
596	for (i = lineno = 0; i < nkept; i++) {
597		for (vent = vhead; vent; vent = vent->next) {
598			(vent->var->oproc)(&kinfo[i], vent);
599			if (vent->next != NULL)
600				(void)putchar(' ');
601		}
602		(void)putchar('\n');
603		if (prtheader && lineno++ == prtheader - 4) {
604			(void)putchar('\n');
605			printheader();
606			lineno = 0;
607		}
608	}
609	free_list(&gidlist);
610	free_list(&pidlist);
611	free_list(&pgrplist);
612	free_list(&ruidlist);
613	free_list(&sesslist);
614	free_list(&ttylist);
615	free_list(&uidlist);
616
617	exit(eval);
618}
619
620static int
621addelem_gid(struct listinfo *inf, const char *elem)
622{
623	struct group *grp;
624	const char *nameorID;
625	char *endp;
626	u_long bigtemp;
627
628	if (*elem == '\0' || strlen(elem) >= MAXLOGNAME) {
629		if (*elem == '\0')
630			warnx("Invalid (zero-length) %s name", inf->lname);
631		else
632			warnx("%s name too long: %s", inf->lname, elem);
633		optfatal = 1;
634		return (0);		/* Do not add this value. */
635	}
636
637	/*
638	 * SUSv3 states that `ps -G grouplist' should match "real-group
639	 * ID numbers", and does not mention group-names.  I do want to
640	 * also support group-names, so this tries for a group-id first,
641	 * and only tries for a name if that doesn't work.  This is the
642	 * opposite order of what is done in addelem_uid(), but in
643	 * practice the order would only matter for group-names which
644	 * are all-numeric.
645	 */
646	grp = NULL;
647	nameorID = "named";
648	errno = 0;
649	bigtemp = strtoul(elem, &endp, 10);
650	if (errno == 0 && *endp == '\0' && bigtemp <= GID_MAX) {
651		nameorID = "name or ID matches";
652		grp = getgrgid((gid_t)bigtemp);
653	}
654	if (grp == NULL)
655		grp = getgrnam(elem);
656	if (grp == NULL) {
657		warnx("No %s %s '%s'", inf->lname, nameorID, elem);
658		optfatal = 1;
659		return (0);		/* Do not add this value. */
660	}
661
662	if (inf->count >= inf->maxcount)
663		expand_list(inf);
664	inf->l.gids[(inf->count)++] = grp->gr_gid;
665	return (1);
666}
667
668#define	BSD_PID_MAX	99999		/* Copy of PID_MAX from sys/proc.h. */
669static int
670addelem_pid(struct listinfo *inf, const char *elem)
671{
672	char *endp;
673	long tempid;
674
675	if (*elem == '\0')
676		tempid = 0L;
677	else {
678		errno = 0;
679		tempid = strtol(elem, &endp, 10);
680		if (*endp != '\0' || tempid < 0 || elem == endp) {
681			warnx("Invalid %s: %s", inf->lname, elem);
682			errno = ERANGE;
683		} else if (errno != 0 || tempid > BSD_PID_MAX) {
684			warnx("%s too large: %s", inf->lname, elem);
685			errno = ERANGE;
686		}
687		if (errno == ERANGE) {
688			optfatal = 1;
689			return (0);	/* Do not add this value. */
690		}
691	}
692
693	if (inf->count >= inf->maxcount)
694		expand_list(inf);
695	inf->l.pids[(inf->count)++] = tempid;
696	return (1);
697}
698#undef	BSD_PID_MAX
699
700static int
701addelem_tty(struct listinfo *inf, const char *elem)
702{
703	const char *ttypath;
704	struct stat sb;
705	char pathbuf[PATH_MAX];
706
707	if (strcmp(elem, "co") == 0)
708		ttypath = strdup(_PATH_CONSOLE);
709	else if (*elem == '/')
710		ttypath = elem;
711	else {
712		strlcpy(pathbuf, _PATH_TTY, sizeof(pathbuf));
713		strlcat(pathbuf, elem, sizeof(pathbuf));
714		ttypath = pathbuf;
715	}
716
717	if (stat(ttypath, &sb) == -1) {
718		warn("%s", ttypath);
719		optfatal = 1;
720		return (0);		/* Do not add this value. */
721	}
722	if (!S_ISCHR(sb.st_mode)) {
723		warn("%s: Not a terminal", ttypath);
724		optfatal = 1;
725		return (0);		/* Do not add this value. */
726	}
727
728	if (inf->count >= inf->maxcount)
729		expand_list(inf);
730	inf->l.ttys[(inf->count)++] = sb.st_rdev;
731	return (1);
732}
733
734static int
735addelem_uid(struct listinfo *inf, const char *elem)
736{
737	struct passwd *pwd;
738	char *endp;
739	u_long bigtemp;
740
741	if (*elem == '\0' || strlen(elem) >= MAXLOGNAME) {
742		if (*elem == '\0')
743			warnx("Invalid (zero-length) %s name", inf->lname);
744		else
745			warnx("%s name too long: %s", inf->lname, elem);
746		optfatal = 1;
747		return (0);		/* Do not add this value. */
748	}
749
750	pwd = getpwnam(elem);
751	if (pwd == NULL) {
752		errno = 0;
753		bigtemp = strtoul(elem, &endp, 10);
754		if (errno != 0 || *endp != '\0' || bigtemp > UID_MAX)
755			warnx("No %s named '%s'", inf->lname, elem);
756		else {
757			/* The string is all digits, so it might be a userID. */
758			pwd = getpwuid((uid_t)bigtemp);
759			if (pwd == NULL)
760				warnx("No %s name or ID matches '%s'",
761				    inf->lname, elem);
762		}
763	}
764	if (pwd == NULL) {
765		/*
766		 * These used to be treated as minor warnings (and the
767		 * option was simply ignored), but now they are fatal
768		 * errors (and the command will be aborted).
769		 */
770		optfatal = 1;
771		return (0);		/* Do not add this value. */
772	}
773
774	if (inf->count >= inf->maxcount)
775		expand_list(inf);
776	inf->l.uids[(inf->count)++] = pwd->pw_uid;
777	return (1);
778}
779
780static void
781add_list(struct listinfo *inf, const char *argp)
782{
783	const char *savep;
784	char *cp, *endp;
785	int toolong;
786	char elemcopy[PATH_MAX];
787
788	while (*argp != '\0') {
789		while (*argp != '\0' && strchr(W_SEP, *argp) != NULL)
790			argp++;
791		savep = argp;
792		toolong = 0;
793		cp = elemcopy;
794		if (strchr(T_SEP, *argp) == NULL) {
795			endp = elemcopy + sizeof(elemcopy) - 1;
796			while (*argp != '\0' && cp <= endp &&
797			    strchr(W_SEP T_SEP, *argp) == NULL)
798				*cp++ = *argp++;
799			if (cp > endp)
800				toolong = 1;
801		}
802		if (!toolong) {
803			*cp = '\0';
804#ifndef ADD_PS_LISTRESET
805			/*
806			 * This is how the standard expects lists to
807			 * be handled.
808			 */
809			inf->addelem(inf, elemcopy);
810#else
811			/*-
812			 * This would add a simple non-standard-but-convienent
813			 * feature.
814			 *
815			 * XXX - The first time I tried to add this check,
816			 *	it increased the total size of `ps' by 3940
817			 *	bytes on i386!  That's 12% of the entire
818			 *	program!  The `ps.o' file grew by only about
819			 *	40 bytes, but the final (stripped) executable
820			 *	in /bin/ps grew by 12%.  I have not had time
821			 *	to investigate, so skip the feature for now.
822			 */
823			/*
824			 * We now have a single element.  Add it to the
825			 * list, unless the element is ":".  In that case,
826			 * reset the list so previous entries are ignored.
827			 */
828			if (strcmp(elemcopy, ":") == 0)
829				inf->count = 0;
830			else
831				inf->addelem(inf, elemcopy);
832#endif
833		} else {
834			/*
835			 * The string is too long to copy.  Find the end
836			 * of the string to print out the warning message.
837			 */
838			while (*argp != '\0' && strchr(W_SEP T_SEP,
839			    *argp) == NULL)
840				argp++;
841			warnx("Value too long: %.*s", (int)(argp - savep),
842			    savep);
843			optfatal = 1;
844		}
845		/*
846		 * Skip over any number of trailing whitespace characters,
847		 * but only one (at most) trailing element-terminating
848		 * character.
849		 */
850		while (*argp != '\0' && strchr(W_SEP, *argp) != NULL)
851			argp++;
852		if (*argp != '\0' && strchr(T_SEP, *argp) != NULL) {
853			argp++;
854			/* Catch case where string ended with a comma. */
855			if (*argp == '\0')
856				inf->addelem(inf, argp);
857		}
858	}
859}
860
861static void *
862expand_list(struct listinfo *inf)
863{
864	void *newlist;
865	int newmax;
866
867	newmax = (inf->maxcount + 1) << 1;
868	newlist = realloc(inf->l.ptr, newmax * inf->elemsize);
869	if (newlist == NULL) {
870		free(inf->l.ptr);
871		errx(1, "realloc to %d %ss failed", newmax,
872		    inf->lname);
873	}
874	inf->maxcount = newmax;
875	inf->l.ptr = newlist;
876
877	return (newlist);
878}
879
880static void
881free_list(struct listinfo *inf)
882{
883
884	inf->count = inf->elemsize = inf->maxcount = 0;
885	if (inf->l.ptr != NULL)
886		free(inf->l.ptr);
887	inf->addelem = NULL;
888	inf->lname = NULL;
889	inf->l.ptr = NULL;
890}
891
892static void
893init_list(struct listinfo *inf, addelem_rtn artn, int elemsize,
894    const char *lname)
895{
896
897	inf->count = inf->maxcount = 0;
898	inf->elemsize = elemsize;
899	inf->addelem = artn;
900	inf->lname = lname;
901	inf->l.ptr = NULL;
902}
903
904VARENT *
905find_varentry(VAR *v)
906{
907	struct varent *vent;
908
909	for (vent = vhead; vent; vent = vent->next) {
910		if (strcmp(vent->var->name, v->name) == 0)
911			return vent;
912	}
913	return NULL;
914}
915
916static void
917scanvars(void)
918{
919	struct varent *vent;
920	VAR *v;
921
922	for (vent = vhead; vent; vent = vent->next) {
923		v = vent->var;
924		if (v->flag & DSIZ) {
925			v->dwidth = v->width;
926			v->width = 0;
927		}
928		if (v->flag & USER)
929			needuser = 1;
930		if (v->flag & COMM)
931			needcomm = 1;
932	}
933}
934
935static void
936dynsizevars(KINFO *ki)
937{
938	struct varent *vent;
939	VAR *v;
940	int i;
941
942	for (vent = vhead; vent; vent = vent->next) {
943		v = vent->var;
944		if (!(v->flag & DSIZ))
945			continue;
946		i = (v->sproc)( ki);
947		if (v->width < i)
948			v->width = i;
949		if (v->width > v->dwidth)
950			v->width = v->dwidth;
951	}
952}
953
954static void
955sizevars(void)
956{
957	struct varent *vent;
958	VAR *v;
959	int i;
960
961	for (vent = vhead; vent; vent = vent->next) {
962		v = vent->var;
963		i = strlen(vent->header);
964		if (v->width < i)
965			v->width = i;
966		totwidth += v->width + 1;	/* +1 for space */
967	}
968	totwidth--;
969}
970
971static const char *
972fmt(char **(*fn)(kvm_t *, const struct kinfo_proc *, int), KINFO *ki,
973    char *comm, int maxlen)
974{
975	const char *s;
976
977	s = fmt_argv((*fn)(kd, ki->ki_p, termwidth), comm, maxlen);
978	return (s);
979}
980
981#define UREADOK(ki)	(forceuread || (ki->ki_p->ki_sflag & PS_INMEM))
982
983static void
984saveuser(KINFO *ki)
985{
986
987	if (ki->ki_p->ki_sflag & PS_INMEM) {
988		/*
989		 * The u-area might be swapped out, and we can't get
990		 * at it because we have a crashdump and no swap.
991		 * If it's here fill in these fields, otherwise, just
992		 * leave them 0.
993		 */
994		ki->ki_valid = 1;
995	} else
996		ki->ki_valid = 0;
997	/*
998	 * save arguments if needed
999	 */
1000	if (needcomm && (UREADOK(ki) || (ki->ki_p->ki_args != NULL))) {
1001		ki->ki_args = strdup(fmt(kvm_getargv, ki, ki->ki_p->ki_comm,
1002		    MAXCOMLEN));
1003	} else if (needcomm) {
1004		asprintf(&ki->ki_args, "(%s)", ki->ki_p->ki_comm);
1005	} else {
1006		ki->ki_args = NULL;
1007	}
1008	if (needenv && UREADOK(ki)) {
1009		ki->ki_env = strdup(fmt(kvm_getenvv, ki, (char *)NULL, 0));
1010	} else if (needenv) {
1011		ki->ki_env = malloc(3);
1012		strcpy(ki->ki_env, "()");
1013	} else {
1014		ki->ki_env = NULL;
1015	}
1016}
1017
1018static int
1019pscomp(const void *a, const void *b)
1020{
1021	const KINFO *ka, *kb;
1022	double cpua, cpub;
1023	segsz_t sizea, sizeb;
1024
1025	ka = a;
1026	kb = b;
1027	/* SORTCPU and SORTMEM are sorted in descending order. */
1028	if (sortby == SORTCPU) {
1029		cpua = getpcpu(ka);
1030		cpub = getpcpu(kb);
1031		if (cpua < cpub)
1032			return (1);
1033		if (cpua > cpub)
1034			return (-1);
1035	}
1036	if (sortby == SORTMEM) {
1037		sizea = ka->ki_p->ki_tsize + ka->ki_p->ki_dsize +
1038		    ka->ki_p->ki_ssize;
1039		sizeb = kb->ki_p->ki_tsize + kb->ki_p->ki_dsize +
1040		    kb->ki_p->ki_ssize;
1041		if (sizea < sizeb)
1042			return (1);
1043		if (sizea > sizeb)
1044			return (-1);
1045	}
1046	/*
1047	 * TTY's are sorted in ascending order, except that all NODEV
1048	 * processes come before all processes with a device.
1049	 */
1050	if (ka->ki_p->ki_tdev == NODEV && kb->ki_p->ki_tdev != NODEV)
1051		return (-1);
1052	if (ka->ki_p->ki_tdev != NODEV && kb->ki_p->ki_tdev == NODEV)
1053		return (1);
1054	if (ka->ki_p->ki_tdev < kb->ki_p->ki_tdev)
1055		return (-1);
1056	if (ka->ki_p->ki_tdev > kb->ki_p->ki_tdev)
1057		return (1);
1058	/* PID's are sorted in ascending order. */
1059	if (ka->ki_p->ki_pid < kb->ki_p->ki_pid)
1060		return (-1);
1061	if (ka->ki_p->ki_pid > kb->ki_p->ki_pid)
1062		return (1);
1063	return (0);
1064}
1065
1066/*
1067 * ICK (all for getopt), would rather hide the ugliness
1068 * here than taint the main code.
1069 *
1070 *  ps foo -> ps -foo
1071 *  ps 34 -> ps -p34
1072 *
1073 * The old convention that 't' with no trailing tty arg means the users
1074 * tty, is only supported if argv[1] doesn't begin with a '-'.  This same
1075 * feature is available with the option 'T', which takes no argument.
1076 */
1077static char *
1078kludge_oldps_options(char *s)
1079{
1080	int have_fmt;
1081	size_t len;
1082	char *newopts, *ns, *cp;
1083
1084	/*
1085	 * If we have an 'o' option, then note it, since we don't want to do
1086	 * some types of munging.
1087	 */
1088	have_fmt = index(s, 'o') != NULL;
1089
1090	len = strlen(s);
1091	if ((newopts = ns = malloc(len + 2)) == NULL)
1092		errx(1, "malloc failed");
1093	/*
1094	 * options begin with '-'
1095	 */
1096	if (*s != '-')
1097		*ns++ = '-';	/* add option flag */
1098	/*
1099	 * gaze to end of argv[1]
1100	 */
1101	cp = s + len - 1;
1102	/*
1103	 * if last letter is a 't' flag with no argument (in the context
1104	 * of the oldps options -- option string NOT starting with a '-' --
1105	 * then convert to 'T' (meaning *this* terminal, i.e. ttyname(0)).
1106	 *
1107	 * However, if a flag accepting a string argument is found in the
1108	 * option string, the remainder of the string is the argument to
1109	 * that flag; do not modify that argument.
1110	 */
1111	if (strcspn(s, "MNOoU") == len && *cp == 't' && *s != '-')
1112		*cp = 'T';
1113	else {
1114		/*
1115		 * otherwise check for trailing number, which *may* be a
1116		 * pid.
1117		 */
1118		while (cp >= s && isdigit(*cp))
1119			--cp;
1120	}
1121	cp++;
1122	memmove(ns, s, (size_t)(cp - s));	/* copy up to trailing number */
1123	ns += cp - s;
1124	/*
1125	 * if there's a trailing number, and not a preceding 'p' (pid) or
1126	 * 't' (tty) flag, then assume it's a pid and insert a 'p' flag.
1127	 */
1128	if (isdigit(*cp) &&
1129	    (cp == s || (cp[-1] != 't' && cp[-1] != 'p')) &&
1130	    (cp - 1 == s || cp[-2] != 't') && !have_fmt)
1131		*ns++ = 'p';
1132	(void)strcpy(ns, cp);		/* and append the number */
1133
1134	return (newopts);
1135}
1136
1137static void
1138usage(void)
1139{
1140#define	SINGLE_OPTS	"[-aC" OPT_LAZY_f "HhjlmrSTuvwXxZ]"
1141
1142	(void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
1143	    "usage: ps " SINGLE_OPTS " [-G gid[,gid]] [-O|o fmt]",
1144	    "          [-p pid[,pid]] [-t tty[,tty]] [-U user[,user]]",
1145	    "          [-M core] [-N system]",
1146	    "       ps [-L]");
1147	exit(1);
1148}
1149