xref: /illumos-gate/usr/src/cmd/w/w.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 w 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  * This program also implements 'uptime'.
47  *
48  * Maintenance note:
49  *
50  * Much of this code is replicated in whodo.c.  If you're
51  * fixing bugs here, then you should probably fix 'em there too.
52  */
53 
54 #include <stdio.h>
55 #include <string.h>
56 #include <stdarg.h>
57 #include <stdlib.h>
58 #include <ctype.h>
59 #include <fcntl.h>
60 #include <time.h>
61 #include <errno.h>
62 #include <sys/types.h>
63 #include <utmpx.h>
64 #include <sys/stat.h>
65 #include <dirent.h>
66 #include <procfs.h>		/* /proc header file */
67 #include <locale.h>
68 #include <unistd.h>
69 #include <sys/loadavg.h>
70 #include <limits.h>
71 #include <priv_utils.h>
72 
73 /*
74  * Use the full lengths from utmpx for user and line.
75  */
76 static struct utmpx dummy;
77 #define	NMAX		(sizeof (dummy.ut_user))
78 #define	LMAX		(sizeof (dummy.ut_line))
79 
80 /* Print minimum field widths. */
81 #define	LOGIN_WIDTH	8
82 #define	LINE_WIDTH	12
83 
84 #define	DIV60(t)	((t+30)/60)	/* x/60 rounded */
85 
86 #ifdef ERR
87 #undef ERR
88 #endif
89 #define	ERR		(-1)
90 
91 #define	HSIZE		256		/* size of process hash table 	*/
92 #define	PROCDIR		"/proc"
93 #define	INITPROCESS	(pid_t)1	/* init process pid */
94 #define	NONE		'n'		/* no state */
95 #define	RUNNING		'r'		/* runnable process */
96 #define	ZOMBIE		'z'		/* zombie process */
97 #define	VISITED		'v'		/* marked node as visited */
98 #define	PRINTF(a)	if (printf a < 0) { \
99 		perror((gettext("%s: printf failed"), prog)); \
100 		exit(1); }
101 
102 struct uproc {
103 	pid_t	p_upid;			/* process id */
104 	char	p_state;		/* numeric value of process state */
105 	dev_t   p_ttyd;			/* controlling tty of process */
106 	time_t  p_time;			/* seconds of user & system time */
107 	time_t	p_ctime;		/* seconds of child user & sys time */
108 	int	p_igintr;		/* 1 = ignores SIGQUIT and SIGINT */
109 	char    p_comm[PRARGSZ+1];	/* command */
110 	char    p_args[PRARGSZ+1];	/* command line arguments */
111 	struct uproc	*p_child,	/* first child pointer */
112 			*p_sibling,	/* sibling pointer */
113 			*p_pgrpl,	/* pgrp link */
114 			*p_link;	/* hash table chain pointer */
115 };
116 
117 /*
118  *  	define hash table for struct uproc
119  *	Hash function uses process id
120  *	and the size of the hash table(HSIZE)
121  *	to determine process index into the table.
122  */
123 static struct uproc	pr_htbl[HSIZE];
124 
125 static struct 	uproc	*findhash(pid_t);
126 static time_t  	findidle(char *);
127 static void	clnarglist(char *);
128 static void	showtotals(struct uproc *);
129 static void	calctotals(struct uproc *);
130 static void	prttime(time_t, char *);
131 static void	prtat(time_t *time);
132 static void	checkampm(char *str);
133 
134 static char	*prog;		/* pointer to invocation name */
135 static int	header = 1;	/* true if -h flag: don't print heading */
136 static int	lflag = 1;	/* set if -l flag; 0 for -s flag: short form */
137 static char	*sel_user;	/* login of particular user selected */
138 static char 	firstchar;	/* first char of name of prog invoked as */
139 static int	login;		/* true if invoked as login shell */
140 static time_t	now;		/* current time of day */
141 static time_t	uptime;		/* time of last reboot & elapsed time since */
142 static int	nusers;		/* number of users logged in now */
143 static time_t	idle;		/* number of minutes user is idle */
144 static time_t	jobtime;	/* total cpu time visible */
145 static char	doing[520];	/* process attached to terminal */
146 static time_t	proctime;	/* cpu time of process in doing */
147 static pid_t	curpid, empty;
148 static int	add_times;	/* boolean: add the cpu times or not */
149 
150 #if SIGQUIT > SIGINT
151 #define	ACTSIZE	SIGQUIT
152 #else
153 #define	ACTSIZE	SIGINT
154 #endif
155 
156 int
157 main(int argc, char *argv[])
158 {
159 	struct utmpx	*ut;
160 	struct utmpx	*utmpbegin;
161 	struct utmpx	*utmpend;
162 	struct utmpx	*utp;
163 	struct uproc	*up, *parent, *pgrp;
164 	struct psinfo	info;
165 	struct sigaction actinfo[ACTSIZE];
166 	struct pstatus	statinfo;
167 	size_t		size;
168 	struct stat	sbuf;
169 	DIR   		*dirp;
170 	struct	dirent	*dp;
171 	char		pname[64];
172 	char 		*fname;
173 	int		procfd;
174 	char		*cp;
175 	int		i;
176 	int		days, hrs, mins;
177 	int		entries;
178 	double		loadavg[3];
179 
180 	/*
181 	 * This program needs the proc_owner privilege
182 	 */
183 	(void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
184 	    (char *)NULL);
185 
186 	(void) setlocale(LC_ALL, "");
187 #if !defined(TEXT_DOMAIN)
188 #define	TEXT_DOMAIN "SYS_TEST"
189 #endif
190 	(void) textdomain(TEXT_DOMAIN);
191 
192 	login = (argv[0][0] == '-');
193 	cp = strrchr(argv[0], '/');
194 	firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1];
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 				case 's':
210 					lflag = 0;
211 					break;
212 
213 				case 'u':
214 				case 'w':
215 					firstchar = argv[1][i];
216 					break;
217 
218 				default:
219 					(void) fprintf(stderr, gettext(
220 					    "%s: bad flag %s\n"),
221 					    prog, argv[1]);
222 					exit(1);
223 				}
224 			}
225 		} else {
226 			if (!isalnum(argv[1][0]) || argc > 2) {
227 				(void) fprintf(stderr, gettext(
228 				    "usage: %s [ -hlsuw ] [ user ]\n"), prog);
229 				exit(1);
230 			} else
231 				sel_user = argv[1];
232 		}
233 		argc--; argv++;
234 	}
235 
236 	/*
237 	 * read the UTMP_FILE (contains information about each logged in user)
238 	 */
239 	if (stat(UTMPX_FILE, &sbuf) == ERR) {
240 		(void) fprintf(stderr, gettext("%s: stat error of %s: %s\n"),
241 		    prog, UTMPX_FILE, strerror(errno));
242 		exit(1);
243 	}
244 	entries = sbuf.st_size / sizeof (struct futmpx);
245 	size = sizeof (struct utmpx) * entries;
246 	if ((ut = malloc(size)) == NULL) {
247 		(void) fprintf(stderr, gettext("%s: malloc error of %s: %s\n"),
248 		    prog, UTMPX_FILE, strerror(errno));
249 		exit(1);
250 	}
251 
252 	(void) utmpxname(UTMPX_FILE);
253 
254 	utmpbegin = ut;
255 	utmpend = (struct utmpx *)((char *)utmpbegin + size);
256 
257 	setutxent();
258 	while ((ut < utmpend) && ((utp = getutxent()) != NULL))
259 		(void) memcpy(ut++, utp, sizeof (*ut));
260 	endutxent();
261 
262 	(void) time(&now);	/* get current time */
263 
264 	if (header) {	/* print a header */
265 		prtat(&now);
266 		for (ut = utmpbegin; ut < utmpend; ut++) {
267 			if (ut->ut_type == USER_PROCESS) {
268 				if (!nonuser(*ut))
269 					nusers++;
270 			} else if (ut->ut_type == BOOT_TIME) {
271 				uptime = now - ut->ut_xtime;
272 				uptime += 30;
273 				days = uptime / (60*60*24);
274 				uptime %= (60*60*24);
275 				hrs = uptime / (60*60);
276 				uptime %= (60*60);
277 				mins = uptime / 60;
278 
279 				PRINTF((gettext("  up")));
280 				if (days > 0)
281 					PRINTF((gettext(
282 					    " %d day(s),"), days));
283 				if (hrs > 0 && mins > 0) {
284 					PRINTF((" %2d:%02d,", hrs, mins));
285 				} else {
286 					if (hrs > 0)
287 						PRINTF((gettext(
288 						    " %d hr(s),"), hrs));
289 					if (mins > 0)
290 						PRINTF((gettext(
291 						    " %d min(s),"), mins));
292 				}
293 			}
294 		}
295 
296 		ut = utmpbegin;	/* rewind utmp data */
297 		PRINTF((((nusers == 1) ?
298 		    gettext("  %d user") : gettext("  %d users")), nusers));
299 		/*
300 		 * Print 1, 5, and 15 minute load averages.
301 		 */
302 		(void) getloadavg(loadavg, 3);
303 		PRINTF((gettext(",  load average: %.2f, %.2f, %.2f\n"),
304 		    loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN],
305 		    loadavg[LOADAVG_15MIN]));
306 
307 		if (firstchar == 'u')	/* uptime command */
308 			exit(0);
309 
310 		if (lflag) {
311 			PRINTF((dcgettext(NULL, "User     tty           "
312 			    "login@  idle   JCPU   PCPU  what\n", LC_TIME)));
313 		} else {
314 			PRINTF((dcgettext(NULL,
315 			    "User     tty           idle   what\n", LC_TIME)));
316 		}
317 
318 		if (fflush(stdout) == EOF) {
319 			perror((gettext("%s: fflush failed\n"), prog));
320 			exit(1);
321 		}
322 	}
323 
324 	/*
325 	 * loop through /proc, reading info about each process
326 	 * and build the parent/child tree
327 	 */
328 	if (!(dirp = opendir(PROCDIR))) {
329 		(void) fprintf(stderr, gettext("%s: could not open %s: %s\n"),
330 		    prog, PROCDIR, strerror(errno));
331 		exit(1);
332 	}
333 
334 	while ((dp = readdir(dirp)) != NULL) {
335 		if (dp->d_name[0] == '.')
336 			continue;
337 retry:
338 		(void) sprintf(pname, "%s/%s/", PROCDIR, dp->d_name);
339 		fname = pname + strlen(pname);
340 		(void) strcpy(fname, "psinfo");
341 		if ((procfd = open(pname, O_RDONLY)) < 0)
342 			continue;
343 		if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
344 			int err = errno;
345 			(void) close(procfd);
346 			if (err == EAGAIN)
347 				goto retry;
348 			if (err != ENOENT)
349 				(void) fprintf(stderr, gettext(
350 				    "%s: read() failed on %s: %s \n"),
351 				    prog, pname, strerror(err));
352 			continue;
353 		}
354 		(void) close(procfd);
355 
356 		up = findhash(info.pr_pid);
357 		up->p_ttyd = info.pr_ttydev;
358 		up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
359 		up->p_time = 0;
360 		up->p_ctime = 0;
361 		up->p_igintr = 0;
362 		(void) strncpy(up->p_comm, info.pr_fname,
363 		    sizeof (info.pr_fname));
364 		up->p_args[0] = 0;
365 
366 		if (up->p_state != NONE && up->p_state != ZOMBIE) {
367 			(void) strcpy(fname, "status");
368 
369 			/* now we need the proc_owner privilege */
370 			(void) __priv_bracket(PRIV_ON);
371 
372 			procfd = open(pname, O_RDONLY);
373 
374 			/* drop proc_owner privilege after open */
375 			(void) __priv_bracket(PRIV_OFF);
376 
377 			if (procfd < 0)
378 				continue;
379 
380 			if (read(procfd, &statinfo, sizeof (statinfo))
381 			    != sizeof (statinfo)) {
382 				int err = errno;
383 				(void) close(procfd);
384 				if (err == EAGAIN)
385 					goto retry;
386 				if (err != ENOENT)
387 					(void) fprintf(stderr, gettext(
388 					    "%s: read() failed on %s: %s \n"),
389 					    prog, pname, strerror(err));
390 				continue;
391 			}
392 			(void) close(procfd);
393 
394 			up->p_time = statinfo.pr_utime.tv_sec +
395 			    statinfo.pr_stime.tv_sec;	/* seconds */
396 			up->p_ctime = statinfo.pr_cutime.tv_sec +
397 			    statinfo.pr_cstime.tv_sec;
398 
399 			(void) strcpy(fname, "sigact");
400 
401 			/* now we need the proc_owner privilege */
402 			(void) __priv_bracket(PRIV_ON);
403 
404 			procfd = open(pname, O_RDONLY);
405 
406 			/* drop proc_owner privilege after open */
407 			(void) __priv_bracket(PRIV_OFF);
408 
409 			if (procfd < 0)
410 				continue;
411 
412 			if (read(procfd, actinfo, sizeof (actinfo))
413 			    != sizeof (actinfo)) {
414 				int err = errno;
415 				(void) close(procfd);
416 				if (err == EAGAIN)
417 					goto retry;
418 				if (err != ENOENT)
419 					(void) fprintf(stderr, gettext(
420 					    "%s: read() failed on %s: %s \n"),
421 					    prog, pname, strerror(err));
422 				continue;
423 			}
424 			(void) close(procfd);
425 
426 			up->p_igintr =
427 			    actinfo[SIGINT-1].sa_handler == SIG_IGN &&
428 			    actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
429 
430 			/*
431 			 * Process args.
432 			 */
433 			up->p_args[0] = 0;
434 			clnarglist(info.pr_psargs);
435 			(void) strcat(up->p_args, info.pr_psargs);
436 			if (up->p_args[0] == 0 ||
437 			    up->p_args[0] == '-' && up->p_args[1] <= ' ' ||
438 			    up->p_args[0] == '?') {
439 				(void) strcat(up->p_args, " (");
440 				(void) strcat(up->p_args, up->p_comm);
441 				(void) strcat(up->p_args, ")");
442 			}
443 		}
444 
445 		/*
446 		 * link pgrp together in case parents go away
447 		 * Pgrp chain is a single linked list originating
448 		 * from the pgrp leader to its group member.
449 		 */
450 		if (info.pr_pgid != info.pr_pid) {	/* not pgrp leader */
451 			pgrp = findhash(info.pr_pgid);
452 			up->p_pgrpl = pgrp->p_pgrpl;
453 			pgrp->p_pgrpl = up;
454 		}
455 		parent = findhash(info.pr_ppid);
456 
457 		/* if this is the new member, link it in */
458 		if (parent->p_upid != INITPROCESS) {
459 			if (parent->p_child) {
460 				up->p_sibling = parent->p_child;
461 				up->p_child = 0;
462 			}
463 			parent->p_child = up;
464 		}
465 	}
466 
467 	/* revert to non-privileged user after opening */
468 	(void) __priv_relinquish();
469 
470 	(void) closedir(dirp);
471 	(void) time(&now);	/* get current time */
472 
473 	/*
474 	 * loop through utmpx file, printing process info
475 	 * about each logged in user
476 	 */
477 	for (ut = utmpbegin; ut < utmpend; ut++) {
478 		if (ut->ut_type != USER_PROCESS)
479 			continue;
480 		if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
481 			continue;	/* we're looking for somebody else */
482 
483 		/* print login name of the user */
484 		PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name));
485 
486 		/* print tty user is on */
487 		if (lflag) {
488 			PRINTF(("%-*.*s", LINE_WIDTH, LMAX, ut->ut_line));
489 		} else {
490 			if (ut->ut_line[0] == 'p' && ut->ut_line[1] == 't' &&
491 			    ut->ut_line[2] == 's' && ut->ut_line[3] == '/') {
492 				PRINTF(("%-*.3s", LMAX, &ut->ut_line[4]));
493 			} else {
494 				PRINTF(("%-*.*s", LINE_WIDTH, LMAX,
495 				    ut->ut_line));
496 			}
497 		}
498 
499 		/* print when the user logged in */
500 		if (lflag) {
501 			time_t tim = ut->ut_xtime;
502 			prtat(&tim);
503 		}
504 
505 		/* print idle time */
506 		idle = findidle(ut->ut_line);
507 		if (idle >= 36 * 60) {
508 			PRINTF((dcgettext(NULL, "%2ddays ", LC_TIME),
509 			    (idle + 12 * 60) / (24 * 60)));
510 		} else
511 			prttime(idle, " ");
512 		showtotals(findhash(ut->ut_pid));
513 	}
514 	if (fclose(stdout) == EOF) {
515 		perror((gettext("%s: fclose failed"), prog));
516 		exit(1);
517 	}
518 	return (0);
519 }
520 
521 /*
522  *  Prints the CPU time for all processes & children,
523  *  and the cpu time for interesting process,
524  *  and what the user is doing.
525  */
526 static void
527 showtotals(struct uproc *up)
528 {
529 	jobtime = 0;
530 	proctime = 0;
531 	empty = 1;
532 	curpid = -1;
533 	add_times = 1;
534 
535 	calctotals(up);
536 
537 	if (lflag) {
538 		/* print CPU time for all processes & children */
539 		/* and need to convert clock ticks to seconds first */
540 		prttime((time_t)jobtime, " ");
541 
542 		/* print cpu time for interesting process */
543 		/* and need to convert clock ticks to seconds first */
544 		prttime((time_t)proctime, " ");
545 	}
546 	/* what user is doing, current process */
547 	PRINTF((" %-.32s\n", doing));
548 }
549 
550 /*
551  *  This recursive routine descends the process
552  *  tree starting from the given process pointer(up).
553  *  It used depth-first search strategy and also marked
554  *  each node as visited as it traversed down the tree.
555  *  It calculates the process time for all processes &
556  *  children.  It also finds the interesting process
557  *  and determines its cpu time and command.
558  */
559 static void
560 calctotals(struct uproc *up)
561 {
562 	struct uproc   *zp;
563 
564 	/*
565 	 * Once a node has been visited, stop adding cpu times
566 	 * for its children so they don't get totalled twice.
567 	 * Still look for the interesting job for this utmp
568 	 * entry, however.
569 	 */
570 	if (up->p_state == VISITED)
571 		add_times = 0;
572 	up->p_state = VISITED;
573 	if (up->p_state == NONE || up->p_state == ZOMBIE)
574 		return;
575 
576 	if (empty && !up->p_igintr) {
577 		empty = 0;
578 		curpid = -1;
579 	}
580 
581 	if (up->p_upid > curpid && (!up->p_igintr || empty)) {
582 		curpid = up->p_upid;
583 		if (lflag)
584 			(void) strcpy(doing, up->p_args);
585 		else
586 			(void) strcpy(doing, up->p_comm);
587 	}
588 
589 	if (add_times == 1) {
590 		jobtime += up->p_time + up->p_ctime;
591 		proctime += up->p_time;
592 	}
593 
594 	/* descend for its children */
595 	if (up->p_child) {
596 		calctotals(up->p_child);
597 		for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
598 			calctotals(zp);
599 	}
600 }
601 
602 /*
603  *   Findhash  finds the appropriate entry in the process
604  *   hash table (pr_htbl) for the given pid in case that
605  *   pid exists on the hash chain. It returns back a pointer
606  *   to that uproc structure. If this is a new pid, it allocates
607  *   a new node, initializes it, links it into the chain (after
608  *   head) and returns a structure pointer.
609  */
610 static struct uproc *
611 findhash(pid_t pid)
612 {
613 	struct uproc *up, *tp;
614 
615 	tp = up = &pr_htbl[pid % HSIZE];
616 	if (up->p_upid == 0) {			/* empty slot */
617 		up->p_upid = pid;
618 		up->p_state = NONE;
619 		up->p_child = up->p_sibling = up->p_pgrpl = up->p_link = 0;
620 		return (up);
621 	}
622 	if (up->p_upid == pid) {		/* found in hash table */
623 		return (up);
624 	}
625 	for (tp = up->p_link; tp; tp = tp->p_link) {	/* follow chain */
626 		if (tp->p_upid == pid)
627 			return (tp);
628 	}
629 	tp = malloc(sizeof (*tp));		/* add new node */
630 	if (!tp) {
631 		(void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
632 		    prog, strerror(errno));
633 		exit(1);
634 	}
635 	(void) memset(tp, 0, sizeof (*tp));
636 	tp->p_upid = pid;
637 	tp->p_state = NONE;
638 	tp->p_child = tp->p_sibling = tp->p_pgrpl = 0;
639 	tp->p_link = up->p_link;		/* insert after head */
640 	up->p_link = tp;
641 	return (tp);
642 }
643 
644 #define	HR	(60 * 60)
645 #define	DAY	(24 * HR)
646 #define	MON	(30 * DAY)
647 
648 /*
649  * prttime prints a time in hours and minutes or minutes and seconds.
650  * The character string tail is printed at the end, obvious
651  * strings to pass are "", " ", or "am".
652  */
653 static void
654 prttime(time_t tim, char *tail)
655 {
656 	if (tim >= 60) {
657 		PRINTF((dcgettext(NULL, "%3d:%02d", LC_TIME),
658 		    (int)tim/60, (int)tim%60));
659 	} else if (tim > 0) {
660 		PRINTF((dcgettext(NULL, "    %2d", LC_TIME), (int)tim));
661 	} else {
662 		PRINTF(("      "));
663 	}
664 	PRINTF(("%s", tail));
665 }
666 
667 /*
668  * prints a 12 hour time given a pointer to a time of day
669  */
670 static void
671 prtat(time_t *time)
672 {
673 	struct tm	*p;
674 
675 	p = localtime(time);
676 	if (now - *time <= 18 * HR) {
677 		char timestr[50];
678 		(void) strftime(timestr, sizeof (timestr),
679 		    dcgettext(NULL, "%l:%M""%p", LC_TIME), p);
680 		checkampm(timestr);
681 		PRINTF((" %s", timestr));
682 	} else if (now - *time <= 7 * DAY) {
683 		char weekdaytime[20];
684 
685 		(void) strftime(weekdaytime, sizeof (weekdaytime),
686 		    dcgettext(NULL, "%a%l%p", LC_TIME), p);
687 		checkampm(weekdaytime);
688 		PRINTF((" %s", weekdaytime));
689 	} else {
690 		char monthtime[20];
691 
692 		(void) strftime(monthtime, sizeof (monthtime),
693 		    dcgettext(NULL, "%e%b%y", LC_TIME), p);
694 		PRINTF((" %s", monthtime));
695 	}
696 }
697 
698 /*
699  * find & return number of minutes current tty has been idle
700  */
701 static time_t
702 findidle(char *devname)
703 {
704 	struct stat stbuf;
705 	time_t lastaction, diff;
706 	char ttyname[64];
707 
708 	(void) strcpy(ttyname, "/dev/");
709 	(void) strcat(ttyname, devname);
710 	if (stat(ttyname, &stbuf) != -1) {
711 		lastaction = stbuf.st_atime;
712 		diff = now - lastaction;
713 		diff = DIV60(diff);
714 		if (diff < 0)
715 			diff = 0;
716 	} else
717 		diff = 0;
718 	return (diff);
719 }
720 
721 /*
722  * given a pointer to the argument string get rid of unsavory characters.
723  */
724 static void
725 clnarglist(char *arglist)
726 {
727 	char	*c;
728 	int 	err = 0;
729 
730 	/* get rid of unsavory characters */
731 	for (c = arglist; *c != NULL; c++) {
732 		if ((*c < ' ') || (*c > 0176)) {
733 			if (err++ > 5) {
734 				*arglist = NULL;
735 				break;
736 			}
737 			*c = '?';
738 		}
739 	}
740 }
741 
742 /* replaces all occurences of AM/PM with am/pm */
743 static void
744 checkampm(char *str)
745 {
746 	char *ampm;
747 	while ((ampm = strstr(str, "AM")) != NULL ||
748 	    (ampm = strstr(str, "PM")) != NULL) {
749 		*ampm = tolower(*ampm);
750 		*(ampm+1) = tolower(*(ampm+1));
751 	}
752 }
753