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 if (mins > 0) 314 PRINTF((gettext( 315 " %d min(s),"), mins)); 316 } 317 } 318 } 319 320 ut = utmpbegin; /* rewind utmp data */ 321 PRINTF((((nusers == 1) ? 322 gettext(" %d user") : gettext(" %d users")), nusers)); 323 /* 324 * Print 1, 5, and 15 minute load averages. 325 */ 326 (void) getloadavg(loadavg, 3); 327 PRINTF((gettext(", load average: %.2f, %.2f, %.2f\n"), 328 loadavg[LOADAVG_1MIN], loadavg[LOADAVG_5MIN], 329 loadavg[LOADAVG_15MIN])); 330 331 if (firstchar == 'u') /* uptime command */ 332 exit(0); 333 334 if (lflag) { 335 PRINTF((dcgettext(NULL, "User tty " 336 "login@ idle JCPU PCPU what\n", 337 LC_TIME))); 338 } else { 339 PRINTF((dcgettext(NULL, 340 "User tty idle what\n", 341 LC_TIME))); 342 } 343 344 if (fflush(stdout) == EOF) { 345 err(EXIT_FAILURE, "fflush failed"); 346 } 347 } 348 349 /* Loop through /proc, reading info about each process */ 350 if ((dirp = opendir(PROCDIR)) == NULL) 351 err(EXIT_FAILURE, gettext("could not open %s"), PROCDIR); 352 353 STAILQ_INIT(&uphead); 354 while ((dp = readdir(dirp)) != NULL) { 355 if (dp->d_name[0] == '.') 356 continue; 357 358 if (snprintf(pname, sizeof (pname), "%s/%s", PROCDIR, 359 dp->d_name) > sizeof (pname)) 360 continue; 361 362 dirfd = priv_proc_open(pname, O_RDONLY | O_DIRECTORY); 363 if (dirfd < 0) 364 continue; 365 366 procfd = priv_proc_openat(dirfd, "psinfo", O_RDONLY); 367 if (procfd < 0) { 368 (void) close(dirfd); 369 continue; 370 } 371 if (!do_proc_read(procfd, &info, sizeof (info))) { 372 warn(gettext("failed to read %s"), pname); 373 (void) close(dirfd); 374 continue; 375 } 376 (void) close(procfd); 377 378 /* Not interested in zombies */ 379 if (info.pr_nlwp == 0) 380 continue; 381 /* Not interested in processes without a terminal */ 382 if (info.pr_ttydev == NODEV) 383 continue; 384 385 procfd = priv_proc_openat(dirfd, "status", O_RDONLY); 386 if (procfd < 0) { 387 (void) close(dirfd); 388 continue; 389 } 390 if (!do_proc_read(procfd, &statinfo, sizeof (statinfo))) { 391 warn(gettext("failed to read %s/status"), pname); 392 (void) close(procfd); 393 (void) close(dirfd); 394 continue; 395 } 396 (void) close(procfd); 397 398 procfd = priv_proc_openat(dirfd, "sigact", O_RDONLY); 399 if (procfd < 0) { 400 (void) close(dirfd); 401 continue; 402 } 403 if (!do_proc_read(procfd, actinfo, sizeof (actinfo))) { 404 warn(gettext("failed to read %s/sigact"), pname); 405 (void) close(procfd); 406 (void) close(dirfd); 407 continue; 408 } 409 (void) close(procfd); 410 (void) close(dirfd); 411 412 up = calloc(1, sizeof (*up)); 413 if (up == NULL) 414 err(EXIT_FAILURE, "calloc"); 415 up->p_upid = info.pr_pid; 416 up->p_ttyd = info.pr_ttydev; 417 up->p_time = 418 statinfo.pr_utime.tv_sec + 419 statinfo.pr_stime.tv_sec; 420 up->p_ctime = 421 statinfo.pr_cutime.tv_sec + 422 statinfo.pr_cstime.tv_sec; 423 up->p_igintr = 424 actinfo[SIGINT-1].sa_handler == SIG_IGN && 425 actinfo[SIGQUIT-1].sa_handler == SIG_IGN; 426 (void) strlcpy(up->p_comm, info.pr_fname, sizeof (up->p_comm)); 427 /* Process args */ 428 clnarglist(info.pr_psargs); 429 (void) strlcpy(up->p_args, info.pr_psargs, sizeof (up->p_args)); 430 if (up->p_args[0] == 0 || up->p_args[0] == '?' || 431 (up->p_args[0] == '-' && up->p_args[1] <= ' ')) { 432 (void) strlcat(up->p_args, " (", sizeof (up->p_args)); 433 (void) strlcat(up->p_args, up->p_comm, 434 sizeof (up->p_args)); 435 (void) strlcat(up->p_args, ")", sizeof (up->p_args)); 436 } 437 STAILQ_INSERT_TAIL(&uphead, up, uprocs); 438 } 439 440 /* revert to non-privileged user after opening */ 441 __priv_relinquish(); 442 if (getuid() == 0) { 443 /* 444 * Since the privilege bracketing functions are effectively 445 * no-ops when running as root, we must explicitly 446 * relinquish PRIV_PROC_OWNER ourselves. 447 */ 448 pset = priv_allocset(); 449 if (pset == NULL) { 450 err(EXIT_FAILURE, 451 gettext("failed to allocate privilege set")); 452 } 453 454 priv_emptyset(pset); 455 456 if (priv_addset(pset, PRIV_PROC_OWNER) != 0) { 457 err(EXIT_FAILURE, gettext("failed to add " 458 "PRIV_PROC_OWNER to privilege set")); 459 } 460 461 if (setppriv(PRIV_OFF, PRIV_PERMITTED, pset) != 0) { 462 err(EXIT_FAILURE, 463 gettext("failed to set permitted privilege set")); 464 } 465 466 priv_freeset(pset); 467 pset = NULL; 468 } 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 struct uproc *upt; 479 char linedev[PATH_MAX]; 480 char what[1024]; 481 time_t idle, jobtime, proctime; 482 pid_t curpid; 483 484 if (ut->ut_type != USER_PROCESS) 485 continue; 486 if (sel_user != NULL && 487 strncmp(ut->ut_name, sel_user, NMAX) != 0) 488 continue; 489 490 /* print login name of the user */ 491 PRINTF(("%-*.*s ", LOGIN_WIDTH, NMAX, ut->ut_name)); 492 493 /* print tty user is on */ 494 if (lflag) { 495 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, ut->ut_line)); 496 } else { 497 if (strncmp(ut->ut_line, "pts/", strlen("pts/")) == 0) { 498 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 499 &ut->ut_line[4])); 500 } else { 501 PRINTF(("%-*.*s ", LINE_WIDTH, LMAX, 502 ut->ut_line)); 503 } 504 } 505 506 /* print when the user logged in */ 507 if (lflag) { 508 time_t tim = ut->ut_xtime; 509 prtat(&tim); 510 } 511 512 /* print idle time */ 513 idle = findidle(ut->ut_line); 514 prttime(idle, 8); 515 516 /* 517 * Go through the list of processes for this terminal, 518 * calculating job/process times, and look for the 519 * "most interesting" process. 520 */ 521 jobtime = 0; 522 proctime = 0; 523 curpid = -1; 524 (void) strlcpy(what, "-", sizeof (what)); 525 526 (void) snprintf(linedev, sizeof (linedev), "/dev/%s", 527 ut->ut_line); 528 if (stat(linedev, &sbuf) == -1 || 529 (sbuf.st_mode & S_IFMT) != S_IFCHR || 530 sbuf.st_rdev == NODEV) 531 goto skip; 532 533 STAILQ_FOREACH_SAFE(up, &uphead, uprocs, upt) { 534 if (up->p_ttyd != sbuf.st_rdev) 535 continue; 536 jobtime += up->p_time + up->p_ctime; 537 proctime += up->p_time; 538 /* 539 * Check for "most interesting" process, currently 540 * the one having the highest PID. 541 */ 542 if (up->p_upid > curpid && !up->p_igintr) { 543 curpid = up->p_upid; 544 if (lflag) { 545 (void) strlcpy(what, up->p_args, 546 sizeof (what)); 547 } else { 548 (void) strlcpy(what, up->p_comm, 549 sizeof (what)); 550 } 551 } 552 STAILQ_REMOVE(&uphead, up, uproc, uprocs); 553 free(up); 554 } 555 556 skip: 557 if (lflag) { 558 /* Print CPU time for all processes & children */ 559 prttime(jobtime, 8); 560 /* Print cpu time for interesting process */ 561 prttime(proctime, 8); 562 } 563 /* "Most interesting" process */ 564 PRINTF(("%-.32s\n", what)); 565 } 566 567 if (fclose(stdout) == EOF) 568 err(EXIT_FAILURE, gettext("fclose failed")); 569 570 return (0); 571 } 572 573 #define HR (60 * 60) 574 #define DAY (24 * HR) 575 #define MON (30 * DAY) 576 577 /* 578 * Prttime prints an elapsed time in hours, minutes, or seconds, 579 * right-justified with the rightmost column always blank. 580 * The second argument is the minimum field width. 581 */ 582 static void 583 prttime(time_t tim, int width) 584 { 585 char value[36]; 586 587 if (tim >= 36 * 60) { 588 (void) snprintf(value, sizeof (value), "%d:%02d:%02d", 589 (int)tim / HR, (int)(tim % HR) / 60, (int)tim % 60); 590 } else if (tim >= 60) { 591 (void) snprintf(value, sizeof (value), "%d:%02d", 592 (int)tim / 60, (int)tim % 60); 593 } else if (tim > 0) { 594 (void) snprintf(value, sizeof (value), "%d", (int)tim); 595 } else { 596 (void) strlcpy(value, "0", sizeof (value)); 597 } 598 width = (width > 2) ? width - 1 : 1; 599 PRINTF(("%*s ", width, value)); 600 } 601 602 /* 603 * Prints the ISO date or time given a pointer to a time of day, 604 * left-justfied in a 12-character expanding field with the 605 * rightmost column always blank. 606 * Includes a dcgettext() override in case a message catalog is needed. 607 */ 608 static void 609 prtat(time_t *time) 610 { 611 struct tm *p; 612 613 p = localtime(time); 614 if (now - *time <= 18 * HR) { 615 char timestr[50]; 616 617 (void) strftime(timestr, sizeof (timestr), 618 dcgettext(NULL, "%T", LC_TIME), p); 619 PRINTF(("%-11s ", timestr)); 620 } else if (now - *time <= 7 * DAY) { 621 char weekdaytime[20]; 622 623 (void) strftime(weekdaytime, sizeof (weekdaytime), 624 dcgettext(NULL, "%a %H:%M", LC_TIME), p); 625 PRINTF(("%-11s ", weekdaytime)); 626 } else { 627 char monthtime[20]; 628 629 (void) strftime(monthtime, sizeof (monthtime), 630 dcgettext(NULL, "%F", LC_TIME), p); 631 PRINTF(("%-11s ", monthtime)); 632 } 633 } 634 635 /* 636 * find & return number of minutes current tty has been idle 637 */ 638 static time_t 639 findidle(char *devname) 640 { 641 struct stat stbuf; 642 time_t lastaction, diff; 643 char ttyname[64]; 644 645 (void) strlcpy(ttyname, "/dev/", sizeof (ttyname)); 646 (void) strlcat(ttyname, devname, sizeof (ttyname)); 647 if (stat(ttyname, &stbuf) != -1) { 648 lastaction = stbuf.st_atime; 649 diff = now - lastaction; 650 diff = DIV60(diff); 651 if (diff < 0) 652 diff = 0; 653 } else 654 diff = 0; 655 return (diff); 656 } 657 658 /* 659 * given a pointer to the argument string get rid of unsavory characters. 660 */ 661 static void 662 clnarglist(char *arglist) 663 { 664 char *c; 665 int err = 0; 666 667 /* get rid of unsavory characters */ 668 for (c = arglist; *c != '\0'; c++) { 669 if ((*c < ' ') || (*c > 0176)) { 670 if (err++ > 5) { 671 *arglist = '\0'; 672 break; 673 } 674 *c = '?'; 675 } 676 } 677 } 678 679 static int 680 priv_proc_open(const char *path, int oflag) 681 { 682 int fd, errsave = 0; 683 684 if (__priv_bracket(PRIV_ON) != 0) 685 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 686 687 do { 688 fd = open(path, oflag); 689 if (fd < 0) 690 errsave = errno; 691 } while (fd < 0 && errno == EAGAIN); 692 693 if (__priv_bracket(PRIV_OFF) != 0) 694 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 695 696 if (fd < 0) 697 errno = errsave; 698 699 return (fd); 700 } 701 702 static int 703 priv_proc_openat(int dfd, const char *path, int mode) 704 { 705 int fd, errsave = 0; 706 707 if (__priv_bracket(PRIV_ON) != 0) 708 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 709 710 do { 711 fd = openat(dfd, path, mode); 712 if (fd < 0) 713 errsave = errno; 714 } while (fd < 0 && errno == EAGAIN); 715 716 if (__priv_bracket(PRIV_OFF) != 0) 717 err(EXIT_FAILURE, gettext("privilege bracketing failed")); 718 719 if (fd < 0) 720 errno = errsave; 721 722 return (fd); 723 } 724 725 static boolean_t 726 do_proc_read(int fd, void *buf, size_t bufsize) 727 { 728 ssize_t n; 729 730 do { 731 n = pread(fd, buf, bufsize, 0); 732 if (n == bufsize) 733 return (B_TRUE); 734 /* 735 * Retry on a partial read or EAGAIN, otherwise fail 736 */ 737 } while (n >= 0 || errno == EAGAIN); 738 739 return (B_FALSE); 740 } 741