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 }