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
main(int argc,char * argv[])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
prttime(time_t tim,int width)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
prtat(time_t * time)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
findidle(char * devname)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
clnarglist(char * arglist)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
priv_proc_open(const char * path,int oflag)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
priv_proc_openat(int dfd,const char * path,int mode)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
do_proc_read(int fd,void * buf,size_t bufsize)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