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