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, "a); 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