1 /* $NetBSD: chfs_gc.c,v 1.2 2011/11/24 21:09:37 agc Exp $ */ 2 3 /*- 4 * Copyright (c) 2010 Department of Software Engineering, 5 * University of Szeged, Hungary 6 * Copyright (c) 2010 Tamas Toth <ttoth@inf.u-szeged.hu> 7 * Copyright (c) 2010 Adam Hoka <ahoka@NetBSD.org> 8 * All rights reserved. 9 * 10 * This code is derived from software contributed to The NetBSD Foundation 11 * by the Department of Software Engineering, University of Szeged, Hungary 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 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include "chfs.h" 36 37 void chfs_gc_release_inode(struct chfs_mount *, 38 struct chfs_inode *); 39 struct chfs_inode *chfs_gc_fetch_inode(struct chfs_mount *, 40 ino_t, uint32_t); 41 int chfs_check(struct chfs_mount *, struct chfs_vnode_cache *); 42 void chfs_clear_inode(struct chfs_mount *, struct chfs_inode *); 43 44 45 struct chfs_eraseblock *find_gc_block(struct chfs_mount *); 46 int chfs_gcollect_pristine(struct chfs_mount *, 47 struct chfs_eraseblock *, 48 struct chfs_vnode_cache *, struct chfs_node_ref *); 49 int chfs_gcollect_live(struct chfs_mount *, 50 struct chfs_eraseblock *, struct chfs_node_ref *, 51 struct chfs_inode *); 52 int chfs_gcollect_vnode(struct chfs_mount *, struct chfs_inode *); 53 int chfs_gcollect_dirent(struct chfs_mount *, 54 struct chfs_eraseblock *, struct chfs_inode *, 55 struct chfs_dirent *); 56 int chfs_gcollect_deletion_dirent(struct chfs_mount *, 57 struct chfs_eraseblock *, struct chfs_inode *, 58 struct chfs_dirent *); 59 int chfs_gcollect_dnode(struct chfs_mount *, 60 struct chfs_eraseblock *, struct chfs_inode *, 61 struct chfs_full_dnode *, uint32_t, uint32_t); 62 63 /* must be called with chm_lock_mountfields held */ 64 void 65 chfs_gc_trigger(struct chfs_mount *chmp) 66 { 67 struct garbage_collector_thread *gc = &chmp->chm_gc_thread; 68 69 //mutex_enter(&chmp->chm_lock_sizes); 70 if (gc->gcth_running && 71 chfs_gc_thread_should_wake(chmp)) { 72 cv_signal(&gc->gcth_wakeup); 73 } 74 //mutex_exit(&chmp->chm_lock_sizes); 75 } 76 77 78 void 79 chfs_gc_thread(void *data) 80 { 81 struct chfs_mount *chmp = data; 82 struct garbage_collector_thread *gc = &chmp->chm_gc_thread; 83 84 dbg_gc("[GC THREAD] thread started\n"); 85 86 mutex_enter(&chmp->chm_lock_mountfields); 87 while (gc->gcth_running) { 88 /* we must call chfs_gc_thread_should_wake with chm_lock_mountfields 89 * held, which is a bit awkwardly done here, but we cant relly 90 * do it otherway with the current design... 91 */ 92 if (chfs_gc_thread_should_wake(chmp)) { 93 // mutex_exit(&chmp->chm_lock_mountfields); 94 if (chfs_gcollect_pass(chmp) == ENOSPC) { 95 dbg_gc("No space for garbage collection\n"); 96 panic("No space for garbage collection\n"); 97 /* XXX why break here? i have added a panic 98 * here to see if it gets triggered -ahoka 99 */ 100 break; 101 } 102 /* XXX gcollect_pass drops the mutex */ 103 mutex_enter(&chmp->chm_lock_mountfields); 104 } 105 106 cv_timedwait_sig(&gc->gcth_wakeup, 107 &chmp->chm_lock_mountfields, mstohz(100)); 108 } 109 mutex_exit(&chmp->chm_lock_mountfields); 110 111 dbg_gc("[GC THREAD] thread stopped\n"); 112 kthread_exit(0); 113 } 114 115 void 116 chfs_gc_thread_start(struct chfs_mount *chmp) 117 { 118 struct garbage_collector_thread *gc = &chmp->chm_gc_thread; 119 120 cv_init(&gc->gcth_wakeup, "chfsgccv"); 121 122 gc->gcth_running = true; 123 kthread_create(PRI_NONE, /*KTHREAD_MPSAFE |*/ KTHREAD_MUSTJOIN, 124 NULL, chfs_gc_thread, chmp, &gc->gcth_thread, 125 "chfsgcth"); 126 } 127 128 void 129 chfs_gc_thread_stop(struct chfs_mount *chmp) 130 { 131 struct garbage_collector_thread *gc = &chmp->chm_gc_thread; 132 133 /* check if it is actually running. if not, do nothing */ 134 if (gc->gcth_running) { 135 gc->gcth_running = false; 136 } else { 137 return; 138 } 139 cv_signal(&gc->gcth_wakeup); 140 dbg_gc("[GC THREAD] stop signal sent\n"); 141 142 kthread_join(gc->gcth_thread); 143 #ifdef BROKEN_KTH_JOIN 144 kpause("chfsthjoin", false, mstohz(1000), NULL); 145 #endif 146 147 cv_destroy(&gc->gcth_wakeup); 148 } 149 150 /* must be called with chm_lock_mountfields held */ 151 int 152 chfs_gc_thread_should_wake(struct chfs_mount *chmp) 153 { 154 int nr_very_dirty = 0; 155 struct chfs_eraseblock *cheb; 156 uint32_t dirty; 157 158 KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); 159 160 if (!TAILQ_EMPTY(&chmp->chm_erase_pending_queue)) { 161 dbg_gc("erase_pending\n"); 162 return 1; 163 } 164 165 if (chmp->chm_unchecked_size) { 166 dbg_gc("unchecked\n"); 167 return 1; 168 } 169 170 dirty = chmp->chm_dirty_size - chmp->chm_nr_erasable_blocks * 171 chmp->chm_ebh->eb_size; 172 173 if (chmp->chm_nr_free_blocks + chmp->chm_nr_erasable_blocks < 174 chmp->chm_resv_blocks_gctrigger && (dirty > chmp->chm_nospc_dirty)) { 175 dbg_gc("free: %d + erasable: %d < resv: %d\n", 176 chmp->chm_nr_free_blocks, chmp->chm_nr_erasable_blocks, 177 chmp->chm_resv_blocks_gctrigger); 178 dbg_gc("dirty: %d > nospc_dirty: %d\n", 179 dirty, chmp->chm_nospc_dirty); 180 181 return 1; 182 } 183 184 TAILQ_FOREACH(cheb, &chmp->chm_very_dirty_queue, queue) { 185 nr_very_dirty++; 186 if (nr_very_dirty == chmp->chm_vdirty_blocks_gctrigger) { 187 dbg_gc("nr_very_dirty\n"); 188 return 1; 189 } 190 } 191 192 return 0; 193 } 194 195 void 196 chfs_gc_release_inode(struct chfs_mount *chmp, 197 struct chfs_inode *ip) 198 { 199 dbg_gc("release inode\n"); 200 //mutex_exit(&ip->inode_lock); 201 //vput(ITOV(ip)); 202 } 203 204 struct chfs_inode * 205 chfs_gc_fetch_inode(struct chfs_mount *chmp, ino_t vno, 206 uint32_t unlinked) 207 { 208 struct vnode *vp = NULL; 209 struct chfs_vnode_cache *vc; 210 struct chfs_inode *ip; 211 dbg_gc("fetch inode %llu\n", (unsigned long long)vno); 212 213 if (unlinked) { 214 dbg_gc("unlinked\n"); 215 vp = chfs_vnode_lookup(chmp, vno); 216 if (!vp) { 217 mutex_enter(&chmp->chm_lock_vnocache); 218 vc = chfs_vnode_cache_get(chmp, vno); 219 if (!vc) { 220 mutex_exit(&chmp->chm_lock_vnocache); 221 return NULL; 222 } 223 if (vc->state != VNO_STATE_CHECKEDABSENT) { 224 //sleep_on_spinunlock(&chmp->chm_lock_vnocache); 225 mutex_exit(&chmp->chm_lock_vnocache); 226 /* XXX why do we need the delay here?! */ 227 // kpause("chvncabs", true, mstohz(50), NULL); 228 KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); 229 cv_timedwait_sig( 230 &chmp->chm_gc_thread.gcth_wakeup, 231 &chmp->chm_lock_mountfields, mstohz(50)); 232 233 // KASSERT(!mutex_owned(&chmp->chm_lock_vnocache)); 234 } else { 235 mutex_exit(&chmp->chm_lock_vnocache); 236 } 237 return NULL; 238 } 239 } else { 240 dbg_gc("vnode lookup\n"); 241 vp = chfs_vnode_lookup(chmp, vno); 242 //VFS_VGET(chmp->chm_fsmp, vno, &vp); 243 } 244 dbg_gc("vp to ip\n"); 245 ip = VTOI(vp); 246 KASSERT(ip); 247 //mutex_enter(&ip->inode_lock); 248 249 return ip; 250 } 251 252 extern rb_tree_ops_t frag_rbtree_ops; 253 254 int 255 chfs_check(struct chfs_mount *chmp, struct chfs_vnode_cache *chvc) 256 { 257 struct chfs_inode *ip; 258 struct vnode *vp; 259 int ret; 260 261 ip = pool_get(&chfs_inode_pool, PR_WAITOK); 262 if (!ip) { 263 return ENOMEM; 264 } 265 266 vp = kmem_zalloc(sizeof(struct vnode), KM_SLEEP); 267 268 ip->chvc = chvc; 269 ip->vp = vp; 270 271 vp->v_data = ip; 272 273 rb_tree_init(&ip->fragtree, &frag_rbtree_ops); 274 TAILQ_INIT(&ip->dents); 275 276 ret = chfs_read_inode_internal(chmp, ip); 277 if (!ret) { 278 chfs_clear_inode(chmp, ip); 279 } 280 281 pool_put(&chfs_inode_pool, ip); 282 283 return ret; 284 } 285 286 void 287 chfs_clear_inode(struct chfs_mount *chmp, struct chfs_inode *ip) 288 { 289 struct chfs_dirent *fd, *tmpfd; 290 struct chfs_vnode_cache *chvc; 291 292 293 /* XXX not sure if this is the correct locking */ 294 // mutex_enter(&chmp->chm_lock_vnocache); 295 chvc = ip->chvc; 296 /* shouldnt this be: */ 297 //bool deleted = (chvc && !(chvc->pvno || chvc->nlink)); 298 int deleted = (chvc && !(chvc->pvno | chvc->nlink)); 299 300 if (chvc && chvc->state != VNO_STATE_CHECKING) { 301 // chfs_vnode_cache_state_set(chmp, chvc, VNO_STATE_CLEARING); 302 chvc->state = VNO_STATE_CLEARING; 303 } 304 305 if (chvc->v && ((struct chfs_vnode_cache *)chvc->v != chvc)) { 306 if (deleted) 307 chfs_mark_node_obsolete(chmp, chvc->v); 308 //chfs_free_refblock(chvc->v); 309 } 310 // mutex_enter(&chmp->chm_lock_vnocache); 311 312 chfs_kill_fragtree(&ip->fragtree); 313 /* 314 fd = TAILQ_FIRST(&ip->dents); 315 while (fd) { 316 TAILQ_REMOVE(&ip->dents, fd, fds); 317 chfs_free_dirent(fd); 318 fd = TAILQ_FIRST(&ip->dents); 319 } 320 */ 321 322 TAILQ_FOREACH_SAFE(fd, &ip->dents, fds, tmpfd) { 323 chfs_free_dirent(fd); 324 } 325 326 if (chvc && chvc->state == VNO_STATE_CHECKING) { 327 chfs_vnode_cache_set_state(chmp, 328 chvc, VNO_STATE_CHECKEDABSENT); 329 if ((struct chfs_vnode_cache *)chvc->v == chvc && 330 (struct chfs_vnode_cache *)chvc->dirents == chvc && 331 (struct chfs_vnode_cache *)chvc->dnode == chvc) 332 chfs_vnode_cache_remove(chmp, chvc); 333 } 334 335 } 336 337 struct chfs_eraseblock * 338 find_gc_block(struct chfs_mount *chmp) 339 { 340 struct chfs_eraseblock *ret; 341 struct chfs_eraseblock_queue *nextqueue; 342 343 KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); 344 345 struct timespec now; 346 vfs_timestamp(&now); 347 348 int n = now.tv_nsec % 128; 349 350 //dbg_gc("n = %d\n", n); 351 again: 352 /* if (!TAILQ_EMPTY(&chmp->chm_bad_used_queue) && chmp->chm_nr_free_blocks > chmp->chm_nr_resv_blocks_gcbad) { 353 dbg_gc("Picking block from bad_used_queue to GC next\n"); 354 nextqueue = &chmp->chm_bad_used_queue; 355 } else */if (n<50 && !TAILQ_EMPTY(&chmp->chm_erase_pending_queue)) { 356 dbg_gc("Picking block from erase_pending_queue to GC next\n"); 357 nextqueue = &chmp->chm_erase_pending_queue; 358 } else if (n<110 && !TAILQ_EMPTY(&chmp->chm_very_dirty_queue) ) { 359 dbg_gc("Picking block from very_dirty_queue to GC next\n"); 360 nextqueue = &chmp->chm_very_dirty_queue; 361 } else if (n<126 && !TAILQ_EMPTY(&chmp->chm_dirty_queue) ) { 362 dbg_gc("Picking block from dirty_queue to GC next\n"); 363 nextqueue = &chmp->chm_dirty_queue; 364 } else if (!TAILQ_EMPTY(&chmp->chm_clean_queue)) { 365 dbg_gc("Picking block from clean_queue to GC next\n"); 366 nextqueue = &chmp->chm_clean_queue; 367 } else if (!TAILQ_EMPTY(&chmp->chm_dirty_queue)) { 368 dbg_gc("Picking block from dirty_queue to GC next" 369 " (clean_queue was empty)\n"); 370 nextqueue = &chmp->chm_dirty_queue; 371 } else if (!TAILQ_EMPTY(&chmp->chm_very_dirty_queue)) { 372 dbg_gc("Picking block from very_dirty_queue to GC next" 373 " (clean_queue and dirty_queue were empty)\n"); 374 nextqueue = &chmp->chm_very_dirty_queue; 375 } else if (!TAILQ_EMPTY(&chmp->chm_erase_pending_queue)) { 376 dbg_gc("Picking block from erase_pending_queue to GC next" 377 " (clean_queue and {very_,}dirty_queue were empty)\n"); 378 nextqueue = &chmp->chm_erase_pending_queue; 379 } else if (!TAILQ_EMPTY(&chmp->chm_erasable_pending_wbuf_queue)) { 380 dbg_gc("Synching wbuf in order to reuse " 381 "erasable_pendig_wbuf_queue blocks\n"); 382 rw_enter(&chmp->chm_lock_wbuf, RW_WRITER); 383 chfs_flush_pending_wbuf(chmp); 384 rw_exit(&chmp->chm_lock_wbuf); 385 goto again; 386 } else { 387 dbg_gc("CHFS: no clean, dirty _or_ erasable" 388 " blocks to GC from! Where are they all?\n"); 389 return NULL; 390 } 391 392 ret = TAILQ_FIRST(nextqueue); 393 if (chmp->chm_nextblock) { 394 dbg_gc("nextblock num: %u - gcblock num: %u\n", 395 chmp->chm_nextblock->lnr, ret->lnr); 396 if (ret == chmp->chm_nextblock) 397 goto again; 398 //KASSERT(ret != chmp->chm_nextblock); 399 //dbg_gc("first node lnr: %u ofs: %u\n", ret->first_node->lnr, ret->first_node->offset); 400 //dbg_gc("last node lnr: %u ofs: %u\n", ret->last_node->lnr, ret->last_node->offset); 401 } 402 TAILQ_REMOVE(nextqueue, ret, queue); 403 chmp->chm_gcblock = ret; 404 ret->gc_node = ret->first_node; 405 406 if (!ret->gc_node) { 407 dbg_gc("Oops! ret->gc_node at LEB: %u is NULL\n", ret->lnr); 408 panic("CHFS BUG - one LEB's gc_node is NULL\n"); 409 } 410 411 /* TODO wasted size? */ 412 return ret; 413 } 414 415 416 int 417 chfs_gcollect_pass(struct chfs_mount *chmp) 418 { 419 struct chfs_vnode_cache *vc; 420 struct chfs_eraseblock *eb; 421 struct chfs_node_ref *nref; 422 uint32_t gcblock_dirty; 423 struct chfs_inode *ip; 424 ino_t vno, pvno; 425 uint32_t nlink; 426 int ret = 0; 427 428 KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); 429 430 // mutex_enter(&chmp->chm_lock_mountfields); 431 for (;;) { 432 mutex_enter(&chmp->chm_lock_sizes); 433 434 dbg_gc("unchecked size == %u\n", chmp->chm_unchecked_size); 435 if (!chmp->chm_unchecked_size) 436 break; 437 438 if (chmp->chm_checked_vno > chmp->chm_max_vno) { 439 mutex_exit(&chmp->chm_lock_sizes); 440 mutex_exit(&chmp->chm_lock_mountfields); 441 dbg_gc("checked_vno (#%llu) > max_vno (#%llu)\n", 442 (unsigned long long)chmp->chm_checked_vno, 443 (unsigned long long)chmp->chm_max_vno); 444 return ENOSPC; 445 } 446 447 mutex_exit(&chmp->chm_lock_sizes); 448 449 mutex_enter(&chmp->chm_lock_vnocache); 450 dbg_gc("checking vno #%llu\n", 451 (unsigned long long)chmp->chm_checked_vno); 452 dbg_gc("get vnode cache\n"); 453 vc = chfs_vnode_cache_get(chmp, chmp->chm_checked_vno++); 454 455 if (!vc) { 456 dbg_gc("!vc\n"); 457 mutex_exit(&chmp->chm_lock_vnocache); 458 continue; 459 } 460 461 if ((vc->pvno | vc->nlink) == 0) { 462 dbg_gc("(pvno | nlink) == 0\n"); 463 mutex_exit(&chmp->chm_lock_vnocache); 464 continue; 465 } 466 467 dbg_gc("switch\n"); 468 switch (vc->state) { 469 case VNO_STATE_CHECKEDABSENT: 470 case VNO_STATE_PRESENT: 471 mutex_exit(&chmp->chm_lock_vnocache); 472 continue; 473 474 case VNO_STATE_GC: 475 case VNO_STATE_CHECKING: 476 mutex_exit(&chmp->chm_lock_vnocache); 477 mutex_exit(&chmp->chm_lock_mountfields); 478 dbg_gc("VNO_STATE GC or CHECKING\n"); 479 panic("CHFS BUG - vc state gc or checking\n"); 480 481 case VNO_STATE_READING: 482 chmp->chm_checked_vno--; 483 mutex_exit(&chmp->chm_lock_vnocache); 484 /* XXX why do we need the delay here?! */ 485 kpause("chvncrea", true, mstohz(50), NULL); 486 487 // sleep_on_spinunlock(&chmp->chm_lock_vnocache); 488 // KASSERT(!mutex_owned(&chmp->chm_lock_vnocache)); 489 mutex_exit(&chmp->chm_lock_mountfields); 490 return 0; 491 492 default: 493 mutex_exit(&chmp->chm_lock_vnocache); 494 mutex_exit(&chmp->chm_lock_mountfields); 495 dbg_gc("default\n"); 496 panic("CHFS BUG - vc state is other what we" 497 " checked\n"); 498 499 case VNO_STATE_UNCHECKED: 500 ; 501 } 502 503 chfs_vnode_cache_set_state(chmp, vc, VNO_STATE_CHECKING); 504 505 /* XXX check if this is too heavy to call under 506 * chm_lock_vnocache 507 */ 508 ret = chfs_check(chmp, vc); 509 dbg_gc("set state\n"); 510 chfs_vnode_cache_set_state(chmp, 511 vc, VNO_STATE_CHECKEDABSENT); 512 513 mutex_exit(&chmp->chm_lock_vnocache); 514 mutex_exit(&chmp->chm_lock_mountfields); 515 516 return ret; 517 } 518 519 520 eb = chmp->chm_gcblock; 521 522 if (!eb) { 523 eb = find_gc_block(chmp); 524 } 525 526 if (!eb) { 527 dbg_gc("!eb\n"); 528 if (!TAILQ_EMPTY(&chmp->chm_erase_pending_queue)) { 529 mutex_exit(&chmp->chm_lock_sizes); 530 mutex_exit(&chmp->chm_lock_mountfields); 531 return EAGAIN; 532 } 533 mutex_exit(&chmp->chm_lock_sizes); 534 mutex_exit(&chmp->chm_lock_mountfields); 535 return EIO; 536 } 537 538 if (!eb->used_size) { 539 dbg_gc("!eb->used_size\n"); 540 goto eraseit; 541 } 542 543 nref = eb->gc_node; 544 //dbg_gc("gc use: %u\n", chmp->chm_nextblock->lnr); 545 //dbg_gc("nref: %u %u\n", nref->nref_lnr, nref->nref_offset); 546 gcblock_dirty = eb->dirty_size; 547 548 while(CHFS_REF_OBSOLETE(nref)) { 549 //dbg_gc("obsoleted nref lnr: %u - offset: %u\n", nref->nref_lnr, nref->nref_offset); 550 #ifdef DBG_MSG_GC 551 if (nref == chmp->chm_blocks[nref->nref_lnr].last_node) { 552 dbg_gc("THIS NODE IS THE LAST NODE OF ITS EB\n"); 553 } 554 #endif 555 nref = node_next(nref); 556 if (!nref) { 557 //dbg_gc("!nref\n"); 558 eb->gc_node = nref; 559 mutex_exit(&chmp->chm_lock_sizes); 560 mutex_exit(&chmp->chm_lock_mountfields); 561 panic("CHFS BUG - nref is NULL)\n"); 562 } 563 } 564 eb->gc_node = nref; 565 //dbg_gc("nref the chosen one lnr: %u - offset: %u\n", nref->nref_lnr, nref->nref_offset); 566 KASSERT(nref->nref_lnr == chmp->chm_gcblock->lnr); 567 568 if (!nref->nref_next) { 569 //dbg_gc("!nref->nref_next\n"); 570 mutex_exit(&chmp->chm_lock_sizes); 571 if (CHFS_REF_FLAGS(nref) == CHFS_PRISTINE_NODE_MASK) { 572 chfs_gcollect_pristine(chmp, eb, NULL, nref); 573 } else { 574 chfs_mark_node_obsolete(chmp, nref); 575 } 576 goto lock_size; 577 } 578 579 dbg_gc("nref lnr: %u - offset: %u\n", nref->nref_lnr, nref->nref_offset); 580 vc = chfs_nref_to_vc(nref); 581 582 mutex_exit(&chmp->chm_lock_sizes); 583 584 //dbg_gc("enter vnocache lock on #%llu\n", vc->vno); 585 mutex_enter(&chmp->chm_lock_vnocache); 586 587 dbg_gc("switch\n"); 588 switch(vc->state) { 589 case VNO_STATE_CHECKEDABSENT: 590 if (CHFS_REF_FLAGS(nref) == CHFS_PRISTINE_NODE_MASK) { 591 chfs_vnode_cache_set_state(chmp, vc, VNO_STATE_GC); 592 } 593 break; 594 595 case VNO_STATE_PRESENT: 596 break; 597 598 case VNO_STATE_UNCHECKED: 599 case VNO_STATE_CHECKING: 600 case VNO_STATE_GC: 601 mutex_exit(&chmp->chm_lock_vnocache); 602 mutex_exit(&chmp->chm_lock_mountfields); 603 panic("CHFS BUG - vc state unchecked," 604 " checking or gc (vno #%llu, num #%d)\n", 605 (unsigned long long)vc->vno, vc->state); 606 607 case VNO_STATE_READING: 608 mutex_exit(&chmp->chm_lock_vnocache); 609 /* XXX why do we need the delay here?! */ 610 kpause("chvncrea", true, mstohz(50), NULL); 611 612 // sleep_on_spinunlock(&chmp->chm_lock_vnocache); 613 // KASSERT(!mutex_owned(&chmp->chm_lock_vnocache)); 614 mutex_exit(&chmp->chm_lock_mountfields); 615 return 0; 616 } 617 618 if (vc->state == VNO_STATE_GC) { 619 dbg_gc("vc->state == VNO_STATE_GC\n"); 620 mutex_exit(&chmp->chm_lock_vnocache); 621 ret = chfs_gcollect_pristine(chmp, eb, NULL, nref); 622 623 // chfs_vnode_cache_state_set(chmp, 624 // vc, VNO_STATE_CHECKEDABSENT); 625 /* XXX locking? */ 626 vc->state = VNO_STATE_CHECKEDABSENT; 627 //TODO wake_up(&chmp->chm_vnocache_wq); 628 if (ret != EBADF) 629 goto test_gcnode; 630 mutex_enter(&chmp->chm_lock_vnocache); 631 } 632 633 vno = vc->vno; 634 pvno = vc->pvno; 635 nlink = vc->nlink; 636 mutex_exit(&chmp->chm_lock_vnocache); 637 638 ip = chfs_gc_fetch_inode(chmp, vno, !(pvno | nlink)); 639 640 if (!ip) { 641 dbg_gc("!ip\n"); 642 ret = 0; 643 goto lock_size; 644 } 645 646 chfs_gcollect_live(chmp, eb, nref, ip); 647 648 chfs_gc_release_inode(chmp, ip); 649 650 test_gcnode: 651 if (eb->dirty_size == gcblock_dirty && 652 !CHFS_REF_OBSOLETE(eb->gc_node)) { 653 dbg_gc("ERROR collecting node at %u failed.\n", 654 CHFS_GET_OFS(eb->gc_node->nref_offset)); 655 656 ret = ENOSPC; 657 } 658 659 lock_size: 660 KASSERT(mutex_owned(&chmp->chm_lock_mountfields)); 661 mutex_enter(&chmp->chm_lock_sizes); 662 eraseit: 663 dbg_gc("eraseit\n"); 664 665 if (chmp->chm_gcblock) { 666 dbg_gc("eb used size = %u\n", chmp->chm_gcblock->used_size); 667 dbg_gc("eb free size = %u\n", chmp->chm_gcblock->free_size); 668 dbg_gc("eb dirty size = %u\n", chmp->chm_gcblock->dirty_size); 669 dbg_gc("eb unchecked size = %u\n", 670 chmp->chm_gcblock->unchecked_size); 671 dbg_gc("eb wasted size = %u\n", chmp->chm_gcblock->wasted_size); 672 673 KASSERT(chmp->chm_gcblock->used_size + chmp->chm_gcblock->free_size + 674 chmp->chm_gcblock->dirty_size + 675 chmp->chm_gcblock->unchecked_size + 676 chmp->chm_gcblock->wasted_size == chmp->chm_ebh->eb_size); 677 678 } 679 680 if (chmp->chm_gcblock && chmp->chm_gcblock->dirty_size + 681 chmp->chm_gcblock->wasted_size == chmp->chm_ebh->eb_size) { 682 dbg_gc("Block at leb #%u completely obsoleted by GC, " 683 "Moving to erase_pending_queue\n", chmp->chm_gcblock->lnr); 684 TAILQ_INSERT_TAIL(&chmp->chm_erase_pending_queue, 685 chmp->chm_gcblock, queue); 686 chmp->chm_gcblock = NULL; 687 chmp->chm_nr_erasable_blocks++; 688 if (!TAILQ_EMPTY(&chmp->chm_erase_pending_queue)) { 689 ret = chfs_remap_leb(chmp); 690 } 691 } 692 693 mutex_exit(&chmp->chm_lock_sizes); 694 mutex_exit(&chmp->chm_lock_mountfields); 695 dbg_gc("return\n"); 696 return ret; 697 } 698 699 700 int 701 chfs_gcollect_pristine(struct chfs_mount *chmp, struct chfs_eraseblock *cheb, 702 struct chfs_vnode_cache *chvc, struct chfs_node_ref *nref) 703 { 704 struct chfs_node_ref *newnref; 705 struct chfs_flash_node_hdr *nhdr; 706 struct chfs_flash_vnode *fvnode; 707 struct chfs_flash_dirent_node *fdirent; 708 struct chfs_flash_data_node *fdata; 709 int ret, retries = 0; 710 uint32_t ofs, crc; 711 size_t totlen = chfs_nref_len(chmp, cheb, nref); 712 char *data; 713 struct iovec vec; 714 size_t retlen; 715 716 dbg_gc("gcollect_pristine\n"); 717 718 data = kmem_alloc(totlen, KM_SLEEP); 719 if (!data) 720 return ENOMEM; 721 722 ofs = CHFS_GET_OFS(nref->nref_offset); 723 724 ret = chfs_read_leb(chmp, nref->nref_lnr, data, ofs, totlen, &retlen); 725 if (ret) { 726 dbg_gc("reading error\n"); 727 return ret; 728 } 729 if (retlen != totlen) { 730 dbg_gc("read size error\n"); 731 return EIO; 732 } 733 nhdr = (struct chfs_flash_node_hdr *)data; 734 /* check the header */ 735 if (le16toh(nhdr->magic) != CHFS_FS_MAGIC_BITMASK) { 736 dbg_gc("node header magic number error\n"); 737 return EBADF; 738 } 739 crc = crc32(0, (uint8_t *)nhdr, CHFS_NODE_HDR_SIZE - 4); 740 if (crc != le32toh(nhdr->hdr_crc)) { 741 dbg_gc("node header crc error\n"); 742 return EBADF; 743 } 744 745 switch(le16toh(nhdr->type)) { 746 case CHFS_NODETYPE_VNODE: 747 fvnode = (struct chfs_flash_vnode *)data; 748 crc = crc32(0, (uint8_t *)fvnode, sizeof(struct chfs_flash_vnode) - 4); 749 if (crc != le32toh(fvnode->node_crc)) { 750 dbg_gc("vnode crc error\n"); 751 return EBADF; 752 } 753 break; 754 case CHFS_NODETYPE_DIRENT: 755 fdirent = (struct chfs_flash_dirent_node *)data; 756 crc = crc32(0, (uint8_t *)fdirent, sizeof(struct chfs_flash_dirent_node) - 4); 757 if (crc != le32toh(fdirent->node_crc)) { 758 dbg_gc("dirent crc error\n"); 759 return EBADF; 760 } 761 crc = crc32(0, fdirent->name, fdirent->nsize); 762 if (crc != le32toh(fdirent->name_crc)) { 763 dbg_gc("dirent name crc error\n"); 764 return EBADF; 765 } 766 break; 767 case CHFS_NODETYPE_DATA: 768 fdata = (struct chfs_flash_data_node *)data; 769 crc = crc32(0, (uint8_t *)fdata, sizeof(struct chfs_flash_data_node) - 4); 770 if (crc != le32toh(fdata->node_crc)) { 771 dbg_gc("data node crc error\n"); 772 return EBADF; 773 } 774 break; 775 default: 776 if (chvc) { 777 dbg_gc("unknown node have vnode cache\n"); 778 return EBADF; 779 } 780 } 781 /* CRC's OK, write node to its new place */ 782 retry: 783 ret = chfs_reserve_space_gc(chmp, totlen); 784 if (ret) 785 return ret; 786 787 newnref = chfs_alloc_node_ref(chmp->chm_nextblock); 788 if (!newnref) 789 return ENOMEM; 790 791 ofs = chmp->chm_ebh->eb_size - chmp->chm_nextblock->free_size; 792 newnref->nref_offset = ofs; 793 794 vec.iov_base = (void *)data; 795 vec.iov_len = totlen; 796 mutex_enter(&chmp->chm_lock_sizes); 797 ret = chfs_write_wbuf(chmp, &vec, 1, ofs, &retlen); 798 799 if (ret || retlen != totlen) { 800 chfs_err("error while writing out to the media\n"); 801 chfs_err("err: %d | size: %zu | retlen : %zu\n", 802 ret, totlen, retlen); 803 804 chfs_change_size_dirty(chmp, chmp->chm_nextblock, totlen); 805 if (retries) { 806 mutex_exit(&chmp->chm_lock_sizes); 807 return EIO; 808 } 809 810 retries++; 811 mutex_exit(&chmp->chm_lock_sizes); 812 goto retry; 813 } 814 815 mutex_exit(&chmp->chm_lock_sizes); 816 //TODO should we set free_size? 817 chfs_mark_node_obsolete(chmp, nref); 818 chfs_add_vnode_ref_to_vc(chmp, chvc, newnref); 819 return 0; 820 } 821 822 823 int 824 chfs_gcollect_live(struct chfs_mount *chmp, 825 struct chfs_eraseblock *cheb, struct chfs_node_ref *nref, 826 struct chfs_inode *ip) 827 { 828 struct chfs_node_frag *frag; 829 struct chfs_full_dnode *fn = NULL; 830 int start = 0, end = 0, nrfrags = 0; 831 struct chfs_dirent *fd = NULL; 832 int ret = 0; 833 bool is_dirent; 834 835 dbg_gc("gcollect_live\n"); 836 837 if (chmp->chm_gcblock != cheb) { 838 dbg_gc("GC block is no longer gcblock. Restart.\n"); 839 goto upnout; 840 } 841 842 if (CHFS_REF_OBSOLETE(nref)) { 843 dbg_gc("node to be GC'd was obsoleted in the meantime.\n"); 844 goto upnout; 845 } 846 847 /* It's a vnode? */ 848 if (ip->chvc->v == nref) { 849 chfs_gcollect_vnode(chmp, ip); 850 goto upnout; 851 } 852 853 /* find fn */ 854 dbg_gc("find full dnode\n"); 855 for(frag = frag_first(&ip->fragtree); 856 frag; frag = frag_next(&ip->fragtree, frag)) { 857 if (frag->node && frag->node->nref == nref) { 858 fn = frag->node; 859 end = frag->ofs + frag->size; 860 if (!nrfrags++) 861 start = frag->ofs; 862 if (nrfrags == frag->node->frags) 863 break; 864 } 865 } 866 867 /* It's a pristine node, or dnode (or hole? XXX have we hole nodes?) */ 868 if (fn) { 869 if (CHFS_REF_FLAGS(nref) == CHFS_PRISTINE_NODE_MASK) { 870 ret = chfs_gcollect_pristine(chmp, 871 cheb, ip->chvc, nref); 872 if (!ret) { 873 frag->node->nref = ip->chvc->v; 874 } 875 if (ret != EBADF) 876 goto upnout; 877 } 878 //ret = chfs_gcollect_hole(chmp, cheb, ip, fn, start, end); 879 ret = chfs_gcollect_dnode(chmp, cheb, ip, fn, start, end); 880 goto upnout; 881 } 882 883 884 /* It's a dirent? */ 885 dbg_gc("find full dirent\n"); 886 is_dirent = false; 887 TAILQ_FOREACH(fd, &ip->dents, fds) { 888 if (fd->nref == nref) { 889 is_dirent = true; 890 break; 891 } 892 } 893 894 if (is_dirent && fd->vno) { 895 ret = chfs_gcollect_dirent(chmp, cheb, ip, fd); 896 } else if (is_dirent) { 897 ret = chfs_gcollect_deletion_dirent(chmp, cheb, ip, fd); 898 } else { 899 dbg_gc("Nref at leb #%u offset 0x%08x wasn't in node list" 900 " for ino #%llu\n", 901 nref->nref_lnr, CHFS_GET_OFS(nref->nref_offset), 902 (unsigned long long)ip->ino); 903 if (CHFS_REF_OBSOLETE(nref)) { 904 dbg_gc("But it's obsolete so we don't mind" 905 " too much.\n"); 906 } 907 } 908 909 upnout: 910 return ret; 911 } 912 913 int 914 chfs_gcollect_vnode(struct chfs_mount *chmp, struct chfs_inode *ip) 915 { 916 int ret; 917 dbg_gc("gcollect_vnode\n"); 918 919 ret = chfs_write_flash_vnode(chmp, ip, ALLOC_GC); 920 921 return ret; 922 } 923 924 int 925 chfs_gcollect_dirent(struct chfs_mount *chmp, 926 struct chfs_eraseblock *cheb, struct chfs_inode *parent, 927 struct chfs_dirent *fd) 928 { 929 struct vnode *vnode = NULL; 930 struct chfs_inode *ip; 931 struct chfs_node_ref *prev; 932 dbg_gc("gcollect_dirent\n"); 933 934 vnode = chfs_vnode_lookup(chmp, fd->vno); 935 936 /* XXX maybe KASSERT or panic on this? */ 937 if (vnode == NULL) { 938 return ENOENT; 939 } 940 941 ip = VTOI(vnode); 942 943 prev = parent->chvc->dirents; 944 if (prev == fd->nref) { 945 parent->chvc->dirents = prev->nref_next; 946 dbg_gc("fd nref removed from dirents list\n"); 947 prev = NULL; 948 } 949 while (prev) { 950 if (prev->nref_next == fd->nref) { 951 prev->nref_next = fd->nref->nref_next; 952 dbg_gc("fd nref removed from dirents list\n"); 953 break; 954 } 955 prev = prev->nref_next; 956 } 957 958 prev = fd->nref; 959 chfs_mark_node_obsolete(chmp, fd->nref); 960 return chfs_write_flash_dirent(chmp, 961 parent, ip, fd, fd->vno, ALLOC_GC); 962 } 963 964 /* Check dirents what are marked as deleted. */ 965 int 966 chfs_gcollect_deletion_dirent(struct chfs_mount *chmp, 967 struct chfs_eraseblock *cheb, struct chfs_inode *parent, 968 struct chfs_dirent *fd) 969 { 970 struct chfs_flash_dirent_node chfdn; 971 struct chfs_node_ref *nref; 972 size_t retlen, name_len, nref_len; 973 uint32_t name_crc; 974 975 int ret; 976 977 struct vnode *vnode = NULL; 978 979 dbg_gc("gcollect_deletion_dirent\n"); 980 981 name_len = strlen(fd->name); 982 name_crc = crc32(0, fd->name, name_len); 983 984 nref_len = chfs_nref_len(chmp, cheb, fd->nref); 985 986 vnode = chfs_vnode_lookup(chmp, fd->vno); 987 988 //dbg_gc("ip from vnode\n"); 989 //VFS_VGET(chmp->chm_fsmp, fd->vno, &vnode); 990 //ip = VTOI(vnode); 991 //vput(vnode); 992 993 //dbg_gc("mutex enter erase_completion_lock\n"); 994 995 // dbg_gc("alloc chfdn\n"); 996 // chfdn = kmem_alloc(nref_len, KM_SLEEP); 997 // if (!chfdn) 998 // return ENOMEM; 999 1000 for (nref = parent->chvc->dirents; 1001 nref != (void*)parent->chvc; 1002 nref = nref->nref_next) { 1003 1004 if (!CHFS_REF_OBSOLETE(nref)) 1005 continue; 1006 1007 /* if node refs have different length, skip */ 1008 if (chfs_nref_len(chmp, NULL, nref) != nref_len) 1009 continue; 1010 1011 if (CHFS_GET_OFS(nref->nref_offset) == 1012 CHFS_GET_OFS(fd->nref->nref_offset)) { 1013 continue; 1014 } 1015 1016 ret = chfs_read_leb(chmp, 1017 nref->nref_lnr, (void*)&chfdn, CHFS_GET_OFS(nref->nref_offset), 1018 nref_len, &retlen); 1019 1020 if (ret) { 1021 dbg_gc("Read error: %d\n", ret); 1022 continue; 1023 } 1024 1025 if (retlen != nref_len) { 1026 dbg_gc("Error reading node:" 1027 " read: %zu insted of: %zu\n", retlen, nref_len); 1028 continue; 1029 } 1030 1031 /* if node type doesn't match, skip */ 1032 if (le16toh(chfdn.type) != CHFS_NODETYPE_DIRENT) 1033 continue; 1034 1035 /* if crc doesn't match, skip */ 1036 if (le32toh(chfdn.name_crc) != name_crc) 1037 continue; 1038 1039 /* if length of name different, or this is an another deletion 1040 * dirent, skip 1041 */ 1042 if (chfdn.nsize != name_len || !le64toh(chfdn.vno)) 1043 continue; 1044 1045 /* check actual name */ 1046 if (memcmp(chfdn.name, fd->name, name_len)) 1047 continue; 1048 1049 // kmem_free(chfdn, nref_len); 1050 1051 chfs_mark_node_obsolete(chmp, fd->nref); 1052 return chfs_write_flash_dirent(chmp, 1053 parent, NULL, fd, fd->vno, ALLOC_GC); 1054 } 1055 1056 // kmem_free(chfdn, nref_len); 1057 1058 TAILQ_REMOVE(&parent->dents, fd, fds); 1059 chfs_free_dirent(fd); 1060 return 0; 1061 } 1062 1063 int 1064 chfs_gcollect_dnode(struct chfs_mount *chmp, 1065 struct chfs_eraseblock *orig_cheb, struct chfs_inode *ip, 1066 struct chfs_full_dnode *fn, uint32_t orig_start, uint32_t orig_end) 1067 { 1068 struct chfs_node_ref *nref, *prev; 1069 struct chfs_full_dnode *newfn; 1070 struct chfs_flash_data_node *fdnode; 1071 int ret = 0, retries = 0; 1072 uint32_t totlen; 1073 char *data = NULL; 1074 struct iovec vec; 1075 size_t retlen; 1076 dbg_gc("gcollect_dnode\n"); 1077 1078 //uint32_t used_size; 1079 1080 /* TODO GC merging frags, should we use it? 1081 1082 uint32_t start, end; 1083 1084 start = orig_start; 1085 end = orig_end; 1086 1087 if (chmp->chm_nr_free_blocks + chmp->chm_nr_erasable_blocks > chmp->chm_resv_blocks_gcmerge) { 1088 struct chfs_node_frag *frag; 1089 uint32_t min, max; 1090 1091 min = start & (PAGE_CACHE_SIZE-1); 1092 max = min + PAGE_CACHE_SIZE; 1093 1094 frag = (struct chfs_node_frag *)rb_tree_find_node_leq(&ip->i_chfs_ext.fragtree, &start); 1095 KASSERT(frag->ofs == start); 1096 1097 while ((frag = frag_prev(&ip->i_chfs_ext.fragtree, frag)) && frag->ofs >= min) { 1098 if (frag->ofs > min) { 1099 start = frag->ofs; 1100 continue; 1101 } 1102 1103 if (!frag->node || !frag->node->nref) { 1104 break; 1105 } else { 1106 struct chfs_node_ref *nref = frag->node->nref; 1107 struct chfs_eraseblock *cheb; 1108 1109 cheb = &chmp->chm_blocks[nref->nref_lnr]; 1110 1111 if (cheb == chmp->chm_gcblock) 1112 start = frag->ofs; 1113 1114 //TODO is this a clean block? 1115 1116 start = frag->ofs; 1117 break; 1118 } 1119 } 1120 1121 end--; 1122 frag = (struct chfs_node_frag *)rb_tree_find_node_leq(&ip->i_chfs_ext.fragtree, &(end)); 1123 1124 while ((frag = frag_next(&ip->i_chfs_ext.fragtree, frag)) && (frag->ofs + frag->size <= max)) { 1125 if (frag->ofs + frag->size < max) { 1126 end = frag->ofs + frag->size; 1127 continue; 1128 } 1129 1130 if (!frag->node || !frag->node->nref) { 1131 break; 1132 } else { 1133 struct chfs_node_ref *nref = frag->node->nref; 1134 struct chfs_eraseblock *cheb; 1135 1136 cheb = &chmp->chm_blocks[nref->nref_lnr]; 1137 1138 if (cheb == chmp->chm_gcblock) 1139 end = frag->ofs + frag->size; 1140 1141 //TODO is this a clean block? 1142 1143 end = frag->ofs + frag->size; 1144 break; 1145 } 1146 } 1147 1148 KASSERT(end <= 1149 frag_last(&ip->i_chfs_ext.fragtree)->ofs + 1150 frag_last(&ip->i_chfs_ext.fragtree)->size); 1151 KASSERT(end >= orig_end); 1152 KASSERT(start <= orig_start); 1153 } 1154 */ 1155 KASSERT(orig_cheb->lnr == fn->nref->nref_lnr); 1156 totlen = chfs_nref_len(chmp, orig_cheb, fn->nref); 1157 data = kmem_alloc(totlen, KM_SLEEP); 1158 1159 ret = chfs_read_leb(chmp, fn->nref->nref_lnr, data, fn->nref->nref_offset, 1160 totlen, &retlen); 1161 1162 fdnode = (struct chfs_flash_data_node *)data; 1163 fdnode->version = htole64(++ip->chvc->highest_version); 1164 fdnode->node_crc = htole32(crc32(0, (uint8_t *)fdnode, 1165 sizeof(*fdnode) - 4)); 1166 1167 vec.iov_base = (void *)data; 1168 vec.iov_len = totlen; 1169 1170 retry: 1171 ret = chfs_reserve_space_gc(chmp, totlen); 1172 if (ret) 1173 goto out; 1174 1175 nref = chfs_alloc_node_ref(chmp->chm_nextblock); 1176 if (!nref) { 1177 ret = ENOMEM; 1178 goto out; 1179 } 1180 1181 mutex_enter(&chmp->chm_lock_sizes); 1182 1183 nref->nref_offset = chmp->chm_ebh->eb_size - chmp->chm_nextblock->free_size; 1184 KASSERT(nref->nref_offset % 4 == 0); 1185 chfs_change_size_free(chmp, chmp->chm_nextblock, -totlen); 1186 1187 ret = chfs_write_wbuf(chmp, &vec, 1, nref->nref_offset, &retlen); 1188 if (ret || retlen != totlen) { 1189 chfs_err("error while writing out to the media\n"); 1190 chfs_err("err: %d | size: %d | retlen : %zu\n", 1191 ret, totlen, retlen); 1192 chfs_change_size_dirty(chmp, chmp->chm_nextblock, totlen); 1193 if (retries) { 1194 ret = EIO; 1195 mutex_exit(&chmp->chm_lock_sizes); 1196 goto out; 1197 } 1198 1199 retries++; 1200 mutex_exit(&chmp->chm_lock_sizes); 1201 goto retry; 1202 } 1203 1204 dbg_gc("new nref lnr: %u - offset: %u\n", nref->nref_lnr, nref->nref_offset); 1205 1206 chfs_change_size_used(chmp, &chmp->chm_blocks[nref->nref_lnr], totlen); 1207 mutex_exit(&chmp->chm_lock_sizes); 1208 KASSERT(chmp->chm_blocks[nref->nref_lnr].used_size <= chmp->chm_ebh->eb_size); 1209 1210 newfn = chfs_alloc_full_dnode(); 1211 newfn->nref = nref; 1212 newfn->ofs = fn->ofs; 1213 newfn->size = fn->size; 1214 newfn->frags = fn->frags; 1215 1216 //TODO should we remove fd from dnode list? 1217 1218 prev = ip->chvc->dnode; 1219 if (prev == fn->nref) { 1220 ip->chvc->dnode = prev->nref_next; 1221 prev = NULL; 1222 } 1223 while (prev) { 1224 if (prev->nref_next == fn->nref) { 1225 prev->nref_next = fn->nref->nref_next; 1226 break; 1227 } 1228 prev = prev->nref_next; 1229 } 1230 1231 chfs_add_full_dnode_to_inode(chmp, ip, newfn); 1232 chfs_add_node_to_list(chmp, 1233 ip->chvc, newfn->nref, &ip->chvc->dnode); 1234 1235 out: 1236 kmem_free(data, totlen); 1237 return ret; 1238 } 1239