xref: /illumos-gate/usr/src/cmd/whodo/whodo.c (revision 0a1278f2)
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	12
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, char *);
137 static void	prtat(time_t *);
138 static void	checkampm(char *);
139 
140 static char	*prog;
141 static int	header = 1;	/* true if -h flag: don't print heading */
142 static int	lflag = 0;	/* true if -l flag: w command format */
143 static char 	*sel_user;	/* login of particular user selected */
144 static time_t	now;		/* current time of day */
145 static time_t	uptime;		/* time of last reboot & elapsed time since */
146 static int	nusers;		/* number of users logged in now */
147 static time_t	idle;		/* number of minutes user is idle */
148 static time_t	jobtime;	/* total cpu time visible */
149 static char	doing[520];	/* process attached to terminal */
150 static time_t	proctime;	/* cpu time of process in doing */
151 static int	empty;
152 static pid_t	curpid;
153 
154 #if SIGQUIT > SIGINT
155 #define	ACTSIZE	SIGQUIT
156 #else
157 #define	ACTSIZE	SIGINT
158 #endif
159 
160 int
161 main(int argc, char *argv[])
162 {
163 	struct utmpx	*ut;
164 	struct utmpx	*utmpbegin;
165 	struct utmpx	*utmpend;
166 	struct utmpx 	*utp;
167 	struct tm		*tm;
168 	struct uproc	*up, *parent, *pgrp;
169 	struct psinfo	info;
170 	struct sigaction actinfo[ACTSIZE];
171 	struct pstatus	statinfo;
172 	size_t		size;
173 	struct stat	sbuf;
174 	struct utsname	uts;
175 	DIR		*dirp;
176 	struct	dirent	*dp;
177 	char 		pname[64];
178 	char 		*fname;
179 	int		procfd;
180 	int		i;
181 	int		days, hrs, mins;
182 	int		entries;
183 
184 	/*
185 	 * This program needs the proc_owner privilege
186 	 */
187 	(void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
188 	    (char *)NULL);
189 
190 	(void) setlocale(LC_ALL, "");
191 #if !defined(TEXT_DOMAIN)
192 #define	TEXT_DOMAIN "SYS_TEST"
193 #endif
194 	(void) textdomain(TEXT_DOMAIN);
195 
196 	prog = argv[0];
197 
198 	while (argc > 1) {
199 		if (argv[1][0] == '-') {
200 			for (i = 1; argv[1][i]; i++) {
201 				switch (argv[1][i]) {
202 
203 				case 'h':
204 					header = 0;
205 					break;
206 
207 				case 'l':
208 					lflag++;
209 					break;
210 
211 				default:
212 					(void) printf(gettext(
213 					    "usage: %s [ -hl ] [ user ]\n"),
214 					    prog);
215 					exit(1);
216 				}
217 			}
218 		} else {
219 			if (!isalnum(argv[1][0]) || argc > 2) {
220 				(void) printf(gettext(
221 				    "usage: %s [ -hl ] [ user ]\n"), prog);
222 				exit(1);
223 			} else
224 				sel_user = argv[1];
225 		}
226 		argc--; argv++;
227 	}
228 
229 	/*
230 	 * read the UTMPX_FILE (contains information about
231 	 * each logged in user)
232 	 */
233 	if (stat(UTMPX_FILE, &sbuf) == ERR) {
234 		(void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"),
235 		    prog, UTMPX_FILE, strerror(errno));
236 		exit(1);
237 	}
238 	entries = sbuf.st_size / sizeof (struct futmpx);
239 	size = sizeof (struct utmpx) * entries;
240 
241 	if ((ut = malloc(size)) == NULL) {
242 		(void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"),
243 		    prog, UTMPX_FILE, strerror(errno));
244 		exit(1);
245 	}
246 
247 	(void) utmpxname(UTMPX_FILE);
248 
249 	utmpbegin = ut;
250 	/* LINTED pointer cast may result in improper alignment */
251 	utmpend = (struct utmpx *)((char *)utmpbegin + size);
252 
253 	setutxent();
254 	while ((ut < utmpend) && ((utp = getutxent()) != NULL))
255 		(void) memcpy(ut++, utp, sizeof (*ut));
256 	endutxent();
257 
258 	(void) time(&now);	/* get current time */
259 
260 	if (header) {	/* print a header */
261 		if (lflag) {	/* w command format header */
262 			prtat(&now);
263 			for (ut = utmpbegin; ut < utmpend; ut++) {
264 				if (ut->ut_type == USER_PROCESS) {
265 					nusers++;
266 				} else if (ut->ut_type == BOOT_TIME) {
267 					uptime = now - ut->ut_xtime;
268 					uptime += 30;
269 					days = uptime / (60*60*24);
270 					uptime %= (60*60*24);
271 					hrs = uptime / (60*60);
272 					uptime %= (60*60);
273 					mins = uptime / 60;
274 
275 					(void) printf(dcgettext(NULL,
276 					    "  up %d day(s), %d hr(s), "
277 					    "%d min(s)", LC_TIME),
278 					    days, hrs, mins);
279 				}
280 			}
281 
282 			ut = utmpbegin; /* rewind utmp data */
283 			(void) printf(dcgettext(NULL,
284 			    "  %d user(s)\n", LC_TIME), nusers);
285 			(void) printf(dcgettext(NULL, "User     tty           "
286 			    "login@  idle   JCPU   PCPU  what\n", 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 			    dcgettext(NULL, "%C", LC_TIME), 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 			if (idle >= 36 * 60)
487 				(void) printf(dcgettext(NULL, "%2ddays ",
488 				    LC_TIME), (idle + 12 * 60) / (24 * 60));
489 			else
490 				prttime(idle, " ");
491 			showtotals(findhash((pid_t)ut->ut_pid));
492 		} else {	/* standard whodo format */
493 			tim = ut->ut_xtime;
494 			tm = localtime(&tim);
495 			(void) printf("\n%-*.*s %-*.*s %2.1d:%2.2d\n",
496 			    LINE_WIDTH, (int)LMAX, ut->ut_line,
497 			    LOGIN_WIDTH, (int)NMAX, ut->ut_name, tm->tm_hour,
498 			    tm->tm_min);
499 			showproc(findhash((pid_t)ut->ut_pid));
500 		}
501 	}
502 
503 	return (0);
504 }
505 
506 /*
507  * Used for standard whodo format.
508  * This is the recursive routine descending the process
509  * tree starting from the given process pointer(up).
510  * It used depth-first search strategy and also marked
511  * each node as printed as it traversed down the tree.
512  */
513 static void
514 showproc(struct uproc *up)
515 {
516 	struct	uproc	*zp;
517 
518 	if (up->p_state == VISITED) /* we already been here */
519 		return;
520 	/* print the data for this process */
521 	if (up->p_state == ZOMBIE)
522 		(void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
523 		    LINE_WIDTH, (int)LMAX, "  ?", (int)up->p_upid, 0L, 0L,
524 		    "<defunct>");
525 	else if (up->p_state != NONE) {
526 		(void) printf("    %-*.*s %5d %4.1ld:%2.2ld %s\n",
527 		    LINE_WIDTH, (int)LMAX, getty(up->p_ttyd), (int)up->p_upid,
528 		    up->p_time / 60L, up->p_time % 60L,
529 		    up->p_comm);
530 	}
531 	up->p_state = VISITED;
532 
533 	/* descend for its children */
534 	if (up->p_child) {
535 		showproc(up->p_child);
536 		for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) {
537 			showproc(zp);
538 		}
539 	}
540 
541 	/* print the pgrp relation */
542 	if (up->p_pgrplink)
543 		showproc(up->p_pgrplink);
544 }
545 
546 
547 /*
548  * Used for -l flag (w command) format.
549  * Prints the CPU time for all processes & children,
550  * and the cpu time for interesting process,
551  * and what the user is doing.
552  */
553 static void
554 showtotals(struct uproc *up)
555 {
556 	jobtime = 0;
557 	proctime = 0;
558 	empty = 1;
559 	curpid = -1;
560 	(void) strcpy(doing, "-"); /* default act: normally never prints */
561 	calctotals(up);
562 
563 	/* print CPU time for all processes & children */
564 	/* and need to convert clock ticks to seconds first */
565 	prttime((time_t)jobtime, " ");
566 
567 	/* print cpu time for interesting process */
568 	/* and need to convert clock ticks to seconds first */
569 	prttime((time_t)proctime, " ");
570 
571 	/* what user is doing, current process */
572 	(void) printf(" %-.32s\n", doing);
573 }
574 
575 /*
576  *  Used for -l flag (w command) format.
577  *  This recursive routine descends the process
578  *  tree starting from the given process pointer(up).
579  *  It used depth-first search strategy and also marked
580  *  each node as visited as it traversed down the tree.
581  *  It calculates the process time for all processes &
582  *  children.  It also finds the "interesting" process
583  *  and determines its cpu time and command.
584  */
585 static void
586 calctotals(struct uproc *up)
587 {
588 	struct uproc	*zp;
589 
590 	if (up->p_state == VISITED)
591 		return;
592 	up->p_state = VISITED;
593 	if (up->p_state == NONE || up->p_state == ZOMBIE)
594 		return;
595 	jobtime += up->p_time + up->p_ctime;
596 	proctime += up->p_time;
597 
598 	if (empty && !up->p_igintr) {
599 		empty = 0;
600 		curpid = -1;
601 	}
602 
603 	if (up->p_upid > curpid && (!up->p_igintr || empty)) {
604 		curpid = up->p_upid;
605 		(void) strcpy(doing, up->p_args);
606 	}
607 
608 	/* descend for its children */
609 	if (up->p_child) {
610 		calctotals(up->p_child);
611 		for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
612 			calctotals(zp);
613 	}
614 }
615 
616 static char *
617 devadd(char *name, dev_t ddev)
618 {
619 	struct devl *dp;
620 	int leng, start, i;
621 
622 	if (ndevs == maxdev) {
623 		maxdev += DNINCR;
624 		dp = realloc(devl, maxdev * sizeof (struct devl));
625 		if (!dp) {
626 			(void) fprintf(stderr,
627 			    gettext("%s: out of memory!: %s\n"),
628 			    prog, strerror(errno));
629 			exit(1);
630 		}
631 		devl = dp;
632 	}
633 	dp = &devl[ndevs++];
634 
635 	dp->ddev = ddev;
636 	if (name == NULL) {
637 		(void) strcpy(dp->dname, "  ?  ");
638 		return (dp->dname);
639 	}
640 
641 	leng = strlen(name);
642 	if (leng < DEVNAMELEN + 4) {
643 		/* strip off "/dev/" */
644 		(void) strcpy(dp->dname, &name[5]);
645 	} else {
646 		/* strip enough off the front to fit */
647 		start = leng - DEVNAMELEN - 1;
648 
649 		for (i = start; i < leng && name[i] != '/'; i++)
650 				;
651 		if (i == leng)
652 			(void) strncpy(dp->dname, &name[start], DEVNAMELEN);
653 		else
654 			(void) strncpy(dp->dname, &name[i+1], DEVNAMELEN);
655 	}
656 	return (dp->dname);
657 }
658 
659 static char *
660 devlookup(dev_t ddev)
661 {
662 	struct devl *dp;
663 	int i;
664 
665 	for (dp = devl, i = 0; i < ndevs; dp++, i++) {
666 		if (dp->ddev == ddev)
667 			return (dp->dname);
668 	}
669 	return (NULL);
670 }
671 
672 /*
673  * This routine gives back a corresponding device name
674  * from the device number given.
675  */
676 static char *
677 getty(dev_t dev)
678 {
679 	extern char *_ttyname_dev(dev_t, char *, size_t);
680 	char devname[TTYNAME_MAX];
681 	char *retval;
682 
683 	if (dev == PRNODEV)
684 		return ("  ?  ");
685 
686 	if ((retval = devlookup(dev)) != NULL)
687 		return (retval);
688 
689 	retval = _ttyname_dev(dev, devname, sizeof (devname));
690 	return (devadd(retval, dev));
691 }
692 
693 /*
694  * Findhash  finds the appropriate entry in the process
695  * hash table (pr_htbl) for the given pid in case that
696  * pid exists on the hash chain. It returns back a pointer
697  * to that uproc structure. If this is a new pid, it allocates
698  * a new node, initializes it, links it into the chain (after
699  * head) and returns a structure pointer.
700  */
701 static struct uproc *
702 findhash(pid_t pid)
703 {
704 	struct uproc *up, *tp;
705 
706 	tp = up = &pr_htbl[(int)pid % HSIZE];
707 	if (up->p_upid == 0) {			/* empty slot */
708 		up->p_upid = pid;
709 		up->p_state = NONE;
710 		up->p_child = up->p_sibling = up->p_pgrplink = up->p_link = 0;
711 		return (up);
712 	}
713 	if (up->p_upid == pid) {		/* found in hash table */
714 		return (up);
715 	}
716 	for (tp = up->p_link; tp; tp = tp->p_link) {	/* follow chain */
717 		if (tp->p_upid == pid) {
718 			return (tp);
719 		}
720 	}
721 	tp = malloc(sizeof (*tp));		/* add new node */
722 	if (!tp) {
723 		(void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
724 		    prog, strerror(errno));
725 		exit(1);
726 	}
727 	(void) memset((char *)tp, 0, sizeof (*tp));
728 	tp->p_upid = pid;
729 	tp->p_state = NONE;
730 	tp->p_child = tp->p_sibling = tp->p_pgrplink = (pid_t)0;
731 	tp->p_link = up->p_link;		/* insert after head */
732 	up->p_link = tp;
733 	return (tp);
734 }
735 
736 #define	HR	(60 * 60)
737 #define	DAY	(24 * HR)
738 #define	MON	(30 * DAY)
739 
740 /*
741  * prints a time in hours and minutes or minutes and seconds.
742  * The character string 'tail' is printed at the end, obvious
743  * strings to pass are "", " ", or "am".
744  */
745 static void
746 prttime(time_t tim, char *tail)
747 {
748 	if (tim >= 60)
749 		(void) printf(dcgettext(NULL, "%3d:%02d", LC_TIME),
750 		    (int)tim/60, (int)tim%60);
751 	else if (tim > 0)
752 		(void) printf(dcgettext(NULL, "    %2d", LC_TIME), (int)tim);
753 	else
754 		(void) printf("      ");
755 	(void) printf("%s", tail);
756 }
757 
758 
759 /*
760  * prints a 12 hour time given a pointer to a time of day
761  */
762 static void
763 prtat(time_t *time)
764 {
765 	struct tm *p;
766 
767 	p = localtime(time);
768 	if (now - *time <= 18 * HR) {
769 		char timestr[50];
770 		(void) strftime(timestr, sizeof (timestr),
771 		    dcgettext(NULL, " %l:%M""%p", LC_TIME), p);
772 		checkampm(timestr);
773 		(void) printf("%s", timestr);
774 	} else if (now - *time <= 7 * DAY) {
775 		char weekdaytime[20];
776 
777 		(void) strftime(weekdaytime, sizeof (weekdaytime),
778 		    dcgettext(NULL, "%a%l%p", LC_TIME), p);
779 		checkampm(weekdaytime);
780 		(void) printf(" %s", weekdaytime);
781 	} else {
782 		char monthtime[20];
783 
784 		(void) strftime(monthtime, sizeof (monthtime),
785 		    dcgettext(NULL, "%e%b%y", LC_TIME), p);
786 		(void) printf(" %s", monthtime);
787 	}
788 }
789 
790 /*
791  * find & return number of minutes current tty has been idle
792  */
793 static time_t
794 findidle(char *devname)
795 {
796 	struct stat stbuf;
797 	time_t lastaction, diff;
798 	char ttyname[64];
799 
800 	(void) strcpy(ttyname, "/dev/");
801 	(void) strcat(ttyname, devname);
802 	if (stat(ttyname, &stbuf) != -1) {
803 		lastaction = stbuf.st_atime;
804 		diff = now - lastaction;
805 		diff = DIV60(diff);
806 		if (diff < 0)
807 			diff = 0;
808 	} else
809 		diff = 0;
810 	return (diff);
811 }
812 
813 /*
814  * given a pointer to the argument string clean out unsavory characters.
815  */
816 static void
817 clnarglist(char *arglist)
818 {
819 	char	*c;
820 	int	err = 0;
821 
822 	/* get rid of unsavory characters */
823 	for (c = arglist; *c == NULL; c++) {
824 		if ((*c < ' ') || (*c > 0176)) {
825 			if (err++ > 5) {
826 				*arglist = NULL;
827 				break;
828 			}
829 			*c = '?';
830 		}
831 	}
832 }
833 
834 /* replaces all occurences of AM/PM with am/pm */
835 static void
836 checkampm(char *str)
837 {
838 	char *ampm;
839 	while ((ampm = strstr(str, "AM")) != NULL ||
840 	    (ampm = strstr(str, "PM")) != NULL) {
841 		*ampm = tolower(*ampm);
842 		*(ampm+1) = tolower(*(ampm+1));
843 	}
844 }
845