Annotation of sys/ufs/ext2fs/ext2fs_inode.c, Revision 1.1
1.1 ! nbrk 1: /* $OpenBSD: ext2fs_inode.c,v 1.38 2007/06/17 20:15:25 jasper Exp $ */
! 2: /* $NetBSD: ext2fs_inode.c,v 1.24 2001/06/19 12:59:18 wiz Exp $ */
! 3:
! 4: /*
! 5: * Copyright (c) 1997 Manuel Bouyer.
! 6: * Copyright (c) 1982, 1986, 1989, 1993
! 7: * The Regents of the University of California. All rights reserved.
! 8: *
! 9: * Redistribution and use in source and binary forms, with or without
! 10: * modification, are permitted provided that the following conditions
! 11: * are met:
! 12: * 1. Redistributions of source code must retain the above copyright
! 13: * notice, this list of conditions and the following disclaimer.
! 14: * 2. Redistributions in binary form must reproduce the above copyright
! 15: * notice, this list of conditions and the following disclaimer in the
! 16: * documentation and/or other materials provided with the distribution.
! 17: * 3. Neither the name of the University nor the names of its contributors
! 18: * may be used to endorse or promote products derived from this software
! 19: * without specific prior written permission.
! 20: *
! 21: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
! 22: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
! 23: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
! 24: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
! 25: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
! 26: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
! 27: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
! 28: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
! 29: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
! 30: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
! 31: * SUCH DAMAGE.
! 32: *
! 33: * @(#)ffs_inode.c 8.8 (Berkeley) 10/19/94
! 34: * Modified for ext2fs by Manuel Bouyer.
! 35: */
! 36:
! 37: #include <sys/param.h>
! 38: #include <sys/systm.h>
! 39: #include <sys/mount.h>
! 40: #include <sys/proc.h>
! 41: #include <sys/file.h>
! 42: #include <sys/buf.h>
! 43: #include <sys/vnode.h>
! 44: #include <sys/kernel.h>
! 45: #include <sys/malloc.h>
! 46: #include <sys/resourcevar.h>
! 47:
! 48: #include <uvm/uvm_extern.h>
! 49:
! 50: #include <ufs/ufs/quota.h>
! 51: #include <ufs/ufs/inode.h>
! 52: #include <ufs/ufs/ufsmount.h>
! 53: #include <ufs/ufs/ufs_extern.h>
! 54:
! 55: #include <ufs/ext2fs/ext2fs.h>
! 56: #include <ufs/ext2fs/ext2fs_extern.h>
! 57:
! 58: static int ext2fs_indirtrunc(struct inode *, int32_t, int32_t,
! 59: int32_t, int, long *);
! 60:
! 61: /*
! 62: * Get the size of an inode.
! 63: */
! 64: u_int64_t
! 65: ext2fs_size(struct inode *ip)
! 66: {
! 67: u_int64_t size = ip->i_e2fs_size;
! 68:
! 69: if ((ip->i_e2fs_mode & IFMT) == IFREG)
! 70: size |= (u_int64_t)ip->i_e2fs_dacl << 32;
! 71:
! 72: return (size);
! 73: }
! 74:
! 75: int
! 76: ext2fs_setsize(struct inode *ip, u_int64_t size)
! 77: {
! 78: if ((ip->i_e2fs_mode & IFMT) == IFREG ||
! 79: ip->i_e2fs_mode == 0) {
! 80: ip->i_e2fs_dacl = size >> 32;
! 81: if (size >= 0x80000000U) {
! 82: struct m_ext2fs *fs = ip->i_e2fs;
! 83:
! 84: if (fs->e2fs.e2fs_rev <= E2FS_REV0) {
! 85: /* Linux automagically upgrades to REV1 here! */
! 86: return (EFBIG);
! 87: }
! 88: if (!(fs->e2fs.e2fs_features_rocompat
! 89: & EXT2F_ROCOMPAT_LARGEFILE)) {
! 90: fs->e2fs.e2fs_features_rocompat |=
! 91: EXT2F_ROCOMPAT_LARGEFILE;
! 92: fs->e2fs_fmod = 1;
! 93: }
! 94: }
! 95: } else if (size >= 0x80000000U)
! 96: return (EFBIG);
! 97:
! 98: ip->i_e2fs_size = size;
! 99:
! 100: return (0);
! 101: }
! 102:
! 103:
! 104: /*
! 105: * Last reference to an inode. If necessary, write or delete it.
! 106: */
! 107: int
! 108: ext2fs_inactive(void *v)
! 109: {
! 110: struct vop_inactive_args *ap = v;
! 111: struct vnode *vp = ap->a_vp;
! 112: struct inode *ip = VTOI(vp);
! 113: struct proc *p = ap->a_p;
! 114: struct timespec ts;
! 115: int error = 0;
! 116: #ifdef DIAGNOSTIC
! 117: extern int prtactive;
! 118:
! 119: if (prtactive && vp->v_usecount != 0)
! 120: vprint("ext2fs_inactive: pushing active", vp);
! 121: #endif
! 122:
! 123: /* Get rid of inodes related to stale file handles. */
! 124: if (ip->i_e2din == NULL || ip->i_e2fs_mode == 0 || ip->i_e2fs_dtime)
! 125: goto out;
! 126:
! 127: error = 0;
! 128: if (ip->i_e2fs_nlink == 0 && (vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {
! 129: if (ext2fs_size(ip) != 0) {
! 130: error = ext2fs_truncate(ip, (off_t)0, 0, NOCRED);
! 131: }
! 132: getnanotime(&ts);
! 133: ip->i_e2fs_dtime = ts.tv_sec;
! 134: ip->i_flag |= IN_CHANGE | IN_UPDATE;
! 135: ext2fs_inode_free(ip, ip->i_number, ip->i_e2fs_mode);
! 136: }
! 137: if (ip->i_flag & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) {
! 138: ext2fs_update(ip, NULL, NULL, 0);
! 139: }
! 140: out:
! 141: VOP_UNLOCK(vp, 0, p);
! 142: /*
! 143: * If we are done with the inode, reclaim it
! 144: * so that it can be reused immediately.
! 145: */
! 146: if (ip->i_e2din == NULL || ip->i_e2fs_dtime != 0)
! 147: vrecycle(vp, p);
! 148: return (error);
! 149: }
! 150:
! 151:
! 152: /*
! 153: * Update the access, modified, and inode change times as specified by the
! 154: * IACCESS, IUPDATE, and ICHANGE flags respectively. The IMODIFIED flag is
! 155: * used to specify that the inode needs to be updated but that the times have
! 156: * already been set. The access and modified times are taken from the second
! 157: * and third parameters; the inode change time is always taken from the current
! 158: * time. If waitfor is set, then wait for the disk write of the inode to
! 159: * complete.
! 160: */
! 161: int
! 162: ext2fs_update(struct inode *ip, struct timespec *atime, struct timespec *mtime,
! 163: int waitfor)
! 164: {
! 165: struct m_ext2fs *fs;
! 166: struct buf *bp;
! 167: int error;
! 168: struct timespec ts;
! 169: caddr_t cp;
! 170:
! 171: if (ITOV(ip)->v_mount->mnt_flag & MNT_RDONLY)
! 172: return (0);
! 173: getnanotime(&ts);
! 174: EXT2FS_ITIMES(ip,
! 175: atime ? atime : &ts,
! 176: mtime ? mtime : &ts);
! 177: if ((ip->i_flag & IN_MODIFIED) == 0)
! 178: return (0);
! 179: ip->i_flag &= ~IN_MODIFIED;
! 180: fs = ip->i_e2fs;
! 181: error = bread(ip->i_devvp,
! 182: fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
! 183: (int)fs->e2fs_bsize, NOCRED, &bp);
! 184: if (error) {
! 185: brelse(bp);
! 186: return (error);
! 187: }
! 188: ip->i_flag &= ~(IN_MODIFIED);
! 189: cp = (caddr_t)bp->b_data +
! 190: (ino_to_fsbo(fs, ip->i_number) * EXT2_DINODE_SIZE);
! 191:
! 192: /*
! 193: * See note about 16-bit UID/GID limitation in ext2fs_vget(). Now
! 194: * that we are about to write the inode, construct the split UID and
! 195: * GID fields out of the two 32-bit fields we kept in memory.
! 196: */
! 197: ip->i_e2fs_uid_low = (u_int16_t)ip->i_e2fs_uid;
! 198: ip->i_e2fs_gid_low = (u_int16_t)ip->i_e2fs_gid;
! 199: ip->i_e2fs_uid_high = ip->i_e2fs_uid >> 16;
! 200: ip->i_e2fs_gid_high = ip->i_e2fs_gid >> 16;
! 201:
! 202: e2fs_isave(ip->i_e2din, (struct ext2fs_dinode *)cp);
! 203: if (waitfor)
! 204: return (bwrite(bp));
! 205: else {
! 206: bdwrite(bp);
! 207: return (0);
! 208: }
! 209: }
! 210:
! 211: #define SINGLE 0 /* index of single indirect block */
! 212: #define DOUBLE 1 /* index of double indirect block */
! 213: #define TRIPLE 2 /* index of triple indirect block */
! 214: /*
! 215: * Truncate the inode oip to at most length size, freeing the
! 216: * disk blocks.
! 217: */
! 218: int
! 219: ext2fs_truncate(struct inode *oip, off_t length, int flags, struct ucred *cred)
! 220: {
! 221: struct vnode *ovp = ITOV(oip);
! 222: int32_t lastblock;
! 223: int32_t bn, lbn, lastiblock[NIADDR], indir_lbn[NIADDR];
! 224: int32_t oldblks[NDADDR + NIADDR], newblks[NDADDR + NIADDR];
! 225: struct m_ext2fs *fs;
! 226: struct buf *bp;
! 227: int offset, size, level;
! 228: long count, nblocks, vflags, blocksreleased = 0;
! 229: int i;
! 230: int aflags, error, allerror;
! 231: off_t osize;
! 232:
! 233: if (length < 0)
! 234: return (EINVAL);
! 235:
! 236: if (ovp->v_type != VREG &&
! 237: ovp->v_type != VDIR &&
! 238: ovp->v_type != VLNK)
! 239: return (0);
! 240:
! 241: if (ovp->v_type == VLNK &&
! 242: (ext2fs_size(oip) < ovp->v_mount->mnt_maxsymlinklen ||
! 243: (ovp->v_mount->mnt_maxsymlinklen == 0 &&
! 244: oip->i_e2fs_nblock == 0))) {
! 245: #ifdef DIAGNOSTIC
! 246: if (length != 0)
! 247: panic("ext2fs_truncate: partial truncate of symlink");
! 248: #endif
! 249: bzero((char *)&oip->i_e2din->e2di_shortlink,
! 250: (u_int)ext2fs_size(oip));
! 251: (void)ext2fs_setsize(oip, 0);
! 252: oip->i_flag |= IN_CHANGE | IN_UPDATE;
! 253: return (ext2fs_update(oip, NULL, NULL, 1));
! 254: }
! 255:
! 256: if (ext2fs_size(oip) == length) {
! 257: oip->i_flag |= IN_CHANGE | IN_UPDATE;
! 258: return (ext2fs_update(oip, NULL, NULL, 0));
! 259: }
! 260: fs = oip->i_e2fs;
! 261: osize = ext2fs_size(oip);
! 262: /*
! 263: * Lengthen the size of the file. We must ensure that the
! 264: * last byte of the file is allocated. Since the smallest
! 265: * value of osize is 0, length will be at least 1.
! 266: */
! 267: if (osize < length) {
! 268: #if 0 /* XXX */
! 269: if (length > fs->fs_maxfilesize)
! 270: return (EFBIG);
! 271: #endif
! 272: offset = blkoff(fs, length - 1);
! 273: lbn = lblkno(fs, length - 1);
! 274: aflags = B_CLRBUF;
! 275: if (flags & IO_SYNC)
! 276: aflags |= B_SYNC;
! 277: error = ext2fs_buf_alloc(oip, lbn, offset + 1, cred, &bp,
! 278: aflags);
! 279: if (error)
! 280: return (error);
! 281: (void)ext2fs_setsize(oip, length);
! 282: uvm_vnp_setsize(ovp, length);
! 283: uvm_vnp_uncache(ovp);
! 284: if (aflags & B_SYNC)
! 285: bwrite(bp);
! 286: else
! 287: bawrite(bp);
! 288: oip->i_flag |= IN_CHANGE | IN_UPDATE;
! 289: return (ext2fs_update(oip, NULL, NULL, 1));
! 290: }
! 291: /*
! 292: * Shorten the size of the file. If the file is not being
! 293: * truncated to a block boundry, the contents of the
! 294: * partial block following the end of the file must be
! 295: * zero'ed in case it ever become accessible again because
! 296: * of subsequent file growth.
! 297: */
! 298: offset = blkoff(fs, length);
! 299: if (offset == 0) {
! 300: (void)ext2fs_setsize(oip, length);
! 301: } else {
! 302: lbn = lblkno(fs, length);
! 303: aflags = B_CLRBUF;
! 304: if (flags & IO_SYNC)
! 305: aflags |= B_SYNC;
! 306: error = ext2fs_buf_alloc(oip, lbn, offset, cred, &bp,
! 307: aflags);
! 308: if (error)
! 309: return (error);
! 310: (void)ext2fs_setsize(oip, length);
! 311: size = fs->e2fs_bsize;
! 312: uvm_vnp_setsize(ovp, length);
! 313: uvm_vnp_uncache(ovp);
! 314: bzero(bp->b_data + offset, (u_int)(size - offset));
! 315: bp->b_bcount = size;
! 316: if (aflags & B_SYNC)
! 317: bwrite(bp);
! 318: else
! 319: bawrite(bp);
! 320: }
! 321: /*
! 322: * Calculate index into inode's block list of
! 323: * last direct and indirect blocks (if any)
! 324: * which we want to keep. Lastblock is -1 when
! 325: * the file is truncated to 0.
! 326: */
! 327: lastblock = lblkno(fs, length + fs->e2fs_bsize - 1) - 1;
! 328: lastiblock[SINGLE] = lastblock - NDADDR;
! 329: lastiblock[DOUBLE] = lastiblock[SINGLE] - NINDIR(fs);
! 330: lastiblock[TRIPLE] = lastiblock[DOUBLE] - NINDIR(fs) * NINDIR(fs);
! 331: nblocks = btodb(fs->e2fs_bsize);
! 332: /*
! 333: * Update file and block pointers on disk before we start freeing
! 334: * blocks. If we crash before free'ing blocks below, the blocks
! 335: * will be returned to the free list. lastiblock values are also
! 336: * normalized to -1 for calls to ext2fs_indirtrunc below.
! 337: */
! 338: memcpy((caddr_t)oldblks, (caddr_t)&oip->i_e2fs_blocks[0], sizeof oldblks);
! 339: for (level = TRIPLE; level >= SINGLE; level--)
! 340: if (lastiblock[level] < 0) {
! 341: oip->i_e2fs_blocks[NDADDR + level] = 0;
! 342: lastiblock[level] = -1;
! 343: }
! 344: for (i = NDADDR - 1; i > lastblock; i--)
! 345: oip->i_e2fs_blocks[i] = 0;
! 346: oip->i_flag |= IN_CHANGE | IN_UPDATE;
! 347: if ((error = ext2fs_update(oip, NULL, NULL, 1)) != 0)
! 348: allerror = error;
! 349: /*
! 350: * Having written the new inode to disk, save its new configuration
! 351: * and put back the old block pointers long enough to process them.
! 352: * Note that we save the new block configuration so we can check it
! 353: * when we are done.
! 354: */
! 355: bcopy((caddr_t)&oip->i_e2fs_blocks[0], (caddr_t)newblks, sizeof newblks);
! 356: bcopy((caddr_t)oldblks, (caddr_t)&oip->i_e2fs_blocks[0], sizeof oldblks);
! 357: (void)ext2fs_setsize(oip, osize);
! 358: vflags = ((length > 0) ? V_SAVE : 0) | V_SAVEMETA;
! 359: allerror = vinvalbuf(ovp, vflags, cred, curproc, 0, 0);
! 360:
! 361: /*
! 362: * Indirect blocks first.
! 363: */
! 364: indir_lbn[SINGLE] = -NDADDR;
! 365: indir_lbn[DOUBLE] = indir_lbn[SINGLE] - NINDIR(fs) -1;
! 366: indir_lbn[TRIPLE] = indir_lbn[DOUBLE] - NINDIR(fs) * NINDIR(fs) - 1;
! 367: for (level = TRIPLE; level >= SINGLE; level--) {
! 368: bn = fs2h32(oip->i_e2fs_blocks[NDADDR + level]);
! 369: if (bn != 0) {
! 370: error = ext2fs_indirtrunc(oip, indir_lbn[level],
! 371: fsbtodb(fs, bn), lastiblock[level], level, &count);
! 372: if (error)
! 373: allerror = error;
! 374: blocksreleased += count;
! 375: if (lastiblock[level] < 0) {
! 376: oip->i_e2fs_blocks[NDADDR + level] = 0;
! 377: ext2fs_blkfree(oip, bn);
! 378: blocksreleased += nblocks;
! 379: }
! 380: }
! 381: if (lastiblock[level] >= 0)
! 382: goto done;
! 383: }
! 384:
! 385: /*
! 386: * All whole direct blocks or frags.
! 387: */
! 388: for (i = NDADDR - 1; i > lastblock; i--) {
! 389: bn = fs2h32(oip->i_e2fs_blocks[i]);
! 390: if (bn == 0)
! 391: continue;
! 392: oip->i_e2fs_blocks[i] = 0;
! 393: ext2fs_blkfree(oip, bn);
! 394: blocksreleased += btodb(fs->e2fs_bsize);
! 395: }
! 396:
! 397: done:
! 398: #ifdef DIAGNOSTIC
! 399: for (level = SINGLE; level <= TRIPLE; level++)
! 400: if (newblks[NDADDR + level] !=
! 401: oip->i_e2fs_blocks[NDADDR + level])
! 402: panic("ext2fs_truncate1");
! 403: for (i = 0; i < NDADDR; i++)
! 404: if (newblks[i] != oip->i_e2fs_blocks[i])
! 405: panic("ext2fs_truncate2");
! 406: if (length == 0 &&
! 407: (!LIST_EMPTY(&ovp->v_cleanblkhd) ||
! 408: !LIST_EMPTY(&ovp->v_dirtyblkhd)))
! 409: panic("ext2fs_truncate3");
! 410: #endif /* DIAGNOSTIC */
! 411: /*
! 412: * Put back the real size.
! 413: */
! 414: (void)ext2fs_setsize(oip, length);
! 415: if (blocksreleased >= oip->i_e2fs_nblock)
! 416: oip->i_e2fs_nblock = 0;
! 417: else
! 418: oip->i_e2fs_nblock -= blocksreleased;
! 419: oip->i_flag |= IN_CHANGE;
! 420: return (allerror);
! 421: }
! 422:
! 423: /*
! 424: * Release blocks associated with the inode ip and stored in the indirect
! 425: * block bn. Blocks are free'd in LIFO order up to (but not including)
! 426: * lastbn. If level is greater than SINGLE, the block is an indirect block
! 427: * and recursive calls to indirtrunc must be used to cleanse other indirect
! 428: * blocks.
! 429: *
! 430: * NB: triple indirect blocks are untested.
! 431: */
! 432: static int
! 433: ext2fs_indirtrunc(struct inode *ip, int32_t lbn, int32_t dbn, int32_t lastbn, int level, long *countp)
! 434: {
! 435: int i;
! 436: struct buf *bp;
! 437: struct m_ext2fs *fs = ip->i_e2fs;
! 438: int32_t *bap;
! 439: struct vnode *vp;
! 440: int32_t *copy = NULL, nb, nlbn, last;
! 441: long blkcount, factor;
! 442: int nblocks, blocksreleased = 0;
! 443: int error = 0, allerror = 0;
! 444:
! 445: /*
! 446: * Calculate index in current block of last
! 447: * block to be kept. -1 indicates the entire
! 448: * block so we need not calculate the index.
! 449: */
! 450: factor = 1;
! 451: for (i = SINGLE; i < level; i++)
! 452: factor *= NINDIR(fs);
! 453: last = lastbn;
! 454: if (lastbn > 0)
! 455: last /= factor;
! 456: nblocks = btodb(fs->e2fs_bsize);
! 457: /*
! 458: * Get buffer of block pointers, zero those entries corresponding
! 459: * to blocks to be free'd, and update on disk copy first. Since
! 460: * double(triple) indirect before single(double) indirect, calls
! 461: * to bmap on these blocks will fail. However, we already have
! 462: * the on disk address, so we have to set the b_blkno field
! 463: * explicitly instead of letting bread do everything for us.
! 464: */
! 465: vp = ITOV(ip);
! 466: bp = getblk(vp, lbn, (int)fs->e2fs_bsize, 0, 0);
! 467: if (!(bp->b_flags & (B_DONE | B_DELWRI))) {
! 468: curproc->p_stats->p_ru.ru_inblock++; /* pay for read */
! 469: bp->b_flags |= B_READ;
! 470: if (bp->b_bcount > bp->b_bufsize)
! 471: panic("ext2fs_indirtrunc: bad buffer size");
! 472: bp->b_blkno = dbn;
! 473: VOP_STRATEGY(bp);
! 474: error = biowait(bp);
! 475: }
! 476: if (error) {
! 477: brelse(bp);
! 478: *countp = 0;
! 479: return (error);
! 480: }
! 481:
! 482: bap = (int32_t *)bp->b_data;
! 483: if (lastbn >= 0) {
! 484: MALLOC(copy, int32_t *, fs->e2fs_bsize, M_TEMP, M_WAITOK);
! 485: memcpy((caddr_t)copy, (caddr_t)bap, (u_int)fs->e2fs_bsize);
! 486: memset((caddr_t)&bap[last + 1], 0,
! 487: (u_int)(NINDIR(fs) - (last + 1)) * sizeof (u_int32_t));
! 488: error = bwrite(bp);
! 489: if (error)
! 490: allerror = error;
! 491: bap = copy;
! 492: }
! 493:
! 494: /*
! 495: * Recursively free totally unused blocks.
! 496: */
! 497: for (i = NINDIR(fs) - 1,
! 498: nlbn = lbn + 1 - i * factor; i > last;
! 499: i--, nlbn += factor) {
! 500: nb = fs2h32(bap[i]);
! 501: if (nb == 0)
! 502: continue;
! 503: if (level > SINGLE) {
! 504: error = ext2fs_indirtrunc(ip, nlbn, fsbtodb(fs, nb),
! 505: (int32_t)-1, level - 1,
! 506: &blkcount);
! 507: if (error)
! 508: allerror = error;
! 509: blocksreleased += blkcount;
! 510: }
! 511: ext2fs_blkfree(ip, nb);
! 512: blocksreleased += nblocks;
! 513: }
! 514:
! 515: /*
! 516: * Recursively free last partial block.
! 517: */
! 518: if (level > SINGLE && lastbn >= 0) {
! 519: last = lastbn % factor;
! 520: nb = fs2h32(bap[i]);
! 521: if (nb != 0) {
! 522: error = ext2fs_indirtrunc(ip, nlbn, fsbtodb(fs, nb),
! 523: last, level - 1, &blkcount);
! 524: if (error)
! 525: allerror = error;
! 526: blocksreleased += blkcount;
! 527: }
! 528: }
! 529:
! 530: if (copy != NULL) {
! 531: FREE(copy, M_TEMP);
! 532: } else {
! 533: bp->b_flags |= B_INVAL;
! 534: brelse(bp);
! 535: }
! 536:
! 537: *countp = blocksreleased;
! 538: return (allerror);
! 539: }
CVSweb