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 continue; 397 398 procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY); 399 if (procfd < 0) { 400 (void) close(dirfd); 401 continue; 402 } 403 404 if (!do_proc_read(procfd, &info, sizeof (info))) { 405 warn(gettext("read() failed on %s"), pname); 406 (void) close(dirfd); 407 continue; 408 } 409 (void) close(procfd); 410 411 up = findhash(info.pr_pid); 412 up->p_ttyd = info.pr_ttydev; 413 up->p_state = (info.pr_nlwp == 0 ? ZOMBIE : RUNNING); 414 up->p_time = 0; 415 up->p_ctime = 0; 416 up->p_igintr = 0; 417 (void) strlcpy(up->p_comm, info.pr_fname, 418 sizeof (up->p_comm)); 419 up->p_args[0] = 0; 420 421 if (up->p_state != NONE && up->p_state != ZOMBIE) { 422 procfd = priv_proc_openat(dirfd, "status", O_RDONLY); 423 if (procfd < 0) { 424 (void) close(dirfd); 425 continue; 426 } 427 428 if (!do_proc_read(procfd, &statinfo, 429 sizeof (statinfo))) { 430 warn(gettext("read() failed on %s/status"), 431 pname); 432 433 (void) close(procfd); 434 (void) close(dirfd); 435 continue; 436 } 437 (void) close(procfd); 438 439 up->p_time = statinfo.pr_utime.tv_sec + 440 statinfo.pr_stime.tv_sec; /* seconds */ 441 up->p_ctime = statinfo.pr_cutime.tv_sec + 442 statinfo.pr_cstime.tv_sec; 443 444 procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY); 445 if (procfd < 0) { 446 (void) close(dirfd); 447 continue; 448 } 449 450 if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) { 451 warn(gettext("read() failed on %s/sigact"), 452 pname); 453 454 (void) close(procfd); 455 (void) close(dirfd); 456 continue; 457 } 458 (void) close(procfd); 459 (void) close(dirfd); 460 up->p_igintr = 461 actinfo[SIGINT-1].sa_handler == SIG_IGN && 462 actinfo[SIGQUIT-1].sa_handler == SIG_IGN; 463 464 /* 465 * Process args. 466 */ 467 up->p_args[0] = '\0'; 468 clnarglist(info.pr_psargs); 469 (void) strlcpy(up->p_args, info.pr_psargs, 470 sizeof (up->p_args)); 471 if (up->p_args[0] == 0 || 472 up->p_args[0] == '-' && up->p_args[1] <= ' ' || 473 up->p_args[0] == '?') { 474 (void) strlcat(up->p_args, " (", 475 sizeof (up->p_args)); 476 (void) strlcat(up->p_args, up->p_comm, 477 sizeof (up->p_args)); 478 (void) strlcat(up->p_args, ")", 479 sizeof (up->p_args)); 480 } 481 } 482 483 /* 484 * link pgrp together in case parents go away 485 * Pgrp chain is a single linked list originating 486 * from the pgrp leader to its group member. 487 */ 488 if (info.pr_pgid != info.pr_pid) { /* not pgrp leader */ 489 pgrp = findhash(info.pr_pgid); 490 up->p_pgrpl = pgrp->p_pgrpl; 491 pgrp->p_pgrpl = up; 492 } 493 parent = findhash(info.pr_ppid); 494 495 /* if this is the new member, link it in */ 496 if (parent->p_upid != INITPROCESS) { 497 if (parent->p_child) { 498 up->p_sibling = parent->p_child; 499 up->p_child = 0; 500 } 501 parent->p_child = up; 502 } 503 } 504 505 /* revert to non-privileged user after opening */ 506 __priv_relinquish(); 507 if (getuid() == 0) { 508 /* 509 * Since the privilege bracketing functions are effectively 510 * no-ops when running as root, we must explicitly 511 * relinquish PRIV_PROC_OWNER ourselves. 512 */ 513 pset = priv_allocset(); 514 if (pset == NULL) { 515 err(EXIT_FAILURE, 516 gettext("failed to allocate privilege set")); 517 } 518 519 priv_emptyset(pset); 520 521 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 522 err(EXIT_FAILURE, gettext("failed to add " 523 "PRIV_PROC_OWNER to privilege set")); 524 } 525 526 if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) { 527 err(EXIT_FAILURE, 528 gettext("failed to set permitted privilege set")); 529 } 530 531 priv_freeset(pset); 532 pset = NULL; 533 } 534 535 (void) closedir(dirp); 536 (void) time(&now); /* get current time */ 537 538 /* 539 * loop through utmpx file, printing process info 540 * about each logged in user 541 */ 542 for (ut = utmpbegin; ut < utmpend; ut++) { 543 if (ut->ut_type != USER_PROCESS) 544 continue; 545 if (sel_user && strncmp(ut->ut_name, sel_user, NMAX) != 0) 546 continue; /* we're looking for somebody else */ 547 548 /* print login name of the user */ 549 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name)); 550 551 /* print tty user is on */ 552 if (lflag) { 553 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line)); 554 } else { 555 if (ut->ut_line[0] == 'p' && ut->ut_line[1] == 't' && 556 ut->ut_line[2] == 's' && ut->ut_line[3] == '/') { 557 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 558 &ut->ut_line[4])); 559 } else { 560 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 561 ut->ut_line)); 562 } 563 } 564 565 /* print when the user logged in */ 566 if (lflag) { 567 time_t tim = ut->ut_xtime; 568 prtat(&tim); 569 } 570 571 /* print idle time */ 572 idle = findidle(ut->ut_line); 573 prttime(idle, 8); 574 showtotals(findhash(ut->ut_pid)); 575 } 576 if (fclose(stdout) == EOF) 577 err(EXIT_FAILURE, gettext("fclose failed")); 578 579 return (0); 580 } 581 582 /* 583 * Prints the CPU time for all processes & children, 584 * and the cpu time for interesting process, 585 * and what the user is doing. 586 */ 587 static void 588 showtotals(struct uproc *up) 589 { 590 jobtime = 0; 591 proctime = 0; 592 empty = 1; 593 curpid = -1; 594 add_times = 1; 595 596 calctotals(up); 597 598 if (lflag) { 599 /* print CPU time for all processes & children */ 600 /* and need to convert clock ticks to seconds first */ 601 prttime((time_t)jobtime, 8); 602 603 /* print cpu time for interesting process */ 604 /* and need to convert clock ticks to seconds first */ 605 prttime((time_t)proctime, 8); 606 } 607 /* what user is doing, current process */ 608 PRINTF(("%-.32s\n", doing)); 609 } 610 611 /* 612 * This recursive routine descends the process 613 * tree starting from the given process pointer(up). 614 * It used depth-first search strategy and also marked 615 * each node as visited as it traversed down the tree. 616 * It calculates the process time for all processes & 617 * children. It also finds the interesting process 618 * and determines its cpu time and command. 619 */ 620 static void 621 calctotals(struct uproc *up) 622 { 623 struct uproc *zp; 624 625 /* 626 * Once a node has been visited, stop adding cpu times 627 * for its children so they don't get totalled twice. 628 * Still look for the interesting job for this utmp 629 * entry, however. 630 */ 631 if (up->p_state == VISITED) 632 add_times = 0; 633 up->p_state = VISITED; 634 if (up->p_state == NONE || up->p_state == ZOMBIE) 635 return; 636 637 if (empty && !up->p_igintr) { 638 empty = 0; 639 curpid = -1; 640 } 641 642 if (up->p_upid > curpid && (!up->p_igintr || empty)) { 643 curpid = up->p_upid; 644 if (lflag) 645 (void) strlcpy(doing, up->p_args, sizeof (doing)); 646 else 647 (void) strlcpy(doing, up->p_comm, sizeof (doing)); 648 } 649 650 if (add_times == 1) { 651 jobtime += up->p_time + up->p_ctime; 652 proctime += up->p_time; 653 } 654 655 /* descend for its children */ 656 if (up->p_child) { 657 calctotals(up->p_child); 658 for (zp = up->p_child->p_sibling; zp; zp = zp->p_sibling) 659 calctotals(zp); 660 } 661 } 662 663 /* 664 * Findhash finds the appropriate entry in the process 665 * hash table (pr_htbl) for the given pid in case that 666 * pid exists on the hash chain. It returns back a pointer 667 * to that uproc structure. If this is a new pid, it allocates 668 * a new node, initializes it, links it into the chain (after 669 * head) and returns a structure pointer. 670 */ 671 static struct uproc * 672 findhash(pid_t pid) 673 { 674 struct uproc *up, *tp; 675 676 tp = up = &pr_htbl[pid % HSIZE]; 677 if (up->p_upid == 0) { /* empty slot */ 678 up->p_upid = pid; 679 up->p_state = NONE; 680 up->p_child = up->p_sibling = up->p_pgrpl = up->p_link = 0; 681 return (up); 682 } 683 if (up->p_upid == pid) { /* found in hash table */ 684 return (up); 685 } 686 for (tp = up->p_link; tp; tp = tp->p_link) { /* follow chain */ 687 if (tp->p_upid == pid) 688 return (tp); 689 } 690 tp = malloc(sizeof (*tp)); /* add new node */ 691 if (tp == NULL) 692 err(EXIT_FAILURE, gettext("out of memory!")); 693 694 (void) memset(tp, 0, sizeof (*tp)); 695 tp->p_upid = pid; 696 tp->p_state = NONE; 697 tp->p_child = tp->p_sibling = tp->p_pgrpl = 0; 698 tp->p_link = up->p_link; /* insert after head */ 699 up->p_link = tp; 700 return (tp); 701 } 702 703 #define HR (60 * 60) 704 #define DAY (24 * HR) 705 #define MON (30 * DAY) 706 707 /* 708 * Prttime prints an elapsed time in hours, minutes, or seconds, 709 * right-justified with the rightmost column always blank. 710 * The second argument is the minimum field width. 711 */ 712 static void 713 prttime(time_t tim, int width) 714 { 715 char value[36]; 716 717 if (tim >= 36 * 60) { 718 (void) snprintf(value, sizeof (value), "%d:%02d:%02d", 719 (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60); 720 } else if (tim >= 60) { 721 (void) snprintf(value, sizeof (value), "%d:%02d", 722 (int)tim / 60, (int)tim % 60); 723 } else if (tim > 0) { 724 (void) snprintf(value, sizeof (value), "%d", (int)tim); 725 } else { 726 (void) strlcpy(value, "0", sizeof (value)); 727 } 728 width = (width > 2) ? width - 1 : 1; 729 PRINTF(("%*s ", width, value)); 730 } 731 732 /* 733 * Prints the ISO date or time given a pointer to a time of day, 734 * left-justfied in a 12-character expanding field with the 735 * rightmost column always blank. 736 * Includes a dcgettext() override in case a message catalog is needed. 737 */ 738 static void 739 prtat(time_t *time) 740 { 741 struct tm *p; 742 743 p = localtime(time); 744 if (now - *time <= 18 * HR) { 745 char timestr[50]; 746 747 (void) strftime(timestr, sizeof (timestr), 748 dcgettext(NULL, "%T", LC_TIME), p); 749 PRINTF(("%-11s ", timestr)); 750 } else if (now - *time <= 7 * DAY) { 751 char weekdaytime[20]; 752 753 (void) strftime(weekdaytime, sizeof (weekdaytime), 754 dcgettext(NULL, "%a %H:%M", LC_TIME), p); 755 PRINTF(("%-11s ", weekdaytime)); 756 } else { 757 char monthtime[20]; 758 759 (void) strftime(monthtime, sizeof (monthtime), 760 dcgettext(NULL, "%F", LC_TIME), p); 761 PRINTF(("%-11s ", monthtime)); 762 } 763 } 764 765 /* 766 * find & return number of minutes current tty has been idle 767 */ 768 static time_t 769 findidle(char *devname) 770 { 771 struct stat stbuf; 772 time_t lastaction, diff; 773 char ttyname[64]; 774 775 (void) strlcpy(ttyname, "/dev/", sizeof (ttyname)); 776 (void) strlcat(ttyname, devname, sizeof (ttyname)); 777 if (stat(ttyname, &stbuf) != -1) { 778 lastaction = stbuf.st_atime; 779 diff = now - lastaction; 780 diff = DIV60(diff); 781 if (diff < 0) 782 diff = 0; 783 } else 784 diff = 0; 785 return (diff); 786 } 787 788 /* 789 * given a pointer to the argument string get rid of unsavory characters. 790 */ 791 static void 792 clnarglist(char *arglist) 793 { 794 char *c; 795 int err = 0; 796 797 /* get rid of unsavory characters */ 798 for (c = arglist; *c != '\0'; c++) { 799 if ((*c < ' ') || (*c > 0176)) { 800 if (err++ > 5) { 801 *arglist = '\0'; 802 break; 803 } 804 *c = '?'; 805 } 806 } 807 } 808 809 static int 810 priv_proc_open(const char *path, int oflag) 811 { 812 int fd, errsave = 0; 813 814 if (__priv_bracket(PRIV_ON) != 0) 815 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 816 817 do { 818 fd = open(path, oflag); 819 if (fd < 0) 820 errsave = errno; 821 } while (fd < 0 && errno == EAGAIN); 822 823 if (__priv_bracket(PRIV_OFF) != 0) 824 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 825 826 if (fd < 0) 827 errno = errsave; 828 829 return (fd); 830 } 831 832 static int 833 priv_proc_openat(int dfd, const char *path, int mode) 834 { 835 int fd, errsave = 0; 836 837 if (__priv_bracket(PRIV_ON) != 0) 838 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 839 840 do { 841 fd = openat(dfd, path, mode); 842 if (fd < 0) 843 errsave = errno; 844 } while (fd < 0 && errno == EAGAIN); 845 846 if (__priv_bracket(PRIV_OFF) != 0) 847 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 848 849 if (fd < 0) 850 errno = errsave; 851 852 return (fd); 853 } 854 855 static boolean_t 856 do_proc_read(int fd, void *buf, size_t bufsize) 857 { 858 ssize_t n; 859 860 do { 861 n = pread(fd, buf, bufsize, 0); 862 if (n == bufsize) 863 return (B_TRUE); 864 /* 865 * Retry on a partial read or EAGAIN, otherwise fail 866 */ 867 } while (n >= 0 || errno == EAGAIN); 868 869 return (B_FALSE); 870 } 871