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