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