1 /* 2 * Copyright (c) 1980, 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Robert Elz at The University of Melbourne. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37 #ifndef lint 38 static const char copyright[] = 39 "@(#) Copyright (c) 1980, 1990, 1993\n\ 40 The Regents of the University of California. All rights reserved.\n"; 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)quotacheck.c 8.3 (Berkeley) 1/29/94"; 46 #endif 47 static const char rcsid[] = 48 "$FreeBSD$"; 49 #endif /* not lint */ 50 51 /* 52 * Fix up / report on disk quotas & usage 53 */ 54 #include <sys/param.h> 55 #include <sys/disklabel.h> 56 #include <sys/stat.h> 57 58 #include <ufs/ufs/dinode.h> 59 #include <ufs/ufs/quota.h> 60 #include <ufs/ffs/fs.h> 61 62 #include <err.h> 63 #include <errno.h> 64 #include <fcntl.h> 65 #include <fstab.h> 66 #include <grp.h> 67 #include <pwd.h> 68 #include <stdio.h> 69 #include <stdlib.h> 70 #include <string.h> 71 #include <unistd.h> 72 73 char *qfname = QUOTAFILENAME; 74 char *qfextension[] = INITQFNAMES; 75 char *quotagroup = QUOTAGROUP; 76 77 union { 78 struct fs sblk; 79 char dummy[MAXBSIZE]; 80 } sb_un; 81 #define sblock sb_un.sblk 82 union { 83 struct cg cgblk; 84 char dummy[MAXBSIZE]; 85 } cg_un; 86 #define cgblk cg_un.cgblk 87 long dev_bsize = 1; 88 ino_t maxino; 89 90 union dinode { 91 struct ufs1_dinode dp1; 92 struct ufs2_dinode dp2; 93 }; 94 #define DIP(dp, field) \ 95 ((sblock.fs_magic == FS_UFS1_MAGIC) ? \ 96 (dp)->dp1.field : (dp)->dp2.field) 97 98 struct quotaname { 99 long flags; 100 char grpqfname[MAXPATHLEN + 1]; 101 char usrqfname[MAXPATHLEN + 1]; 102 }; 103 #define HASUSR 1 104 #define HASGRP 2 105 106 struct fileusage { 107 struct fileusage *fu_next; 108 u_long fu_curinodes; 109 u_long fu_curblocks; 110 u_long fu_id; 111 char fu_name[1]; 112 /* actually bigger */ 113 }; 114 #define FUHASH 1024 /* must be power of two */ 115 struct fileusage *fuhead[MAXQUOTAS][FUHASH]; 116 117 int aflag; /* all file systems */ 118 int gflag; /* check group quotas */ 119 int uflag; /* check user quotas */ 120 int vflag; /* verbose */ 121 int fi; /* open disk file descriptor */ 122 u_long highid[MAXQUOTAS]; /* highest addid()'ed identifier per type */ 123 124 struct fileusage * 125 addid(u_long, int, char *); 126 char *blockcheck(char *); 127 void bread(ufs2_daddr_t, char *, long); 128 extern int checkfstab(int, int, void * (*)(struct fstab *), 129 int (*)(char *, char *, struct quotaname *)); 130 int chkquota(char *, char *, struct quotaname *); 131 void freeinodebuf(void); 132 union dinode * 133 getnextinode(ino_t); 134 int getquotagid(void); 135 int hasquota(struct fstab *, int, char **); 136 struct fileusage * 137 lookup(u_long, int); 138 void *needchk(struct fstab *); 139 int oneof(char *, char*[], int); 140 void setinodebuf(ino_t); 141 int update(char *, char *, int); 142 void usage(void); 143 144 int 145 main(argc, argv) 146 int argc; 147 char *argv[]; 148 { 149 struct fstab *fs; 150 struct passwd *pw; 151 struct group *gr; 152 struct quotaname *auxdata; 153 int i, argnum, maxrun, errs; 154 long done = 0; 155 char ch, *name; 156 157 errs = maxrun = 0; 158 while ((ch = getopt(argc, argv, "aguvl:")) != -1) { 159 switch(ch) { 160 case 'a': 161 aflag++; 162 break; 163 case 'g': 164 gflag++; 165 break; 166 case 'u': 167 uflag++; 168 break; 169 case 'v': 170 vflag++; 171 break; 172 case 'l': 173 maxrun = atoi(optarg); 174 break; 175 default: 176 usage(); 177 } 178 } 179 argc -= optind; 180 argv += optind; 181 if ((argc == 0 && !aflag) || (argc > 0 && aflag)) 182 usage(); 183 if (!gflag && !uflag) { 184 gflag++; 185 uflag++; 186 } 187 if (gflag) { 188 setgrent(); 189 while ((gr = getgrent()) != NULL) 190 (void) addid((u_long)gr->gr_gid, GRPQUOTA, gr->gr_name); 191 endgrent(); 192 } 193 if (uflag) { 194 setpwent(); 195 while ((pw = getpwent()) != NULL) 196 (void) addid((u_long)pw->pw_uid, USRQUOTA, pw->pw_name); 197 endpwent(); 198 } 199 if (aflag) 200 exit(checkfstab(1, maxrun, needchk, chkquota)); 201 if (setfsent() == 0) 202 errx(1, "%s: can't open", FSTAB); 203 while ((fs = getfsent()) != NULL) { 204 if (((argnum = oneof(fs->fs_file, argv, argc)) >= 0 || 205 (argnum = oneof(fs->fs_spec, argv, argc)) >= 0) && 206 (auxdata = needchk(fs)) && 207 (name = blockcheck(fs->fs_spec))) { 208 done |= 1 << argnum; 209 errs += chkquota(name, fs->fs_file, auxdata); 210 } 211 } 212 endfsent(); 213 for (i = 0; i < argc; i++) 214 if ((done & (1 << i)) == 0) 215 fprintf(stderr, "%s not found in %s\n", 216 argv[i], FSTAB); 217 exit(errs); 218 } 219 220 void 221 usage() 222 { 223 (void)fprintf(stderr, "%s\n%s\n", 224 "usage: quotacheck -a [-guv]", 225 " quotacheck [-guv] filesystem ..."); 226 exit(1); 227 } 228 229 void * 230 needchk(fs) 231 struct fstab *fs; 232 { 233 struct quotaname *qnp; 234 char *qfnp; 235 236 if (strcmp(fs->fs_vfstype, "ufs") || 237 strcmp(fs->fs_type, FSTAB_RW)) 238 return (NULL); 239 if ((qnp = malloc(sizeof(*qnp))) == NULL) 240 errx(1, "malloc failed"); 241 qnp->flags = 0; 242 if (gflag && hasquota(fs, GRPQUOTA, &qfnp)) { 243 strcpy(qnp->grpqfname, qfnp); 244 qnp->flags |= HASGRP; 245 } 246 if (uflag && hasquota(fs, USRQUOTA, &qfnp)) { 247 strcpy(qnp->usrqfname, qfnp); 248 qnp->flags |= HASUSR; 249 } 250 if (qnp->flags) 251 return (qnp); 252 free(qnp); 253 return (NULL); 254 } 255 256 /* 257 * Possible superblock locations ordered from most to least likely. 258 */ 259 static int sblock_try[] = SBLOCKSEARCH; 260 261 /* 262 * Scan the specified file system to check quota(s) present on it. 263 */ 264 int 265 chkquota(fsname, mntpt, qnp) 266 char *fsname, *mntpt; 267 struct quotaname *qnp; 268 { 269 struct fileusage *fup; 270 union dinode *dp; 271 int cg, i, mode, errs = 0; 272 ino_t ino, inosused; 273 char *cp; 274 275 if ((fi = open(fsname, O_RDONLY, 0)) < 0) { 276 warn("%s", fsname); 277 return (1); 278 } 279 if (vflag) { 280 (void)printf("*** Checking "); 281 if (qnp->flags & HASUSR) 282 (void)printf("%s%s", qfextension[USRQUOTA], 283 (qnp->flags & HASGRP) ? " and " : ""); 284 if (qnp->flags & HASGRP) 285 (void)printf("%s", qfextension[GRPQUOTA]); 286 (void)printf(" quotas for %s (%s)\n", fsname, mntpt); 287 } 288 sync(); 289 dev_bsize = 1; 290 for (i = 0; sblock_try[i] != -1; i++) { 291 bread(sblock_try[i], (char *)&sblock, (long)SBLOCKSIZE); 292 if ((sblock.fs_magic == FS_UFS1_MAGIC || 293 (sblock.fs_magic == FS_UFS2_MAGIC && 294 sblock.fs_sblockloc == sblock_try[i])) && 295 sblock.fs_bsize <= MAXBSIZE && 296 sblock.fs_bsize >= sizeof(struct fs)) 297 break; 298 } 299 if (sblock_try[i] == -1) { 300 warn("Cannot find file system superblock"); 301 return (1); 302 } 303 dev_bsize = sblock.fs_fsize / fsbtodb(&sblock, 1); 304 maxino = sblock.fs_ncg * sblock.fs_ipg; 305 for (cg = 0; cg < sblock.fs_ncg; cg++) { 306 ino = cg * sblock.fs_ipg; 307 setinodebuf(ino); 308 bread(fsbtodb(&sblock, cgtod(&sblock, cg)), (char *)(&cgblk), 309 sblock.fs_cgsize); 310 if (sblock.fs_magic == FS_UFS2_MAGIC) 311 inosused = cgblk.cg_initediblk; 312 else 313 inosused = sblock.fs_ipg; 314 /* 315 * If we are using soft updates, then we can trust the 316 * cylinder group inode allocation maps to tell us which 317 * inodes are allocated. We will scan the used inode map 318 * to find the inodes that are really in use, and then 319 * read only those inodes in from disk. 320 */ 321 if (sblock.fs_flags & FS_DOSOFTDEP) { 322 if (!cg_chkmagic(&cgblk)) 323 errx(1, "CG %d: BAD MAGIC NUMBER\n", cg); 324 cp = &cg_inosused(&cgblk)[(inosused - 1) / CHAR_BIT]; 325 for ( ; inosused > 0; inosused -= CHAR_BIT, cp--) { 326 if (*cp == 0) 327 continue; 328 for (i = 1 << (CHAR_BIT - 1); i > 0; i >>= 1) { 329 if (*cp & i) 330 break; 331 inosused--; 332 } 333 break; 334 } 335 if (inosused <= 0) 336 continue; 337 } 338 for (i = 0; i < inosused; i++, ino++) { 339 if ((dp = getnextinode(ino)) == NULL || ino < ROOTINO || 340 (mode = DIP(dp, di_mode) & IFMT) == 0) 341 continue; 342 if (qnp->flags & HASGRP) { 343 fup = addid((u_long)DIP(dp, di_gid), GRPQUOTA, 344 (char *)0); 345 fup->fu_curinodes++; 346 if (mode == IFREG || mode == IFDIR || 347 mode == IFLNK) 348 fup->fu_curblocks += DIP(dp, di_blocks); 349 } 350 if (qnp->flags & HASUSR) { 351 fup = addid((u_long)DIP(dp, di_uid), USRQUOTA, 352 (char *)0); 353 fup->fu_curinodes++; 354 if (mode == IFREG || mode == IFDIR || 355 mode == IFLNK) 356 fup->fu_curblocks += DIP(dp, di_blocks); 357 } 358 } 359 } 360 freeinodebuf(); 361 if (qnp->flags & HASUSR) 362 errs += update(mntpt, qnp->usrqfname, USRQUOTA); 363 if (qnp->flags & HASGRP) 364 errs += update(mntpt, qnp->grpqfname, GRPQUOTA); 365 close(fi); 366 return (errs); 367 } 368 369 /* 370 * Update a specified quota file. 371 */ 372 int 373 update(fsname, quotafile, type) 374 char *fsname, *quotafile; 375 int type; 376 { 377 struct fileusage *fup; 378 FILE *qfi, *qfo; 379 u_long id, lastid; 380 off_t offset; 381 struct dqblk dqbuf; 382 static int warned = 0; 383 static struct dqblk zerodqbuf; 384 static struct fileusage zerofileusage; 385 386 if ((qfo = fopen(quotafile, "r+")) == NULL) { 387 if (errno == ENOENT) 388 qfo = fopen(quotafile, "w+"); 389 if (qfo) { 390 warnx("creating quota file %s", quotafile); 391 #define MODE (S_IRUSR|S_IWUSR|S_IRGRP) 392 (void) fchown(fileno(qfo), getuid(), getquotagid()); 393 (void) fchmod(fileno(qfo), MODE); 394 } else { 395 warn("%s", quotafile); 396 return (1); 397 } 398 } 399 if ((qfi = fopen(quotafile, "r")) == NULL) { 400 warn("%s", quotafile); 401 (void) fclose(qfo); 402 return (1); 403 } 404 if (quotactl(fsname, QCMD(Q_SYNC, type), (u_long)0, (caddr_t)0) < 0 && 405 errno == EOPNOTSUPP && !warned && vflag) { 406 warned++; 407 (void)printf("*** Warning: %s\n", 408 "Quotas are not compiled into this kernel"); 409 } 410 for (lastid = highid[type], id = 0, offset = 0; id <= lastid; 411 id++, offset += sizeof(struct dqblk)) { 412 if (fread((char *)&dqbuf, sizeof(struct dqblk), 1, qfi) == 0) 413 dqbuf = zerodqbuf; 414 if ((fup = lookup(id, type)) == 0) 415 fup = &zerofileusage; 416 if (dqbuf.dqb_curinodes == fup->fu_curinodes && 417 dqbuf.dqb_curblocks == fup->fu_curblocks) { 418 fup->fu_curinodes = 0; 419 fup->fu_curblocks = 0; 420 continue; 421 } 422 if (vflag) { 423 if (aflag) 424 printf("%s: ", fsname); 425 printf("%-8s fixed:", fup->fu_name); 426 if (dqbuf.dqb_curinodes != fup->fu_curinodes) 427 (void)printf("\tinodes %lu -> %lu", 428 (u_long)dqbuf.dqb_curinodes, 429 (u_long)fup->fu_curinodes); 430 if (dqbuf.dqb_curblocks != fup->fu_curblocks) 431 (void)printf("\tblocks %lu -> %lu", 432 (u_long)dqbuf.dqb_curblocks, 433 (u_long)fup->fu_curblocks); 434 (void)printf("\n"); 435 } 436 /* 437 * Reset time limit if have a soft limit and were 438 * previously under it, but are now over it. 439 */ 440 if (dqbuf.dqb_bsoftlimit && 441 dqbuf.dqb_curblocks < dqbuf.dqb_bsoftlimit && 442 fup->fu_curblocks >= dqbuf.dqb_bsoftlimit) 443 dqbuf.dqb_btime = 0; 444 if (dqbuf.dqb_isoftlimit && 445 dqbuf.dqb_curblocks < dqbuf.dqb_isoftlimit && 446 fup->fu_curblocks >= dqbuf.dqb_isoftlimit) 447 dqbuf.dqb_itime = 0; 448 dqbuf.dqb_curinodes = fup->fu_curinodes; 449 dqbuf.dqb_curblocks = fup->fu_curblocks; 450 if (fseek(qfo, offset, SEEK_SET) < 0) { 451 warn("%s: seek failed", quotafile); 452 return(1); 453 } 454 fwrite((char *)&dqbuf, sizeof(struct dqblk), 1, qfo); 455 (void) quotactl(fsname, QCMD(Q_SETUSE, type), id, 456 (caddr_t)&dqbuf); 457 fup->fu_curinodes = 0; 458 fup->fu_curblocks = 0; 459 } 460 fclose(qfi); 461 fflush(qfo); 462 ftruncate(fileno(qfo), 463 (((off_t)highid[type] + 1) * sizeof(struct dqblk))); 464 fclose(qfo); 465 return (0); 466 } 467 468 /* 469 * Check to see if target appears in list of size cnt. 470 */ 471 int 472 oneof(target, list, cnt) 473 char *target, *list[]; 474 int cnt; 475 { 476 int i; 477 478 for (i = 0; i < cnt; i++) 479 if (strcmp(target, list[i]) == 0) 480 return (i); 481 return (-1); 482 } 483 484 /* 485 * Determine the group identifier for quota files. 486 */ 487 int 488 getquotagid() 489 { 490 struct group *gr; 491 492 if ((gr = getgrnam(quotagroup)) != NULL) 493 return (gr->gr_gid); 494 return (-1); 495 } 496 497 /* 498 * Check to see if a particular quota is to be enabled. 499 */ 500 int 501 hasquota(fs, type, qfnamep) 502 struct fstab *fs; 503 int type; 504 char **qfnamep; 505 { 506 char *opt; 507 char *cp; 508 static char initname, usrname[100], grpname[100]; 509 static char buf[BUFSIZ]; 510 511 if (!initname) { 512 (void)snprintf(usrname, sizeof(usrname), 513 "%s%s", qfextension[USRQUOTA], qfname); 514 (void)snprintf(grpname, sizeof(grpname), 515 "%s%s", qfextension[GRPQUOTA], qfname); 516 initname = 1; 517 } 518 strcpy(buf, fs->fs_mntops); 519 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 520 if ((cp = index(opt, '=')) != NULL) 521 *cp++ = '\0'; 522 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 523 break; 524 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 525 break; 526 } 527 if (!opt) 528 return (0); 529 if (cp) 530 *qfnamep = cp; 531 else { 532 (void)snprintf(buf, sizeof(buf), 533 "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 534 *qfnamep = buf; 535 } 536 return (1); 537 } 538 539 /* 540 * Routines to manage the file usage table. 541 * 542 * Lookup an id of a specific type. 543 */ 544 struct fileusage * 545 lookup(id, type) 546 u_long id; 547 int type; 548 { 549 struct fileusage *fup; 550 551 for (fup = fuhead[type][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 552 if (fup->fu_id == id) 553 return (fup); 554 return (NULL); 555 } 556 557 /* 558 * Add a new file usage id if it does not already exist. 559 */ 560 struct fileusage * 561 addid(id, type, name) 562 u_long id; 563 int type; 564 char *name; 565 { 566 struct fileusage *fup, **fhp; 567 int len; 568 569 if ((fup = lookup(id, type)) != NULL) 570 return (fup); 571 if (name) 572 len = strlen(name); 573 else 574 len = 10; 575 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 576 errx(1, "calloc failed"); 577 fhp = &fuhead[type][id & (FUHASH - 1)]; 578 fup->fu_next = *fhp; 579 *fhp = fup; 580 fup->fu_id = id; 581 if (id > highid[type]) 582 highid[type] = id; 583 if (name) 584 bcopy(name, fup->fu_name, len + 1); 585 else { 586 (void)sprintf(fup->fu_name, "%lu", id); 587 if (vflag) 588 printf("unknown %cid: %lu\n", 589 type == USRQUOTA ? 'u' : 'g', id); 590 } 591 return (fup); 592 } 593 594 /* 595 * Special purpose version of ginode used to optimize pass 596 * over all the inodes in numerical order. 597 */ 598 static ino_t nextino, lastinum, lastvalidinum; 599 static long readcnt, readpercg, fullcnt, inobufsize, partialcnt, partialsize; 600 static caddr_t inodebuf; 601 #define INOBUFSIZE 56*1024 /* size of buffer to read inodes */ 602 603 union dinode * 604 getnextinode(ino_t inumber) 605 { 606 long size; 607 ufs2_daddr_t dblk; 608 union dinode *dp; 609 static caddr_t nextinop; 610 611 if (inumber != nextino++ || inumber > lastvalidinum) 612 errx(1, "bad inode number %d to nextinode", inumber); 613 if (inumber >= lastinum) { 614 readcnt++; 615 dblk = fsbtodb(&sblock, ino_to_fsba(&sblock, lastinum)); 616 if (readcnt % readpercg == 0) { 617 size = partialsize; 618 lastinum += partialcnt; 619 } else { 620 size = inobufsize; 621 lastinum += fullcnt; 622 } 623 /* 624 * If bread returns an error, it will already have zeroed 625 * out the buffer, so we do not need to do so here. 626 */ 627 bread(dblk, inodebuf, size); 628 nextinop = inodebuf; 629 } 630 dp = (union dinode *)nextinop; 631 if (sblock.fs_magic == FS_UFS1_MAGIC) 632 nextinop += sizeof(struct ufs1_dinode); 633 else 634 nextinop += sizeof(struct ufs2_dinode); 635 return (dp); 636 } 637 638 /* 639 * Prepare to scan a set of inodes. 640 */ 641 void 642 setinodebuf(ino_t inum) 643 { 644 645 if (inum % sblock.fs_ipg != 0) 646 errx(1, "bad inode number %d to setinodebuf", inum); 647 lastvalidinum = inum + sblock.fs_ipg - 1; 648 nextino = inum; 649 lastinum = inum; 650 readcnt = 0; 651 if (inodebuf != NULL) 652 return; 653 inobufsize = blkroundup(&sblock, INOBUFSIZE); 654 fullcnt = inobufsize / ((sblock.fs_magic == FS_UFS1_MAGIC) ? 655 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 656 readpercg = sblock.fs_ipg / fullcnt; 657 partialcnt = sblock.fs_ipg % fullcnt; 658 partialsize = partialcnt * ((sblock.fs_magic == FS_UFS1_MAGIC) ? 659 sizeof(struct ufs1_dinode) : sizeof(struct ufs2_dinode)); 660 if (partialcnt != 0) { 661 readpercg++; 662 } else { 663 partialcnt = fullcnt; 664 partialsize = inobufsize; 665 } 666 if ((inodebuf = malloc((unsigned)inobufsize)) == NULL) 667 errx(1, "cannot allocate space for inode buffer"); 668 } 669 670 /* 671 * Free up data structures used to scan inodes. 672 */ 673 void 674 freeinodebuf() 675 { 676 677 if (inodebuf != NULL) 678 free(inodebuf); 679 inodebuf = NULL; 680 } 681 682 /* 683 * Read specified disk blocks. 684 */ 685 void 686 bread(bno, buf, cnt) 687 ufs2_daddr_t bno; 688 char *buf; 689 long cnt; 690 { 691 692 if (lseek(fi, (off_t)bno * dev_bsize, SEEK_SET) < 0 || 693 read(fi, buf, cnt) != cnt) 694 errx(1, "bread failed on block %ld", (long)bno); 695 } 696