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