Merge commit 'b1e7e97d3b60469b243b3b2e22c7d8cbd11c7c90'
[unleashed.git] / usr / src / cmd / whodo / whodo.c
blobf97a0ae6ef6e7bdd62e7cd35988e8d963860a40f
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 2013 Gary Mills
24 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
25 * Use is subject to license terms.
28 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
29 /* All Rights Reserved */
32 * University Copyright- Copyright (c) 1982, 1986, 1988
33 * The Regents of the University of California
34 * All Rights Reserved
36 * University Acknowledgment- Portions of this document are derived from
37 * software developed by the University of California, Berkeley, and its
38 * contributors.
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.
46 * Maintenance note:
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.
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>
73 * Use the full lengths from utmpx for user and line.
75 #define NMAX (sizeof (((struct utmpx *)0)->ut_user))
76 #define LMAX (sizeof (((struct utmpx *)0)->ut_line))
78 /* Print minimum field widths. */
79 #define LOGIN_WIDTH 8
80 #define LINE_WIDTH 8
82 #define DIV60(t) ((t+30)/60) /* x/60 rounded */
84 #ifdef ERR
85 #undef ERR
86 #endif
87 #define ERR (-1)
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 */
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;
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 */
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.
127 static struct uproc pr_htbl[HSIZE];
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 *);
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;
153 #if SIGQUIT > SIGINT
154 #define ACTSIZE SIGQUIT
155 #else
156 #define ACTSIZE SIGINT
157 #endif
160 main(int argc, char *argv[])
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;
184 * This program needs the proc_owner privilege
186 (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
187 NULL);
189 (void) setlocale(LC_ALL, "");
190 #if !defined(TEXT_DOMAIN)
191 #define TEXT_DOMAIN "SYS_TEST"
192 #endif
193 (void) textdomain(TEXT_DOMAIN);
195 prog = argv[0];
197 while (argc > 1) {
198 if (argv[1][0] == '-') {
199 for (i = 1; argv[1][i]; i++) {
200 switch (argv[1][i]) {
202 case 'h':
203 header = 0;
204 break;
206 case 'l':
207 lflag++;
208 break;
210 default:
211 (void) printf(gettext(
212 "usage: %s [ -hl ] [ user ]\n"),
213 prog);
214 exit(1);
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];
225 argc--; argv++;
229 * read the UTMPX_FILE (contains information about
230 * each logged in user)
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);
237 entries = sbuf.st_size / sizeof (struct futmpx);
238 size = sizeof (struct utmpx) * entries;
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);
246 (void) utmpxname(UTMPX_FILE);
248 utmpbegin = ut;
249 /* LINTED pointer cast may result in improper alignment */
250 utmpend = (struct utmpx *)((char *)utmpbegin + size);
252 setutxent();
253 while ((ut < utmpend) && ((utp = getutxent()) != NULL))
254 (void) memcpy(ut++, utp, sizeof (*ut));
255 endutxent();
257 (void) time(&now); /* get current time */
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;
274 (void) printf(dcgettext(NULL,
275 "up %d day(s), %d hr(s), "
276 "%d min(s)", LC_TIME),
277 days, hrs, mins);
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];
291 * print current time and date
293 (void) strftime(date_buf, sizeof (date_buf),
294 "%c", localtime(&now));
295 (void) printf("%s\n", date_buf);
298 * print system name
300 (void) uname(&uts);
301 (void) printf("%s\n", uts.nodename);
306 * loop through /proc, reading info about each process
307 * and build the parent/child tree
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);
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;
336 (void) close(procfd);
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;
348 if (up->p_state != NONE && up->p_state != ZOMBIE) {
349 (void) strcpy(fname, "status");
351 /* now we need the proc_owner privilege */
352 (void) __priv_bracket(PRIV_ON);
354 procfd = open(pname, O_RDONLY);
356 /* drop proc_owner privilege after open */
357 (void) __priv_bracket(PRIV_OFF);
359 if (procfd < 0)
360 continue;
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;
374 (void) close(procfd);
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;
381 (void) strcpy(fname, "sigact");
383 /* now we need the proc_owner privilege */
384 (void) __priv_bracket(PRIV_ON);
386 procfd = open(pname, O_RDONLY);
388 /* drop proc_owner privilege after open */
389 (void) __priv_bracket(PRIV_OFF);
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;
405 (void) close(procfd);
407 up->p_igintr =
408 actinfo[SIGINT-1].sa_handler == SIG_IGN &&
409 actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
411 up->p_args[0] = 0;
414 * Process args if there's a chance we'll print it.
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, ")");
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.
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;
441 parent = findhash(info.pr_ppid);
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;
449 parent->p_child = up;
454 /* revert to non-privileged user */
455 (void) __priv_relinquish();
457 (void) closedir(dirp);
458 (void) time(&now); /* get current time */
461 * loop through utmpx file, printing process info
462 * about each logged in user
464 for (ut = utmpbegin; ut < utmpend; ut++) {
465 time_t tim;
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);
476 /* print tty user is on */
477 (void) printf("%-*.*s ", LINE_WIDTH, (int)LMAX,
478 ut->ut_line);
480 /* print when the user logged in */
481 tim = ut->ut_xtime;
482 (void) prtat(&tim);
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));
499 return (0);
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.
509 static void
510 showproc(struct uproc *up)
512 struct uproc *zp;
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);
527 up->p_state = VISITED;
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);
537 /* print the pgrp relation */
538 if (up->p_pgrplink)
539 showproc(up->p_pgrplink);
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.
549 static void
550 showtotals(struct uproc *up)
552 jobtime = 0;
553 proctime = 0;
554 empty = 1;
555 curpid = -1;
556 (void) strcpy(doing, "-"); /* default act: normally never prints */
557 calctotals(up);
559 /* print CPU time for all processes & children */
560 /* and need to convert clock ticks to seconds first */
561 prttime((time_t)jobtime, 8);
563 /* print cpu time for interesting process */
564 /* and need to convert clock ticks to seconds first */
565 prttime((time_t)proctime, 8);
567 /* what user is doing, current process */
568 (void) printf("%-.32s\n", doing);
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.
581 static void
582 calctotals(struct uproc *up)
584 struct uproc *zp;
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;
594 if (empty && !up->p_igintr) {
595 empty = 0;
596 curpid = -1;
599 if (up->p_upid > curpid && (!up->p_igintr || empty)) {
600 curpid = up->p_upid;
601 (void) strcpy(doing, up->p_args);
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);
612 static char *
613 devadd(char *name, dev_t ddev)
615 struct devl *dp;
616 int leng, start, i;
618 if (ndevs == maxdev) {
619 maxdev += DNINCR;
620 dp = reallocarray(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);
627 devl = dp;
629 dp = &devl[ndevs++];
631 dp->ddev = ddev;
632 if (name == NULL) {
633 (void) strcpy(dp->dname, " ? ");
634 return (dp->dname);
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;
645 for (i = start; i < leng && name[i] != '/'; i++)
647 if (i == leng)
648 (void) strncpy(dp->dname, &name[start], DEVNAMELEN);
649 else
650 (void) strncpy(dp->dname, &name[i+1], DEVNAMELEN);
652 return (dp->dname);
655 static char *
656 devlookup(dev_t ddev)
658 struct devl *dp;
659 int i;
661 for (dp = devl, i = 0; i < ndevs; dp++, i++) {
662 if (dp->ddev == ddev)
663 return (dp->dname);
665 return (NULL);
669 * This routine gives back a corresponding device name
670 * from the device number given.
672 static char *
673 getty(dev_t dev)
675 extern char *_ttyname_dev(dev_t, char *, size_t);
676 char devname[TTYNAME_MAX];
677 char *retval;
679 if (dev == PRNODEV)
680 return (" ? ");
682 if ((retval = devlookup(dev)) != NULL)
683 return (retval);
685 retval = _ttyname_dev(dev, devname, sizeof (devname));
686 return (devadd(retval, dev));
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.
697 static struct uproc *
698 findhash(pid_t pid)
700 struct uproc *up, *tp;
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);
709 if (up->p_upid == pid) { /* found in hash table */
710 return (up);
712 for (tp = up->p_link; tp; tp = tp->p_link) { /* follow chain */
713 if (tp->p_upid == pid) {
714 return (tp);
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);
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);
732 #define HR (60 * 60)
733 #define DAY (24 * HR)
734 #define MON (30 * DAY)
735 #define PRINTF(a) (void) printf a
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.
742 static void
743 prttime(time_t tim, int width)
745 char value[36];
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");
758 width = (width > 2) ? width - 1 : 1;
759 PRINTF(("%*s ", width, value));
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.
768 static void
769 prtat(time_t *time)
771 struct tm *p;
773 p = localtime(time);
774 if (now - *time <= 18 * HR) {
775 char timestr[50];
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];
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];
789 (void) strftime(monthtime, sizeof (monthtime),
790 dcgettext(NULL, "%F", LC_TIME), p);
791 PRINTF(("%-11s ", monthtime));
796 * find & return number of minutes current tty has been idle
798 static time_t
799 findidle(char *devname)
801 struct stat stbuf;
802 time_t lastaction, diff;
803 char ttyname[64];
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);
819 * given a pointer to the argument string clean out unsavory characters.
821 static void
822 clnarglist(char *arglist)
824 char *c;
825 int err = 0;
827 /* get rid of unsavory characters */
828 for (c = arglist; *c == '\0'; c++) {
829 if ((*c < ' ') || (*c > 0176)) {
830 if (err++ > 5) {
831 *arglist = '\0';
832 break;
834 *c = '?';