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
, NULL
);
184 (void) setlocale(LC_ALL
, "");
185 #if !defined(TEXT_DOMAIN)
186 #define TEXT_DOMAIN "SYS_TEST"
188 (void) textdomain(TEXT_DOMAIN
);
190 login
= (argv
[0][0] == '-');
191 cp
= strrchr(argv
[0], '/');
192 firstchar
= login
? argv
[0][1] : (cp
== 0) ? argv
[0][0] : cp
[1];
196 if (argv
[1][0] == '-') {
197 for (i
= 1; argv
[1][i
]; i
++) {
198 switch (argv
[1][i
]) {
213 firstchar
= argv
[1][i
];
217 (void) fprintf(stderr
, gettext(
218 "%s: bad flag %s\n"),
224 if (!isalnum(argv
[1][0]) || argc
> 2) {
225 (void) fprintf(stderr
, gettext(
226 "usage: %s [ -hlsuw ] [ user ]\n"), prog
);
235 * read the UTMPX_FILE (contains information about each logged in user)
237 if (stat(UTMPX_FILE
, &sbuf
) == ERR
) {
238 (void) fprintf(stderr
, gettext("%s: stat error of %s: %s\n"),
239 prog
, UTMPX_FILE
, strerror(errno
));
242 entries
= sbuf
.st_size
/ sizeof (struct futmpx
);
243 size
= sizeof (struct utmpx
) * entries
;
244 if ((ut
= malloc(size
)) == NULL
) {
245 (void) fprintf(stderr
, gettext("%s: malloc error of %s: %s\n"),
246 prog
, UTMPX_FILE
, strerror(errno
));
250 (void) utmpxname(UTMPX_FILE
);
253 utmpend
= (struct utmpx
*)((char *)utmpbegin
+ size
);
256 while ((ut
< utmpend
) && ((utp
= getutxent()) != NULL
))
257 (void) memcpy(ut
++, utp
, sizeof (*ut
));
260 (void) time(&now
); /* get current time */
262 if (header
) { /* print a header */
264 for (ut
= utmpbegin
; ut
< utmpend
; ut
++) {
265 if (ut
->ut_type
== USER_PROCESS
) {
268 } else if (ut
->ut_type
== BOOT_TIME
) {
269 uptime
= now
- ut
->ut_xtime
;
271 days
= uptime
/ (60*60*24);
272 uptime
%= (60*60*24);
273 hrs
= uptime
/ (60*60);
277 PRINTF((gettext("up")));
280 " %d day(s),"), days
));
281 if (hrs
> 0 && mins
> 0) {
282 PRINTF((" %2d:%02d,", hrs
, mins
));
286 " %d hr(s),"), hrs
));
288 " %d min(s),"), mins
));
293 ut
= utmpbegin
; /* rewind utmp data */
294 PRINTF((((nusers
== 1) ?
295 gettext(" %d user") : gettext(" %d users")), nusers
));
297 * Print 1, 5, and 15 minute load averages.
299 (void) getloadavg(loadavg
, 3);
300 PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"),
301 loadavg
[LOADAVG_1MIN
], loadavg
[LOADAVG_5MIN
],
302 loadavg
[LOADAVG_15MIN
]));
304 if (firstchar
== 'u') /* uptime command */
308 PRINTF((dcgettext(NULL
, "User tty "
309 "login@ idle JCPU PCPU what\n",
312 PRINTF((dcgettext(NULL
,
313 "User tty idle what\n",
317 if (fflush(stdout
) == EOF
) {
318 perror((gettext("%s: fflush failed\n"), prog
));
324 * loop through /proc, reading info about each process
325 * and build the parent/child tree
327 if (!(dirp
= opendir(PROCDIR
))) {
328 (void) fprintf(stderr
, gettext("%s: could not open %s: %s\n"),
329 prog
, PROCDIR
, strerror(errno
));
333 while ((dp
= readdir(dirp
)) != NULL
) {
334 if (dp
->d_name
[0] == '.')
337 (void) sprintf(pname
, "%s/%s/", PROCDIR
, dp
->d_name
);
338 fname
= pname
+ strlen(pname
);
339 (void) strcpy(fname
, "psinfo");
340 if ((procfd
= open(pname
, O_RDONLY
)) < 0)
342 if (read(procfd
, &info
, sizeof (info
)) != sizeof (info
)) {
344 (void) close(procfd
);
348 (void) fprintf(stderr
, gettext(
349 "%s: read() failed on %s: %s \n"),
350 prog
, pname
, strerror(err
));
353 (void) close(procfd
);
355 up
= findhash(info
.pr_pid
);
356 up
->p_ttyd
= info
.pr_ttydev
;
357 up
->p_state
= (info
.pr_nlwp
== 0? ZOMBIE
: RUNNING
);
361 (void) strncpy(up
->p_comm
, info
.pr_fname
,
362 sizeof (info
.pr_fname
));
365 if (up
->p_state
!= NONE
&& up
->p_state
!= ZOMBIE
) {
366 (void) strcpy(fname
, "status");
368 /* now we need the proc_owner privilege */
369 (void) __priv_bracket(PRIV_ON
);
371 procfd
= open(pname
, O_RDONLY
);
373 /* drop proc_owner privilege after open */
374 (void) __priv_bracket(PRIV_OFF
);
379 if (read(procfd
, &statinfo
, sizeof (statinfo
))
380 != sizeof (statinfo
)) {
382 (void) close(procfd
);
386 (void) fprintf(stderr
, gettext(
387 "%s: read() failed on %s: %s \n"),
388 prog
, pname
, strerror(err
));
391 (void) close(procfd
);
393 up
->p_time
= statinfo
.pr_utime
.tv_sec
+
394 statinfo
.pr_stime
.tv_sec
; /* seconds */
395 up
->p_ctime
= statinfo
.pr_cutime
.tv_sec
+
396 statinfo
.pr_cstime
.tv_sec
;
398 (void) strcpy(fname
, "sigact");
400 /* now we need the proc_owner privilege */
401 (void) __priv_bracket(PRIV_ON
);
403 procfd
= open(pname
, O_RDONLY
);
405 /* drop proc_owner privilege after open */
406 (void) __priv_bracket(PRIV_OFF
);
411 if (read(procfd
, actinfo
, sizeof (actinfo
))
412 != sizeof (actinfo
)) {
414 (void) close(procfd
);
418 (void) fprintf(stderr
, gettext(
419 "%s: read() failed on %s: %s \n"),
420 prog
, pname
, strerror(err
));
423 (void) close(procfd
);
426 actinfo
[SIGINT
-1].sa_handler
== SIG_IGN
&&
427 actinfo
[SIGQUIT
-1].sa_handler
== SIG_IGN
;
433 clnarglist(info
.pr_psargs
);
434 (void) strcat(up
->p_args
, info
.pr_psargs
);
435 if (up
->p_args
[0] == 0 ||
436 up
->p_args
[0] == '-' && up
->p_args
[1] <= ' ' ||
437 up
->p_args
[0] == '?') {
438 (void) strcat(up
->p_args
, " (");
439 (void) strcat(up
->p_args
, up
->p_comm
);
440 (void) strcat(up
->p_args
, ")");
445 * link pgrp together in case parents go away
446 * Pgrp chain is a single linked list originating
447 * from the pgrp leader to its group member.
449 if (info
.pr_pgid
!= info
.pr_pid
) { /* not pgrp leader */
450 pgrp
= findhash(info
.pr_pgid
);
451 up
->p_pgrpl
= pgrp
->p_pgrpl
;
454 parent
= findhash(info
.pr_ppid
);
456 /* if this is the new member, link it in */
457 if (parent
->p_upid
!= INITPROCESS
) {
458 if (parent
->p_child
) {
459 up
->p_sibling
= parent
->p_child
;
462 parent
->p_child
= up
;
466 /* revert to non-privileged user after opening */
467 (void) __priv_relinquish();
469 (void) closedir(dirp
);
470 (void) time(&now
); /* get current time */
473 * loop through utmpx file, printing process info
474 * about each logged in user
476 for (ut
= utmpbegin
; ut
< utmpend
; ut
++) {
477 if (ut
->ut_type
!= USER_PROCESS
)
479 if (sel_user
&& strncmp(ut
->ut_name
, sel_user
, NMAX
) != 0)
480 continue; /* we're looking for somebody else */
482 /* print login name of the user */
483 PRINTF(("%-*.*s ", LOGIN_WIDTH
, NMAX
, ut
->ut_name
));
485 /* print tty user is on */
487 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
, ut
->ut_line
));
489 if (ut
->ut_line
[0] == 'p' && ut
->ut_line
[1] == 't' &&
490 ut
->ut_line
[2] == 's' && ut
->ut_line
[3] == '/') {
491 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
,
494 PRINTF(("%-*.*s ", LINE_WIDTH
, LMAX
,
499 /* print when the user logged in */
501 time_t tim
= ut
->ut_xtime
;
505 /* print idle time */
506 idle
= findidle(ut
->ut_line
);
508 showtotals(findhash(ut
->ut_pid
));
510 if (fclose(stdout
) == EOF
) {
511 perror((gettext("%s: fclose failed"), prog
));
518 * Prints the CPU time for all processes & children,
519 * and the cpu time for interesting process,
520 * and what the user is doing.
523 showtotals(struct uproc
*up
)
534 /* print CPU time for all processes & children */
535 /* and need to convert clock ticks to seconds first */
536 prttime((time_t)jobtime
, 8);
538 /* print cpu time for interesting process */
539 /* and need to convert clock ticks to seconds first */
540 prttime((time_t)proctime
, 8);
542 /* what user is doing, current process */
543 PRINTF(("%-.32s\n", doing
));
547 * This recursive routine descends the process
548 * tree starting from the given process pointer(up).
549 * It used depth-first search strategy and also marked
550 * each node as visited as it traversed down the tree.
551 * It calculates the process time for all processes &
552 * children. It also finds the interesting process
553 * and determines its cpu time and command.
556 calctotals(struct uproc
*up
)
561 * Once a node has been visited, stop adding cpu times
562 * for its children so they don't get totalled twice.
563 * Still look for the interesting job for this utmp
566 if (up
->p_state
== VISITED
)
568 up
->p_state
= VISITED
;
569 if (up
->p_state
== NONE
|| up
->p_state
== ZOMBIE
)
572 if (empty
&& !up
->p_igintr
) {
577 if (up
->p_upid
> curpid
&& (!up
->p_igintr
|| empty
)) {
580 (void) strcpy(doing
, up
->p_args
);
582 (void) strcpy(doing
, up
->p_comm
);
585 if (add_times
== 1) {
586 jobtime
+= up
->p_time
+ up
->p_ctime
;
587 proctime
+= up
->p_time
;
590 /* descend for its children */
592 calctotals(up
->p_child
);
593 for (zp
= up
->p_child
->p_sibling
; zp
; zp
= zp
->p_sibling
)
599 * Findhash finds the appropriate entry in the process
600 * hash table (pr_htbl) for the given pid in case that
601 * pid exists on the hash chain. It returns back a pointer
602 * to that uproc structure. If this is a new pid, it allocates
603 * a new node, initializes it, links it into the chain (after
604 * head) and returns a structure pointer.
606 static struct uproc
*
609 struct uproc
*up
, *tp
;
611 tp
= up
= &pr_htbl
[pid
% HSIZE
];
612 if (up
->p_upid
== 0) { /* empty slot */
615 up
->p_child
= up
->p_sibling
= up
->p_pgrpl
= up
->p_link
= 0;
618 if (up
->p_upid
== pid
) { /* found in hash table */
621 for (tp
= up
->p_link
; tp
; tp
= tp
->p_link
) { /* follow chain */
622 if (tp
->p_upid
== pid
)
625 tp
= malloc(sizeof (*tp
)); /* add new node */
627 (void) fprintf(stderr
, gettext("%s: out of memory!: %s\n"),
628 prog
, strerror(errno
));
631 (void) memset(tp
, 0, sizeof (*tp
));
634 tp
->p_child
= tp
->p_sibling
= tp
->p_pgrpl
= 0;
635 tp
->p_link
= up
->p_link
; /* insert after head */
641 #define DAY (24 * HR)
642 #define MON (30 * DAY)
645 * Prttime prints an elapsed time in hours, minutes, or seconds,
646 * right-justified with the rightmost column always blank.
647 * The second argument is the minimum field width.
650 prttime(time_t tim
, int width
)
654 if (tim
>= 36 * 60) {
655 (void) snprintf(value
, sizeof (value
), "%d:%02d:%02d",
656 (int)tim
/ HR
, (int)(tim
% HR
) / 60, (int)tim
% 60);
657 } else if (tim
>= 60) {
658 (void) snprintf(value
, sizeof (value
), "%d:%02d",
659 (int)tim
/ 60, (int)tim
% 60);
660 } else if (tim
> 0) {
661 (void) snprintf(value
, sizeof (value
), "%d", (int)tim
);
663 (void) strcpy(value
, "0");
665 width
= (width
> 2) ? width
- 1 : 1;
666 PRINTF(("%*s ", width
, value
));
670 * Prints the ISO date or time given a pointer to a time of day,
671 * left-justfied in a 12-character expanding field with the
672 * rightmost column always blank.
673 * Includes a dcgettext() override in case a message catalog is needed.
681 if (now
- *time
<= 18 * HR
) {
684 (void) strftime(timestr
, sizeof (timestr
),
685 dcgettext(NULL
, "%T", LC_TIME
), p
);
686 PRINTF(("%-11s ", timestr
));
687 } else if (now
- *time
<= 7 * DAY
) {
688 char weekdaytime
[20];
690 (void) strftime(weekdaytime
, sizeof (weekdaytime
),
691 dcgettext(NULL
, "%a %H:%M", LC_TIME
), p
);
692 PRINTF(("%-11s ", weekdaytime
));
696 (void) strftime(monthtime
, sizeof (monthtime
),
697 dcgettext(NULL
, "%F", LC_TIME
), p
);
698 PRINTF(("%-11s ", monthtime
));
703 * find & return number of minutes current tty has been idle
706 findidle(char *devname
)
709 time_t lastaction
, diff
;
712 (void) strcpy(ttyname
, "/dev/");
713 (void) strcat(ttyname
, devname
);
714 if (stat(ttyname
, &stbuf
) != -1) {
715 lastaction
= stbuf
.st_atime
;
716 diff
= now
- lastaction
;
726 * given a pointer to the argument string get rid of unsavory characters.
729 clnarglist(char *arglist
)
734 /* get rid of unsavory characters */
735 for (c
= arglist
; *c
!= '\0'; c
++) {
736 if ((*c
< ' ') || (*c
> 0176)) {