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
main(int argc,char ** argv)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
preen(int listcnt,char ** listp)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
waiter(struct active ** alp)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
chkquota(char * fsdev,char * fsfile,char * qffile)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
acct(struct dinode * ip)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
oneof(char * target,char ** olistp,int on)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 *
ginode()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
bread(diskaddr_t bno,char * buf,int cnt)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 *
lookup(uid_t uid)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 *
adduid(uid_t uid)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
usage()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
quotactl(int cmd,char * mountp,uid_t uid,caddr_t addr)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, "a);
763 if (fd != 0)
764 close(fd);
765 return (status);
766 }
767
768 char *
hasvfsopt(struct vfstab * vfs,char * opt)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