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