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