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
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 */
148
149 #if SIGQUIT > SIGINT
150 #define ACTSIZE SIGQUIT
151 #else
152 #define ACTSIZE SIGINT
153 #endif
154
155 int
156 main(int argc, char *argv[])
157 {
158 struct utmpx *ut;
159 struct utmpx *utmpbegin;
160 struct utmpx *utmpend;
161 struct utmpx *utp;
162 struct uproc *up, *parent, *pgrp;
163 struct psinfo info;
164 struct sigaction actinfo[ACTSIZE];
165 struct pstatus statinfo;
166 size_t size;
167 struct stat sbuf;
168 DIR *dirp;
169 struct dirent *dp;
170 char pname[64];
171 char *fname;
172 int procfd;
173 char *cp;
174 int i;
175 int days, hrs, mins;
176 int entries;
177 double loadavg[3];
178
179 /*
180 * This program needs the proc_owner privilege
181 */
182 (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER,
183 (char *)NULL);
184
185 (void) setlocale(LC_ALL, "");
186 #if !defined(TEXT_DOMAIN)
187 #define TEXT_DOMAIN "SYS_TEST"
188 #endif
189 (void) textdomain(TEXT_DOMAIN);
190
191 login = (argv[0][0] == '-');
192 cp = strrchr(argv[0], '/');
193 firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1];
194 prog = argv[0];
195
196 while (argc > 1) {
197 if (argv[1][0] == '-') {
198 for (i = 1; argv[1][i]; i++) {
199 switch (argv[1][i]) {
200
201 case 'h':
202 header = 0;
203 break;
204
205 case 'l':
206 lflag++;
207 break;
208 case 's':
209 lflag = 0;
210 break;
211
212 case 'u':
213 case 'w':
214 firstchar = argv[1][i];
215 break;
216
217 default:
218 (void) fprintf(stderr, gettext(
219 "%s: bad flag %s\n"),
220 prog, argv[1]);
221 exit(1);
222 }
223 }
224 } else {
225 if (!isalnum(argv[1][0]) || argc > 2) {
226 (void) fprintf(stderr, gettext(
227 "usage: %s [ -hlsuw ] [ user ]\n"), prog);
228 exit(1);
229 } else
230 sel_user = argv[1];
231 }
232 argc--; argv++;
233 }
234
235 /*
236 * read the UTMP_FILE (contains information about each logged in user)
237 */
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));
241 exit(1);
242 }
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));
248 exit(1);
249 }
250
251 (void) utmpxname(UTMPX_FILE);
252
253 utmpbegin = ut;
254 utmpend = (struct utmpx *)((char *)utmpbegin + size);
255
256 setutxent();
257 while ((ut < utmpend) && ((utp = getutxent()) != NULL))
258 (void) memcpy(ut++, utp, sizeof (*ut));
259 endutxent();
260
261 (void) time(&now); /* get current time */
262
263 if (header) { /* print a header */
264 prtat(&now);
265 for (ut = utmpbegin; ut < utmpend; ut++) {
266 if (ut->ut_type == USER_PROCESS) {
267 if (!nonuser(*ut))
268 nusers++;
269 } else if (ut->ut_type == BOOT_TIME) {
270 uptime = now - ut->ut_xtime;
271 uptime += 30;
272 days = uptime / (60*60*24);
273 uptime %= (60*60*24);
274 hrs = uptime / (60*60);
275 uptime %= (60*60);
276 mins = uptime / 60;
277
278 PRINTF((gettext(" up")));
279 if (days > 0)
280 PRINTF((gettext(
281 " %d day(s),"), days));
282 if (hrs > 0 && mins > 0) {
283 PRINTF((" %2d:%02d,", hrs, mins));
284 } else {
285 if (hrs > 0)
286 PRINTF((gettext(
287 " %d hr(s),"), hrs));
288 if (mins > 0)
289 PRINTF((gettext(
290 " %d min(s),"), mins));
291 }
292 }
293 }
294
295 ut = utmpbegin; /* rewind utmp data */
296 PRINTF((((nusers == 1) ?
297 gettext(" %d user") : gettext(" %d users")), nusers));
298 /*
299 * Print 1, 5, and 15 minute load averages.
300 */
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]));
305
306 if (firstchar == 'u') /* uptime command */
307 exit(0);
308
309 if (lflag) {
310 PRINTF((dcgettext(NULL, "User tty "
311 "login@ idle JCPU PCPU what\n", LC_TIME)));
312 } else {
313 PRINTF((dcgettext(NULL,
314 "User tty idle what\n", LC_TIME)));
315 }
316
317 if (fflush(stdout) == EOF) {
318 perror((gettext("%s: fflush failed\n"), prog));
319 exit(1);
320 }
321 }
322
323 /*
324 * loop through /proc, reading info about each process
325 * and build the parent/child tree
326 */
327 if (!(dirp = opendir(PROCDIR))) {
328 (void) fprintf(stderr, gettext("%s: could not open %s: %s\n"),
329 prog, PROCDIR, strerror(errno));
330 exit(1);
331 }
332
333 while ((dp = readdir(dirp)) != NULL) {
334 if (dp->d_name[0] == '.')
335 continue;
336 retry:
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)
341 continue;
342 if (read(procfd, &info, sizeof (info)) != sizeof (info)) {
343 int err = errno;
344 (void) close(procfd);
345 if (err == EAGAIN)
346 goto retry;
347 if (err != ENOENT)
348 (void) fprintf(stderr, gettext(
349 "%s: read() failed on %s: %s \n"),
350 prog, pname, strerror(err));
351 continue;
352 }
353 (void) close(procfd);
354
355 up = findhash(info.pr_pid);
356 up->p_ttyd = info.pr_ttydev;
357 up->p_state = (info.pr_nlwp == 0? ZOMBIE : RUNNING);
358 up->p_time = 0;
359 up->p_ctime = 0;
360 up->p_igintr = 0;
361 (void) strncpy(up->p_comm, info.pr_fname,
362 sizeof (info.pr_fname));
363 up->p_args[0] = 0;
364
365 if (up->p_state != NONE && up->p_state != ZOMBIE) {
366 (void) strcpy(fname, "status");
367
368 /* now we need the proc_owner privilege */
369 (void) __priv_bracket(PRIV_ON);
370
371 procfd = open(pname, O_RDONLY);
372
373 /* drop proc_owner privilege after open */
374 (void) __priv_bracket(PRIV_OFF);
375
376 if (procfd < 0)
377 continue;
378
379 if (read(procfd, &statinfo, sizeof (statinfo))
380 != sizeof (statinfo)) {
381 int err = errno;
382 (void) close(procfd);
383 if (err == EAGAIN)
384 goto retry;
385 if (err != ENOENT)
386 (void) fprintf(stderr, gettext(
387 "%s: read() failed on %s: %s \n"),
388 prog, pname, strerror(err));
389 continue;
390 }
391 (void) close(procfd);
392
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;
397
398 (void) strcpy(fname, "sigact");
399
400 /* now we need the proc_owner privilege */
401 (void) __priv_bracket(PRIV_ON);
402
403 procfd = open(pname, O_RDONLY);
404
405 /* drop proc_owner privilege after open */
406 (void) __priv_bracket(PRIV_OFF);
407
408 if (procfd < 0)
409 continue;
410
411 if (read(procfd, actinfo, sizeof (actinfo))
412 != sizeof (actinfo)) {
413 int err = errno;
414 (void) close(procfd);
415 if (err == EAGAIN)
416 goto retry;
417 if (err != ENOENT)
418 (void) fprintf(stderr, gettext(
419 "%s: read() failed on %s: %s \n"),
420 prog, pname, strerror(err));
421 continue;
422 }
423 (void) close(procfd);
424
425 up->p_igintr =
426 actinfo[SIGINT-1].sa_handler == SIG_IGN &&
427 actinfo[SIGQUIT-1].sa_handler == SIG_IGN;
428
429 /*
430 * Process args.
431 */
432 up->p_args[0] = 0;
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, ")");
441 }
442 }
443
444 /*
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.
448 */
449 if (info.pr_pgid != info.pr_pid) { /* not pgrp leader */
450 pgrp = findhash(info.pr_pgid);
451 up->p_pgrpl = pgrp->p_pgrpl;
452 pgrp->p_pgrpl = up;
453 }
454 parent = findhash(info.pr_ppid);
455
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;
460 up->p_child = 0;
461 }
462 parent->p_child = up;
463 }
464 }
465
466 /* revert to non-privileged user after opening */
467 (void) __priv_relinquish();
468
469 (void) closedir(dirp);
470 (void) time(&now); /* get current time */
471
472 /*
473 * loop through utmpx file, printing process info
474 * about each logged in user
475 */
476 for (ut = utmpbegin; ut < utmpend; ut++) {
477 if (ut->ut_type != USER_PROCESS)
478 continue;
479 if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0)
480 continue; /* we're looking for somebody else */
481
482 /* print login name of the user */
483 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name));
484
485 /* print tty user is on */
486 if (lflag) {
487 PRINTF(("%-*.*s", LINE_WIDTH, LMAX, ut->ut_line));
488 } else {
489 if (ut->ut_line[0] == 'p' && ut->ut_line[1] == 't' &&
490 ut->ut_line[2] == 's' && ut->ut_line[3] == '/') {
491 PRINTF(("%-*.3s", LMAX, &ut->ut_line[4]));
492 } else {
493 PRINTF(("%-*.*s", LINE_WIDTH, LMAX,
494 ut->ut_line));
495 }
496 }
497
498 /* print when the user logged in */
499 if (lflag) {
500 time_t tim = ut->ut_xtime;
501 prtat(&tim);
502 }
503
504 /* print idle time */
505 idle = findidle(ut->ut_line);
506 if (idle >= 36 * 60) {
507 PRINTF((dcgettext(NULL, "%2ddays ", LC_TIME),
508 (idle + 12 * 60) / (24 * 60)));
509 } else
510 prttime(idle, " ");
511 showtotals(findhash(ut->ut_pid));
512 }
513 if (fclose(stdout) == EOF) {
514 perror((gettext("%s: fclose failed"), prog));
515 exit(1);
516 }
517 return (0);
518 }
519
520 /*
521 * Prints the CPU time for all processes & children,
522 * and the cpu time for interesting process,
523 * and what the user is doing.
524 */
525 static void
526 showtotals(struct uproc *up)
527 {
528 jobtime = 0;
529 proctime = 0;
530 empty = 1;
531 curpid = -1;
532 add_times = 1;
533
534 calctotals(up);
535
536 if (lflag) {
537 /* print CPU time for all processes & children */
538 /* and need to convert clock ticks to seconds first */
539 prttime((time_t)jobtime, " ");
540
541 /* print cpu time for interesting process */
542 /* and need to convert clock ticks to seconds first */
543 prttime((time_t)proctime, " ");
544 }
545 /* what user is doing, current process */
546 PRINTF((" %-.32s\n", doing));
547 }
548
549 /*
550 * This recursive routine descends the process
551 * tree starting from the given process pointer(up).
552 * It used depth-first search strategy and also marked
553 * each node as visited as it traversed down the tree.
554 * It calculates the process time for all processes &
555 * children. It also finds the interesting process
556 * and determines its cpu time and command.
557 */
558 static void
559 calctotals(struct uproc *up)
560 {
561 struct uproc *zp;
562
563 /*
564 * Once a node has been visited, stop adding cpu times
565 * for its children so they don't get totalled twice.
566 * Still look for the interesting job for this utmp
567 * entry, however.
568 */
569 if (up->p_state == VISITED)
570 add_times = 0;
571 up->p_state = VISITED;
572 if (up->p_state == NONE || up->p_state == ZOMBIE)
573 return;
574
575 if (empty && !up->p_igintr) {
576 empty = 0;
577 curpid = -1;
578 }
579
580 if (up->p_upid > curpid && (!up->p_igintr || empty)) {
581 curpid = up->p_upid;
582 if (lflag)
583 (void) strcpy(doing, up->p_args);
584 else
585 (void) strcpy(doing, up->p_comm);
586 }
587
588 if (add_times == 1) {
589 jobtime += up->p_time + up->p_ctime;
590 proctime += up->p_time;
591 }
592
593 /* descend for its children */
594 if (up->p_child) {
595 calctotals(up->p_child);
596 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling)
597 calctotals(zp);
598 }
599 }
600
601 /*
602 * Findhash finds the appropriate entry in the process
603 * hash table (pr_htbl) for the given pid in case that
604 * pid exists on the hash chain. It returns back a pointer
605 * to that uproc structure. If this is a new pid, it allocates
606 * a new node, initializes it, links it into the chain (after
607 * head) and returns a structure pointer.
608 */
609 static struct uproc *
610 findhash(pid_t pid)
611 {
612 struct uproc *up, *tp;
613
614 tp = up = &pr_htbl[pid % HSIZE];
615 if (up->p_upid == 0) { /* empty slot */
616 up->p_upid = pid;
617 up->p_state = NONE;
618 up->p_child = up->p_sibling = up->p_pgrpl = up->p_link = 0;
619 return (up);
620 }
621 if (up->p_upid == pid) { /* found in hash table */
622 return (up);
623 }
624 for (tp = up->p_link; tp; tp = tp->p_link) { /* follow chain */
625 if (tp->p_upid == pid)
626 return (tp);
627 }
628 tp = malloc(sizeof (*tp)); /* add new node */
629 if (!tp) {
630 (void) fprintf(stderr, gettext("%s: out of memory!: %s\n"),
631 prog, strerror(errno));
632 exit(1);
633 }
634 (void) memset(tp, 0, sizeof (*tp));
635 tp->p_upid = pid;
636 tp->p_state = NONE;
637 tp->p_child = tp->p_sibling = tp->p_pgrpl = 0;
638 tp->p_link = up->p_link; /* insert after head */
639 up->p_link = tp;
640 return (tp);
641 }
642
643 #define HR (60 * 60)
644 #define DAY (24 * HR)
645 #define MON (30 * DAY)
646
647 /*
648 * prttime prints a time in hours and minutes or minutes and seconds.
649 * The character string tail is printed at the end, obvious
650 * strings to pass are "", " ", or "am".
651 */
652 static void
653 prttime(time_t tim, char *tail)
654 {
655 if (tim >= 60) {
656 PRINTF((dcgettext(NULL, "%3d:%02d", LC_TIME),
657 (int)tim/60, (int)tim%60));
658 } else if (tim > 0) {
659 PRINTF((dcgettext(NULL, " %2d", LC_TIME), (int)tim));
660 } else {
661 PRINTF((" "));
662 }
663 PRINTF(("%s", tail));
664 }
665
666 /*
667 * prints a 12 hour time given a pointer to a time of day
668 */
669 static void
670 prtat(time_t *time)
671 {
672 struct tm *p;
673
674 p = localtime(time);
675 if (now - *time <= 18 * HR) {
676 char timestr[50];
677 (void) strftime(timestr, sizeof (timestr),
678 " %R ", p);
679 PRINTF((" %s", timestr));
680 } else if (now - *time <= 7 * DAY) {
681 char weekdaytime[20];
682
683 (void) strftime(weekdaytime, sizeof (weekdaytime),
684 "%a%H ", p);
685 PRINTF((" %s", weekdaytime));
686 } else {
687 char monthtime[20];
688
689 (void) strftime(monthtime, sizeof (monthtime),
690 "%e%b%y", p);
691 PRINTF((" %s", monthtime));
692 }
693 }
694
695 /*
696 * find & return number of minutes current tty has been idle
697 */
698 static time_t
699 findidle(char *devname)
700 {
701 struct stat stbuf;
702 time_t lastaction, diff;
703 char ttyname[64];
704
705 (void) strcpy(ttyname, "/dev/");
706 (void) strcat(ttyname, devname);
707 if (stat(ttyname, &stbuf) != -1) {
708 lastaction = stbuf.st_atime;
709 diff = now - lastaction;
710 diff = DIV60(diff);
711 if (diff < 0)
712 diff = 0;
713 } else
714 diff = 0;
715 return (diff);
716 }
717
718 /*
719 * given a pointer to the argument string get rid of unsavory characters.
720 */
721 static void
722 clnarglist(char *arglist)
723 {
724 char *c;
725 int err = 0;
726
727 /* get rid of unsavory characters */
728 for (c = arglist; *c != NULL; c++) {
729 if ((*c < ' ') || (*c > 0176)) {
730 if (err++ > 5) {
731 *arglist = NULL;
732 break;
733 }
734 *c = '?';
735 }
736 }
737 }