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