xref: /illumos-gate/usr/src/cmd/fs.d/ufs/quota/quota.c (revision 168c213023b7f347f11abfc72f448b0c621ab718)
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 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*
30  * University Copyright- Copyright (c) 1982, 1986, 1988
31  * The Regents of the University of California
32  * All Rights Reserved
33  *
34  * University Acknowledgment- Portions of this document are derived from
35  * software developed by the University of California, Berkeley, and its
36  * contributors.
37  */
38 
39 #pragma ident	"%Z%%M%	%I%	%E% SMI"
40 
41 /*
42  * Disk quota reporting program.
43  */
44 #include <stdio.h>
45 #include <sys/mnttab.h>
46 #include <ctype.h>
47 #include <pwd.h>
48 #include <errno.h>
49 #include <fcntl.h>
50 #include <memory.h>
51 #include <sys/time.h>
52 #include <sys/param.h>
53 #include <sys/types.h>
54 #include <sys/sysmacros.h>
55 #include <sys/mntent.h>
56 #include <sys/file.h>
57 #include <sys/stat.h>
58 #include <sys/fs/ufs_quota.h>
59 #include <priv_utils.h>
60 #include <locale.h>
61 
62 int	vflag;
63 int	nolocalquota;
64 
65 extern int	optind;
66 extern char	*optarg;
67 
68 #define	QFNAME	"quotas"
69 
70 #if DEV_BSIZE < 1024
71 #define	kb(x)	((x) / (1024 / DEV_BSIZE))
72 #else
73 #define	kb(x)	((x) * (DEV_BSIZE / 1024))
74 #endif
75 
76 #if	!defined(TEXT_DOMAIN)   /* Should be defined by cc -D */
77 #define	TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
78 #endif
79 
80 static int getnfsquota(char *, char *, uid_t, struct dqblk *);
81 static void showuid(uid_t);
82 static void showquotas(uid_t, char *);
83 static void warn(struct mnttab *, struct dqblk *);
84 static void heading(uid_t, char *);
85 static void prquota(struct mnttab *, struct dqblk *);
86 static void fmttime(char *, long);
87 
88 int
89 main(int argc, char *argv[])
90 {
91 	int	opt;
92 	int	i;
93 	int	status = 0;
94 
95 	(void) setlocale(LC_ALL, "");
96 	(void) textdomain(TEXT_DOMAIN);
97 
98 	/*
99 	 * PRIV_FILE_DAC_READ is needed to read the QFNAME file
100 	 * Clear all other privleges from the limit set, and add
101 	 * the required privilege to the bracketed set.
102 	 */
103 
104 	if (__init_suid_priv(PU_CLEARLIMITSET, PRIV_FILE_DAC_READ,
105 				NULL) == -1) {
106 		(void) fprintf(stderr,
107 				gettext("Insufficient privileges, "
108 				"quota must be set-uid root or have "
109 				"file_dac_read privileges\n"));
110 
111 		exit(1);
112 	}
113 
114 	while ((opt = getopt(argc, argv, "vV")) != EOF) {
115 		switch (opt) {
116 
117 		case 'v':
118 			vflag++;
119 			break;
120 
121 		case 'V':		/* Print command line */
122 			{
123 			char	*opt_text;
124 			int	opt_count;
125 
126 			(void) fprintf(stdout, "quota -F UFS ");
127 			for (opt_count = 1; opt_count < argc; opt_count++) {
128 				opt_text = argv[opt_count];
129 				if (opt_text)
130 				    (void) fprintf(stdout, " %s ", opt_text);
131 			}
132 			(void) fprintf(stdout, "\n");
133 			}
134 			break;
135 
136 		case '?':
137 			fprintf(stderr, "ufs usage: quota [-v] [username]\n");
138 			exit(32);
139 		}
140 	}
141 	if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 && errno == EINVAL) {
142 		if (vflag)
143 			fprintf(stderr, "There are no quotas on this system\n");
144 		nolocalquota++;
145 	}
146 	if (argc == optind) {
147 		showuid(getuid());
148 		exit(0);
149 	}
150 	for (i = optind; i < argc; i++) {
151 		if (alldigits(argv[i])) {
152 			showuid((uid_t)atoi(argv[i]));
153 		} else
154 			status |= showname(argv[i]);
155 	}
156 	__priv_relinquish();
157 	return (status);
158 }
159 
160 static void
161 showuid(uid_t uid)
162 {
163 	struct passwd *pwd = getpwuid(uid);
164 
165 	if (uid == 0) {
166 		if (vflag)
167 			printf("no disk quota for uid 0\n");
168 		return;
169 	}
170 	if (pwd == NULL)
171 		showquotas(uid, "(no account)");
172 	else
173 		showquotas(uid, pwd->pw_name);
174 }
175 
176 int
177 showname(char *name)
178 {
179 	struct passwd *pwd = getpwnam(name);
180 
181 	if (pwd == NULL) {
182 		fprintf(stderr, "quota: %s: unknown user\n", name);
183 		return (32);
184 	}
185 	if (pwd->pw_uid == 0) {
186 		if (vflag)
187 			printf("no disk quota for %s (uid 0)\n", name);
188 		return (0);
189 	}
190 	showquotas(pwd->pw_uid, name);
191 	return (0);
192 }
193 
194 #include "../../nfs/lib/replica.h"
195 
196 static void
197 showquotas(uid_t uid, char *name)
198 {
199 	struct mnttab mnt;
200 	FILE *mtab;
201 	struct dqblk dqblk;
202 	uid_t myuid;
203 
204 	myuid = getuid();
205 	if (uid != myuid && myuid != 0) {
206 		printf("quota: %s (uid %d): permission denied\n", name, uid);
207 		exit(32);
208 	}
209 	if (vflag)
210 		heading(uid, name);
211 	mtab = fopen(MNTTAB, "r");
212 	while (getmntent(mtab, &mnt) == NULL) {
213 		if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0) {
214 			if (nolocalquota ||
215 			    (quotactl(Q_GETQUOTA,
216 				mnt.mnt_mountp, uid, &dqblk) != 0 &&
217 				!(vflag && getdiskquota(&mnt, uid, &dqblk))))
218 					continue;
219 		} else if (strcmp(mnt.mnt_fstype, MNTTYPE_NFS) == 0) {
220 
221 			struct replica *rl;
222 			int count;
223 
224 			if (hasopt(MNTOPT_NOQUOTA, mnt.mnt_mntopts))
225 				continue;
226 
227 			/*
228 			 * Skip quota processing if mounted with public
229 			 * option. We are not likely to be able to pierce
230 			 * a fire wall to contact the quota server.
231 			 */
232 			if (hasopt(MNTOPT_PUBLIC, mnt.mnt_mntopts))
233 				continue;
234 
235 			rl = parse_replica(mnt.mnt_special, &count);
236 
237 			if (rl == NULL) {
238 
239 				if (count < 0)
240 					fprintf(stderr, "cannot find hostname "
241 					    "and/or pathname for %s\n",
242 					    mnt.mnt_mountp);
243 				else
244 					fprintf(stderr, "no memory to parse "
245 					    "mnttab entry for %s\n",
246 					    mnt.mnt_mountp);
247 				continue;
248 			}
249 
250 			/*
251 			 * We skip quota reporting on mounts with replicas
252 			 * for the following reasons:
253 			 *
254 			 * (1) Very little point in reporting quotas on
255 			 * a set of read-only replicas ... how will the
256 			 * user correct the problem?
257 			 *
258 			 * (2) Which replica would we report the quota
259 			 * for? If we pick the current replica, what
260 			 * happens when a fail over event occurs? The
261 			 * next time quota is run, the quota will look
262 			 * all different, or there won't even be one.
263 			 * This has the potential to break scripts.
264 			 *
265 			 * If we prnt quouta for all replicas, how do
266 			 * we present the output without breaking scripts?
267 			 */
268 
269 			if (count > 1) {
270 				free_replica(rl, count);
271 				continue;
272 			}
273 
274 			/*
275 			 * Skip file systems mounted using public fh.
276 			 * We are not likely to be able to pierce
277 			 * a fire wall to contact the quota server.
278 			 */
279 			if (strcmp(rl[0].host, "nfs") == 0 &&
280 			    strncmp(rl[0].path, "//", 2) == 0) {
281 				free_replica(rl, count);
282 				continue;
283 			}
284 
285 			if (!getnfsquota(rl[0].host, rl[0].path, uid, &dqblk)) {
286 				free_replica(rl, count);
287 				continue;
288 			}
289 
290 			free_replica(rl, count);
291 
292 		} else {
293 			continue;
294 		}
295 		if (dqblk.dqb_bsoftlimit == 0 && dqblk.dqb_bhardlimit == 0 &&
296 		    dqblk.dqb_fsoftlimit == 0 && dqblk.dqb_fhardlimit == 0)
297 			continue;
298 		if (vflag)
299 			prquota(&mnt, &dqblk);
300 		else
301 			warn(&mnt, &dqblk);
302 	}
303 	fclose(mtab);
304 }
305 
306 static void
307 warn(struct mnttab *mntp, struct dqblk *dqp)
308 {
309 	struct timeval tv;
310 
311 	time(&(tv.tv_sec));
312 	tv.tv_usec = 0;
313 	if (dqp->dqb_bhardlimit &&
314 		dqp->dqb_curblocks >= dqp->dqb_bhardlimit) {
315 		printf("Block limit reached on %s\n", mntp->mnt_mountp);
316 	} else if (dqp->dqb_bsoftlimit &&
317 		dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) {
318 		if (dqp->dqb_btimelimit == 0) {
319 			printf("Over disk quota on %s, remove %luK\n",
320 			    mntp->mnt_mountp,
321 			    kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1));
322 		} else if (dqp->dqb_btimelimit > tv.tv_sec) {
323 			char btimeleft[80];
324 
325 			fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec);
326 			printf("Over disk quota on %s, remove %luK within %s\n",
327 			    mntp->mnt_mountp,
328 			    kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1),
329 			    btimeleft);
330 		} else {
331 			printf(
332 		"Over disk quota on %s, time limit has expired, remove %luK\n",
333 			    mntp->mnt_mountp,
334 			    kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1));
335 		}
336 	}
337 	if (dqp->dqb_fhardlimit &&
338 	    dqp->dqb_curfiles >= dqp->dqb_fhardlimit) {
339 		printf("File count limit reached on %s\n", mntp->mnt_mountp);
340 	} else if (dqp->dqb_fsoftlimit &&
341 	    dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) {
342 		if (dqp->dqb_ftimelimit == 0) {
343 			printf("Over file quota on %s, remove %lu file%s\n",
344 			    mntp->mnt_mountp,
345 			    dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1,
346 			    ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ?
347 				"s" : ""));
348 		} else if (dqp->dqb_ftimelimit > tv.tv_sec) {
349 			char ftimeleft[80];
350 
351 			fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec);
352 			printf(
353 "Over file quota on %s, remove %lu file%s within %s\n",
354 			    mntp->mnt_mountp,
355 			    dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1,
356 			    ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ?
357 				"s" : ""), ftimeleft);
358 		} else {
359 			printf(
360 "Over file quota on %s, time limit has expired, remove %lu file%s\n",
361 			    mntp->mnt_mountp,
362 			    dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1,
363 			    ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ?
364 				"s" : ""));
365 		}
366 	}
367 }
368 
369 static void
370 heading(uid_t uid, char *name)
371 {
372 	printf("Disk quotas for %s (uid %ld):\n", name, (long)uid);
373 	printf("%-12s %7s%7s%7s%12s%7s%7s%7s%12s\n",
374 		"Filesystem",
375 		"usage",
376 		"quota",
377 		"limit",
378 		"timeleft",
379 		"files",
380 		"quota",
381 		"limit",
382 		"timeleft");
383 }
384 
385 static void
386 prquota(struct mnttab *mntp, struct dqblk *dqp)
387 {
388 	struct timeval tv;
389 	char ftimeleft[80], btimeleft[80];
390 	char *cp;
391 
392 	time(&(tv.tv_sec));
393 	tv.tv_usec = 0;
394 	if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) {
395 		if (dqp->dqb_btimelimit == 0) {
396 			strcpy(btimeleft, "NOT STARTED");
397 		} else if (dqp->dqb_btimelimit > tv.tv_sec) {
398 			fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec);
399 		} else {
400 			strcpy(btimeleft, "EXPIRED");
401 		}
402 	} else {
403 		btimeleft[0] = '\0';
404 	}
405 	if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) {
406 		if (dqp->dqb_ftimelimit == 0) {
407 			strcpy(ftimeleft, "NOT STARTED");
408 		} else if (dqp->dqb_ftimelimit > tv.tv_sec) {
409 			fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec);
410 		} else {
411 			strcpy(ftimeleft, "EXPIRED");
412 		}
413 	} else {
414 		ftimeleft[0] = '\0';
415 	}
416 	if (strlen(mntp->mnt_mountp) > 12) {
417 		printf("%s\n", mntp->mnt_mountp);
418 		cp = "";
419 	} else {
420 		cp = mntp->mnt_mountp;
421 	}
422 	printf("%-12.12s %7d %6d %6d %11s %6d %6d %6d %11s\n",
423 	    cp,
424 	    kb(dqp->dqb_curblocks),
425 	    kb(dqp->dqb_bsoftlimit),
426 	    kb(dqp->dqb_bhardlimit),
427 	    btimeleft,
428 	    dqp->dqb_curfiles,
429 	    dqp->dqb_fsoftlimit,
430 	    dqp->dqb_fhardlimit,
431 	    ftimeleft);
432 }
433 
434 static void
435 fmttime(char *buf, long time)
436 {
437 	int i;
438 	static struct {
439 		int c_secs;		/* conversion units in secs */
440 		char *c_str;		/* unit string */
441 	} cunits [] = {
442 		{60*60*24*28, "months"},
443 		{60*60*24*7, "weeks"},
444 		{60*60*24, "days"},
445 		{60*60, "hours"},
446 		{60, "mins"},
447 		{1, "secs"}
448 	};
449 
450 	if (time <= 0) {
451 		strcpy(buf, "EXPIRED");
452 		return;
453 	}
454 	for (i = 0; i < sizeof (cunits)/sizeof (cunits[0]); i++) {
455 		if (time >= cunits[i].c_secs)
456 			break;
457 	}
458 	sprintf(buf, "%.1f %s", (double)time/cunits[i].c_secs, cunits[i].c_str);
459 }
460 
461 int
462 alldigits(char *s)
463 {
464 	int c;
465 
466 	c = *s++;
467 	do {
468 		if (!isdigit(c))
469 			return (0);
470 	} while (c = *s++);
471 	return (1);
472 }
473 
474 int
475 getdiskquota(struct mnttab *mntp, uid_t uid, struct dqblk *dqp)
476 {
477 	int fd;
478 	dev_t fsdev;
479 	struct stat64 statb;
480 	char qfilename[MAXPATHLEN];
481 
482 	if (stat64(mntp->mnt_special, &statb) < 0 ||
483 	    (statb.st_mode & S_IFMT) != S_IFBLK)
484 		return (0);
485 	fsdev = statb.st_rdev;
486 	(void) snprintf(qfilename, sizeof (qfilename), "%s/%s",
487 		mntp->mnt_mountp, QFNAME);
488 	if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev)
489 		return (0);
490 	(void) __priv_bracket(PRIV_ON);
491 	fd = open64(qfilename, O_RDONLY);
492 	(void) __priv_bracket(PRIV_OFF);
493 	if (fd < 0)
494 		return (0);
495 	(void) llseek(fd, (offset_t)dqoff(uid), L_SET);
496 	switch (read(fd, dqp, sizeof (struct dqblk))) {
497 	case 0:				/* EOF */
498 		/*
499 		 * Convert implicit 0 quota (EOF)
500 		 * into an explicit one (zero'ed dqblk).
501 		 */
502 		memset((caddr_t)dqp, 0, sizeof (struct dqblk));
503 		break;
504 
505 	case sizeof (struct dqblk):	/* OK */
506 		break;
507 
508 	default:			/* ERROR */
509 		close(fd);
510 		return (0);
511 	}
512 	close(fd);
513 	return (1);
514 }
515 
516 int
517 quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr)
518 {
519 	int		fd;
520 	int		status;
521 	struct quotctl	quota;
522 	char		qfile[MAXPATHLEN];
523 
524 	FILE		*fstab;
525 	struct mnttab	mnt;
526 
527 
528 	if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
529 	/*
530 	 * Find the mount point of any mounted file system. This is
531 	 * because the ioctl that implements the quotactl call has
532 	 * to go to a real file, and not to the block device.
533 	 */
534 		if ((fstab = fopen(MNTTAB, "r")) == NULL) {
535 			fprintf(stderr, "%s: ", MNTTAB);
536 			perror("open");
537 			exit(32);
538 		}
539 		fd = -1;
540 		while ((status = getmntent(fstab, &mnt)) == NULL) {
541 			if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0 ||
542 				hasopt(MNTOPT_RO, mnt.mnt_mntopts))
543 				continue;
544 			if ((strlcpy(qfile, mnt.mnt_mountp,
545 				sizeof (qfile)) >= sizeof (qfile)) ||
546 			    (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
547 				sizeof (qfile))) {
548 				continue;
549 			}
550 			(void) __priv_bracket(PRIV_ON);
551 			fd = open64(qfile, O_RDONLY);
552 			(void) __priv_bracket(PRIV_OFF);
553 			if (fd != -1)
554 				break;
555 		}
556 		fclose(fstab);
557 		if (fd == -1) {
558 			errno = ENOENT;
559 			return (-1);
560 		}
561 	} else {
562 		if (mountp == NULL || mountp[0] == '\0') {
563 			errno = ENOENT;
564 			return (-1);
565 		}
566 		if ((strlcpy(qfile, mountp, sizeof (qfile)) >= sizeof
567 			(qfile)) ||
568 		    (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof
569 			(qfile))) {
570 			errno = ENOENT;
571 			return (-1);
572 		}
573 		(void) __priv_bracket(PRIV_ON);
574 		fd = open64(qfile, O_RDONLY);
575 		(void) __priv_bracket(PRIV_OFF);
576 		if (fd < 0)
577 			return (-1);
578 	}	/* else */
579 	quota.op = cmd;
580 	quota.uid = uid;
581 	quota.addr = addr;
582 	status = ioctl(fd, Q_QUOTACTL, &quota);
583 	if (fd != 0)
584 		close(fd);
585 	return (status);
586 }
587 
588 
589 /*
590  * Return 1 if opt appears in optlist
591  */
592 int
593 hasopt(char *opt, char *optlist)
594 {
595 	char *value;
596 	char *opts[2];
597 
598 	opts[0] = opt;
599 	opts[1] = NULL;
600 
601 	if (optlist == NULL)
602 		return (0);
603 	while (*optlist != '\0') {
604 		if (getsubopt(&optlist, opts, &value) == 0)
605 			return (1);
606 	}
607 	return (0);
608 }
609 
610 #include <rpc/rpc.h>
611 #include <netdb.h>
612 #include <rpcsvc/rquota.h>
613 
614 static int
615 getnfsquota(char *hostp, char *path, uid_t uid, struct dqblk *dqp)
616 {
617 	struct getquota_args gq_args;
618 	struct getquota_rslt gq_rslt;
619 	struct rquota *rquota;
620 	extern char *strchr();
621 
622 	gq_args.gqa_pathp = path;
623 	gq_args.gqa_uid = uid;
624 	if (callaurpc(hostp, RQUOTAPROG, RQUOTAVERS,
625 	    (vflag? RQUOTAPROC_GETQUOTA: RQUOTAPROC_GETACTIVEQUOTA),
626 	    xdr_getquota_args, &gq_args, xdr_getquota_rslt, &gq_rslt) != 0) {
627 		return (0);
628 	}
629 	switch (gq_rslt.status) {
630 	case Q_OK:
631 		{
632 		struct timeval tv;
633 		u_longlong_t limit;
634 
635 		rquota = &gq_rslt.getquota_rslt_u.gqr_rquota;
636 
637 		if (!vflag && rquota->rq_active == FALSE)
638 			return (0);
639 		gettimeofday(&tv, NULL);
640 		limit = (u_longlong_t)(rquota->rq_bhardlimit) *
641 		    rquota->rq_bsize / DEV_BSIZE;
642 		dqp->dqb_bhardlimit = limit;
643 		limit = (u_longlong_t)(rquota->rq_bsoftlimit) *
644 		    rquota->rq_bsize / DEV_BSIZE;
645 		dqp->dqb_bsoftlimit = limit;
646 		limit = (u_longlong_t)(rquota->rq_curblocks) *
647 		    rquota->rq_bsize / DEV_BSIZE;
648 		dqp->dqb_curblocks = limit;
649 		dqp->dqb_fhardlimit = rquota->rq_fhardlimit;
650 		dqp->dqb_fsoftlimit = rquota->rq_fsoftlimit;
651 		dqp->dqb_curfiles = rquota->rq_curfiles;
652 		dqp->dqb_btimelimit =
653 		    tv.tv_sec + rquota->rq_btimeleft;
654 		dqp->dqb_ftimelimit =
655 		    tv.tv_sec + rquota->rq_ftimeleft;
656 		return (1);
657 		}
658 
659 	case Q_NOQUOTA:
660 		break;
661 
662 	case Q_EPERM:
663 		fprintf(stderr, "quota permission error, host: %s\n", hostp);
664 		break;
665 
666 	default:
667 		fprintf(stderr, "bad rpc result, host: %s\n",  hostp);
668 		break;
669 	}
670 	return (0);
671 }
672 
673 int
674 callaurpc(char *host, int prognum, int versnum, int procnum,
675 		xdrproc_t inproc, char *in, xdrproc_t outproc, char *out)
676 {
677 	static enum clnt_stat clnt_stat;
678 	struct timeval tottimeout;
679 
680 	static CLIENT *cl = NULL;
681 	static int oldprognum, oldversnum;
682 	static char oldhost[MAXHOSTNAMELEN+1];
683 
684 	/*
685 	 * Cache the client handle in case there are lots
686 	 * of entries in the /etc/mnttab for the same
687 	 * server. If the server returns an error, don't
688 	 * make further calls.
689 	 */
690 	if (cl == NULL || oldprognum != prognum || oldversnum != versnum ||
691 		strcmp(oldhost, host) != 0) {
692 		if (cl) {
693 			clnt_destroy(cl);
694 			cl = NULL;
695 		}
696 		cl = clnt_create(host, prognum, versnum, "udp");
697 		if (cl == NULL)
698 			return ((int)RPC_TIMEDOUT);
699 		if ((cl->cl_auth = authunix_create_default()) == NULL) {
700 			clnt_destroy(cl);
701 			return (RPC_CANTSEND);
702 		}
703 		oldprognum = prognum;
704 		oldversnum = versnum;
705 		(void) strcpy(oldhost, host);
706 		clnt_stat = RPC_SUCCESS;
707 	}
708 
709 	if (clnt_stat != RPC_SUCCESS)
710 		return ((int)clnt_stat);	/* don't bother retrying */
711 
712 	tottimeout.tv_sec  = 5;
713 	tottimeout.tv_usec = 0;
714 	clnt_stat = clnt_call(cl, procnum, inproc, in,
715 	    outproc, out, tottimeout);
716 
717 	return ((int)clnt_stat);
718 }
719