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 * Copyright 2020 Joyent, Inc. 28 */ 29 30 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 31 /* All Rights Reserved */ 32 33 /* 34 * University Copyright- Copyright (c) 1982, 1986, 1988 35 * The Regents of the University of California 36 * All Rights Reserved 37 * 38 * University Acknowledgment- Portions of this document are derived from 39 * software developed by the University of California, Berkeley, and its 40 * contributors. 41 */ 42 43 /* 44 * This is the new w command which takes advantage of 45 * the /proc interface to gain access to the information 46 * of all the processes currently on the system. 47 * 48 * This program also implements 'uptime'. 49 * 50 * Maintenance note: 51 * 52 * Much of this code is replicated in whodo.c. If you're 53 * fixing bugs here, then you should probably fix 'em there too. 54 */ 55 56 #include <stdio.h> 57 #include <string.h> 58 #include <stdarg.h> 59 #include <stdlib.h> 60 #include <ctype.h> 61 #include <fcntl.h> 62 #include <time.h> 63 #include <err.h> 64 #include <errno.h> 65 #include <sys/types.h> 66 #include <utmpx.h> 67 #include <sys/stat.h> 68 #include <dirent.h> 69 #include <procfs.h> /* /proc header file */ 70 #include <locale.h> 71 #include <unistd.h> 72 #include <sys/loadavg.h> 73 #include <limits.h> 74 #include <priv_utils.h> 75 #include <sys/sysmacros.h> 76 77 /* 78 * Use the full lengths from utmpx for user and line. 79 */ 80 static struct utmpx dummy; 81 #define NMAX (sizeof (dummy.ut_user)) 82 #define LMAX (sizeof (dummy.ut_line)) 83 84 /* Print minimum field widths. */ 85 #define LOGIN_WIDTH 8 86 #define LINE_WIDTH 8 87 88 #define DIV60(t) ((t+30)/60) /* x/60 rounded */ 89 90 #ifdef ERR 91 #undef ERR 92 #endif 93 #define ERR (-1) 94 95 #define HSIZE 256 /* size of process hash table */ 96 #define PROCDIR "/proc" 97 #define INITPROCESS (pid_t)1 /* init process pid */ 98 #define NONE 'n' /* no state */ 99 #define RUNNING 'r' /* runnable process */ 100 #define ZOMBIE 'z' /* zombie process */ 101 #define VISITED 'v' /* marked node as visited */ 102 #define PRINTF(a) if (printf a < 0) { \ 103 perror((gettext("%s: printf failed"), prog)); \ 104 exit(1); } 105 106 struct uproc { 107 pid_t p_upid; /* process id */ 108 char p_state; /* numeric value of process state */ 109 dev_t p_ttyd; /* controlling tty of process */ 110 time_t p_time; /* seconds of user & system time */ 111 time_t p_ctime; /* seconds of child user & sys time */ 112 int p_igintr; /* 1 = ignores SIGQUIT and SIGINT */ 113 char p_comm[PRARGSZ+1]; /* command */ 114 char p_args[PRARGSZ+1]; /* command line arguments */ 115 struct uproc *p_child, /* first child pointer */ 116 *p_sibling, /* sibling pointer */ 117 *p_pgrpl, /* pgrp link */ 118 *p_link; /* hash table chain pointer */ 119 }; 120 121 /* 122 * define hash table for struct uproc 123 * Hash function uses process id 124 * and the size of the hash table(HSIZE) 125 * to determine process index into the table. 126 */ 127 static struct uproc pr_htbl[HSIZE]; 128 129 static struct uproc *findhash(pid_t); 130 static time_t findidle(char *); 131 static void clnarglist(char *); 132 static void showtotals(struct uproc *); 133 static void calctotals(struct uproc *); 134 static void prttime(time_t, int); 135 static void prtat(time_t *time); 136 137 static int priv_proc_open(const char *, int); 138 static int priv_proc_openat(int, const char *, int); 139 static boolean_t do_proc_read(int, void *, size_t); 140 141 static char *prog; /* pointer to invocation name */ 142 static int header = 1; /* true if -h flag: don't print heading */ 143 static int lflag = 1; /* set if -l flag; 0 for -s flag: short form */ 144 static char *sel_user; /* login of particular user selected */ 145 static char firstchar; /* first char of name of prog invoked as */ 146 static int login; /* true if invoked as login shell */ 147 static time_t now; /* current time of day */ 148 static time_t uptime; /* time of last reboot & elapsed time since */ 149 static int nusers; /* number of users logged in now */ 150 static time_t idle; /* number of minutes user is idle */ 151 static time_t jobtime; /* total cpu time visible */ 152 static char doing[520]; /* process attached to terminal */ 153 static time_t proctime; /* cpu time of process in doing */ 154 static pid_t curpid, empty; 155 static int add_times; /* boolean: add the cpu times or not */ 156 157 /* 158 * Basic privs we never need and can drop. This is likely not exhaustive, 159 * but should significantly reduce any potential attack surfaces. 160 */ 161 static const char *drop_privs[] = { 162 PRIV_FILE_WRITE, 163 PRIV_NET_ACCESS, 164 PRIV_PROC_EXEC, 165 PRIV_PROC_FORK, 166 PRIV_FILE_LINK_ANY 167 }; 168 169 #if SIGQUIT > SIGINT 170 #define ACTSIZE SIGQUIT 171 #else 172 #define ACTSIZE SIGINT 173 #endif 174 175 int 176 main(int argc, char *argv[]) 177 { 178 struct utmpx *ut; 179 struct utmpx *utmpbegin; 180 struct utmpx *utmpend; 181 struct utmpx *utp; 182 struct uproc *up, *parent, *pgrp; 183 struct psinfo info; 184 struct sigaction actinfo[ACTSIZE]; 185 struct pstatus statinfo; 186 struct stat sbuf; 187 DIR *dirp; 188 struct dirent *dp; 189 char pname[PATH_MAX]; 190 int procfd; 191 int dirfd; 192 char *cp; 193 int i; 194 int days, hrs, mins; 195 int entries; 196 double loadavg[3]; 197 priv_set_t *pset; 198 199 if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER, NULL) != 0) { 200 err(EXIT_FAILURE, "failed to enable privilege bracketing"); 201 } 202 203 /* 204 * After setting up privilege bracketing, we can further reduce the 205 * privileges in use. The effective set is set to the basic set minus 206 * the privs in drop_privs. The permitted set is the effective set 207 * plus PRIV_PROC_OWNER (i.e. the privilege being bracketed). 208 */ 209 pset = priv_allocset(); 210 if (pset == NULL) 211 err(EXIT_FAILURE, "priv_allocset failed"); 212 213 priv_basicset(pset); 214 for (i = 0; i < ARRAY_SIZE(drop_privs); i++) { 215 if (priv_delset(pset, drop_privs[i]) != 0) { 216 err(EXIT_FAILURE, 217 "failed to remove %s privilege from privilege set", 218 drop_privs[i]); 219 } 220 } 221 222 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) 223 err(EXIT_FAILURE, "failed setting effective privilege set"); 224 225 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 226 err(EXIT_FAILURE, 227 "failed to add PRIV_PROC_OWNER privilege to privilege set"); 228 } 229 230 if (setppriv(PRIV_SET, PRIV_PERMITTED, pset) < 0) 231 err(EXIT_FAILURE, "failed to set permitted privilege set"); 232 233 /* 234 * Unfortunately, when run as root, privilege bracketing is a no-op, 235 * so we have to add PRIV_PROC_OWNER into our effective set for things 236 * to work. 237 */ 238 if (getuid() == 0 && setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) { 239 err(EXIT_FAILURE, "failed to set effective privilege set"); 240 } 241 242 priv_freeset(pset); 243 pset = NULL; 244 245 (void) setlocale(LC_ALL, ""); 246 #if !defined(TEXT_DOMAIN) 247 #define TEXT_DOMAIN "SYS_TEST" 248 #endif 249 (void) textdomain(TEXT_DOMAIN); 250 251 login = (argv[0][0] == '-'); 252 cp = strrchr(argv[0], '/'); 253 firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1]; 254 prog = argv[0]; 255 256 while (argc > 1) { 257 if (argv[1][0] == '-') { 258 for (i = 1; argv[1][i]; i++) { 259 switch (argv[1][i]) { 260 261 case 'h': 262 header = 0; 263 break; 264 265 case 'l': 266 lflag++; 267 break; 268 case 's': 269 lflag = 0; 270 break; 271 272 case 'u': 273 case 'w': 274 firstchar = argv[1][i]; 275 break; 276 277 default: 278 (void) fprintf(stderr, gettext( 279 "%s: bad flag %s\n"), 280 prog, argv[1]); 281 exit(1); 282 } 283 } 284 } else { 285 if (!isalnum(argv[1][0]) || argc > 2) { 286 (void) fprintf(stderr, gettext( 287 "usage: %s [ -hlsuw ] [ user ]\n"), prog); 288 exit(1); 289 } else 290 sel_user = argv[1]; 291 } 292 argc--; argv++; 293 } 294 295 /* 296 * read the UTMPX_FILE (contains information about each logged in user) 297 */ 298 if (stat(UTMPX_FILE, &sbuf) < 0) 299 err(EXIT_FAILURE, gettext("stat error of %s"), UTMPX_FILE); 300 301 entries = sbuf.st_size / sizeof (struct futmpx); 302 if ((ut = calloc(entries, sizeof (struct utmpx))) == NULL) 303 err(EXIT_FAILURE, gettext("calloc error of %s"), UTMPX_FILE); 304 305 (void) utmpxname(UTMPX_FILE); 306 307 utmpbegin = ut; 308 utmpend = utmpbegin + entries; 309 310 setutxent(); 311 while ((ut < utmpend) && ((utp = getutxent()) != NULL)) 312 (void) memcpy(ut++, utp, sizeof (*ut)); 313 endutxent(); 314 315 (void) time(&now); /* get current time */ 316 317 if (header) { /* print a header */ 318 prtat(&now); 319 for (ut = utmpbegin; ut < utmpend; ut++) { 320 if (ut->ut_type == USER_PROCESS) { 321 if (!nonuserx(*ut)) 322 nusers++; 323 } else if (ut->ut_type == BOOT_TIME) { 324 uptime = now - ut->ut_xtime; 325 uptime += 30; 326 days = uptime / (60*60*24); 327 uptime %= (60*60*24); 328 hrs = uptime / (60*60); 329 uptime %= (60*60); 330 mins = uptime / 60; 331 332 PRINTF((gettext("up"))); 333 if (days > 0) 334 PRINTF((gettext( 335 " %d day(s),"), days)); 336 if (hrs > 0 && mins > 0) { 337 PRINTF((" %2d:%02d,", hrs, mins)); 338 } else { 339 if (hrs > 0) 340 PRINTF((gettext( 341 " %d hr(s),"), hrs)); 342 if (mins > 0) 343 PRINTF((gettext( 344 " %d min(s),"), mins)); 345 } 346 } 347 } 348 349 ut = utmpbegin; /* rewind utmp data */ 350 PRINTF((((nusers == 1) ? 351 gettext(" %d user") : gettext(" %d users")), nusers)); 352 /* 353 * Print 1, 5, and 15 minute load averages. 354 */ 355 (void) getloadavg(loadavg, 3); 356 PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"), 357 loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN], 358 loadavg[LOADAVG_15MIN])); 359 360 if (firstchar == 'u') /* uptime command */ 361 exit(0); 362 363 if (lflag) { 364 PRINTF((dcgettext(NULL, "User tty " 365 "login@ idle JCPU PCPU what\n", 366 LC_TIME))); 367 } else { 368 PRINTF((dcgettext(NULL, 369 "User tty idle what\n", 370 LC_TIME))); 371 } 372 373 if (fflush(stdout) == EOF) { 374 err(EXIT_FAILURE, "fflush failed"); 375 } 376 } 377 378 /* 379 * loop through /proc, reading info about each process 380 * and build the parent/child tree 381 */ 382 if ((dirp = opendir(PROCDIR)) == NULL) 383 err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR); 384 385 while ((dp = readdir(dirp)) != NULL) { 386 if (dp->d_name[0] == '.') 387 continue; 388 389 if (snprintf(pname, sizeof (pname), "%s/%s", PROCDIR, 390 dp->d_name) > sizeof (pname)) 391 continue; 392 393 dirfd = priv_proc_open(pname, O_RDONLY | O_DIRECTORY); 394 395 if (dirfd < 0) { 396 if (errno == ENOENT) 397 continue; 398 warn(gettext("failed to open %s"), pname); 399 continue; 400 } 401 402 procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY); 403 if (procfd < 0) { 404 (void) close(dirfd); 405 continue; 406 } 407 408 if (!do_proc_read(procfd, &info, sizeof (info))) { 409 warn(gettext("read() failed on %s"), pname); 410 (void) close(dirfd); 411 continue; 412 } 413 (void) close(procfd); 414 415 up = findhash(info.pr_pid); 416 up->p_ttyd = info.pr_ttydev; 417 up->p_state = (info.pr_nlwp == 0 ? ZOMBIE : RUNNING); 418 up->p_time = 0; 419 up->p_ctime = 0; 420 up->p_igintr = 0; 421 (void) strlcpy(up->p_comm, info.pr_fname, 422 sizeof (up->p_comm)); 423 up->p_args[0] = 0; 424 425 if (up->p_state != NONE && up->p_state != ZOMBIE) { 426 procfd = priv_proc_openat(dirfd, "status", O_RDONLY); 427 if (procfd < 0) { 428 (void) close(dirfd); 429 continue; 430 } 431 432 if (!do_proc_read(procfd, &statinfo, 433 sizeof (statinfo))) { 434 warn(gettext("read() failed on %s/status"), 435 pname); 436 437 (void) close(procfd); 438 (void) close(dirfd); 439 continue; 440 } 441 (void) close(procfd); 442 443 up->p_time = statinfo.pr_utime.tv_sec + 444 statinfo.pr_stime.tv_sec; /* seconds */ 445 up->p_ctime = statinfo.pr_cutime.tv_sec + 446 statinfo.pr_cstime.tv_sec; 447 448 procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY); 449 if (procfd < 0) { 450 (void) close(dirfd); 451 continue; 452 } 453 454 if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) { 455 warn(gettext("read() failed on %s/sigact"), 456 pname); 457 458 (void) close(procfd); 459 (void) close(dirfd); 460 continue; 461 } 462 (void) close(procfd); 463 (void) close(dirfd); 464 up->p_igintr = 465 actinfo[SIGINT-1].sa_handler == SIG_IGN && 466 actinfo[SIGQUIT-1].sa_handler == SIG_IGN; 467 468 /* 469 * Process args. 470 */ 471 up->p_args[0] = '\0'; 472 clnarglist(info.pr_psargs); 473 (void) strlcpy(up->p_args, info.pr_psargs, 474 sizeof (up->p_args)); 475 if (up->p_args[0] == 0 || 476 up->p_args[0] == '-' && up->p_args[1] <= ' ' || 477 up->p_args[0] == '?') { 478 (void) strlcat(up->p_args, " (", 479 sizeof (up->p_args)); 480 (void) strlcat(up->p_args, up->p_comm, 481 sizeof (up->p_args)); 482 (void) strlcat(up->p_args, ")", 483 sizeof (up->p_args)); 484 } 485 } 486 487 /* 488 * link pgrp together in case parents go away 489 * Pgrp chain is a single linked list originating 490 * from the pgrp leader to its group member. 491 */ 492 if (info.pr_pgid != info.pr_pid) { /* not pgrp leader */ 493 pgrp = findhash(info.pr_pgid); 494 up->p_pgrpl = pgrp->p_pgrpl; 495 pgrp->p_pgrpl = up; 496 } 497 parent = findhash(info.pr_ppid); 498 499 /* if this is the new member, link it in */ 500 if (parent->p_upid != INITPROCESS) { 501 if (parent->p_child) { 502 up->p_sibling = parent->p_child; 503 up->p_child = 0; 504 } 505 parent->p_child = up; 506 } 507 } 508 509 /* revert to non-privileged user after opening */ 510 __priv_relinquish(); 511 if (getuid() == 0) { 512 /* 513 * Since the privilege bracketing functions are effectively 514 * no-ops when running as root, we must explicitly 515 * relinquish PRIV_PROC_OWNER ourselves. 516 */ 517 pset = priv_allocset(); 518 if (pset == NULL) { 519 err(EXIT_FAILURE, 520 gettext("failed to allocate privilege set")); 521 } 522 523 priv_emptyset(pset); 524 525 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 526 err(EXIT_FAILURE, gettext("failed to add " 527 "PRIV_PROC_OWNER to privilege set")); 528 } 529 530 if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) { 531 err(EXIT_FAILURE, 532 gettext("failed to set permitted privilege set")); 533 } 534 535 priv_freeset(pset); 536 pset = NULL; 537 } 538 539 (void) closedir(dirp); 540 (void) time(&now); /* get current time */ 541 542 /* 543 * loop through utmpx file, printing process info 544 * about each logged in user 545 */ 546 for (ut = utmpbegin; ut < utmpend; ut++) { 547 if (ut->ut_type != USER_PROCESS) 548 continue; 549 if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0) 550 continue; /* we're looking for somebody else */ 551 552 /* print login name of the user */ 553 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name)); 554 555 /* print tty user is on */ 556 if (lflag) { 557 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line)); 558 } else { 559 if (ut->ut_line[0] == 'p' && ut->ut_line[1] == 't' && 560 ut->ut_line[2] == 's' && ut->ut_line[3] == '/') { 561 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 562 &ut->ut_line[4])); 563 } else { 564 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 565 ut->ut_line)); 566 } 567 } 568 569 /* print when the user logged in */ 570 if (lflag) { 571 time_t tim = ut->ut_xtime; 572 prtat(&tim); 573 } 574 575 /* print idle time */ 576 idle = findidle(ut->ut_line); 577 prttime(idle, 8); 578 showtotals(findhash(ut->ut_pid)); 579 } 580 if (fclose(stdout) == EOF) 581 err(EXIT_FAILURE, gettext("fclose failed")); 582 583 return (0); 584 } 585 586 /* 587 * Prints the CPU time for all processes & children, 588 * and the cpu time for interesting process, 589 * and what the user is doing. 590 */ 591 static void 592 showtotals(struct uproc *up) 593 { 594 jobtime = 0; 595 proctime = 0; 596 empty = 1; 597 curpid = -1; 598 add_times = 1; 599 600 calctotals(up); 601 602 if (lflag) { 603 /* print CPU time for all processes & children */ 604 /* and need to convert clock ticks to seconds first */ 605 prttime((time_t)jobtime, 8); 606 607 /* print cpu time for interesting process */ 608 /* and need to convert clock ticks to seconds first */ 609 prttime((time_t)proctime, 8); 610 } 611 /* what user is doing, current process */ 612 PRINTF(("%-.32s\n", doing)); 613 } 614 615 /* 616 * This recursive routine descends the process 617 * tree starting from the given process pointer(up). 618 * It used depth-first search strategy and also marked 619 * each node as visited as it traversed down the tree. 620 * It calculates the process time for all processes & 621 * children. It also finds the interesting process 622 * and determines its cpu time and command. 623 */ 624 static void 625 calctotals(struct uproc *up) 626 { 627 struct uproc *zp; 628 629 /* 630 * Once a node has been visited, stop adding cpu times 631 * for its children so they don't get totalled twice. 632 * Still look for the interesting job for this utmp 633 * entry, however. 634 */ 635 if (up->p_state == VISITED) 636 add_times = 0; 637 up->p_state = VISITED; 638 if (up->p_state == NONE || up->p_state == ZOMBIE) 639 return; 640 641 if (empty && !up->p_igintr) { 642 empty = 0; 643 curpid = -1; 644 } 645 646 if (up->p_upid > curpid && (!up->p_igintr || empty)) { 647 curpid = up->p_upid; 648 if (lflag) 649 (void) strlcpy(doing, up->p_args, sizeof (doing)); 650 else 651 (void) strlcpy(doing, up->p_comm, sizeof (doing)); 652 } 653 654 if (add_times == 1) { 655 jobtime += up->p_time + up->p_ctime; 656 proctime += up->p_time; 657 } 658 659 /* descend for its children */ 660 if (up->p_child) { 661 calctotals(up->p_child); 662 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) 663 calctotals(zp); 664 } 665 } 666 667 /* 668 * Findhash finds the appropriate entry in the process 669 * hash table (pr_htbl) for the given pid in case that 670 * pid exists on the hash chain. It returns back a pointer 671 * to that uproc structure. If this is a new pid, it allocates 672 * a new node, initializes it, links it into the chain (after 673 * head) and returns a structure pointer. 674 */ 675 static struct uproc * 676 findhash(pid_t pid) 677 { 678 struct uproc *up, *tp; 679 680 tp = up = &pr_htbl[pid % HSIZE]; 681 if (up->p_upid == 0) { /* empty slot */ 682 up->p_upid = pid; 683 up->p_state = NONE; 684 up->p_child = up->p_sibling = up->p_pgrpl = up->p_link = 0; 685 return (up); 686 } 687 if (up->p_upid == pid) { /* found in hash table */ 688 return (up); 689 } 690 for (tp = up->p_link; tp; tp = tp->p_link) { /* follow chain */ 691 if (tp->p_upid == pid) 692 return (tp); 693 } 694 tp = malloc(sizeof (*tp)); /* add new node */ 695 if (tp == NULL) 696 err(EXIT_FAILURE, gettext("out of memory!")); 697 698 (void) memset(tp, 0, sizeof (*tp)); 699 tp->p_upid = pid; 700 tp->p_state = NONE; 701 tp->p_child = tp->p_sibling = tp->p_pgrpl = 0; 702 tp->p_link = up->p_link; /* insert after head */ 703 up->p_link = tp; 704 return (tp); 705 } 706 707 #define HR (60 * 60) 708 #define DAY (24 * HR) 709 #define MON (30 * DAY) 710 711 /* 712 * Prttime prints an elapsed time in hours, minutes, or seconds, 713 * right-justified with the rightmost column always blank. 714 * The second argument is the minimum field width. 715 */ 716 static void 717 prttime(time_t tim, int width) 718 { 719 char value[36]; 720 721 if (tim >= 36 * 60) { 722 (void) snprintf(value, sizeof (value), "%d:%02d:%02d", 723 (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60); 724 } else if (tim >= 60) { 725 (void) snprintf(value, sizeof (value), "%d:%02d", 726 (int)tim / 60, (int)tim % 60); 727 } else if (tim > 0) { 728 (void) snprintf(value, sizeof (value), "%d", (int)tim); 729 } else { 730 (void) strlcpy(value, "0", sizeof (value)); 731 } 732 width = (width > 2) ? width - 1 : 1; 733 PRINTF(("%*s ", width, value)); 734 } 735 736 /* 737 * Prints the ISO date or time given a pointer to a time of day, 738 * left-justfied in a 12-character expanding field with the 739 * rightmost column always blank. 740 * Includes a dcgettext() override in case a message catalog is needed. 741 */ 742 static void 743 prtat(time_t *time) 744 { 745 struct tm *p; 746 747 p = localtime(time); 748 if (now - *time <= 18 * HR) { 749 char timestr[50]; 750 751 (void) strftime(timestr, sizeof (timestr), 752 dcgettext(NULL, "%T", LC_TIME), p); 753 PRINTF(("%-11s ", timestr)); 754 } else if (now - *time <= 7 * DAY) { 755 char weekdaytime[20]; 756 757 (void) strftime(weekdaytime, sizeof (weekdaytime), 758 dcgettext(NULL, "%a %H:%M", LC_TIME), p); 759 PRINTF(("%-11s ", weekdaytime)); 760 } else { 761 char monthtime[20]; 762 763 (void) strftime(monthtime, sizeof (monthtime), 764 dcgettext(NULL, "%F", LC_TIME), p); 765 PRINTF(("%-11s ", monthtime)); 766 } 767 } 768 769 /* 770 * find & return number of minutes current tty has been idle 771 */ 772 static time_t 773 findidle(char *devname) 774 { 775 struct stat stbuf; 776 time_t lastaction, diff; 777 char ttyname[64]; 778 779 (void) strlcpy(ttyname, "/dev/", sizeof (ttyname)); 780 (void) strlcat(ttyname, devname, sizeof (ttyname)); 781 if (stat(ttyname, &stbuf) != -1) { 782 lastaction = stbuf.st_atime; 783 diff = now - lastaction; 784 diff = DIV60(diff); 785 if (diff < 0) 786 diff = 0; 787 } else 788 diff = 0; 789 return (diff); 790 } 791 792 /* 793 * given a pointer to the argument string get rid of unsavory characters. 794 */ 795 static void 796 clnarglist(char *arglist) 797 { 798 char *c; 799 int err = 0; 800 801 /* get rid of unsavory characters */ 802 for (c = arglist; *c != '\0'; c++) { 803 if ((*c < ' ') || (*c > 0176)) { 804 if (err++ > 5) { 805 *arglist = '\0'; 806 break; 807 } 808 *c = '?'; 809 } 810 } 811 } 812 813 static int 814 priv_proc_open(const char *path, int oflag) 815 { 816 int fd, errsave = 0; 817 818 if (__priv_bracket(PRIV_ON) != 0) 819 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 820 821 do { 822 fd = open(path, oflag); 823 if (fd < 0) 824 errsave = errno; 825 } while (fd < 0 && errno == EAGAIN); 826 827 if (__priv_bracket(PRIV_OFF) != 0) 828 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 829 830 if (fd < 0) 831 errno = errsave; 832 833 return (fd); 834 } 835 836 static int 837 priv_proc_openat(int dfd, const char *path, int mode) 838 { 839 int fd, errsave = 0; 840 841 if (__priv_bracket(PRIV_ON) != 0) 842 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 843 844 do { 845 fd = openat(dfd, path, mode); 846 if (fd < 0) 847 errsave = errno; 848 } while (fd < 0 && errno == EAGAIN); 849 850 if (__priv_bracket(PRIV_OFF) != 0) 851 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 852 853 if (fd < 0) 854 errno = errsave; 855 856 return (fd); 857 } 858 859 static boolean_t 860 do_proc_read(int fd, void *buf, size_t bufsize) 861 { 862 ssize_t n; 863 864 do { 865 n = pread(fd, buf, bufsize, 0); 866 if (n == bufsize) 867 return (B_TRUE); 868 /* 869 * Retry on a partial read or EAGAIN, otherwise fail 870 */ 871 } while (n >= 0 || errno == EAGAIN); 872 873 return (B_FALSE); 874 } 875