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