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 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.
46 * This program also implements 'uptime'.
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.
62 #include <sys/types.h>
66 #include <procfs.h> /* /proc header file */
69 #include <sys/loadavg.h>
71 #include <priv_utils.h>
74 * Use the full lengths from utmpx for user and line.
76 static struct utmpx dummy
;
77 #define NMAX (sizeof (dummy.ut_user))
78 #define LMAX (sizeof (dummy.ut_line))
80 /* Print minimum field widths. */
84 #define DIV60(t) ((t+30)/60) /* x/60 rounded */
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)); \
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 */
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.
123 static struct uproc pr_htbl
[HSIZE
];
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, int);
131 static void prtat(time_t *time
);
133 static char *prog
; /* pointer to invocation name */
134 static int header
= 1; /* true if -h flag: don't print heading */
135 static int lflag
= 1; /* set if -l flag; 0 for -s flag: short form */
136 static char *sel_user
; /* login of particular user selected */
137 static char firstchar
; /* first char of name of prog invoked as */
138 static int login
; /* true if invoked as login shell */
139 static time_t now
; /* current time of day */
140 static time_t uptime
; /* time of last reboot & elapsed time since */
141 static int nusers
; /* number of users logged in now */
142 static time_t idle
; /* number of minutes user is idle */
143 static time_t jobtime
; /* total cpu time visible */
144 static char doing
[520]; /* process attached to terminal */
145 static time_t proctime
; /* cpu time of process in doing */
146 static pid_t curpid
, empty
;
147 static int add_times
; /* boolean: add the cpu times or not */
150 #define ACTSIZE SIGQUIT
152 #define ACTSIZE SIGINT
156 main(int argc
, char *argv
[])
159 struct utmpx
*utmpbegin
;
160 struct utmpx
*utmpend
;
162 struct uproc
*up
, *parent
, *pgrp
;
164 struct sigaction actinfo
[ACTSIZE
];
165 struct pstatus statinfo
;
180 * This program needs the proc_owner privilege
182 (void) __init_suid_priv(PU_CLEARLIMITSET
, PRIV_PROC_OWNER
,
185 (void) setlocale(LC_ALL
, "");
186 #if !defined(TEXT_DOMAIN)
187 #define TEXT_DOMAIN "SYS_TEST"
189 (void) textdomain(TEXT_DOMAIN
);
191 login
= (argv
[0][0] == '-');
192 cp
= strrchr(argv
[0], '/');
193 firstchar
= login
? argv
[0][1] : (cp
== 0) ? argv
[0][0] : cp
[1];
197 if (argv
[1][0] == '-') {
198 for (i
= 1; argv
[1][i
]; i
++) {
199 switch (argv
[1][i
]) {
214 firstchar
= argv
[1][i
];
218 (void) fprintf(stderr
, gettext(
219 "%s: bad flag %s\n"),
225 if (!isalnum(argv
[1][0]) || argc
> 2) {
226 (void) fprintf(stderr
, gettext(
227 "usage: %s [ -hlsuw ] [ user ]\n"), prog
);
236 * read the UTMPX_FILE (contains information about each logged in user)
238 if (stat(UTMPX_FILE
, &sbuf
) == ERR
) {
239 (void) fprintf(stderr
, gettext("%s: stat error of %s: %s\n"),
240 prog
, UTMPX_FILE
, strerror(errno
));
243 entries
= sbuf
.st_size
/ sizeof (struct futmpx
);
244 size
= sizeof (struct utmpx
) * entries
;
245 if ((ut
= malloc(size
)) == NULL
) {
246 (void) fprintf(stderr
, gettext("%s: malloc error of %s: %s\n"),
247 prog
, UTMPX_FILE
, strerror(errno
));
251 (void) utmpxname(UTMPX_FILE
);
254 utmpend
= (struct utmpx
*)((char *)utmpbegin
+ size
);
257 while ((ut
< utmpend
) && ((utp
= getutxent()) != NULL
))
258 (void) memcpy(ut
++, utp
, sizeof (*ut
));
261 (void) time(&now
); /* get current time */
263 if (header
) { /* print a header */
265 for (ut
= utmpbegin
; ut
< utmpend
; ut
++) {
266 if (ut
->ut_type
== USER_PROCESS
) {
269 } else if (ut
->ut_type
== BOOT_TIME
) {
270 uptime
= now
- ut
->ut_xtime
;
272 days
= uptime
/ (60*60*24);
273 uptime
%= (60*60*24);
274 hrs
= uptime
/ (60*60);
278 PRINTF((gettext("up")));
281 " %d day(s),"), days
));
282 if (hrs
> 0 && mins
> 0) {
283 PRINTF((" %2d:%02d,", hrs
, mins
));
287 " %d hr(s),"), hrs
));
290 " %d min(s),"), mins
));
295 ut
= utmpbegin
; /* rewind utmp data */
296 PRINTF((((nusers
== 1) ?
297 gettext(" %d user") : gettext(" %d users")), nusers
));
299 * Print 1, 5, and 15 minute load averages.
301 (void) getloadavg(loadavg
, 3);
302 PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"),
303 loadavg
[LOADAVG_1MIN
], loadavg
[LOADAVG_5MIN
],
304 loadavg
[LOADAVG_15MIN
]));
306 if (firstchar
== 'u') /* uptime command */
310 PRINTF((dcgettext(NULL
, "User tty "
311 "login@ idle JCPU PCPU what\n",
314 PRINTF((dcgettext(NULL
,
315 "User tty idle what\n",
319 if (fflush(stdout
) == EOF
) {
320 perror((gettext("%s: fflush failed\n"), prog
));
326 * loop through /proc, reading info about each process
327 * and build the parent/child tree
329 if (!(dirp
= opendir(PROCDIR
))) {
330 (void) fprintf(stderr
, gettext("%s: could not open %s: %s\n"),
331 prog
, PROCDIR
, strerror(errno
));
335 while ((dp
= readdir(dirp
)) != NULL
) {
336 if (dp
->d_name
[0] == '.')
339 (void) sprintf(pname
, "%s/%s/", PROCDIR
, dp
->d_name
);
340 fname
= pname
+ strlen(pname
);
341 (void) strcpy(fname
, "psinfo");
342 if ((procfd
= open(pname
, O_RDONLY
)) < 0)
344 if (read(procfd
, &info
, sizeof (info
)) != sizeof (info
)) {
346 (void) close(procfd
);
350 (void) fprintf(stderr
, gettext(
351 "%s: read() failed on %s: %s \n"),
352 prog
, pname
, strerror(err
));
355 (void) close(procfd
);
357 up
= findhash(info
.pr_pid
);
358 up
->p_ttyd
= info
.pr_ttydev
;
359 up
->p_state
= (info
.pr_nlwp
== 0? ZOMBIE
: RUNNING
);
363 (void) strncpy(up
->p_comm
, info
.pr_fname
,
364 sizeof (info
.pr_fname
));
367 if (up
->p_state
!= NONE
&& up
->p_state
!= ZOMBIE
) {
368 (void) strcpy(fname
, "status");
370 /* now we need the proc_owner privilege */
371 (void) __priv_bracket(PRIV_ON
);
373 procfd
= open(pname
, O_RDONLY
);
375 /* drop proc_owner privilege after open */
376 (void) __priv_bracket(PRIV_OFF
);
381 if (read(procfd
, &statinfo
, sizeof (statinfo
))
382 != sizeof (statinfo
)) {
384 (void) close(procfd
);
388 (void) fprintf(stderr
, gettext(
389 "%s: read() failed on %s: %s \n"),
390 prog
, pname
, strerror(err
));
393 (void) close(procfd
);
395 up
->p_time
= statinfo
.pr_utime
.tv_sec
+
396 statinfo
.pr_stime
.tv_sec
; /* seconds */
397 up
->p_ctime
= statinfo
.pr_cutime
.tv_sec
+
398 statinfo
.pr_cstime
.tv_sec
;
400 (void) strcpy(fname
, "sigact");
402 /* now we need the proc_owner privilege */
403 (void) __priv_bracket(PRIV_ON
);
405 procfd
= open(pname
, O_RDONLY
);
407 /* drop proc_owner privilege after open */
408 (void) __priv_bracket(PRIV_OFF
);
413 if (read(procfd
, actinfo
, sizeof (actinfo
))
414 != sizeof (actinfo
)) {
416 (void) close(procfd
);
420 (void) fprintf(stderr
, gettext(
421 "%s: read() failed on %s: %s \n"),
422 prog
, pname
, strerror(err
));
425 (void) close(procfd
);
428 actinfo
[SIGINT
-1].sa_handler
== SIG_IGN
&&
429 actinfo
[SIGQUIT
-1].sa_handler
== SIG_IGN
;
435 clnarglist(info
.pr_psargs
);
436 (void) strcat(up
->p_args
, info
.pr_psargs
);
437 if (up
->p_args
[0] == 0 ||
438 up
->p_args
[0] == '-' && up
->p_args
[1] <= ' ' ||
439 up
->p_args
[0] == '?') {
440 (void) strcat(up
->p_args
, " (");
441 (void) strcat(up
->p_args
, up
->p_comm
);
442 (void) strcat(up
->p_args
, ")");
447 * link pgrp together in case parents go away
448 * Pgrp chain is a single linked list originating
449 * from the pgrp leader to its group member.
451 if (info
.pr_pgid
!= info
.pr_pid
) { /* not pgrp leader */
452 pgrp
= findhash(info
.pr_pgid
);
453 up
->p_pgrpl
= pgrp
->p_pgrpl
;
456 parent
= findhash(info
.pr_ppid
);
458 /* if this is the new member, link it in */
459 if (parent
->p_upid
!= INITPROCESS
) {
460 if (parent
->p_child
) {
461 up
->p_sibling
= parent
->p_child
;
464 parent
->p_child
= up
;
468 /* revert to non-privileged user after opening */
469 (void) __priv_relinquish();
471 (void) closedir(dirp
);
472 (void) time(&now
); /* get current time */
475 * loop through utmpx file, printing process info
476 * about each logged in user
478 for (ut
= utmpbegin
; ut
< utmpend
; ut
++) {
479 if (ut
->ut_type
!= USER_PROCESS
)
481 if (sel_user
&& strncmp(ut
->ut_name
, sel_user
, NMAX
) != 0)
482 continue; /* we're looking for somebody else */
484 /* print login name of the user */
485 PRINTF(("%-*.*s ", LOGIN_WIDTH
, NMAX
, ut
->ut_name
));
487 /* print tty user is on */
489 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
, ut
->ut_line
));
491 if (ut
->ut_line
[0] == 'p' && ut
->ut_line
[1] == 't' &&
492 ut
->ut_line
[2] == 's' && ut
->ut_line
[3] == '/') {
493 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
,
496 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
,
501 /* print when the user logged in */
503 time_t tim
= ut
->ut_xtime
;
507 /* print idle time */
508 idle
= findidle(ut
->ut_line
);
510 showtotals(findhash(ut
->ut_pid
));
512 if (fclose(stdout
) == EOF
) {
513 perror((gettext("%s: fclose failed"), prog
));
520 * Prints the CPU time for all processes & children,
521 * and the cpu time for interesting process,
522 * and what the user is doing.
525 showtotals(struct uproc
*up
)
536 /* print CPU time for all processes & children */
537 /* and need to convert clock ticks to seconds first */
538 prttime((time_t)jobtime
, 8);
540 /* print cpu time for interesting process */
541 /* and need to convert clock ticks to seconds first */
542 prttime((time_t)proctime
, 8);
544 /* what user is doing, current process */
545 PRINTF(("%-.32s\n", doing
));
549 * This recursive routine descends the process
550 * tree starting from the given process pointer(up).
551 * It used depth-first search strategy and also marked
552 * each node as visited as it traversed down the tree.
553 * It calculates the process time for all processes &
554 * children. It also finds the interesting process
555 * and determines its cpu time and command.
558 calctotals(struct uproc
*up
)
563 * Once a node has been visited, stop adding cpu times
564 * for its children so they don't get totalled twice.
565 * Still look for the interesting job for this utmp
568 if (up
->p_state
== VISITED
)
570 up
->p_state
= VISITED
;
571 if (up
->p_state
== NONE
|| up
->p_state
== ZOMBIE
)
574 if (empty
&& !up
->p_igintr
) {
579 if (up
->p_upid
> curpid
&& (!up
->p_igintr
|| empty
)) {
582 (void) strcpy(doing
, up
->p_args
);
584 (void) strcpy(doing
, up
->p_comm
);
587 if (add_times
== 1) {
588 jobtime
+= up
->p_time
+ up
->p_ctime
;
589 proctime
+= up
->p_time
;
592 /* descend for its children */
594 calctotals(up
->p_child
);
595 for (zp
= up
->p_child
->p_sibling
; zp
; zp
= zp
->p_sibling
)
601 * Findhash finds the appropriate entry in the process
602 * hash table (pr_htbl) for the given pid in case that
603 * pid exists on the hash chain. It returns back a pointer
604 * to that uproc structure. If this is a new pid, it allocates
605 * a new node, initializes it, links it into the chain (after
606 * head) and returns a structure pointer.
608 static struct uproc
*
611 struct uproc
*up
, *tp
;
613 tp
= up
= &pr_htbl
[pid
% HSIZE
];
614 if (up
->p_upid
== 0) { /* empty slot */
617 up
->p_child
= up
->p_sibling
= up
->p_pgrpl
= up
->p_link
= 0;
620 if (up
->p_upid
== pid
) { /* found in hash table */
623 for (tp
= up
->p_link
; tp
; tp
= tp
->p_link
) { /* follow chain */
624 if (tp
->p_upid
== pid
)
627 tp
= malloc(sizeof (*tp
)); /* add new node */
629 (void) fprintf(stderr
, gettext("%s: out of memory!: %s\n"),
630 prog
, strerror(errno
));
633 (void) memset(tp
, 0, sizeof (*tp
));
636 tp
->p_child
= tp
->p_sibling
= tp
->p_pgrpl
= 0;
637 tp
->p_link
= up
->p_link
; /* insert after head */
643 #define DAY (24 * HR)
644 #define MON (30 * DAY)
647 * Prttime prints an elapsed time in hours, minutes, or seconds,
648 * right-justified with the rightmost column always blank.
649 * The second argument is the minimum field width.
652 prttime(time_t tim
, int width
)
656 if (tim
>= 36 * 60) {
657 (void) snprintf(value
, sizeof (value
), "%d:%02d:%02d",
658 (int)tim
/ HR
, (int)(tim
% HR
) / 60, (int)tim
% 60);
659 } else if (tim
>= 60) {
660 (void) snprintf(value
, sizeof (value
), "%d:%02d",
661 (int)tim
/ 60, (int)tim
% 60);
662 } else if (tim
> 0) {
663 (void) snprintf(value
, sizeof (value
), "%d", (int)tim
);
665 (void) strcpy(value
, "0");
667 width
= (width
> 2) ? width
- 1 : 1;
668 PRINTF(("%*s ", width
, value
));
672 * Prints the ISO date or time given a pointer to a time of day,
673 * left-justfied in a 12-character expanding field with the
674 * rightmost column always blank.
675 * Includes a dcgettext() override in case a message catalog is needed.
683 if (now
- *time
<= 18 * HR
) {
686 (void) strftime(timestr
, sizeof (timestr
),
687 dcgettext(NULL
, "%T", LC_TIME
), p
);
688 PRINTF(("%-11s ", timestr
));
689 } else if (now
- *time
<= 7 * DAY
) {
690 char weekdaytime
[20];
692 (void) strftime(weekdaytime
, sizeof (weekdaytime
),
693 dcgettext(NULL
, "%a %H:%M", LC_TIME
), p
);
694 PRINTF(("%-11s ", weekdaytime
));
698 (void) strftime(monthtime
, sizeof (monthtime
),
699 dcgettext(NULL
, "%F", LC_TIME
), p
);
700 PRINTF(("%-11s ", monthtime
));
705 * find & return number of minutes current tty has been idle
708 findidle(char *devname
)
711 time_t lastaction
, diff
;
714 (void) strcpy(ttyname
, "/dev/");
715 (void) strcat(ttyname
, devname
);
716 if (stat(ttyname
, &stbuf
) != -1) {
717 lastaction
= stbuf
.st_atime
;
718 diff
= now
- lastaction
;
728 * given a pointer to the argument string get rid of unsavory characters.
731 clnarglist(char *arglist
)
736 /* get rid of unsavory characters */
737 for (c
= arglist
; *c
!= NULL
; c
++) {
738 if ((*c
< ' ') || (*c
> 0176)) {