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 <sys/types.h> 57 #include <sys/loadavg.h> 58 #include <sys/queue.h> 59 #include <sys/stat.h> 60 #include <sys/sysmacros.h> 61 62 #include <ctype.h> 63 #include <dirent.h> 64 #include <err.h> 65 #include <errno.h> 66 #include <fcntl.h> 67 #include <limits.h> 68 #include <locale.h> 69 #include <priv_utils.h> 70 #include <procfs.h> /* /proc header file */ 71 #include <stdarg.h> 72 #include <stdio.h> 73 #include <stdlib.h> 74 #include <string.h> 75 #include <time.h> 76 #include <unistd.h> 77 #include <utmpx.h> 78 79 /* 80 * Use the full lengths from utmpx for user and line. 81 */ 82 static struct utmpx dummy; 83 #define NMAX (sizeof (dummy.ut_user)) 84 #define LMAX (sizeof (dummy.ut_line)) 85 86 /* Print minimum field widths. */ 87 #define LOGIN_WIDTH 8 88 #define LINE_WIDTH 8 89 90 #define DIV60(t) ((t+30)/60) /* x/60 rounded */ 91 92 #define PROCDIR "/proc" 93 #define PRINTF(a) if (printf a < 0) { \ 94 perror((gettext("%s: printf failed"), prog)); \ 95 exit(1); } 96 97 struct uproc { 98 pid_t p_upid; /* process id */ 99 dev_t p_ttyd; /* controlling tty of process */ 100 time_t p_time; /* seconds of user & system time */ 101 time_t p_ctime; /* seconds of child user & sys time */ 102 int p_igintr; /* 1 = ignores SIGQUIT and SIGINT */ 103 char p_comm[PRARGSZ+1]; /* command */ 104 char p_args[PRARGSZ+1]; /* command line arguments */ 105 STAILQ_ENTRY(uproc) uprocs; 106 }; 107 STAILQ_HEAD(uprochead, uproc) uphead; 108 109 static time_t findidle(char *); 110 static void clnarglist(char *); 111 static void prttime(time_t, int); 112 static void prtat(time_t *time); 113 114 static int priv_proc_open(const char *, int); 115 static int priv_proc_openat(int, const char *, int); 116 static boolean_t do_proc_read(int, void *, size_t); 117 118 static char *prog; /* pointer to invocation name */ 119 static int header = 1; /* true if -h flag: don't print heading */ 120 static int lflag = 1; /* set if -l flag; 0 for -s flag: short form */ 121 static char *sel_user; /* login of particular user selected */ 122 static char firstchar; /* first char of name of prog invoked as */ 123 static int login; /* true if invoked as login shell */ 124 static time_t now; /* current time of day */ 125 static time_t uptime; /* time of last reboot & elapsed time since */ 126 static int nusers; /* number of users logged in now */ 127 128 /* 129 * Basic privs we never need and can drop. This is likely not exhaustive, 130 * but should significantly reduce any potential attack surfaces. 131 */ 132 static const char *drop_privs[] = { 133 PRIV_FILE_WRITE, 134 PRIV_NET_ACCESS, 135 PRIV_PROC_EXEC, 136 PRIV_PROC_FORK, 137 PRIV_FILE_LINK_ANY 138 }; 139 140 #if SIGQUIT > SIGINT 141 #define ACTSIZE SIGQUIT 142 #else 143 #define ACTSIZE SIGINT 144 #endif 145 146 int 147 main(int argc, char *argv[]) 148 { 149 struct utmpx *ut; 150 struct utmpx *utmpbegin; 151 struct utmpx *utmpend; 152 struct utmpx *utp; 153 struct uproc *up; 154 struct psinfo info; 155 struct sigaction actinfo[ACTSIZE]; 156 struct pstatus statinfo; 157 struct stat sbuf; 158 DIR *dirp; 159 struct dirent *dp; 160 char pname[PATH_MAX]; 161 int procfd; 162 int dirfd; 163 char *cp; 164 int i; 165 int days, hrs, mins; 166 int entries; 167 double loadavg[3]; 168 priv_set_t *pset; 169 170 if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_PROC_OWNER, NULL) != 0) { 171 err(EXIT_FAILURE, "failed to enable privilege bracketing"); 172 } 173 174 /* 175 * After setting up privilege bracketing, we can further reduce the 176 * privileges in use. The effective set is set to the basic set minus 177 * the privs in drop_privs. The permitted set is the effective set 178 * plus PRIV_PROC_OWNER (i.e. the privilege being bracketed). 179 */ 180 pset = priv_allocset(); 181 if (pset == NULL) 182 err(EXIT_FAILURE, "priv_allocset failed"); 183 184 priv_basicset(pset); 185 for (i = 0; i < ARRAY_SIZE(drop_privs); i++) { 186 if (priv_delset(pset, drop_privs[i]) != 0) { 187 err(EXIT_FAILURE, 188 "failed to remove %s privilege from privilege set", 189 drop_privs[i]); 190 } 191 } 192 193 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) 194 err(EXIT_FAILURE, "failed setting effective privilege set"); 195 196 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 197 err(EXIT_FAILURE, 198 "failed to add PRIV_PROC_OWNER privilege to privilege set"); 199 } 200 201 if (setppriv(PRIV_SET, PRIV_PERMITTED, pset) < 0) 202 err(EXIT_FAILURE, "failed to set permitted privilege set"); 203 204 /* 205 * Unfortunately, when run as root, privilege bracketing is a no-op, 206 * so we have to add PRIV_PROC_OWNER into our effective set for things 207 * to work. 208 */ 209 if (getuid() == 0 && setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) < 0) { 210 err(EXIT_FAILURE, "failed to set effective privilege set"); 211 } 212 213 priv_freeset(pset); 214 pset = NULL; 215 216 (void) setlocale(LC_ALL, ""); 217 #if !defined(TEXT_DOMAIN) 218 #define TEXT_DOMAIN "SYS_TEST" 219 #endif 220 (void) textdomain(TEXT_DOMAIN); 221 222 login = (argv[0][0] == '-'); 223 cp = strrchr(argv[0], '/'); 224 firstchar = login ? argv[0][1] : (cp == 0) ? argv[0][0] : cp[1]; 225 prog = argv[0]; 226 227 while (argc > 1) { 228 if (argv[1][0] == '-') { 229 for (i = 1; argv[1][i]; i++) { 230 switch (argv[1][i]) { 231 232 case 'h': 233 header = 0; 234 break; 235 236 case 'l': 237 lflag++; 238 break; 239 case 's': 240 lflag = 0; 241 break; 242 243 case 'u': 244 case 'w': 245 firstchar = argv[1][i]; 246 break; 247 248 default: 249 (void) fprintf(stderr, gettext( 250 "%s: bad flag %s\n"), 251 prog, argv[1]); 252 exit(1); 253 } 254 } 255 } else { 256 if (!isalnum(argv[1][0]) || argc > 2) { 257 (void) fprintf(stderr, gettext( 258 "usage: %s [ -hlsuw ] [ user ]\n"), prog); 259 exit(1); 260 } else 261 sel_user = argv[1]; 262 } 263 argc--; argv++; 264 } 265 266 /* 267 * read the UTMPX_FILE (contains information about each logged in user) 268 */ 269 if (stat(UTMPX_FILE, &sbuf) < 0) 270 err(EXIT_FAILURE, gettext("stat error of %s"), UTMPX_FILE); 271 272 entries = sbuf.st_size / sizeof (struct futmpx); 273 if ((ut = calloc(entries, sizeof (struct utmpx))) == NULL) 274 err(EXIT_FAILURE, gettext("calloc error of %s"), UTMPX_FILE); 275 276 (void) utmpxname(UTMPX_FILE); 277 278 utmpbegin = ut; 279 utmpend = utmpbegin + entries; 280 281 setutxent(); 282 while ((ut < utmpend) && ((utp = getutxent()) != NULL)) 283 (void) memcpy(ut++, utp, sizeof (*ut)); 284 endutxent(); 285 286 (void) time(&now); /* get current time */ 287 288 if (header) { /* print a header */ 289 prtat(&now); 290 for (ut = utmpbegin; ut < utmpend; ut++) { 291 if (ut->ut_type == USER_PROCESS) { 292 if (!nonuserx(*ut)) 293 nusers++; 294 } else if (ut->ut_type == BOOT_TIME) { 295 uptime = now - ut->ut_xtime; 296 uptime += 30; 297 days = uptime / (60*60*24); 298 uptime %= (60*60*24); 299 hrs = uptime / (60*60); 300 uptime %= (60*60); 301 mins = uptime / 60; 302 303 PRINTF((gettext("up"))); 304 if (days > 0) 305 PRINTF((gettext( 306 " %d day(s),"), days)); 307 if (hrs > 0 && mins > 0) { 308 PRINTF((" %2d:%02d,", hrs, mins)); 309 } else { 310 if (hrs > 0) { 311 PRINTF((gettext( 312 " %d hr(s),"), hrs)); 313 } else { /* mins can be zero */ 314 PRINTF((gettext( 315 " %d min(s),"), mins)); 316 } 317 } 318 } 319 } 320 321 ut = utmpbegin; /* rewind utmp data */ 322 PRINTF((((nusers == 1) ? 323 gettext(" %d user") : gettext(" %d users")), nusers)); 324 /* 325 * Print 1, 5, and 15 minute load averages. 326 */ 327 (void) getloadavg(loadavg, 3); 328 PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"), 329 loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN], 330 loadavg[LOADAVG_15MIN])); 331 332 if (firstchar == 'u') /* uptime command */ 333 exit(0); 334 335 if (lflag) { 336 PRINTF((dcgettext(NULL, "User tty " 337 "login@ idle JCPU PCPU what\n", 338 LC_TIME))); 339 } else { 340 PRINTF((dcgettext(NULL, 341 "User tty idle what\n", 342 LC_TIME))); 343 } 344 345 if (fflush(stdout) == EOF) { 346 err(EXIT_FAILURE, "fflush failed"); 347 } 348 } 349 350 /* Loop through /proc, reading info about each process */ 351 if ((dirp = opendir(PROCDIR)) == NULL) 352 err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR); 353 354 STAILQ_INIT(&uphead); 355 while ((dp = readdir(dirp)) != NULL) { 356 if (dp->d_name[0] == '.') 357 continue; 358 359 if (snprintf(pname, sizeof (pname), "%s/%s", PROCDIR, 360 dp->d_name) > sizeof (pname)) 361 continue; 362 363 dirfd = priv_proc_open(pname, O_RDONLY | O_DIRECTORY); 364 if (dirfd < 0) 365 continue; 366 367 procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY); 368 if (procfd < 0) { 369 (void) close(dirfd); 370 continue; 371 } 372 if (!do_proc_read(procfd, &info, sizeof (info))) { 373 warn(gettext("failed to read %s"), pname); 374 (void) close(dirfd); 375 continue; 376 } 377 (void) close(procfd); 378 379 /* Not interested in zombies */ 380 if (info.pr_nlwp == 0) 381 continue; 382 /* Not interested in processes without a terminal */ 383 if (info.pr_ttydev == NODEV) 384 continue; 385 386 procfd = priv_proc_openat(dirfd, "status", O_RDONLY); 387 if (procfd < 0) { 388 (void) close(dirfd); 389 continue; 390 } 391 if (!do_proc_read(procfd, &statinfo, sizeof (statinfo))) { 392 warn(gettext("failed to read %s/status"), pname); 393 (void) close(procfd); 394 (void) close(dirfd); 395 continue; 396 } 397 (void) close(procfd); 398 399 procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY); 400 if (procfd < 0) { 401 (void) close(dirfd); 402 continue; 403 } 404 if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) { 405 warn(gettext("failed to read %s/sigact"), pname); 406 (void) close(procfd); 407 (void) close(dirfd); 408 continue; 409 } 410 (void) close(procfd); 411 (void) close(dirfd); 412 413 up = calloc(1, sizeof (*up)); 414 if (up == NULL) 415 err(EXIT_FAILURE, "calloc"); 416 up->p_upid = info.pr_pid; 417 up->p_ttyd = info.pr_ttydev; 418 up->p_time = 419 statinfo.pr_utime.tv_sec + 420 statinfo.pr_stime.tv_sec; 421 up->p_ctime = 422 statinfo.pr_cutime.tv_sec + 423 statinfo.pr_cstime.tv_sec; 424 up->p_igintr = 425 actinfo[SIGINT-1].sa_handler == SIG_IGN && 426 actinfo[SIGQUIT-1].sa_handler == SIG_IGN; 427 (void) strlcpy(up->p_comm, info.pr_fname, sizeof (up->p_comm)); 428 /* Process args */ 429 clnarglist(info.pr_psargs); 430 (void) strlcpy(up->p_args, info.pr_psargs, sizeof (up->p_args)); 431 if (up->p_args[0] == 0 || up->p_args[0] == '?' || 432 (up->p_args[0] == '-' && up->p_args[1] <= ' ')) { 433 (void) strlcat(up->p_args, " (", sizeof (up->p_args)); 434 (void) strlcat(up->p_args, up->p_comm, 435 sizeof (up->p_args)); 436 (void) strlcat(up->p_args, ")", sizeof (up->p_args)); 437 } 438 STAILQ_INSERT_TAIL(&uphead, up, uprocs); 439 } 440 441 /* revert to non-privileged user after opening */ 442 __priv_relinquish(); 443 if (getuid() == 0) { 444 /* 445 * Since the privilege bracketing functions are effectively 446 * no-ops when running as root, we must explicitly 447 * relinquish PRIV_PROC_OWNER ourselves. 448 */ 449 pset = priv_allocset(); 450 if (pset == NULL) { 451 err(EXIT_FAILURE, 452 gettext("failed to allocate privilege set")); 453 } 454 455 priv_emptyset(pset); 456 457 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 458 err(EXIT_FAILURE, gettext("failed to add " 459 "PRIV_PROC_OWNER to privilege set")); 460 } 461 462 if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) { 463 err(EXIT_FAILURE, 464 gettext("failed to set permitted privilege set")); 465 } 466 467 priv_freeset(pset); 468 pset = NULL; 469 } 470 471 (void) closedir(dirp); 472 (void) time(&now); /* get current time */ 473 474 /* 475 * loop through utmpx file, printing process info 476 * about each logged in user 477 */ 478 for (ut = utmpbegin; ut < utmpend; ut++) { 479 struct uproc *upt; 480 char linedev[PATH_MAX]; 481 char what[1024]; 482 time_t idle, jobtime, proctime; 483 pid_t curpid; 484 485 if (ut->ut_type != USER_PROCESS) 486 continue; 487 if (sel_user != NULL && 488 strncmp(ut->ut_name, sel_user, NMAX) != 0) 489 continue; 490 491 /* print login name of the user */ 492 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name)); 493 494 /* print tty user is on */ 495 if (lflag) { 496 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line)); 497 } else { 498 if (strncmp(ut->ut_line, "pts/", strlen("pts/")) == 0) { 499 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 500 &ut->ut_line[4])); 501 } else { 502 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 503 ut->ut_line)); 504 } 505 } 506 507 /* print when the user logged in */ 508 if (lflag) { 509 time_t tim = ut->ut_xtime; 510 prtat(&tim); 511 } 512 513 /* print idle time */ 514 idle = findidle(ut->ut_line); 515 prttime(idle, 8); 516 517 /* 518 * Go through the list of processes for this terminal, 519 * calculating job/process times, and look for the 520 * "most interesting" process. 521 */ 522 jobtime = 0; 523 proctime = 0; 524 curpid = -1; 525 (void) strlcpy(what, "-", sizeof (what)); 526 527 (void) snprintf(linedev, sizeof (linedev), "/dev/%s", 528 ut->ut_line); 529 if (stat(linedev, &sbuf) == -1 || 530 (sbuf.st_mode & S_IFMT) != S_IFCHR || 531 sbuf.st_rdev == NODEV) 532 goto skip; 533 534 STAILQ_FOREACH_SAFE(up, &uphead, uprocs, upt) { 535 if (up->p_ttyd != sbuf.st_rdev) 536 continue; 537 jobtime += up->p_time + up->p_ctime; 538 proctime += up->p_time; 539 /* 540 * Check for "most interesting" process, currently 541 * the one having the highest PID. 542 */ 543 if (up->p_upid > curpid && !up->p_igintr) { 544 curpid = up->p_upid; 545 if (lflag) { 546 (void) strlcpy(what, up->p_args, 547 sizeof (what)); 548 } else { 549 (void) strlcpy(what, up->p_comm, 550 sizeof (what)); 551 } 552 } 553 STAILQ_REMOVE(&uphead, up, uproc, uprocs); 554 free(up); 555 } 556 557 skip: 558 if (lflag) { 559 /* Print CPU time for all processes & children */ 560 prttime(jobtime, 8); 561 /* Print cpu time for interesting process */ 562 prttime(proctime, 8); 563 } 564 /* "Most interesting" process */ 565 PRINTF(("%-.32s\n", what)); 566 } 567 568 if (fclose(stdout) == EOF) 569 err(EXIT_FAILURE, gettext("fclose failed")); 570 571 return (0); 572 } 573 574 #define HR (60 * 60) 575 #define DAY (24 * HR) 576 #define MON (30 * DAY) 577 578 /* 579 * Prttime prints an elapsed time in hours, minutes, or seconds, 580 * right-justified with the rightmost column always blank. 581 * The second argument is the minimum field width. 582 */ 583 static void 584 prttime(time_t tim, int width) 585 { 586 char value[36]; 587 588 if (tim >= 36 * 60) { 589 (void) snprintf(value, sizeof (value), "%d:%02d:%02d", 590 (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60); 591 } else if (tim >= 60) { 592 (void) snprintf(value, sizeof (value), "%d:%02d", 593 (int)tim / 60, (int)tim % 60); 594 } else if (tim > 0) { 595 (void) snprintf(value, sizeof (value), "%d", (int)tim); 596 } else { 597 (void) strlcpy(value, "0", sizeof (value)); 598 } 599 width = (width > 2) ? width - 1 : 1; 600 PRINTF(("%*s ", width, value)); 601 } 602 603 /* 604 * Prints the ISO date or time given a pointer to a time of day, 605 * left-justfied in a 12-character expanding field with the 606 * rightmost column always blank. 607 * Includes a dcgettext() override in case a message catalog is needed. 608 */ 609 static void 610 prtat(time_t *time) 611 { 612 struct tm *p; 613 614 p = localtime(time); 615 if (now - *time <= 18 * HR) { 616 char timestr[50]; 617 618 (void) strftime(timestr, sizeof (timestr), 619 dcgettext(NULL, "%T", LC_TIME), p); 620 PRINTF(("%-11s ", timestr)); 621 } else if (now - *time <= 7 * DAY) { 622 char weekdaytime[20]; 623 624 (void) strftime(weekdaytime, sizeof (weekdaytime), 625 dcgettext(NULL, "%a %H:%M", LC_TIME), p); 626 PRINTF(("%-11s ", weekdaytime)); 627 } else { 628 char monthtime[20]; 629 630 (void) strftime(monthtime, sizeof (monthtime), 631 dcgettext(NULL, "%F", LC_TIME), p); 632 PRINTF(("%-11s ", monthtime)); 633 } 634 } 635 636 /* 637 * find & return number of minutes current tty has been idle 638 */ 639 static time_t 640 findidle(char *devname) 641 { 642 struct stat stbuf; 643 time_t lastaction, diff; 644 char ttyname[64]; 645 646 (void) strlcpy(ttyname, "/dev/", sizeof (ttyname)); 647 (void) strlcat(ttyname, devname, sizeof (ttyname)); 648 if (stat(ttyname, &stbuf) != -1) { 649 lastaction = stbuf.st_atime; 650 diff = now - lastaction; 651 diff = DIV60(diff); 652 if (diff < 0) 653 diff = 0; 654 } else 655 diff = 0; 656 return (diff); 657 } 658 659 /* 660 * given a pointer to the argument string get rid of unsavory characters. 661 */ 662 static void 663 clnarglist(char *arglist) 664 { 665 char *c; 666 int err = 0; 667 668 /* get rid of unsavory characters */ 669 for (c = arglist; *c != '\0'; c++) { 670 if ((*c < ' ') || (*c > 0176)) { 671 if (err++ > 5) { 672 *arglist = '\0'; 673 break; 674 } 675 *c = '?'; 676 } 677 } 678 } 679 680 static int 681 priv_proc_open(const char *path, int oflag) 682 { 683 int fd, errsave = 0; 684 685 if (__priv_bracket(PRIV_ON) != 0) 686 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 687 688 do { 689 fd = open(path, oflag); 690 if (fd < 0) 691 errsave = errno; 692 } while (fd < 0 && errno == EAGAIN); 693 694 if (__priv_bracket(PRIV_OFF) != 0) 695 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 696 697 if (fd < 0) 698 errno = errsave; 699 700 return (fd); 701 } 702 703 static int 704 priv_proc_openat(int dfd, const char *path, int mode) 705 { 706 int fd, errsave = 0; 707 708 if (__priv_bracket(PRIV_ON) != 0) 709 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 710 711 do { 712 fd = openat(dfd, path, mode); 713 if (fd < 0) 714 errsave = errno; 715 } while (fd < 0 && errno == EAGAIN); 716 717 if (__priv_bracket(PRIV_OFF) != 0) 718 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 719 720 if (fd < 0) 721 errno = errsave; 722 723 return (fd); 724 } 725 726 static boolean_t 727 do_proc_read(int fd, void *buf, size_t bufsize) 728 { 729 ssize_t n; 730 731 do { 732 n = pread(fd, buf, bufsize, 0); 733 if (n == bufsize) 734 return (B_TRUE); 735 /* 736 * Retry on a partial read or EAGAIN, otherwise fail 737 */ 738 } while (n >= 0 || errno == EAGAIN); 739 740 return (B_FALSE); 741 } 742