1/*- 2 * Copyright (c) 2010-2012 Semihalf. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h> 28__FBSDID("$FreeBSD: releng/11.0/sys/fs/nandfs/nandfs_cleaner.c 236188 2012-05-28 16:33:58Z marcel $"); 29 30#include <sys/param.h> 31#include <sys/systm.h> 32#include <sys/conf.h> 33#include <sys/kernel.h> 34#include <sys/lock.h> 35#include <sys/malloc.h> 36#include <sys/mount.h> 37#include <sys/mutex.h> 38#include <sys/buf.h> 39#include <sys/namei.h> 40#include <sys/vnode.h> 41#include <sys/bio.h> 42 43#include <fs/nandfs/nandfs_mount.h> 44#include <fs/nandfs/nandfs.h> 45#include <fs/nandfs/nandfs_subr.h> 46 47#define NANDFS_CLEANER_KILL 1 48 49static void nandfs_cleaner(struct nandfs_device *); 50static int nandfs_cleaner_clean_segments(struct nandfs_device *, 51 struct nandfs_vinfo *, uint32_t, struct nandfs_period *, uint32_t, 52 struct nandfs_bdesc *, uint32_t, uint64_t *, uint32_t); 53 54static int 55nandfs_process_bdesc(struct nandfs_device *nffsdev, struct nandfs_bdesc *bd, 56 uint64_t nmembs); 57 58static void 59nandfs_wakeup_wait_cleaner(struct nandfs_device *fsdev, int reason) 60{ 61 62 mtx_lock(&fsdev->nd_clean_mtx); 63 if (reason == NANDFS_CLEANER_KILL) 64 fsdev->nd_cleaner_exit = 1; 65 if (fsdev->nd_cleaning == 0) { 66 fsdev->nd_cleaning = 1; 67 wakeup(&fsdev->nd_cleaning); 68 } 69 cv_wait(&fsdev->nd_clean_cv, &fsdev->nd_clean_mtx); 70 mtx_unlock(&fsdev->nd_clean_mtx); 71} 72 73int 74nandfs_start_cleaner(struct nandfs_device *fsdev) 75{ 76 int error; 77 78 MPASS(fsdev->nd_cleaner == NULL); 79 80 fsdev->nd_cleaner_exit = 0; 81 82 error = kthread_add((void(*)(void *))nandfs_cleaner, fsdev, NULL, 83 &fsdev->nd_cleaner, 0, 0, "nandfs_cleaner"); 84 if (error) 85 printf("nandfs: could not start cleaner: %d\n", error); 86 87 return (error); 88} 89 90int 91nandfs_stop_cleaner(struct nandfs_device *fsdev) 92{ 93 94 MPASS(fsdev->nd_cleaner != NULL); 95 nandfs_wakeup_wait_cleaner(fsdev, NANDFS_CLEANER_KILL); 96 fsdev->nd_cleaner = NULL; 97 98 DPRINTF(CLEAN, ("cleaner stopped\n")); 99 return (0); 100} 101 102static int 103nandfs_cleaner_finished(struct nandfs_device *fsdev) 104{ 105 int exit; 106 107 mtx_lock(&fsdev->nd_clean_mtx); 108 fsdev->nd_cleaning = 0; 109 if (!fsdev->nd_cleaner_exit) { 110 DPRINTF(CLEAN, ("%s: sleep\n", __func__)); 111 msleep(&fsdev->nd_cleaning, &fsdev->nd_clean_mtx, PRIBIO, "-", 112 hz * nandfs_cleaner_interval); 113 } 114 exit = fsdev->nd_cleaner_exit; 115 cv_broadcast(&fsdev->nd_clean_cv); 116 mtx_unlock(&fsdev->nd_clean_mtx); 117 if (exit) { 118 DPRINTF(CLEAN, ("%s: no longer active\n", __func__)); 119 return (1); 120 } 121 122 return (0); 123} 124 125static void 126print_suinfo(struct nandfs_suinfo *suinfo, int nsegs) 127{ 128 int i; 129 130 for (i = 0; i < nsegs; i++) { 131 DPRINTF(CLEAN, ("%jx %jd %c%c%c %10u\n", 132 suinfo[i].nsi_num, suinfo[i].nsi_lastmod, 133 (suinfo[i].nsi_flags & 134 (NANDFS_SEGMENT_USAGE_ACTIVE) ? 'a' : '-'), 135 (suinfo[i].nsi_flags & 136 (NANDFS_SEGMENT_USAGE_DIRTY) ? 'd' : '-'), 137 (suinfo[i].nsi_flags & 138 (NANDFS_SEGMENT_USAGE_ERROR) ? 'e' : '-'), 139 suinfo[i].nsi_blocks)); 140 } 141} 142 143static int 144nandfs_cleaner_vblock_is_alive(struct nandfs_device *fsdev, 145 struct nandfs_vinfo *vinfo, struct nandfs_cpinfo *cp, uint32_t ncps) 146{ 147 int64_t idx, min, max; 148 149 if (vinfo->nvi_end >= fsdev->nd_last_cno) 150 return (1); 151 152 if (ncps == 0) 153 return (0); 154 155 if (vinfo->nvi_end < cp[0].nci_cno || 156 vinfo->nvi_start > cp[ncps - 1].nci_cno) 157 return (0); 158 159 idx = min = 0; 160 max = ncps - 1; 161 while (min <= max) { 162 idx = (min + max) / 2; 163 if (vinfo->nvi_start == cp[idx].nci_cno) 164 return (1); 165 if (vinfo->nvi_start < cp[idx].nci_cno) 166 max = idx - 1; 167 else 168 min = idx + 1; 169 } 170 171 return (vinfo->nvi_end >= cp[idx].nci_cno); 172} 173 174static void 175nandfs_cleaner_vinfo_mark_alive(struct nandfs_device *fsdev, 176 struct nandfs_vinfo *vinfo, uint32_t nmembs, struct nandfs_cpinfo *cp, 177 uint32_t ncps) 178{ 179 uint32_t i; 180 181 for (i = 0; i < nmembs; i++) 182 vinfo[i].nvi_alive = 183 nandfs_cleaner_vblock_is_alive(fsdev, &vinfo[i], cp, ncps); 184} 185 186static int 187nandfs_cleaner_bdesc_is_alive(struct nandfs_device *fsdev, 188 struct nandfs_bdesc *bdesc) 189{ 190 int alive; 191 192 alive = bdesc->bd_oblocknr == bdesc->bd_blocknr; 193 if (!alive) 194 MPASS(abs(bdesc->bd_oblocknr - bdesc->bd_blocknr) > 2); 195 196 return (alive); 197} 198 199static void 200nandfs_cleaner_bdesc_mark_alive(struct nandfs_device *fsdev, 201 struct nandfs_bdesc *bdesc, uint32_t nmembs) 202{ 203 uint32_t i; 204 205 for (i = 0; i < nmembs; i++) 206 bdesc[i].bd_alive = nandfs_cleaner_bdesc_is_alive(fsdev, 207 &bdesc[i]); 208} 209 210static void 211nandfs_cleaner_iterate_psegment(struct nandfs_device *fsdev, 212 struct nandfs_segment_summary *segsum, union nandfs_binfo *binfo, 213 nandfs_daddr_t blk, struct nandfs_vinfo **vipp, struct nandfs_bdesc **bdpp) 214{ 215 int i; 216 217 DPRINTF(CLEAN, ("%s nbinfos %x\n", __func__, segsum->ss_nbinfos)); 218 for (i = 0; i < segsum->ss_nbinfos; i++) { 219 if (binfo[i].bi_v.bi_ino == NANDFS_DAT_INO) { 220 (*bdpp)->bd_oblocknr = blk + segsum->ss_nblocks - 221 segsum->ss_nbinfos + i; 222 /* 223 * XXX Hack 224 */ 225 if (segsum->ss_flags & NANDFS_SS_SR) 226 (*bdpp)->bd_oblocknr--; 227 (*bdpp)->bd_level = binfo[i].bi_dat.bi_level; 228 (*bdpp)->bd_offset = binfo[i].bi_dat.bi_blkoff; 229 (*bdpp)++; 230 } else { 231 (*vipp)->nvi_ino = binfo[i].bi_v.bi_ino; 232 (*vipp)->nvi_vblocknr = binfo[i].bi_v.bi_vblocknr; 233 (*vipp)++; 234 } 235 } 236} 237 238static int 239nandfs_cleaner_iterate_segment(struct nandfs_device *fsdev, uint64_t segno, 240 struct nandfs_vinfo **vipp, struct nandfs_bdesc **bdpp, int *select) 241{ 242 struct nandfs_segment_summary *segsum; 243 union nandfs_binfo *binfo; 244 struct buf *bp; 245 uint32_t nblocks; 246 nandfs_daddr_t curr, start, end; 247 int error = 0; 248 249 nandfs_get_segment_range(fsdev, segno, &start, &end); 250 251 DPRINTF(CLEAN, ("%s: segno %jx start %jx end %jx\n", __func__, segno, 252 start, end)); 253 254 *select = 0; 255 256 for (curr = start; curr < end; curr += nblocks) { 257 error = nandfs_dev_bread(fsdev, curr, NOCRED, 0, &bp); 258 if (error) { 259 brelse(bp); 260 nandfs_error("%s: couldn't load segment summary of %jx: %d\n", 261 __func__, segno, error); 262 return (error); 263 } 264 265 segsum = (struct nandfs_segment_summary *)bp->b_data; 266 binfo = (union nandfs_binfo *)(bp->b_data + segsum->ss_bytes); 267 268 if (!nandfs_segsum_valid(segsum)) { 269 brelse(bp); 270 nandfs_error("nandfs: invalid summary of segment %jx\n", segno); 271 return (error); 272 } 273 274 DPRINTF(CLEAN, ("%s: %jx magic %x bytes %x nblocks %x nbinfos " 275 "%x\n", __func__, segno, segsum->ss_magic, segsum->ss_bytes, 276 segsum->ss_nblocks, segsum->ss_nbinfos)); 277 278 nandfs_cleaner_iterate_psegment(fsdev, segsum, binfo, curr, 279 vipp, bdpp); 280 nblocks = segsum->ss_nblocks; 281 brelse(bp); 282 } 283 284 if (error == 0) 285 *select = 1; 286 287 return (error); 288} 289 290static int 291nandfs_cleaner_choose_segment(struct nandfs_device *fsdev, uint64_t **segpp, 292 uint64_t nsegs, uint64_t *rseg) 293{ 294 struct nandfs_suinfo *suinfo; 295 uint64_t i, ssegs; 296 int error; 297 298 suinfo = malloc(sizeof(*suinfo) * nsegs, M_NANDFSTEMP, 299 M_ZERO | M_WAITOK); 300 301 if (*rseg >= fsdev->nd_fsdata.f_nsegments) 302 *rseg = 0; 303 304retry: 305 error = nandfs_get_segment_info_filter(fsdev, suinfo, nsegs, *rseg, 306 &ssegs, NANDFS_SEGMENT_USAGE_DIRTY, 307 NANDFS_SEGMENT_USAGE_ACTIVE | NANDFS_SEGMENT_USAGE_ERROR | 308 NANDFS_SEGMENT_USAGE_GC); 309 if (error) { 310 nandfs_error("%s:%d", __FILE__, __LINE__); 311 goto out; 312 } 313 if (ssegs == 0 && *rseg != 0) { 314 *rseg = 0; 315 goto retry; 316 } 317 if (ssegs > 0) { 318 print_suinfo(suinfo, ssegs); 319 320 for (i = 0; i < ssegs; i++) { 321 (**segpp) = suinfo[i].nsi_num; 322 (*segpp)++; 323 } 324 *rseg = suinfo[i - 1].nsi_num + 1; 325 } 326 327out: 328 free(suinfo, M_NANDFSTEMP); 329 return (error); 330} 331 332static int 333nandfs_cleaner_body(struct nandfs_device *fsdev, uint64_t *rseg) 334{ 335 struct nandfs_vinfo *vinfo, *vip, *vipi; 336 struct nandfs_bdesc *bdesc, *bdp, *bdpi; 337 struct nandfs_cpstat cpstat; 338 struct nandfs_cpinfo *cpinfo = NULL; 339 uint64_t *segnums, *segp; 340 int select, selected; 341 int error = 0; 342 int nsegs; 343 int i; 344 345 nsegs = nandfs_cleaner_segments; 346 347 vip = vinfo = malloc(sizeof(*vinfo) * 348 fsdev->nd_fsdata.f_blocks_per_segment * nsegs, M_NANDFSTEMP, 349 M_ZERO | M_WAITOK); 350 bdp = bdesc = malloc(sizeof(*bdesc) * 351 fsdev->nd_fsdata.f_blocks_per_segment * nsegs, M_NANDFSTEMP, 352 M_ZERO | M_WAITOK); 353 segp = segnums = malloc(sizeof(*segnums) * nsegs, M_NANDFSTEMP, 354 M_WAITOK); 355 356 error = nandfs_cleaner_choose_segment(fsdev, &segp, nsegs, rseg); 357 if (error) { 358 nandfs_error("%s:%d", __FILE__, __LINE__); 359 goto out; 360 } 361 362 if (segnums == segp) 363 goto out; 364 365 selected = 0; 366 for (i = 0; i < segp - segnums; i++) { 367 error = nandfs_cleaner_iterate_segment(fsdev, segnums[i], &vip, 368 &bdp, &select); 369 if (error) { 370 /* 371 * XXX deselect (see below)? 372 */ 373 goto out; 374 } 375 if (!select) 376 segnums[i] = NANDFS_NOSEGMENT; 377 else { 378 error = nandfs_markgc_segment(fsdev, segnums[i]); 379 if (error) { 380 nandfs_error("%s:%d\n", __FILE__, __LINE__); 381 goto out; 382 } 383 selected++; 384 } 385 } 386 387 if (selected == 0) { 388 MPASS(vinfo == vip); 389 MPASS(bdesc == bdp); 390 goto out; 391 } 392 393 error = nandfs_get_cpstat(fsdev->nd_cp_node, &cpstat); 394 if (error) { 395 nandfs_error("%s:%d\n", __FILE__, __LINE__); 396 goto out; 397 } 398 399 if (cpstat.ncp_nss != 0) { 400 cpinfo = malloc(sizeof(struct nandfs_cpinfo) * cpstat.ncp_nss, 401 M_NANDFSTEMP, M_WAITOK); 402 error = nandfs_get_cpinfo(fsdev->nd_cp_node, 1, NANDFS_SNAPSHOT, 403 cpinfo, cpstat.ncp_nss, NULL); 404 if (error) { 405 nandfs_error("%s:%d\n", __FILE__, __LINE__); 406 goto out_locked; 407 } 408 } 409 410 NANDFS_WRITELOCK(fsdev); 411 DPRINTF(CLEAN, ("%s: got lock\n", __func__)); 412 413 error = nandfs_get_dat_vinfo(fsdev, vinfo, vip - vinfo); 414 if (error) { 415 nandfs_error("%s:%d\n", __FILE__, __LINE__); 416 goto out_locked; 417 } 418 419 nandfs_cleaner_vinfo_mark_alive(fsdev, vinfo, vip - vinfo, cpinfo, 420 cpstat.ncp_nss); 421 422 error = nandfs_get_dat_bdescs(fsdev, bdesc, bdp - bdesc); 423 if (error) { 424 nandfs_error("%s:%d\n", __FILE__, __LINE__); 425 goto out_locked; 426 } 427 428 nandfs_cleaner_bdesc_mark_alive(fsdev, bdesc, bdp - bdesc); 429 430 DPRINTF(CLEAN, ("got:\n")); 431 for (vipi = vinfo; vipi < vip; vipi++) { 432 DPRINTF(CLEAN, ("v ino %jx vblocknr %jx start %jx end %jx " 433 "alive %d\n", vipi->nvi_ino, vipi->nvi_vblocknr, 434 vipi->nvi_start, vipi->nvi_end, vipi->nvi_alive)); 435 } 436 for (bdpi = bdesc; bdpi < bdp; bdpi++) { 437 DPRINTF(CLEAN, ("b oblocknr %jx blocknr %jx offset %jx " 438 "alive %d\n", bdpi->bd_oblocknr, bdpi->bd_blocknr, 439 bdpi->bd_offset, bdpi->bd_alive)); 440 } 441 DPRINTF(CLEAN, ("end list\n")); 442 443 error = nandfs_cleaner_clean_segments(fsdev, vinfo, vip - vinfo, NULL, 444 0, bdesc, bdp - bdesc, segnums, segp - segnums); 445 if (error) 446 nandfs_error("%s:%d\n", __FILE__, __LINE__); 447 448out_locked: 449 NANDFS_WRITEUNLOCK(fsdev); 450out: 451 free(cpinfo, M_NANDFSTEMP); 452 free(segnums, M_NANDFSTEMP); 453 free(bdesc, M_NANDFSTEMP); 454 free(vinfo, M_NANDFSTEMP); 455 456 return (error); 457} 458 459static void 460nandfs_cleaner(struct nandfs_device *fsdev) 461{ 462 uint64_t checked_seg = 0; 463 int error; 464 465 while (!nandfs_cleaner_finished(fsdev)) { 466 if (!nandfs_cleaner_enable || rebooting) 467 continue; 468 469 DPRINTF(CLEAN, ("%s: run started\n", __func__)); 470 471 fsdev->nd_cleaning = 1; 472 473 error = nandfs_cleaner_body(fsdev, &checked_seg); 474 475 DPRINTF(CLEAN, ("%s: run finished error %d\n", __func__, 476 error)); 477 } 478 479 DPRINTF(CLEAN, ("%s: exiting\n", __func__)); 480 kthread_exit(); 481} 482 483static int 484nandfs_cleaner_clean_segments(struct nandfs_device *nffsdev, 485 struct nandfs_vinfo *vinfo, uint32_t nvinfo, 486 struct nandfs_period *pd, uint32_t npd, 487 struct nandfs_bdesc *bdesc, uint32_t nbdesc, 488 uint64_t *segments, uint32_t nsegs) 489{ 490 struct nandfs_node *gc; 491 struct buf *bp; 492 uint32_t i; 493 int error = 0; 494 495 gc = nffsdev->nd_gc_node; 496 497 DPRINTF(CLEAN, ("%s: enter\n", __func__)); 498 499 VOP_LOCK(NTOV(gc), LK_EXCLUSIVE); 500 for (i = 0; i < nvinfo; i++) { 501 if (!vinfo[i].nvi_alive) 502 continue; 503 DPRINTF(CLEAN, ("%s: read vblknr:%#jx blk:%#jx\n", 504 __func__, (uintmax_t)vinfo[i].nvi_vblocknr, 505 (uintmax_t)vinfo[i].nvi_blocknr)); 506 error = nandfs_bread(nffsdev->nd_gc_node, vinfo[i].nvi_blocknr, 507 NULL, 0, &bp); 508 if (error) { 509 nandfs_error("%s:%d", __FILE__, __LINE__); 510 VOP_UNLOCK(NTOV(gc), 0); 511 goto out; 512 } 513 nandfs_vblk_set(bp, vinfo[i].nvi_vblocknr); 514 nandfs_buf_set(bp, NANDFS_VBLK_ASSIGNED); 515 nandfs_dirty_buf(bp, 1); 516 } 517 VOP_UNLOCK(NTOV(gc), 0); 518 519 /* Delete checkpoints */ 520 for (i = 0; i < npd; i++) { 521 DPRINTF(CLEAN, ("delete checkpoint: %jx\n", 522 (uintmax_t)pd[i].p_start)); 523 error = nandfs_delete_cp(nffsdev->nd_cp_node, pd[i].p_start, 524 pd[i].p_end); 525 if (error) { 526 nandfs_error("%s:%d", __FILE__, __LINE__); 527 goto out; 528 } 529 } 530 531 /* Update vblocks */ 532 for (i = 0; i < nvinfo; i++) { 533 if (vinfo[i].nvi_alive) 534 continue; 535 DPRINTF(CLEAN, ("freeing vblknr: %jx\n", vinfo[i].nvi_vblocknr)); 536 error = nandfs_vblock_free(nffsdev, vinfo[i].nvi_vblocknr); 537 if (error) { 538 nandfs_error("%s:%d", __FILE__, __LINE__); 539 goto out; 540 } 541 } 542 543 error = nandfs_process_bdesc(nffsdev, bdesc, nbdesc); 544 if (error) { 545 nandfs_error("%s:%d", __FILE__, __LINE__); 546 goto out; 547 } 548 549 /* Add segments to clean */ 550 if (nffsdev->nd_free_count) { 551 nffsdev->nd_free_base = realloc(nffsdev->nd_free_base, 552 (nffsdev->nd_free_count + nsegs) * sizeof(uint64_t), 553 M_NANDFSTEMP, M_WAITOK | M_ZERO); 554 memcpy(&nffsdev->nd_free_base[nffsdev->nd_free_count], segments, 555 nsegs * sizeof(uint64_t)); 556 nffsdev->nd_free_count += nsegs; 557 } else { 558 nffsdev->nd_free_base = malloc(nsegs * sizeof(uint64_t), 559 M_NANDFSTEMP, M_WAITOK|M_ZERO); 560 memcpy(nffsdev->nd_free_base, segments, 561 nsegs * sizeof(uint64_t)); 562 nffsdev->nd_free_count = nsegs; 563 } 564 565out: 566 567 DPRINTF(CLEAN, ("%s: exit error %d\n", __func__, error)); 568 569 return (error); 570} 571 572static int 573nandfs_process_bdesc(struct nandfs_device *nffsdev, struct nandfs_bdesc *bd, 574 uint64_t nmembs) 575{ 576 struct nandfs_node *dat_node; 577 struct buf *bp; 578 uint64_t i; 579 int error; 580 581 dat_node = nffsdev->nd_dat_node; 582 583 VOP_LOCK(NTOV(dat_node), LK_EXCLUSIVE); 584 585 for (i = 0; i < nmembs; i++) { 586 if (!bd[i].bd_alive) 587 continue; 588 DPRINTF(CLEAN, ("%s: idx %jx offset %jx\n", 589 __func__, i, bd[i].bd_offset)); 590 if (bd[i].bd_level) { 591 error = nandfs_bread_meta(dat_node, bd[i].bd_offset, 592 NULL, 0, &bp); 593 if (error) { 594 nandfs_error("%s: cannot read dat node " 595 "level:%d\n", __func__, bd[i].bd_level); 596 brelse(bp); 597 VOP_UNLOCK(NTOV(dat_node), 0); 598 return (error); 599 } 600 nandfs_dirty_buf_meta(bp, 1); 601 nandfs_bmap_dirty_blocks(VTON(bp->b_vp), bp, 1); 602 } else { 603 error = nandfs_bread(dat_node, bd[i].bd_offset, NULL, 604 0, &bp); 605 if (error) { 606 nandfs_error("%s: cannot read dat node\n", 607 __func__); 608 brelse(bp); 609 VOP_UNLOCK(NTOV(dat_node), 0); 610 return (error); 611 } 612 nandfs_dirty_buf(bp, 1); 613 } 614 DPRINTF(CLEAN, ("%s: bp: %p\n", __func__, bp)); 615 } 616 617 VOP_UNLOCK(NTOV(dat_node), 0); 618 619 return (0); 620} 621