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]
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
36 * University Acknowledgment- Portions of this document are derived from
37 * software developed by the University of California, Berkeley, and its
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.
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.
59 #include <sys/types.h>
61 #include <sys/utsname.h>
63 #include <sys/mkdev.h>
65 #include <procfs.h> /* /proc header file */
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. */
82 #define DIV60(t) ((t+30)/60) /* x/60 rounded */
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 */
101 static struct devl
{ /* device list */
102 char dname
[DEVNAMELEN
]; /* device name */
103 dev_t ddev
; /* device number */
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 *);
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 */
154 #define ACTSIZE SIGQUIT
156 #define ACTSIZE SIGINT
160 main(int argc
, char *argv
[])
163 struct utmpx
*utmpbegin
;
164 struct utmpx
*utmpend
;
167 struct uproc
*up
, *parent
, *pgrp
;
169 struct sigaction actinfo
[ACTSIZE
];
170 struct pstatus statinfo
;
184 * This program needs the proc_owner privilege
186 (void) __init_suid_priv(PU_CLEARLIMITSET
, PRIV_PROC_OWNER
,
189 (void) setlocale(LC_ALL
, "");
190 #if !defined(TEXT_DOMAIN)
191 #define TEXT_DOMAIN "SYS_TEST"
193 (void) textdomain(TEXT_DOMAIN
);
198 if (argv
[1][0] == '-') {
199 for (i
= 1; argv
[1][i
]; i
++) {
200 switch (argv
[1][i
]) {
211 (void) printf(gettext(
212 "usage: %s [ -hl ] [ user ]\n"),
218 if (!isalnum(argv
[1][0]) || argc
> 2) {
219 (void) printf(gettext(
220 "usage: %s [ -hl ] [ user ]\n"), prog
);
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
));
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
));
246 (void) utmpxname(UTMPX_FILE
);
249 /* LINTED pointer cast may result in improper alignment */
250 utmpend
= (struct utmpx
*)((char *)utmpbegin
+ size
);
253 while ((ut
< utmpend
) && ((utp
= getutxent()) != NULL
))
254 (void) memcpy(ut
++, utp
, sizeof (*ut
));
257 (void) time(&now
); /* get current time */
259 if (header
) { /* print a header */
260 if (lflag
) { /* w command format header */
262 for (ut
= utmpbegin
; ut
< utmpend
; ut
++) {
263 if (ut
->ut_type
== USER_PROCESS
) {
265 } else if (ut
->ut_type
== BOOT_TIME
) {
266 uptime
= now
- ut
->ut_xtime
;
268 days
= uptime
/ (60*60*24);
269 uptime
%= (60*60*24);
270 hrs
= uptime
/ (60*60);
274 (void) printf(dcgettext(NULL
,
275 "up %d day(s), %d hr(s), "
276 "%d min(s)", LC_TIME
),
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",
287 } else { /* standard whodo header */
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
);
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
));
315 while ((dp
= readdir(dirp
)) != NULL
) {
316 if (dp
->d_name
[0] == '.')
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)
325 if (read(procfd
, &info
, sizeof (info
)) != sizeof (info
)) {
327 (void) close(procfd
);
331 (void) fprintf(stderr
, gettext(
332 "%s: read() failed on %s: %s\n"),
333 prog
, pname
, strerror(err
));
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
);
344 (void) strncpy(up
->p_comm
, info
.pr_fname
,
345 sizeof (info
.pr_fname
));
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
);
362 if (read(procfd
, &statinfo
, sizeof (statinfo
))
363 != sizeof (statinfo
)) {
365 (void) close(procfd
);
369 (void) fprintf(stderr
, gettext(
370 "%s: read() failed on %s: %s \n"),
371 prog
, pname
, strerror(err
));
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
);
393 if (read(procfd
, actinfo
, sizeof (actinfo
))
394 != sizeof (actinfo
)) {
396 (void) close(procfd
);
400 (void) fprintf(stderr
, gettext(
401 "%s: read() failed on %s: %s \n"),
402 prog
, pname
, strerror(err
));
405 (void) close(procfd
);
408 actinfo
[SIGINT
-1].sa_handler
== SIG_IGN
&&
409 actinfo
[SIGQUIT
-1].sa_handler
== SIG_IGN
;
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
;
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
++) {
467 if (ut
->ut_type
!= USER_PROCESS
)
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
,
476 /* print tty user is on */
477 (void) printf("%-*.*s ", LINE_WIDTH
, (int)LMAX
,
480 /* print when the user logged in */
484 /* print idle time */
485 idle
= findidle(ut
->ut_line
);
487 showtotals(findhash((pid_t
)ut
->ut_pid
));
488 } else { /* standard whodo format */
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
,
495 showproc(findhash((pid_t
)ut
->ut_pid
));
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.
510 showproc(struct uproc
*up
)
514 if (up
->p_state
== VISITED
) /* we already been here */
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,
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,
527 up
->p_state
= VISITED
;
529 /* descend for its children */
531 showproc(up
->p_child
);
532 for (zp
= up
->p_child
->p_sibling
; zp
; zp
= zp
->p_sibling
) {
537 /* print the pgrp relation */
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.
550 showtotals(struct uproc
*up
)
556 (void) strcpy(doing
, "-"); /* default act: normally never prints */
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.
582 calctotals(struct uproc
*up
)
586 if (up
->p_state
== VISITED
)
588 up
->p_state
= VISITED
;
589 if (up
->p_state
== NONE
|| up
->p_state
== ZOMBIE
)
591 jobtime
+= up
->p_time
+ up
->p_ctime
;
592 proctime
+= up
->p_time
;
594 if (empty
&& !up
->p_igintr
) {
599 if (up
->p_upid
> curpid
&& (!up
->p_igintr
|| empty
)) {
601 (void) strcpy(doing
, up
->p_args
);
604 /* descend for its children */
606 calctotals(up
->p_child
);
607 for (zp
= up
->p_child
->p_sibling
; zp
; zp
= zp
->p_sibling
)
613 devadd(char *name
, dev_t ddev
)
618 if (ndevs
== maxdev
) {
620 dp
= reallocarray(devl
, maxdev
, sizeof (struct devl
));
622 (void) fprintf(stderr
,
623 gettext("%s: out of memory!: %s\n"),
624 prog
, strerror(errno
));
633 (void) strcpy(dp
->dname
, " ? ");
638 if (leng
< DEVNAMELEN
+ 4) {
639 /* strip off "/dev/" */
640 (void) strcpy(dp
->dname
, &name
[5]);
642 /* strip enough off the front to fit */
643 start
= leng
- DEVNAMELEN
- 1;
645 for (i
= start
; i
< leng
&& name
[i
] != '/'; i
++)
648 (void) strncpy(dp
->dname
, &name
[start
], DEVNAMELEN
);
650 (void) strncpy(dp
->dname
, &name
[i
+1], DEVNAMELEN
);
656 devlookup(dev_t ddev
)
661 for (dp
= devl
, i
= 0; i
< ndevs
; dp
++, i
++) {
662 if (dp
->ddev
== ddev
)
669 * This routine gives back a corresponding device name
670 * from the device number given.
675 extern char *_ttyname_dev(dev_t
, char *, size_t);
676 char devname
[TTYNAME_MAX
];
682 if ((retval
= devlookup(dev
)) != NULL
)
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
*
700 struct uproc
*up
, *tp
;
702 tp
= up
= &pr_htbl
[(int)pid
% HSIZE
];
703 if (up
->p_upid
== 0) { /* empty slot */
706 up
->p_child
= up
->p_sibling
= up
->p_pgrplink
= up
->p_link
= 0;
709 if (up
->p_upid
== pid
) { /* found in hash table */
712 for (tp
= up
->p_link
; tp
; tp
= tp
->p_link
) { /* follow chain */
713 if (tp
->p_upid
== pid
) {
717 tp
= malloc(sizeof (*tp
)); /* add new node */
719 (void) fprintf(stderr
, gettext("%s: out of memory!: %s\n"),
720 prog
, strerror(errno
));
723 (void) memset((char *)tp
, 0, sizeof (*tp
));
726 tp
->p_child
= tp
->p_sibling
= tp
->p_pgrplink
= (pid_t
)0;
727 tp
->p_link
= up
->p_link
; /* insert after head */
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.
743 prttime(time_t tim
, int width
)
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
);
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.
774 if (now
- *time
<= 18 * HR
) {
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
));
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
799 findidle(char *devname
)
802 time_t lastaction
, diff
;
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
;
819 * given a pointer to the argument string clean out unsavory characters.
822 clnarglist(char *arglist
)
827 /* get rid of unsavory characters */
828 for (c
= arglist
; *c
== '\0'; c
++) {
829 if ((*c
< ' ') || (*c
> 0176)) {