xref: /illumos-gate/usr/src/cmd/fs.d/ufs/quotacheck/quotacheck.c (revision 1a2d662a91cee3bf82f41cd47c7ae6f3825d9db2)
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 /*
40  * Fix up / report on disc quotas & usage
41  */
42 #include <stdlib.h>
43 #include <string.h>
44 #include <stdio.h>
45 #include <ctype.h>
46 #include <signal.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <sys/filio.h>
50 #include <limits.h>
51 #include <sys/param.h>
52 #include <sys/types.h>
53 #include <sys/mntent.h>
54 
55 #include <sys/vnode.h>
56 #include <sys/fs/ufs_inode.h>
57 #include <sys/fs/ufs_fs.h>
58 #include <sys/fs/ufs_quota.h>
59 #include <sys/stat.h>
60 #include <sys/wait.h>
61 #include <sys/mnttab.h>
62 #include <sys/vfstab.h>
63 #include <pwd.h>
64 #include <iso/limits_iso.h>
65 
66 union {
67 	struct	fs	sblk;
68 	char	dummy[MAXBSIZE];
69 } un;
70 #define	sblock	un.sblk
71 
72 #define	ITABSZ	256
73 struct	dinode	itab[ITABSZ];
74 struct	dinode	*dp;
75 
76 struct fileusage {
77 	struct fileusage *fu_next;
78 	ulong_t fu_curfiles;
79 	uint64_t fu_curblocks;
80 	uid_t	fu_uid;
81 };
82 #define	FUHASH 997
83 struct fileusage *fuhead[FUHASH];
84 struct fileusage *lookup(uid_t);
85 struct fileusage *adduid(uid_t);
86 
87 int fi;
88 ino_t ino;
89 struct	dinode	*ginode();
90 char *mntopt(), *hasvfsopt(), *hasmntopt();
91 
92 extern int	optind;
93 extern char	*optarg;
94 extern int	fsync(int);
95 
96 static void acct();
97 static void bread();
98 static void usage();
99 static int chkquota();
100 static int quotactl();
101 static int preen();
102 static int waiter();
103 static int oneof();
104 
105 int	vflag;		/* verbose */
106 int	aflag;		/* all file systems */
107 int	pflag;		/* fsck like parallel check */
108 int	fflag;		/* force flag */
109 
110 #define	QFNAME "quotas"
111 #define	CHUNK	50
112 char **listbuf;
113 struct dqblk zerodqbuf;
114 struct fileusage zerofileusage;
115 
116 int
117 main(int argc, char **argv)
118 {
119 	struct mnttab mntp;
120 	struct vfstab vfsbuf;
121 	char **listp;
122 	int listcnt;
123 	int listmax = 0;
124 	char quotafile[MAXPATHLEN];
125 	FILE *mtab, *vfstab;
126 	int errs = 0;
127 	int	opt;
128 
129 	if ((listbuf = (char **)malloc(sizeof (char *) * CHUNK)) == NULL) {
130 		fprintf(stderr, "Can't alloc lisbuf array.");
131 		exit(31+1);
132 	}
133 	listmax = CHUNK;
134 	while ((opt = getopt(argc, argv, "vapVf")) != EOF) {
135 		switch (opt) {
136 
137 		case 'v':
138 			vflag++;
139 			break;
140 
141 		case 'a':
142 			aflag++;
143 			break;
144 
145 		case 'p':
146 			pflag++;
147 			break;
148 
149 		case 'V':		/* Print command line */
150 			{
151 				char		*opt_text;
152 				int		opt_count;
153 
154 				(void) fprintf(stdout, "quotacheck -F UFS ");
155 				for (opt_count = 1; opt_count < argc;
156 				    opt_count++) {
157 					opt_text = argv[opt_count];
158 					if (opt_text)
159 						(void) fprintf(stdout, " %s ",
160 						    opt_text);
161 				}
162 				(void) fprintf(stdout, "\n");
163 			}
164 			break;
165 
166 		case 'f':
167 			fflag++;
168 			break;
169 
170 		case '?':
171 			usage();
172 		}
173 	}
174 	if (argc <= optind && !aflag) {
175 		usage();
176 	}
177 
178 	if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 &&
179 	    errno == EINVAL && vflag)
180 		printf("Warning: Quotas are not compiled into this kernel\n");
181 	sync();
182 
183 	if (aflag) {
184 		/*
185 		 * Go through vfstab and make a list of appropriate
186 		 * filesystems.
187 		 */
188 		listp = listbuf;
189 		listcnt = 0;
190 		if ((vfstab = fopen(VFSTAB, "r")) == NULL) {
191 			fprintf(stderr, "Can't open ");
192 			perror(VFSTAB);
193 			exit(31+8);
194 		}
195 		while (getvfsent(vfstab, &vfsbuf) == 0) {
196 			if (strcmp(vfsbuf.vfs_fstype, MNTTYPE_UFS) != 0 ||
197 			    (vfsbuf.vfs_mntopts == 0) ||
198 			    hasvfsopt(&vfsbuf, MNTOPT_RO) ||
199 			    (!hasvfsopt(&vfsbuf, MNTOPT_RQ) &&
200 			    !hasvfsopt(&vfsbuf, MNTOPT_QUOTA)))
201 				continue;
202 			*listp = malloc(strlen(vfsbuf.vfs_special) + 1);
203 			strcpy(*listp, vfsbuf.vfs_special);
204 			listp++;
205 			listcnt++;
206 			/* grow listbuf if needed */
207 			if (listcnt >= listmax) {
208 				listmax += CHUNK;
209 				listbuf = (char **)realloc(listbuf,
210 					sizeof (char *) * listmax);
211 				if (listbuf == NULL) {
212 					fprintf(stderr,
213 						"Can't grow listbuf.\n");
214 					exit(31+1);
215 				}
216 				listp = &listbuf[listcnt];
217 			}
218 		}
219 		fclose(vfstab);
220 		*listp = (char *)0;
221 		listp = listbuf;
222 	} else {
223 		listp = &argv[optind];
224 		listcnt = argc - optind;
225 	}
226 	if (pflag) {
227 		errs = preen(listcnt, listp);
228 	} else {
229 		if ((mtab = fopen(MNTTAB, "r")) == NULL) {
230 			fprintf(stderr, "Can't open ");
231 			perror(MNTTAB);
232 			exit(31+8);
233 		}
234 		while (getmntent(mtab, &mntp) == 0) {
235 			if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 &&
236 			    !hasmntopt(&mntp, MNTOPT_RO) &&
237 			    (oneof(mntp.mnt_special, listp, listcnt) ||
238 			    oneof(mntp.mnt_mountp, listp, listcnt))) {
239 				(void) snprintf(quotafile, sizeof (quotafile),
240 					"%s/%s", mntp.mnt_mountp, QFNAME);
241 				errs +=
242 				    chkquota(mntp.mnt_special,
243 					mntp.mnt_mountp, quotafile);
244 			}
245 		}
246 		fclose(mtab);
247 	}
248 	while (listcnt--) {
249 		if (*listp) {
250 			fprintf(stderr, "Cannot check %s\n", *listp);
251 			errs++;
252 		}
253 		listp++;
254 	}
255 	if (errs > 0)
256 		errs += 31;
257 	return (errs);
258 }
259 
260 struct active {
261 	char *rdev;
262 	pid_t pid;
263 	struct active *nxt;
264 };
265 
266 int
267 preen(int listcnt, char **listp)
268 {
269 	int i, rc, errs;
270 	char **lp, *rdev, *bdev;
271 	extern char *getfullrawname(), *getfullblkname();
272 	struct mnttab mntp, mpref;
273 	struct active *alist, *ap;
274 	FILE *mtab;
275 	char quotafile[MAXPATHLEN];
276 	char name[MAXPATHLEN];
277 	int nactive, serially;
278 
279 	if ((mtab = fopen(MNTTAB, "r")) == NULL) {
280 		fprintf(stderr, "Can't open ");
281 		perror(MNTTAB);
282 		exit(31+8);
283 	}
284 	memset(&mpref, 0, sizeof (struct mnttab));
285 	errs = 0;
286 
287 	for (lp = listp, i = 0; i < listcnt; lp++, i++) {
288 		serially = 0;
289 		rdev = getfullrawname(*lp);
290 		if (rdev == NULL || *rdev == '\0') {
291 			fprintf(stderr, "can't get rawname for `%s'\n", *lp);
292 			serially = 1;
293 		} else if (preen_addev(rdev) != 0) {
294 			fprintf(stderr, "preen_addev error\n");
295 			serially = 1;
296 		}
297 
298 		if (rdev != NULL)
299 			free(rdev);
300 
301 		if (serially) {
302 			rewind(mtab);
303 			mpref.mnt_special = *lp;
304 			if (getmntany(mtab, &mntp, &mpref) == 0 &&
305 			    strcmp(mntp.mnt_fstype, MNTTYPE_UFS) == 0 &&
306 			    !hasmntopt(&mntp, MNTOPT_RO)) {
307 				errs += (31+chkquota(mntp.mnt_special,
308 				    mntp.mnt_mountp, quotafile));
309 				*lp = (char *)0;
310 			}
311 		}
312 	}
313 
314 	nactive = 0;
315 	alist = NULL;
316 	while ((rc = preen_getdev(name)) > 0) {
317 		switch (rc) {
318 		case 1:
319 			bdev = getfullblkname(name);
320 			if (bdev == NULL || *bdev == '\0') {
321 				fprintf(stderr, "can't get blkname for `%s'\n",
322 				    name);
323 				if (bdev)
324 					free(bdev);
325 				continue;
326 			}
327 			rewind(mtab);
328 			mpref.mnt_special = bdev;
329 			if (getmntany(mtab, &mntp, &mpref) != 0) {
330 				fprintf(stderr, "`%s' not mounted?\n", name);
331 				preen_releasedev(name);
332 				free(bdev);
333 				continue;
334 			} else if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
335 			    hasmntopt(&mntp, MNTOPT_RO) ||
336 			    (!oneof(mntp.mnt_special, listp, listcnt) &&
337 			    !oneof(mntp.mnt_mountp, listp, listcnt))) {
338 				preen_releasedev(name);
339 				free(bdev);
340 				continue;
341 			}
342 			free(bdev);
343 			ap = (struct active *)malloc(sizeof (struct active));
344 			if (ap == NULL) {
345 				fprintf(stderr, "out of memory\n");
346 				exit(31+8);
347 			}
348 			ap->rdev = (char *)strdup(name);
349 			if (ap->rdev == NULL) {
350 				fprintf(stderr, "out of memory\n");
351 				exit(31+8);
352 			}
353 			ap->nxt = alist;
354 			alist = ap;
355 			switch (ap->pid = fork()) {
356 			case -1:
357 				perror("fork");
358 				exit(31+8);
359 				break;
360 			case 0:
361 				(void) snprintf(quotafile, sizeof (quotafile),
362 					"%s/%s", mntp.mnt_mountp, QFNAME);
363 				exit(31+chkquota(mntp.mnt_special,
364 				    mntp.mnt_mountp, quotafile));
365 				break;
366 			default:
367 				nactive++;
368 				break;
369 			}
370 			break;
371 		case 2:
372 			errs += waiter(&alist);
373 			nactive--;
374 			break;
375 		}
376 	}
377 	fclose(mtab);
378 
379 	while (nactive > 0) {
380 		errs += waiter(&alist);
381 		nactive--;
382 	}
383 	return (errs);
384 }
385 
386 int
387 waiter(struct active **alp)
388 {
389 	pid_t curpid;
390 	int status;
391 	struct active *ap, *lap;
392 
393 	curpid = wait(&status);
394 	if (curpid == -1) {
395 		if (errno == ECHILD)
396 			return (0);
397 		perror("wait");
398 		exit(31+8);
399 	}
400 
401 	for (lap = NULL, ap = *alp; ap != NULL; lap = ap, ap = ap->nxt) {
402 		if (ap->pid == curpid)
403 			break;
404 	}
405 
406 	if (ap == NULL) {
407 		fprintf(stderr, "wait returns unknown pid\n");
408 		exit(31+8);
409 	} else if (lap) {
410 		lap->nxt = ap->nxt;
411 	} else {
412 		*alp = ap->nxt;
413 	}
414 	preen_releasedev(ap->rdev);
415 	free(ap->rdev);
416 	free(ap);
417 	return (WHIBYTE(status));
418 }
419 
420 int
421 chkquota(char *fsdev, char *fsfile, char *qffile)
422 {
423 	struct fileusage *fup;
424 	dev_t quotadev;
425 	FILE *qf;
426 	uid_t uid;
427 	struct passwd *pw;
428 	int cg, i;
429 	char *rawdisk;
430 	struct stat64 statb;
431 	struct dqblk dqbuf;
432 	extern char *getfullrawname();
433 
434 	if ((rawdisk = getfullrawname(fsdev)) == NULL) {
435 		fprintf(stderr, "malloc failed\n");
436 		return (1);
437 	}
438 
439 	if (*rawdisk == '\0') {
440 		fprintf(stderr, "Could not find character device for %s\n",
441 		    fsdev);
442 		return (1);
443 	}
444 
445 	if (vflag)
446 		printf("*** Checking quotas for %s (%s)\n", rawdisk, fsfile);
447 	fi = open64(rawdisk, 0);
448 	if (fi < 0) {
449 		perror(rawdisk);
450 		return (1);
451 	}
452 	qf = fopen64(qffile, "r+");
453 	if (qf == NULL) {
454 		perror(qffile);
455 		close(fi);
456 		return (1);
457 	}
458 	if (fstat64(fileno(qf), &statb) < 0) {
459 		perror(qffile);
460 		fclose(qf);
461 		close(fi);
462 		return (1);
463 	}
464 	quotadev = statb.st_dev;
465 	if (stat64(fsdev, &statb) < 0) {
466 		perror(fsdev);
467 		fclose(qf);
468 		close(fi);
469 		return (1);
470 	}
471 	if (quotadev != statb.st_rdev) {
472 		fprintf(stderr, "%s dev (0x%x) mismatch %s dev (0x%x)\n",
473 			qffile, quotadev, fsdev, statb.st_rdev);
474 		fclose(qf);
475 		close(fi);
476 		return (1);
477 	}
478 	bread((diskaddr_t)SBLOCK, (char *)&sblock, SBSIZE);
479 
480 	/*
481 	 * Flush filesystem since we are going to read
482 	 * disk raw and we want to make sure everything is
483 	 * synced to disk before we read it.
484 	 */
485 
486 	if (ioctl(fileno(qf), _FIOFFS, NULL) == -1) {
487 		perror(qffile);
488 		(void) fprintf(stderr, "%s: cannot flush file system.\n",
489 				qffile);
490 		(void) fclose(qf);
491 		return (1);
492 	}
493 
494 	/*
495 	 * no need to quotacheck a rw, mounted, and logging file system
496 	 */
497 	if ((fflag == 0) && pflag &&
498 	    (FSOKAY == (sblock.fs_state + sblock.fs_time)) &&
499 	    (sblock.fs_clean == FSLOG)) {
500 		fclose(qf);
501 		close(fi);
502 		return (0);
503 	}
504 	ino = 0;
505 	for (cg = 0; cg < sblock.fs_ncg; cg++) {
506 		dp = NULL;
507 		for (i = 0; i < sblock.fs_ipg; i++)
508 			acct(ginode());
509 	}
510 	for (uid = 0; uid <= MAXUID && uid >= 0; uid++) {
511 		(void) fread(&dqbuf, sizeof (struct dqblk), 1, qf);
512 		if (feof(qf))
513 			break;
514 		fup = lookup(uid);
515 		if (fup == 0)
516 			fup = &zerofileusage;
517 		if (dqbuf.dqb_bhardlimit == 0 && dqbuf.dqb_bsoftlimit == 0 &&
518 		    dqbuf.dqb_fhardlimit == 0 && dqbuf.dqb_fsoftlimit == 0) {
519 			fup->fu_curfiles = 0;
520 			fup->fu_curblocks = 0;
521 		}
522 		if (dqbuf.dqb_curfiles == fup->fu_curfiles &&
523 		    dqbuf.dqb_curblocks == fup->fu_curblocks) {
524 			fup->fu_curfiles = 0;
525 			fup->fu_curblocks = 0;
526 			continue;
527 		}
528 		/*
529 		 * The maximum number of blocks that can be stored in the
530 		 * dqb_curblocks field in the quota record is 2^32 - 1,
531 		 * since it must fit into an unsigned 32-bit quantity.
532 		 * If this user has more blocks than that, print a message
533 		 * to that effect and reduce the count of allocated blocks
534 		 * to the maximum value, which is UINT_MAX.
535 		 */
536 		if (fup->fu_curblocks > UINT_MAX) {
537 			if (pflag || aflag)
538 				printf("%s: ", rawdisk);
539 			printf("512-byte blocks allocated to user ");
540 			if ((pw = getpwuid(uid)) && pw->pw_name[0])
541 				printf("%-10s ", pw->pw_name);
542 			else
543 				printf("#%-9d ", uid);
544 			printf(" = %lld\n", fup->fu_curblocks);
545 			printf(
546 "This exceeds the maximum number of blocks recordable in a quota record.\n");
547 			printf(
548 "The value will be set to the maximum, which is %lu.\n", UINT_MAX);
549 			fup->fu_curblocks = UINT_MAX;
550 		}
551 
552 		if (vflag) {
553 			if (pflag || aflag)
554 				printf("%s: ", rawdisk);
555 			if ((pw = getpwuid(uid)) && pw->pw_name[0])
556 				printf("%-10s fixed:", pw->pw_name);
557 			else
558 				printf("#%-9d fixed:", uid);
559 			if (dqbuf.dqb_curfiles != fup->fu_curfiles)
560 				printf("  files %lu -> %lu",
561 				    dqbuf.dqb_curfiles, fup->fu_curfiles);
562 			if (dqbuf.dqb_curblocks != fup->fu_curblocks)
563 				printf("  blocks %lu -> %llu",
564 				    dqbuf.dqb_curblocks, fup->fu_curblocks);
565 			printf("\n");
566 		}
567 		dqbuf.dqb_curfiles = fup->fu_curfiles;
568 		dqbuf.dqb_curblocks = fup->fu_curblocks;
569 		/*
570 		 * If quotas are not enabled for the current filesystem
571 		 * then just update the quotas file directly.
572 		 */
573 		if ((quotactl(Q_SETQUOTA, fsfile, uid, &dqbuf) < 0) &&
574 		    (errno == ESRCH)) {
575 			/* back up, overwrite the entry we just read */
576 			(void) fseeko64(qf, (offset_t)dqoff(uid), 0);
577 			(void) fwrite(&dqbuf, sizeof (struct dqblk), 1, qf);
578 			(void) fflush(qf);
579 		}
580 		fup->fu_curfiles = 0;
581 		fup->fu_curblocks = 0;
582 	}
583 	(void) fflush(qf);
584 	(void) fsync(fileno(qf));
585 	fclose(qf);
586 	close(fi);
587 	return (0);
588 }
589 
590 void
591 acct(struct dinode *ip)
592 {
593 	struct fileusage *fup;
594 
595 	if (ip == NULL)
596 		return;
597 	ip->di_mode = ip->di_smode;
598 	if (ip->di_suid != UID_LONG) {
599 		ip->di_uid = ip->di_suid;
600 	}
601 	if (ip->di_mode == 0)
602 		return;
603 	fup = adduid(ip->di_uid);
604 	fup->fu_curfiles++;
605 	if ((ip->di_mode & IFMT) == IFCHR || (ip->di_mode & IFMT) == IFBLK)
606 		return;
607 	fup->fu_curblocks += ip->di_blocks;
608 }
609 
610 int
611 oneof(char *target, char **olistp, int on)
612 {
613 	char **listp = olistp;
614 	int n = on;
615 
616 	while (n--) {
617 		if (*listp && strcmp(target, *listp) == 0) {
618 			*listp = (char *)0;
619 			return (1);
620 		}
621 		listp++;
622 	}
623 	return (0);
624 }
625 
626 struct dinode *
627 ginode()
628 {
629 	ulong_t iblk;
630 
631 	if (dp == NULL || ++dp >= &itab[ITABSZ]) {
632 		iblk = itod(&sblock, ino);
633 		bread(fsbtodb(&sblock, iblk),
634 		    (char *)itab, sizeof (itab));
635 		dp = &itab[(int)ino % (int)INOPB(&sblock)];
636 	}
637 	if (ino++ < UFSROOTINO)
638 		return (NULL);
639 	return (dp);
640 }
641 
642 void
643 bread(diskaddr_t bno, char *buf, int cnt)
644 {
645 	extern offset_t llseek();
646 	offset_t pos;
647 
648 	pos = (offset_t)bno * DEV_BSIZE;
649 	if (llseek(fi, pos, 0) != pos) {
650 		perror("lseek");
651 		exit(31+1);
652 	}
653 	if (read(fi, buf, cnt) != cnt) {
654 		perror("read");
655 		exit(31+1);
656 	}
657 }
658 
659 struct fileusage *
660 lookup(uid_t uid)
661 {
662 	struct fileusage *fup;
663 
664 	for (fup = fuhead[uid % FUHASH]; fup != 0; fup = fup->fu_next)
665 		if (fup->fu_uid == uid)
666 			return (fup);
667 	return ((struct fileusage *)0);
668 }
669 
670 struct fileusage *
671 adduid(uid_t uid)
672 {
673 	struct fileusage *fup, **fhp;
674 
675 	fup = lookup(uid);
676 	if (fup != 0)
677 		return (fup);
678 	fup = (struct fileusage *)calloc(1, sizeof (struct fileusage));
679 	if (fup == 0) {
680 		fprintf(stderr, "out of memory for fileusage structures\n");
681 		exit(31+1);
682 	}
683 	fhp = &fuhead[uid % FUHASH];
684 	fup->fu_next = *fhp;
685 	*fhp = fup;
686 	fup->fu_uid = uid;
687 	return (fup);
688 }
689 
690 void
691 usage()
692 {
693 	fprintf(stderr, "ufs usage:\n");
694 	fprintf(stderr, "\tquotacheck [-v] [-f] [-p] -a\n");
695 	fprintf(stderr, "\tquotacheck [-v] [-f] [-p] filesys ...\n");
696 	exit(31+1);
697 }
698 
699 int
700 quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr)
701 {
702 	int		fd;
703 	int		status;
704 	struct quotctl	quota;
705 	char		qfile[MAXPATHLEN];
706 	FILE		*fstab;
707 	struct mnttab	mntp;
708 
709 
710 	if ((mountp == NULL) && (cmd == Q_ALLSYNC)) {
711 		/*
712 		 * Find the mount point of any ufs file system.   This is
713 		 * because the ioctl that implements the quotactl call has
714 		 * to go to a real file, and not to the block device.
715 		 */
716 		if ((fstab = fopen(MNTTAB, "r")) == NULL) {
717 			fprintf(stderr, "%s: ", MNTTAB);
718 			perror("open");
719 			exit(31+1);
720 		}
721 		fd = -1;
722 		while ((status = getmntent(fstab, &mntp)) == 0) {
723 			if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 ||
724 				hasmntopt(&mntp, MNTOPT_RO))
725 				continue;
726 			if ((strlcpy(qfile, mntp.mnt_mountp,
727 				sizeof (qfile)) >= sizeof (qfile)) ||
728 			    (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
729 				sizeof (qfile))) {
730 				continue;
731 			}
732 			if ((fd = open64(qfile, O_RDWR)) == -1)
733 				break;
734 		}
735 		fclose(fstab);
736 		if (fd == -1) {
737 			errno = ENOENT;
738 			return (-1);
739 		}
740 	} else {
741 		if (mountp == NULL || mountp[0] == '\0') {
742 			errno =  ENOENT;
743 			return (-1);
744 		}
745 		if ((strlcpy(qfile, mountp, sizeof (qfile)) >=
746 			sizeof (qfile)) ||
747 		    (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >=
748 			sizeof (qfile))) {
749 			errno = ENOENT;
750 			return (-1);
751 		}
752 		if ((fd = open64(qfile, O_RDWR)) < 0) {
753 			fprintf(stderr, "quotactl: ");
754 			perror("open");
755 			exit(31+1);
756 		}
757 	}	/* else */
758 
759 	quota.op = cmd;
760 	quota.uid = uid;
761 	quota.addr = addr;
762 	status = ioctl(fd, Q_QUOTACTL, &quota);
763 	if (fd != 0)
764 		close(fd);
765 	return (status);
766 }
767 
768 char *
769 hasvfsopt(struct vfstab *vfs, char *opt)
770 {
771 	char *f, *opts;
772 	static char *tmpopts;
773 
774 	if (tmpopts == 0) {
775 		tmpopts = (char *)calloc(256, sizeof (char));
776 		if (tmpopts == 0)
777 			return (0);
778 	}
779 	strcpy(tmpopts, vfs->vfs_mntopts);
780 	opts = tmpopts;
781 	f = mntopt(&opts);
782 	for (; *f; f = mntopt(&opts)) {
783 		if (strncmp(opt, f, strlen(opt)) == 0)
784 			return (f - tmpopts + vfs->vfs_mntopts);
785 	}
786 	return (NULL);
787 }
788