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