1 /*- 2 * modified for Lites 1.1 3 * 4 * Aug 1995, Godmar Back (gback@cs.utah.edu) 5 * University of Utah, Department of Computer Science 6 */ 7 /*- 8 * SPDX-License-Identifier: BSD-3-Clause 9 * 10 * Copyright (c) 1982, 1986, 1989, 1993 11 * The Regents of the University of California. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 1. Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in the 20 * documentation and/or other materials provided with the distribution. 21 * 3. Neither the name of the University nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 * 37 * @(#)ffs_inode.c 8.5 (Berkeley) 12/30/93 38 * $FreeBSD$ 39 */ 40 41 #include <sys/param.h> 42 #include <sys/systm.h> 43 #include <sys/mount.h> 44 #include <sys/bio.h> 45 #include <sys/buf.h> 46 #include <sys/vnode.h> 47 #include <sys/malloc.h> 48 #include <sys/rwlock.h> 49 50 #include <vm/vm.h> 51 #include <vm/vm_extern.h> 52 53 #include <fs/ext2fs/fs.h> 54 #include <fs/ext2fs/inode.h> 55 #include <fs/ext2fs/ext2_mount.h> 56 #include <fs/ext2fs/ext2fs.h> 57 #include <fs/ext2fs/fs.h> 58 #include <fs/ext2fs/ext2_extern.h> 59 #include <fs/ext2fs/ext2_extattr.h> 60 61 /* 62 * Update the access, modified, and inode change times as specified by the 63 * IN_ACCESS, IN_UPDATE, and IN_CHANGE flags respectively. Write the inode 64 * to disk if the IN_MODIFIED flag is set (it may be set initially, or by 65 * the timestamp update). The IN_LAZYMOD flag is set to force a write 66 * later if not now. If we write now, then clear both IN_MODIFIED and 67 * IN_LAZYMOD to reflect the presumably successful write, and if waitfor is 68 * set, then wait for the write to complete. 69 */ 70 int 71 ext2_update(struct vnode *vp, int waitfor) 72 { 73 struct m_ext2fs *fs; 74 struct buf *bp; 75 struct inode *ip; 76 int error; 77 78 ASSERT_VOP_ELOCKED(vp, "ext2_update"); 79 ext2_itimes(vp); 80 ip = VTOI(vp); 81 if ((ip->i_flag & IN_MODIFIED) == 0 && waitfor == 0) 82 return (0); 83 ip->i_flag &= ~(IN_LAZYACCESS | IN_LAZYMOD | IN_MODIFIED); 84 fs = ip->i_e2fs; 85 if (fs->e2fs_ronly) 86 return (0); 87 if ((error = bread(ip->i_devvp, 88 fsbtodb(fs, ino_to_fsba(fs, ip->i_number)), 89 (int)fs->e2fs_bsize, NOCRED, &bp)) != 0) { 90 brelse(bp); 91 return (error); 92 } 93 error = ext2_i2ei(ip, (struct ext2fs_dinode *)((char *)bp->b_data + 94 EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number))); 95 if (error) { 96 brelse(bp); 97 return (error); 98 } 99 if (waitfor && !DOINGASYNC(vp)) 100 return (bwrite(bp)); 101 else { 102 bdwrite(bp); 103 return (0); 104 } 105 } 106 107 #define SINGLE 0 /* index of single indirect block */ 108 #define DOUBLE 1 /* index of double indirect block */ 109 #define TRIPLE 2 /* index of triple indirect block */ 110 111 /* 112 * Release blocks associated with the inode ip and stored in the indirect 113 * block bn. Blocks are free'd in LIFO order up to (but not including) 114 * lastbn. If level is greater than SINGLE, the block is an indirect block 115 * and recursive calls to indirtrunc must be used to cleanse other indirect 116 * blocks. 117 * 118 * NB: triple indirect blocks are untested. 119 */ 120 static int 121 ext2_indirtrunc(struct inode *ip, daddr_t lbn, daddr_t dbn, 122 daddr_t lastbn, int level, e4fs_daddr_t *countp) 123 { 124 struct buf *bp; 125 struct m_ext2fs *fs = ip->i_e2fs; 126 struct vnode *vp; 127 e2fs_daddr_t *bap, *copy; 128 int i, nblocks, error = 0, allerror = 0; 129 e2fs_lbn_t nb, nlbn, last; 130 e4fs_daddr_t blkcount, factor, blocksreleased = 0; 131 132 /* 133 * Calculate index in current block of last 134 * block to be kept. -1 indicates the entire 135 * block so we need not calculate the index. 136 */ 137 factor = 1; 138 for (i = SINGLE; i < level; i++) 139 factor *= NINDIR(fs); 140 last = lastbn; 141 if (lastbn > 0) 142 last /= factor; 143 nblocks = btodb(fs->e2fs_bsize); 144 /* 145 * Get buffer of block pointers, zero those entries corresponding 146 * to blocks to be free'd, and update on disk copy first. Since 147 * double(triple) indirect before single(double) indirect, calls 148 * to bmap on these blocks will fail. However, we already have 149 * the on disk address, so we have to set the b_blkno field 150 * explicitly instead of letting bread do everything for us. 151 */ 152 vp = ITOV(ip); 153 bp = getblk(vp, lbn, (int)fs->e2fs_bsize, 0, 0, 0); 154 if ((bp->b_flags & (B_DONE | B_DELWRI)) == 0) { 155 bp->b_iocmd = BIO_READ; 156 if (bp->b_bcount > bp->b_bufsize) 157 panic("ext2_indirtrunc: bad buffer size"); 158 bp->b_blkno = dbn; 159 vfs_busy_pages(bp, 0); 160 bp->b_iooffset = dbtob(bp->b_blkno); 161 bstrategy(bp); 162 error = bufwait(bp); 163 } 164 if (error) { 165 brelse(bp); 166 *countp = 0; 167 return (error); 168 } 169 bap = (e2fs_daddr_t *)bp->b_data; 170 copy = malloc(fs->e2fs_bsize, M_TEMP, M_WAITOK); 171 bcopy((caddr_t)bap, (caddr_t)copy, (u_int)fs->e2fs_bsize); 172 bzero((caddr_t)&bap[last + 1], 173 (NINDIR(fs) - (last + 1)) * sizeof(e2fs_daddr_t)); 174 if (last == -1) 175 bp->b_flags |= B_INVAL; 176 if (DOINGASYNC(vp)) { 177 bdwrite(bp); 178 } else { 179 error = bwrite(bp); 180 if (error) 181 allerror = error; 182 } 183 bap = copy; 184 185 /* 186 * Recursively free totally unused blocks. 187 */ 188 for (i = NINDIR(fs) - 1, nlbn = lbn + 1 - i * factor; i > last; 189 i--, nlbn += factor) { 190 nb = bap[i]; 191 if (nb == 0) 192 continue; 193 if (level > SINGLE) { 194 if ((error = ext2_indirtrunc(ip, nlbn, 195 fsbtodb(fs, nb), (int32_t)-1, level - 1, &blkcount)) != 0) 196 allerror = error; 197 blocksreleased += blkcount; 198 } 199 ext2_blkfree(ip, nb, fs->e2fs_bsize); 200 blocksreleased += nblocks; 201 } 202 203 /* 204 * Recursively free last partial block. 205 */ 206 if (level > SINGLE && lastbn >= 0) { 207 last = lastbn % factor; 208 nb = bap[i]; 209 if (nb != 0) { 210 if ((error = ext2_indirtrunc(ip, nlbn, fsbtodb(fs, nb), 211 last, level - 1, &blkcount)) != 0) 212 allerror = error; 213 blocksreleased += blkcount; 214 } 215 } 216 free(copy, M_TEMP); 217 *countp = blocksreleased; 218 return (allerror); 219 } 220 221 /* 222 * Truncate the inode oip to at most length size, freeing the 223 * disk blocks. 224 */ 225 static int 226 ext2_ind_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred, 227 struct thread *td) 228 { 229 struct vnode *ovp = vp; 230 e4fs_daddr_t lastblock; 231 struct inode *oip; 232 e4fs_daddr_t bn, lbn, lastiblock[EXT2_NIADDR], indir_lbn[EXT2_NIADDR]; 233 uint32_t oldblks[EXT2_NDADDR + EXT2_NIADDR]; 234 uint32_t newblks[EXT2_NDADDR + EXT2_NIADDR]; 235 struct m_ext2fs *fs; 236 struct buf *bp; 237 int offset, size, level; 238 e4fs_daddr_t count, nblocks, blocksreleased = 0; 239 int error, i, allerror; 240 off_t osize; 241 #ifdef INVARIANTS 242 struct bufobj *bo; 243 #endif 244 245 oip = VTOI(ovp); 246 #ifdef INVARIANTS 247 bo = &ovp->v_bufobj; 248 #endif 249 250 fs = oip->i_e2fs; 251 osize = oip->i_size; 252 /* 253 * Lengthen the size of the file. We must ensure that the 254 * last byte of the file is allocated. Since the smallest 255 * value of osize is 0, length will be at least 1. 256 */ 257 if (osize < length) { 258 if (length > oip->i_e2fs->e2fs_maxfilesize) 259 return (EFBIG); 260 vnode_pager_setsize(ovp, length); 261 offset = blkoff(fs, length - 1); 262 lbn = lblkno(fs, length - 1); 263 flags |= BA_CLRBUF; 264 error = ext2_balloc(oip, lbn, offset + 1, cred, &bp, flags); 265 if (error) { 266 vnode_pager_setsize(vp, osize); 267 return (error); 268 } 269 oip->i_size = length; 270 if (bp->b_bufsize == fs->e2fs_bsize) 271 bp->b_flags |= B_CLUSTEROK; 272 if (flags & IO_SYNC) 273 bwrite(bp); 274 else if (DOINGASYNC(ovp)) 275 bdwrite(bp); 276 else 277 bawrite(bp); 278 oip->i_flag |= IN_CHANGE | IN_UPDATE; 279 return (ext2_update(ovp, !DOINGASYNC(ovp))); 280 } 281 /* 282 * Shorten the size of the file. If the file is not being 283 * truncated to a block boundary, the contents of the 284 * partial block following the end of the file must be 285 * zero'ed in case it ever become accessible again because 286 * of subsequent file growth. 287 */ 288 /* I don't understand the comment above */ 289 offset = blkoff(fs, length); 290 if (offset == 0) { 291 oip->i_size = length; 292 } else { 293 lbn = lblkno(fs, length); 294 flags |= BA_CLRBUF; 295 error = ext2_balloc(oip, lbn, offset, cred, &bp, flags); 296 if (error) 297 return (error); 298 oip->i_size = length; 299 size = blksize(fs, oip, lbn); 300 bzero((char *)bp->b_data + offset, (u_int)(size - offset)); 301 allocbuf(bp, size); 302 if (bp->b_bufsize == fs->e2fs_bsize) 303 bp->b_flags |= B_CLUSTEROK; 304 if (flags & IO_SYNC) 305 bwrite(bp); 306 else if (DOINGASYNC(ovp)) 307 bdwrite(bp); 308 else 309 bawrite(bp); 310 } 311 /* 312 * Calculate index into inode's block list of 313 * last direct and indirect blocks (if any) 314 * which we want to keep. Lastblock is -1 when 315 * the file is truncated to 0. 316 */ 317 lastblock = lblkno(fs, length + fs->e2fs_bsize - 1) - 1; 318 lastiblock[SINGLE] = lastblock - EXT2_NDADDR; 319 lastiblock[DOUBLE] = lastiblock[SINGLE] - NINDIR(fs); 320 lastiblock[TRIPLE] = lastiblock[DOUBLE] - NINDIR(fs) * NINDIR(fs); 321 nblocks = btodb(fs->e2fs_bsize); 322 /* 323 * Update file and block pointers on disk before we start freeing 324 * blocks. If we crash before free'ing blocks below, the blocks 325 * will be returned to the free list. lastiblock values are also 326 * normalized to -1 for calls to ext2_indirtrunc below. 327 */ 328 for (level = TRIPLE; level >= SINGLE; level--) { 329 oldblks[EXT2_NDADDR + level] = oip->i_ib[level]; 330 if (lastiblock[level] < 0) { 331 oip->i_ib[level] = 0; 332 lastiblock[level] = -1; 333 } 334 } 335 for (i = 0; i < EXT2_NDADDR; i++) { 336 oldblks[i] = oip->i_db[i]; 337 if (i > lastblock) 338 oip->i_db[i] = 0; 339 } 340 oip->i_flag |= IN_CHANGE | IN_UPDATE; 341 allerror = ext2_update(ovp, !DOINGASYNC(ovp)); 342 343 /* 344 * Having written the new inode to disk, save its new configuration 345 * and put back the old block pointers long enough to process them. 346 * Note that we save the new block configuration so we can check it 347 * when we are done. 348 */ 349 for (i = 0; i < EXT2_NDADDR; i++) { 350 newblks[i] = oip->i_db[i]; 351 oip->i_db[i] = oldblks[i]; 352 } 353 for (i = 0; i < EXT2_NIADDR; i++) { 354 newblks[EXT2_NDADDR + i] = oip->i_ib[i]; 355 oip->i_ib[i] = oldblks[EXT2_NDADDR + i]; 356 } 357 oip->i_size = osize; 358 error = vtruncbuf(ovp, cred, length, (int)fs->e2fs_bsize); 359 if (error && (allerror == 0)) 360 allerror = error; 361 vnode_pager_setsize(ovp, length); 362 363 /* 364 * Indirect blocks first. 365 */ 366 indir_lbn[SINGLE] = -EXT2_NDADDR; 367 indir_lbn[DOUBLE] = indir_lbn[SINGLE] - NINDIR(fs) - 1; 368 indir_lbn[TRIPLE] = indir_lbn[DOUBLE] - NINDIR(fs) * NINDIR(fs) - 1; 369 for (level = TRIPLE; level >= SINGLE; level--) { 370 bn = oip->i_ib[level]; 371 if (bn != 0) { 372 error = ext2_indirtrunc(oip, indir_lbn[level], 373 fsbtodb(fs, bn), lastiblock[level], level, &count); 374 if (error) 375 allerror = error; 376 blocksreleased += count; 377 if (lastiblock[level] < 0) { 378 oip->i_ib[level] = 0; 379 ext2_blkfree(oip, bn, fs->e2fs_fsize); 380 blocksreleased += nblocks; 381 } 382 } 383 if (lastiblock[level] >= 0) 384 goto done; 385 } 386 387 /* 388 * All whole direct blocks or frags. 389 */ 390 for (i = EXT2_NDADDR - 1; i > lastblock; i--) { 391 long bsize; 392 393 bn = oip->i_db[i]; 394 if (bn == 0) 395 continue; 396 oip->i_db[i] = 0; 397 bsize = blksize(fs, oip, i); 398 ext2_blkfree(oip, bn, bsize); 399 blocksreleased += btodb(bsize); 400 } 401 if (lastblock < 0) 402 goto done; 403 404 /* 405 * Finally, look for a change in size of the 406 * last direct block; release any frags. 407 */ 408 bn = oip->i_db[lastblock]; 409 if (bn != 0) { 410 long oldspace, newspace; 411 412 /* 413 * Calculate amount of space we're giving 414 * back as old block size minus new block size. 415 */ 416 oldspace = blksize(fs, oip, lastblock); 417 oip->i_size = length; 418 newspace = blksize(fs, oip, lastblock); 419 if (newspace == 0) 420 panic("ext2_truncate: newspace"); 421 if (oldspace - newspace > 0) { 422 /* 423 * Block number of space to be free'd is 424 * the old block # plus the number of frags 425 * required for the storage we're keeping. 426 */ 427 bn += numfrags(fs, newspace); 428 ext2_blkfree(oip, bn, oldspace - newspace); 429 blocksreleased += btodb(oldspace - newspace); 430 } 431 } 432 done: 433 #ifdef INVARIANTS 434 for (level = SINGLE; level <= TRIPLE; level++) 435 if (newblks[EXT2_NDADDR + level] != oip->i_ib[level]) 436 panic("itrunc1"); 437 for (i = 0; i < EXT2_NDADDR; i++) 438 if (newblks[i] != oip->i_db[i]) 439 panic("itrunc2"); 440 BO_LOCK(bo); 441 if (length == 0 && (bo->bo_dirty.bv_cnt != 0 || 442 bo->bo_clean.bv_cnt != 0)) 443 panic("itrunc3"); 444 BO_UNLOCK(bo); 445 #endif /* INVARIANTS */ 446 /* 447 * Put back the real size. 448 */ 449 oip->i_size = length; 450 if (oip->i_blocks >= blocksreleased) 451 oip->i_blocks -= blocksreleased; 452 else /* sanity */ 453 oip->i_blocks = 0; 454 oip->i_flag |= IN_CHANGE; 455 vnode_pager_setsize(ovp, length); 456 return (allerror); 457 } 458 459 static int 460 ext2_ext_truncate(struct vnode *vp, off_t length, int flags, 461 struct ucred *cred, struct thread *td) 462 { 463 struct vnode *ovp = vp; 464 int32_t lastblock; 465 struct m_ext2fs *fs; 466 struct inode *oip; 467 struct buf *bp; 468 uint32_t lbn, offset; 469 int error, size; 470 off_t osize; 471 472 oip = VTOI(ovp); 473 fs = oip->i_e2fs; 474 osize = oip->i_size; 475 476 if (osize < length) { 477 if (length > oip->i_e2fs->e2fs_maxfilesize) { 478 return (EFBIG); 479 } 480 vnode_pager_setsize(ovp, length); 481 offset = blkoff(fs, length - 1); 482 lbn = lblkno(fs, length - 1); 483 flags |= BA_CLRBUF; 484 error = ext2_balloc(oip, lbn, offset + 1, cred, &bp, flags); 485 if (error) { 486 vnode_pager_setsize(vp, osize); 487 return (error); 488 } 489 oip->i_size = length; 490 if (bp->b_bufsize == fs->e2fs_bsize) 491 bp->b_flags |= B_CLUSTEROK; 492 if (flags & IO_SYNC) 493 bwrite(bp); 494 else if (DOINGASYNC(ovp)) 495 bdwrite(bp); 496 else 497 bawrite(bp); 498 oip->i_flag |= IN_CHANGE | IN_UPDATE; 499 return (ext2_update(ovp, !DOINGASYNC(ovp))); 500 } 501 502 lastblock = (length + fs->e2fs_bsize - 1) / fs->e2fs_bsize; 503 error = ext4_ext_remove_space(oip, lastblock, flags, cred, td); 504 if (error) 505 return (error); 506 507 offset = blkoff(fs, length); 508 if (offset == 0) { 509 oip->i_size = length; 510 } else { 511 lbn = lblkno(fs, length); 512 flags |= BA_CLRBUF; 513 error = ext2_balloc(oip, lbn, offset, cred, &bp, flags); 514 if (error) { 515 return (error); 516 } 517 oip->i_size = length; 518 size = blksize(fs, oip, lbn); 519 bzero((char *)bp->b_data + offset, (u_int)(size - offset)); 520 allocbuf(bp, size); 521 if (bp->b_bufsize == fs->e2fs_bsize) 522 bp->b_flags |= B_CLUSTEROK; 523 if (flags & IO_SYNC) 524 bwrite(bp); 525 else if (DOINGASYNC(ovp)) 526 bdwrite(bp); 527 else 528 bawrite(bp); 529 } 530 531 oip->i_size = osize; 532 error = vtruncbuf(ovp, cred, length, (int)fs->e2fs_bsize); 533 if (error) 534 return (error); 535 536 vnode_pager_setsize(ovp, length); 537 538 oip->i_size = length; 539 oip->i_flag |= IN_CHANGE | IN_UPDATE; 540 error = ext2_update(ovp, !DOINGASYNC(ovp)); 541 542 return (error); 543 } 544 545 /* 546 * Truncate the inode ip to at most length size, freeing the 547 * disk blocks. 548 */ 549 int 550 ext2_truncate(struct vnode *vp, off_t length, int flags, struct ucred *cred, 551 struct thread *td) 552 { 553 struct inode *ip; 554 int error; 555 556 ASSERT_VOP_LOCKED(vp, "ext2_truncate"); 557 558 if (length < 0) 559 return (EINVAL); 560 561 ip = VTOI(vp); 562 if (vp->v_type == VLNK && 563 ip->i_size < vp->v_mount->mnt_maxsymlinklen) { 564 #ifdef INVARIANTS 565 if (length != 0) 566 panic("ext2_truncate: partial truncate of symlink"); 567 #endif 568 bzero((char *)&ip->i_shortlink, (u_int)ip->i_size); 569 ip->i_size = 0; 570 ip->i_flag |= IN_CHANGE | IN_UPDATE; 571 return (ext2_update(vp, 1)); 572 } 573 if (ip->i_size == length) { 574 ip->i_flag |= IN_CHANGE | IN_UPDATE; 575 return (ext2_update(vp, 0)); 576 } 577 578 if (ip->i_flag & IN_E4EXTENTS) 579 error = ext2_ext_truncate(vp, length, flags, cred, td); 580 else 581 error = ext2_ind_truncate(vp, length, flags, cred, td); 582 583 return (error); 584 } 585 586 /* 587 * discard preallocated blocks 588 */ 589 int 590 ext2_inactive(struct vop_inactive_args *ap) 591 { 592 struct vnode *vp = ap->a_vp; 593 struct inode *ip = VTOI(vp); 594 struct thread *td = ap->a_td; 595 int mode, error = 0; 596 597 /* 598 * Ignore inodes related to stale file handles. 599 */ 600 if (ip->i_mode == 0) 601 goto out; 602 if (ip->i_nlink <= 0) { 603 ext2_extattr_free(ip); 604 error = ext2_truncate(vp, (off_t)0, 0, NOCRED, td); 605 if (!(ip->i_flag & IN_E4EXTENTS)) 606 ip->i_rdev = 0; 607 mode = ip->i_mode; 608 ip->i_mode = 0; 609 ip->i_flag |= IN_CHANGE | IN_UPDATE; 610 ext2_vfree(vp, ip->i_number, mode); 611 } 612 if (ip->i_flag & (IN_ACCESS | IN_CHANGE | IN_MODIFIED | IN_UPDATE)) 613 ext2_update(vp, 0); 614 out: 615 /* 616 * If we are done with the inode, reclaim it 617 * so that it can be reused immediately. 618 */ 619 if (ip->i_mode == 0) 620 vrecycle(vp); 621 return (error); 622 } 623 624 /* 625 * Reclaim an inode so that it can be used for other purposes. 626 */ 627 int 628 ext2_reclaim(struct vop_reclaim_args *ap) 629 { 630 struct inode *ip; 631 struct vnode *vp = ap->a_vp; 632 633 ip = VTOI(vp); 634 if (ip->i_flag & IN_LAZYMOD) { 635 ip->i_flag |= IN_MODIFIED; 636 ext2_update(vp, 0); 637 } 638 vfs_hash_remove(vp); 639 free(vp->v_data, M_EXT2NODE); 640 vp->v_data = 0; 641 vnode_destroy_vobject(vp); 642 return (0); 643 } 644