xref: /illumos-gate/usr/src/cmd/whodo/whodo.c (revision fca543ca45b12c44a243625bce68b645ba8ed791)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2013 Gary Mills
23  *
24  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 
28 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /*	  All Rights Reserved	*/
30 
31 /*
32  * University Copyright- Copyright (c) 1982, 1986, 1988
33  * The Regents of the University of California
34  * All Rights Reserved
35  *
36  * University Acknowledgment- Portions of this document are derived from
37  * software developed by the University of California, Berkeley, and its
38  * contributors.
39  */
40 
41 /*
42  * This is the new whodo command which takes advantage of
43  * the /proc interface to gain access to the information
44  * of all the processes currently on the system.
45  *
46  * Maintenance note:
47  *
48  * Much of this code is replicated in w.c.  If you're
49  * fixing bugs here, then you should probably fix 'em there too.
50  */
51 
52 #include <stdio.h>
53 #include <string.h>
54 #include <stdlib.h>
55 #include <ctype.h>
56 #include <fcntl.h>
57 #include <time.h>
58 #include <errno.h>
59 #include <sys/types.h>
60 #include <utmpx.h>
61 #include <sys/utsname.h>
62 #include <sys/stat.h>
63 #include <sys/mkdev.h>
64 #include <dirent.h>
65 #include <procfs.h>		/* /proc header file */
66 #include <sys/wait.h>
67 #include <locale.h>
68 #include <unistd.h>
69 #include <limits.h>
70 #include <priv_utils.h>
71 
72 /*
73  * Use the full lengths from utmpx for user and line.
74  */
75 #define	NMAX	(sizeof (((struct utmpx *)0)->ut_user))
76 #define	LMAX	(sizeof (((struct utmpx *)0)->ut_line))
77 
78 /* Print minimum field widths. */
79 #define	LOGIN_WIDTH	8
80 #define	LINE_WIDTH	8
81 
82 #define	DIV60(t)	((t+30)/60)    /* x/60 rounded */
83 
84 #ifdef ERR
85 #undef ERR
86 #endif
87 #define	ERR		(-1)
88 
89 #define	DEVNAMELEN	14
90 #define	HSIZE		256		/* size of process hash table */
91 #define	PROCDIR		"/proc"
92 #define	INITPROCESS	(pid_t)1	/* init process pid */
93 #define	NONE		'n'		/* no state */
94 #define	RUNNING		'r'		/* runnable process */
95 #define	ZOMBIE		'z'		/* zombie process */
96 #define	VISITED		'v'		/* marked node as visited */
97 
98 static int	ndevs;			/* number of configured devices */
99 static int	maxdev;			/* slots for configured devices */
100 #define	DNINCR	100
101 static struct devl {			/* device list   */
102 	char	dname[DEVNAMELEN];	/* device name   */
103 	dev_t	ddev;			/* device number */
104 } *devl;
105 
106 struct uproc {
107 	pid_t	p_upid;			/* user process id */
108 	char	p_state;		/* numeric value of process state */
109 	dev_t	p_ttyd;			/* controlling tty of process */
110 	time_t	p_time;			/* ticks of user & system time */
111 	time_t	p_ctime;		/* ticks of child user & system time */
112 	int	p_igintr;		/* 1=ignores SIGQUIT and SIGINT */
113 	char	p_comm[PRARGSZ+1];	/* command */
114 	char	p_args[PRARGSZ+1];	/* command line arguments */
115 	struct uproc	*p_child,	/* first child pointer */
116 			*p_sibling,	/* sibling pointer */
117 			*p_pgrplink,	/* pgrp link */
118 			*p_link;	/* hash table chain pointer */
119 };
120 
121 /*
122  *	define	hash table for struct uproc
123  *	Hash function uses process id
124  *	and the size of the hash table(HSIZE)
125  *	to determine process index into the table.
126  */
127 static struct uproc	pr_htbl[HSIZE];
128 
129 static struct	uproc	*findhash(pid_t);
130 static time_t	findidle(char *);
131 static void	clnarglist(char *);
132 static void	showproc(struct uproc *);
133 static void	showtotals(struct uproc *);
134 static void	calctotals(struct uproc *);
135 static char	*getty(dev_t);
136 static void	prttime(time_t, int);
137 static void	prtat(time_t *);
138 
139 static char	*prog;
140 static int	header = 1;	/* true if -h flag: don't print heading */
141 static int	lflag = 0;	/* true if -l flag: w command format */
142 static char 	*sel_user;	/* login of particular user selected */
143 static time_t	now;		/* current time of day */
144 static time_t	uptime;		/* time of last reboot & elapsed time since */
145 static int	nusers;		/* number of users logged in now */
146 static time_t	idle;		/* number of minutes user is idle */
147 static time_t	jobtime;	/* total cpu time visible */
148 static char	doing[520];	/* process attached to terminal */
149 static time_t	proctime;	/* cpu time of process in doing */
150 static int	empty;
151 static pid_t	curpid;
152 
153 #if SIGQUIT > SIGINT
154 #define	ACTSIZE	SIGQUIT
155 #else
156 #define	ACTSIZE	SIGINT
157 #endif
158 
159 int
160 main(int argc, char *argv[])
161 {
162 	struct utmpx	*ut;
163 	struct utmpx	*utmpbegin;
164 	struct utmpx	*utmpend;
165 	struct utmpx 	*utp;
166 	struct tm		*tm;
167 	struct uproc	*up, *parent, *pgrp;
168 	struct psinfo	info;
169 	struct sigaction actinfo[ACTSIZE];
170 	struct pstatus	statinfo;
171 	size_t		size;
172 	struct stat	sbuf;
173 	struct utsname	uts;
174 	DIR		*dirp;
175 	struct	dirent	*dp;
176 	char 		pname[64];
177 	char 		*fname;
178 	int		procfd;
179 	int		i;
180 	int		days, hrs, mins;
181 	int		entries;
182 
183 	/*
184 	 * This program needs the proc_owner privilege
185 	 */
186 	(void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
187 	    (char *)NULL);
188 
189 	(void) setlocale(LC_ALL, "");
190 #if !defined(TEXT_DOMAIN)
191 #define	TEXT_DOMAIN "SYS_TEST"
192 #endif
193 	(void) textdomain(TEXT_DOMAIN);
194 
195 	prog = argv[0];
196 
197 	while (argc > 1) {
198 		if (argv[1][0] == '-') {
199 			for (i = 1; argv[1][i]; i++) {
200 				switch (argv[1][i]) {
201 
202 				case 'h':
203 					header = 0;
204 					break;
205 
206 				case 'l':
207 					lflag++;
208 					break;
209 
210 				default:
211 					(void) printf(gettext(
212 					    "usage: %s [ -hl ] [ user ]\n"),
213 					    prog);
214 					exit(1);
215 				}
216 			}
217 		} else {
218 			if (!isalnum(argv[1][0]) || argc > 2) {
219 				(void) printf(gettext(
220 				    "usage: %s [ -hl ] [ user ]\n"), prog);
221 				exit(1);
222 			} else
223 				sel_user = argv[1];
224 		}
225 		argc--; argv++;
226 	}
227 
228 	/*
229 	 * read the UTMPX_FILE (contains information about
230 	 * each logged in user)
231 	 */
232 	if (stat(UTMPX_FILE, &sbuf) == ERR) {
233 		(void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"),
234 		    prog, UTMPX_FILE, strerror(errno));
235 		exit(1);
236 	}
237 	entries = sbuf.st_size / sizeof (struct futmpx);
238 	size = sizeof (struct utmpx) * entries;
239 
240 	if ((ut = malloc(size)) == NULL) {
241 		(void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"),
242 		    prog, UTMPX_FILE, strerror(errno));
243 		exit(1);
244 	}
245 
246 	(void) utmpxname(UTMPX_FILE);
247 
248 	utmpbegin = ut;
249 	/* LINTED pointer cast may result in improper alignment */
250 	utmpend = (struct utmpx *)((char *)utmpbegin + size);
251 
252 	setutxent();
253 	while ((ut < utmpend) && ((utp = getutxent()) != NULL))
254 		(void) memcpy(ut++, utp, sizeof (*ut));
255 	endutxent();
256 
257 	(void) time(&now);	/* get current time */
258 
259 	if (header) {	/* print a header */
260 		if (lflag) {	/* w command format header */
261 			prtat(&now);
262 			for (ut = utmpbegin; ut < utmpend; ut++) {
263 				if (ut->ut_type == USER_PROCESS) {
264 					nusers++;
265 				} else if (ut->ut_type == BOOT_TIME) {
266 					uptime = now - ut->ut_xtime;
267 					uptime += 30;
268 					days = uptime / (60*60*24);
269 					uptime %= (60*60*24);
270 					hrs = uptime / (60*60);
271 					uptime %= (60*60);
272 					mins = uptime / 60;
273 
274 					(void) printf(dcgettext(NULL,
275 					    "up %d day(s), %d hr(s), "
276 					    "%d min(s)", LC_TIME),
277 					    days, hrs, mins);
278 				}
279 			}
280 
281 			ut = utmpbegin; /* rewind utmp data */
282 			(void) printf(dcgettext(NULL,
283 			    "  %d user(s)\n", LC_TIME), nusers);
284 			(void) printf(dcgettext(NULL, "User     tty      "
285 			    "login@         idle    JCPU    PCPU what\n",
286 			    LC_TIME));
287 		} else {	/* standard whodo header */
288 			char date_buf[100];
289 
290 			/*
291 			 * print current time and date
292 			 */
293 			(void) strftime(date_buf, sizeof (date_buf),
294 			    "%c", localtime(&now));
295 			(void) printf("%s\n", date_buf);
296 
297 			/*
298 			 * print system name
299 			 */
300 			(void) uname(&uts);
301 			(void) printf("%s\n", uts.nodename);
302 		}
303 	}
304 
305 	/*
306 	 * loop through /proc, reading info about each process
307 	 * and build the parent/child tree
308 	 */
309 	if (!(dirp = opendir(PROCDIR))) {
310 		(void) fprintf(stderr, gettext("%s: could not open %s: %s\n"),
311 		    prog, PROCDIR, strerror(errno));
312 		exit(1);
313 	}
314 
315 	while ((dp = readdir(dirp)) != NULL) {
316 		if (dp->d_name[0] == '.')
317 			continue;
318 retry:
319 		(void) snprintf(pname, sizeof (pname),
320 		    "%s/%s/", PROCDIR, dp->d_name);
321 		fname = pname + strlen(pname);
322 		(void) strcpy(fname, "psinfo");
323 		if ((procfd = open(pname, O_RDONLY)) < 0)
324 			continue;
325 		if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
326 			int err = errno;
327 			(void) close(procfd);
328 			if (err == EAGAIN)
329 				goto retry;
330 			if (err != ENOENT)
331 				(void) fprintf(stderr, gettext(
332 				    "%s: read() failed on %s: %s\n"),
333 				    prog, pname, strerror(err));
334 			continue;
335 		}
336 		(void) close(procfd);
337 
338 		up = findhash(info.pr_pid);
339 		up->p_ttyd = info.pr_ttydev;
340 		up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
341 		up->p_time = 0;
342 		up->p_ctime = 0;
343 		up->p_igintr = 0;
344 		(void) strncpy(up->p_comm, info.pr_fname,
345 		    sizeof (info.pr_fname));
346 		up->p_args[0] = 0;
347 
348 		if (up->p_state != NONE && up->p_state != ZOMBIE) {
349 			(void) strcpy(fname, "status");
350 
351 			/* now we need the proc_owner privilege */
352 			(void) __priv_bracket(PRIV_ON);
353 
354 			procfd = open(pname, O_RDONLY);
355 
356 			/* drop proc_owner privilege after open */
357 			(void) __priv_bracket(PRIV_OFF);
358 
359 			if (procfd  < 0)
360 				continue;
361 
362 			if (read(procfd, &statinfo, sizeof (statinfo))
363 			    != sizeof (statinfo)) {
364 				int err = errno;
365 				(void) close(procfd);
366 				if (err == EAGAIN)
367 					goto retry;
368 				if (err != ENOENT)
369 					(void) fprintf(stderr, gettext(
370 					    "%s: read() failed on %s: %s \n"),
371 					    prog, pname, strerror(err));
372 				continue;
373 			}
374 			(void) close(procfd);
375 
376 			up->p_time = statinfo.pr_utime.tv_sec +
377 			    statinfo.pr_stime.tv_sec;
378 			up->p_ctime = statinfo.pr_cutime.tv_sec +
379 			    statinfo.pr_cstime.tv_sec;
380 
381 			(void) strcpy(fname, "sigact");
382 
383 			/* now we need the proc_owner privilege */
384 			(void) __priv_bracket(PRIV_ON);
385 
386 			procfd = open(pname, O_RDONLY);
387 
388 			/* drop proc_owner privilege after open */
389 			(void) __priv_bracket(PRIV_OFF);
390 
391 			if (procfd  < 0)
392 				continue;
393 			if (read(procfd, actinfo, sizeof (actinfo))
394 			    != sizeof (actinfo)) {
395 				int err = errno;
396 				(void) close(procfd);
397 				if (err == EAGAIN)
398 					goto retry;
399 				if (err != ENOENT)
400 					(void) fprintf(stderr, gettext(
401 					    "%s: read() failed on %s: %s \n"),
402 					    prog, pname, strerror(err));
403 				continue;
404 			}
405 			(void) close(procfd);
406 
407 			up->p_igintr =
408 			    actinfo[SIGINT-1].sa_handler == SIG_IGN &&
409 			    actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
410 
411 			up->p_args[0] = 0;
412 
413 			/*
414 			 * Process args if there's a chance we'll print it.
415 			 */
416 			if (lflag) { /* w command needs args */
417 				clnarglist(info.pr_psargs);
418 				(void) strcpy(up->p_args, info.pr_psargs);
419 				if (up->p_args[0] == 0 ||
420 				    up->p_args[0] == '-' &&
421 				    up->p_args[1] <= ' ' ||
422 				    up->p_args[0] == '?') {
423 					(void) strcat(up->p_args, " (");
424 					(void) strcat(up->p_args, up->p_comm);
425 					(void) strcat(up->p_args, ")");
426 				}
427 			}
428 
429 		}
430 
431 		/*
432 		 * link pgrp together in case parents go away
433 		 * Pgrp chain is a single linked list originating
434 		 * from the pgrp leader to its group member.
435 		 */
436 		if (info.pr_pgid != info.pr_pid) {	/* not pgrp leader */
437 			pgrp = findhash(info.pr_pgid);
438 			up->p_pgrplink = pgrp->p_pgrplink;
439 			pgrp->p_pgrplink = up;
440 		}
441 		parent = findhash(info.pr_ppid);
442 
443 		/* if this is the new member, link it in */
444 		if (parent->p_upid != INITPROCESS) {
445 			if (parent->p_child) {
446 				up->p_sibling = parent->p_child;
447 				up->p_child = 0;
448 			}
449 			parent->p_child = up;
450 		}
451 
452 	}
453 
454 	/* revert to non-privileged user */
455 	(void) __priv_relinquish();
456 
457 	(void) closedir(dirp);
458 	(void) time(&now);	/* get current time */
459 
460 	/*
461 	 * loop through utmpx file, printing process info
462 	 * about each logged in user
463 	 */
464 	for (ut = utmpbegin; ut < utmpend; ut++) {
465 		time_t tim;
466 
467 		if (ut->ut_type != USER_PROCESS)
468 			continue;
469 		if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
470 			continue;	/* we're looking for somebody else */
471 		if (lflag) {	/* -l flag format (w command) */
472 			/* print login name of the user */
473 			(void) printf("%-*.*s ", LOGIN_WIDTH, (int)NMAX,
474 			    ut->ut_name);
475 
476 			/* print tty user is on */
477 			(void) printf("%-*.*s ", LINE_WIDTH, (int)LMAX,
478 			    ut->ut_line);
479 
480 			/* print when the user logged in */
481 			tim = ut->ut_xtime;
482 			(void) prtat(&tim);
483 
484 			/* print idle time */
485 			idle = findidle(ut->ut_line);
486 			prttime(idle, 8);
487 			showtotals(findhash((pid_t)ut->ut_pid));
488 		} else {	/* standard whodo format */
489 			tim = ut->ut_xtime;
490 			tm = localtime(&tim);
491 			(void) printf("\n%-*.*s %-*.*s %2.1d:%2.2d\n",
492 			    LINE_WIDTH, (int)LMAX, ut->ut_line,
493 			    LOGIN_WIDTH, (int)NMAX, ut->ut_name, tm->tm_hour,
494 			    tm->tm_min);
495 			showproc(findhash((pid_t)ut->ut_pid));
496 		}
497 	}
498 
499 	return (0);
500 }
501 
502 /*
503  * Used for standard whodo format.
504  * This is the recursive routine descending the process
505  * tree starting from the given process pointer(up).
506  * It used depth-first search strategy and also marked
507  * each node as printed as it traversed down the tree.
508  */
509 static void
510 showproc(struct uproc *up)
511 {
512 	struct	uproc	*zp;
513 
514 	if (up->p_state == VISITED) /* we already been here */
515 		return;
516 	/* print the data for this process */
517 	if (up->p_state == ZOMBIE)
518 		(void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
519 		    LINE_WIDTH, (int)LMAX, "  ?", (int)up->p_upid, 0L, 0L,
520 		    "<defunct>");
521 	else if (up->p_state != NONE) {
522 		(void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
523 		    LINE_WIDTH, (int)LMAX, getty(up->p_ttyd), (int)up->p_upid,
524 		    up->p_time / 60L, up->p_time % 60L,
525 		    up->p_comm);
526 	}
527 	up->p_state = VISITED;
528 
529 	/* descend for its children */
530 	if (up->p_child) {
531 		showproc(up->p_child);
532 		for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) {
533 			showproc(zp);
534 		}
535 	}
536 
537 	/* print the pgrp relation */
538 	if (up->p_pgrplink)
539 		showproc(up->p_pgrplink);
540 }
541 
542 
543 /*
544  * Used for -l flag (w command) format.
545  * Prints the CPU time for all processes & children,
546  * and the cpu time for interesting process,
547  * and what the user is doing.
548  */
549 static void
550 showtotals(struct uproc *up)
551 {
552 	jobtime = 0;
553 	proctime = 0;
554 	empty = 1;
555 	curpid = -1;
556 	(void) strcpy(doing, "-"); /* default act: normally never prints */
557 	calctotals(up);
558 
559 	/* print CPU time for all processes & children */
560 	/* and need to convert clock ticks to seconds first */
561 	prttime((time_t)jobtime, 8);
562 
563 	/* print cpu time for interesting process */
564 	/* and need to convert clock ticks to seconds first */
565 	prttime((time_t)proctime, 8);
566 
567 	/* what user is doing, current process */
568 	(void) printf("%-.32s\n", doing);
569 }
570 
571 /*
572  *  Used for -l flag (w command) format.
573  *  This recursive routine descends the process
574  *  tree starting from the given process pointer(up).
575  *  It used depth-first search strategy and also marked
576  *  each node as visited as it traversed down the tree.
577  *  It calculates the process time for all processes &
578  *  children.  It also finds the "interesting" process
579  *  and determines its cpu time and command.
580  */
581 static void
582 calctotals(struct uproc *up)
583 {
584 	struct uproc	*zp;
585 
586 	if (up->p_state == VISITED)
587 		return;
588 	up->p_state = VISITED;
589 	if (up->p_state == NONE || up->p_state == ZOMBIE)
590 		return;
591 	jobtime += up->p_time + up->p_ctime;
592 	proctime += up->p_time;
593 
594 	if (empty && !up->p_igintr) {
595 		empty = 0;
596 		curpid = -1;
597 	}
598 
599 	if (up->p_upid > curpid && (!up->p_igintr || empty)) {
600 		curpid = up->p_upid;
601 		(void) strcpy(doing, up->p_args);
602 	}
603 
604 	/* descend for its children */
605 	if (up->p_child) {
606 		calctotals(up->p_child);
607 		for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
608 			calctotals(zp);
609 	}
610 }
611 
612 static char *
613 devadd(char *name, dev_t ddev)
614 {
615 	struct devl *dp;
616 	int leng, start, i;
617 
618 	if (ndevs == maxdev) {
619 		maxdev += DNINCR;
620 		dp = realloc(devl, maxdev * sizeof (struct devl));
621 		if (!dp) {
622 			(void) fprintf(stderr,
623 			    gettext("%s: out of memory!: %s\n"),
624 			    prog, strerror(errno));
625 			exit(1);
626 		}
627 		devl = dp;
628 	}
629 	dp = &devl[ndevs++];
630 
631 	dp->ddev = ddev;
632 	if (name == NULL) {
633 		(void) strcpy(dp->dname, "  ?  ");
634 		return (dp->dname);
635 	}
636 
637 	leng = strlen(name);
638 	if (leng < DEVNAMELEN + 4) {
639 		/* strip off "/dev/" */
640 		(void) strcpy(dp->dname, &name[5]);
641 	} else {
642 		/* strip enough off the front to fit */
643 		start = leng - DEVNAMELEN - 1;
644 
645 		for (i = start; i < leng && name[i] != '/'; i++)
646 				;
647 		if (i == leng)
648 			(void) strncpy(dp->dname, &name[start], DEVNAMELEN);
649 		else
650 			(void) strncpy(dp->dname, &name[i+1], DEVNAMELEN);
651 	}
652 	return (dp->dname);
653 }
654 
655 static char *
656 devlookup(dev_t ddev)
657 {
658 	struct devl *dp;
659 	int i;
660 
661 	for (dp = devl, i = 0; i < ndevs; dp++, i++) {
662 		if (dp->ddev == ddev)
663 			return (dp->dname);
664 	}
665 	return (NULL);
666 }
667 
668 /*
669  * This routine gives back a corresponding device name
670  * from the device number given.
671  */
672 static char *
673 getty(dev_t dev)
674 {
675 	extern char *_ttyname_dev(dev_t, char *, size_t);
676 	char devname[TTYNAME_MAX];
677 	char *retval;
678 
679 	if (dev == PRNODEV)
680 		return ("  ?  ");
681 
682 	if ((retval = devlookup(dev)) != NULL)
683 		return (retval);
684 
685 	retval = _ttyname_dev(dev, devname, sizeof (devname));
686 	return (devadd(retval, dev));
687 }
688 
689 /*
690  * Findhash  finds the appropriate entry in the process
691  * hash table (pr_htbl) for the given pid in case that
692  * pid exists on the hash chain. It returns back a pointer
693  * to that uproc structure. If this is a new pid, it allocates
694  * a new node, initializes it, links it into the chain (after
695  * head) and returns a structure pointer.
696  */
697 static struct uproc *
698 findhash(pid_t pid)
699 {
700 	struct uproc *up, *tp;
701 
702 	tp = up = &pr_htbl[(int)pid % HSIZE];
703 	if (up->p_upid == 0) {			/* empty slot */
704 		up->p_upid = pid;
705 		up->p_state = NONE;
706 		up->p_child = up->p_sibling = up->p_pgrplink = up->p_link = 0;
707 		return (up);
708 	}
709 	if (up->p_upid == pid) {		/* found in hash table */
710 		return (up);
711 	}
712 	for (tp = up->p_link; tp; tp = tp->p_link) {	/* follow chain */
713 		if (tp->p_upid == pid) {
714 			return (tp);
715 		}
716 	}
717 	tp = malloc(sizeof (*tp));		/* add new node */
718 	if (!tp) {
719 		(void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
720 		    prog, strerror(errno));
721 		exit(1);
722 	}
723 	(void) memset((char *)tp, 0, sizeof (*tp));
724 	tp->p_upid = pid;
725 	tp->p_state = NONE;
726 	tp->p_child = tp->p_sibling = tp->p_pgrplink = (pid_t)0;
727 	tp->p_link = up->p_link;		/* insert after head */
728 	up->p_link = tp;
729 	return (tp);
730 }
731 
732 #define	HR	(60 * 60)
733 #define	DAY	(24 * HR)
734 #define	MON	(30 * DAY)
735 #define	PRINTF(a)	(void) printf a
736 
737 /*
738  * Prttime prints an elapsed time in hours, minutes, or seconds,
739  * right-justified with the rightmost column always blank.
740  * The second argument is the minimum field width.
741  */
742 static void
743 prttime(time_t tim, int width)
744 {
745 	char value[36];
746 
747 	if (tim >= 36 * 60) {
748 		(void) snprintf(value, sizeof (value), "%d:%02d:%02d",
749 		    (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60);
750 	} else if (tim >= 60) {
751 		(void) snprintf(value, sizeof (value), "%d:%02d",
752 		    (int)tim / 60, (int)tim % 60);
753 	} else if (tim > 0) {
754 		(void) snprintf(value, sizeof (value), "%d", (int)tim);
755 	} else {
756 		(void) strcpy(value, "0");
757 	}
758 	width = (width > 2) ? width - 1 : 1;
759 	PRINTF(("%*s ", width, value));
760 }
761 
762 /*
763  * Prints the ISO date or time given a pointer to a time of day,
764  * left-justfied in a 12-character expanding field with the
765  * rightmost column always blank.
766  * Includes a dcgettext() override in case a message catalog is needed.
767  */
768 static void
769 prtat(time_t *time)
770 {
771 	struct tm	*p;
772 
773 	p = localtime(time);
774 	if (now - *time <= 18 * HR) {
775 		char timestr[50];
776 
777 		(void) strftime(timestr, sizeof (timestr),
778 		    dcgettext(NULL, "%T", LC_TIME), p);
779 		PRINTF(("%-11s ", timestr));
780 	} else if (now - *time <= 7 * DAY) {
781 		char weekdaytime[20];
782 
783 		(void) strftime(weekdaytime, sizeof (weekdaytime),
784 		    dcgettext(NULL, "%a %H:%M", LC_TIME), p);
785 		PRINTF(("%-11s ", weekdaytime));
786 	} else {
787 		char monthtime[20];
788 
789 		(void) strftime(monthtime, sizeof (monthtime),
790 		    dcgettext(NULL, "%F", LC_TIME), p);
791 		PRINTF(("%-11s ", monthtime));
792 	}
793 }
794 
795 /*
796  * find & return number of minutes current tty has been idle
797  */
798 static time_t
799 findidle(char *devname)
800 {
801 	struct stat stbuf;
802 	time_t lastaction, diff;
803 	char ttyname[64];
804 
805 	(void) strcpy(ttyname, "/dev/");
806 	(void) strcat(ttyname, devname);
807 	if (stat(ttyname, &stbuf) != -1) {
808 		lastaction = stbuf.st_atime;
809 		diff = now - lastaction;
810 		diff = DIV60(diff);
811 		if (diff < 0)
812 			diff = 0;
813 	} else
814 		diff = 0;
815 	return (diff);
816 }
817 
818 /*
819  * given a pointer to the argument string clean out unsavory characters.
820  */
821 static void
822 clnarglist(char *arglist)
823 {
824 	char	*c;
825 	int	err = 0;
826 
827 	/* get rid of unsavory characters */
828 	for (c = arglist; *c == NULL; c++) {
829 		if ((*c < ' ') || (*c > 0176)) {
830 			if (err++ > 5) {
831 				*arglist = NULL;
832 				break;
833 			}
834 			*c = '?';
835 		}
836 	}
837 }
838