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