1 /* 2 * Copyright 2004-2020, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <new> 8 #include <stdlib.h> 9 10 #include "DoublyLinkedList.h" 11 #include "fssh_atomic.h" 12 #include "fssh_errno.h" 13 #include "fssh_fs_cache.h" 14 #include "fssh_kernel_export.h" 15 #include "fssh_lock.h" 16 #include "fssh_string.h" 17 #include "fssh_unistd.h" 18 #include "hash.h" 19 #include "vfs.h" 20 21 // TODO: this is a naive but growing implementation to test the API: 22 // 1) block reading/writing is not at all optimized for speed, it will 23 // just read and write single blocks. 24 // 2) the locking could be improved; getting a block should not need to 25 // wait for blocks to be written 26 // TODO: the retrieval/copy of the original data could be delayed until the 27 // new data must be written, ie. in low memory situations. 28 29 //#define TRACE_BLOCK_CACHE 30 #ifdef TRACE_BLOCK_CACHE 31 # define TRACE(x) fssh_dprintf x 32 #else 33 # define TRACE(x) ; 34 #endif 35 36 using std::nothrow; 37 38 // This macro is used for fatal situations that are acceptable in a running 39 // system, like out of memory situations - should only panic for debugging. 40 #define FATAL(x) fssh_panic x 41 42 #undef offsetof 43 #define offsetof(struct, member) 0 44 // TODO: I don't know why the offsetof() macro doesn't work in this context, 45 // but (0) is okay here... 46 47 namespace FSShell { 48 49 struct hash_table; 50 struct vm_page; 51 52 53 //#define DEBUG_CHANGED 54 #undef DEBUG_CHANGED 55 56 57 struct cache_transaction; 58 struct cached_block; 59 struct block_cache; 60 typedef DoublyLinkedListLink<cached_block> block_link; 61 62 struct cached_block { 63 cached_block* next; // next in hash 64 cached_block* transaction_next; 65 block_link link; 66 fssh_off_t block_number; 67 void* current_data; 68 void* original_data; 69 void* parent_data; 70 #ifdef DEBUG_CHANGED 71 void *compare; 72 #endif 73 int32_t ref_count; 74 int32_t accessed; 75 bool busy : 1; 76 bool is_writing : 1; 77 bool is_dirty : 1; 78 bool unused : 1; 79 bool discard : 1; 80 cache_transaction* transaction; 81 cache_transaction* previous_transaction; 82 83 static int Compare(void* _cacheEntry, const void* _block); 84 static uint32_t Hash(void* _cacheEntry, const void* _block, uint32_t range); 85 }; 86 87 typedef DoublyLinkedList<cached_block, 88 DoublyLinkedListMemberGetLink<cached_block, 89 &cached_block::link> > block_list; 90 91 struct cache_notification : DoublyLinkedListLinkImpl<cache_notification> { 92 int32_t transaction_id; 93 int32_t events_pending; 94 int32_t events; 95 fssh_transaction_notification_hook hook; 96 void* data; 97 bool delete_after_event; 98 }; 99 100 typedef DoublyLinkedList<cache_notification> NotificationList; 101 102 struct block_cache { 103 hash_table* hash; 104 fssh_mutex lock; 105 int fd; 106 fssh_off_t max_blocks; 107 fssh_size_t block_size; 108 int32_t allocated_block_count; 109 int32_t next_transaction_id; 110 cache_transaction* last_transaction; 111 hash_table* transaction_hash; 112 113 block_list unused_blocks; 114 115 bool read_only; 116 117 NotificationList pending_notifications; 118 119 block_cache(int fd, fssh_off_t numBlocks, 120 fssh_size_t blockSize, bool readOnly); 121 ~block_cache(); 122 123 fssh_status_t Init(); 124 125 void Free(void* buffer); 126 void* Allocate(); 127 void RemoveUnusedBlocks(int32_t maxAccessed = INT32_MAX, 128 int32_t count = INT32_MAX); 129 void RemoveBlock(cached_block* block); 130 void DiscardBlock(cached_block* block); 131 void FreeBlock(cached_block* block); 132 cached_block* NewBlock(fssh_off_t blockNumber); 133 }; 134 135 static const int32_t kMaxBlockCount = 1024; 136 137 struct cache_listener; 138 typedef DoublyLinkedListLink<cache_listener> listener_link; 139 140 struct cache_listener : cache_notification { 141 listener_link link; 142 }; 143 144 typedef DoublyLinkedList<cache_listener, 145 DoublyLinkedListMemberGetLink<cache_listener, 146 &cache_listener::link> > ListenerList; 147 148 struct cache_transaction { 149 cache_transaction(); 150 151 cache_transaction* next; 152 int32_t id; 153 int32_t num_blocks; 154 int32_t main_num_blocks; 155 int32_t sub_num_blocks; 156 cached_block* first_block; 157 block_list blocks; 158 fssh_transaction_notification_hook notification_hook; 159 void* notification_data; 160 ListenerList listeners; 161 bool open; 162 bool has_sub_transaction; 163 }; 164 165 166 static fssh_status_t write_cached_block(block_cache* cache, cached_block* block, 167 bool deleteTransaction = true); 168 169 170 static fssh_mutex sNotificationsLock; 171 172 173 // #pragma mark - notifications/listener 174 175 176 /*! Checks whether or not this is an event that closes a transaction. */ 177 static inline bool 178 is_closing_event(int32_t event) 179 { 180 return (event & (FSSH_TRANSACTION_ABORTED | FSSH_TRANSACTION_ENDED)) != 0; 181 } 182 183 184 static inline bool 185 is_written_event(int32_t event) 186 { 187 return (event & FSSH_TRANSACTION_WRITTEN) != 0; 188 } 189 190 191 /*! From the specified \a notification, it will remove the lowest pending 192 event, and return that one in \a _event. 193 If there is no pending event anymore, it will return \c false. 194 */ 195 static bool 196 get_next_pending_event(cache_notification* notification, int32_t* _event) 197 { 198 for (int32_t eventMask = 1; eventMask <= FSSH_TRANSACTION_IDLE; eventMask <<= 1) { 199 int32_t pending = fssh_atomic_and(¬ification->events_pending, 200 ~eventMask); 201 202 bool more = (pending & ~eventMask) != 0; 203 204 if ((pending & eventMask) != 0) { 205 *_event = eventMask; 206 return more; 207 } 208 } 209 210 return false; 211 } 212 213 214 static void 215 flush_pending_notifications(block_cache* cache) 216 { 217 while (true) { 218 MutexLocker locker(sNotificationsLock); 219 220 cache_notification* notification = cache->pending_notifications.Head(); 221 if (notification == NULL) 222 return; 223 224 bool deleteAfterEvent = false; 225 int32_t event = -1; 226 if (!get_next_pending_event(notification, &event)) { 227 // remove the notification if this was the last pending event 228 cache->pending_notifications.Remove(notification); 229 deleteAfterEvent = notification->delete_after_event; 230 } 231 232 if (event >= 0) { 233 // Notify listener, we need to copy the notification, as it might 234 // be removed when we unlock the list. 235 cache_notification copy = *notification; 236 locker.Unlock(); 237 238 copy.hook(copy.transaction_id, event, copy.data); 239 240 locker.Lock(); 241 } 242 243 if (deleteAfterEvent) 244 delete notification; 245 } 246 } 247 248 249 /*! Initializes the \a notification as specified. */ 250 static void 251 set_notification(cache_transaction* transaction, 252 cache_notification ¬ification, int32_t events, 253 fssh_transaction_notification_hook hook, void* data) 254 { 255 notification.transaction_id = transaction != NULL ? transaction->id : -1; 256 notification.events_pending = 0; 257 notification.events = events; 258 notification.hook = hook; 259 notification.data = data; 260 notification.delete_after_event = false; 261 } 262 263 264 /*! Makes sure the notification is deleted. It either deletes it directly, 265 when possible, or marks it for deletion if the notification is pending. 266 */ 267 static void 268 delete_notification(cache_notification* notification) 269 { 270 MutexLocker locker(sNotificationsLock); 271 272 if (notification->events_pending != 0) 273 notification->delete_after_event = true; 274 else 275 delete notification; 276 } 277 278 279 /*! Adds the notification to the pending notifications list, or, if it's 280 already part of it, updates its events_pending field. 281 Also marks the notification to be deleted if \a deleteNotification 282 is \c true. 283 Triggers the notifier thread to run. 284 */ 285 static void 286 add_notification(block_cache* cache, cache_notification* notification, 287 int32_t event, bool deleteNotification) 288 { 289 if (notification->hook == NULL) 290 return; 291 292 int32_t pending = fssh_atomic_or(¬ification->events_pending, event); 293 if (pending == 0) { 294 // not yet part of the notification list 295 MutexLocker locker(sNotificationsLock); 296 if (deleteNotification) 297 notification->delete_after_event = true; 298 cache->pending_notifications.Add(notification); 299 } else if (deleteNotification) { 300 // we might need to delete it ourselves if we're late 301 delete_notification(notification); 302 } 303 } 304 305 306 /*! Notifies all interested listeners of this transaction about the \a event. 307 If \a event is a closing event (ie. TRANSACTION_ENDED, and 308 TRANSACTION_ABORTED), all listeners except those listening to 309 TRANSACTION_WRITTEN will be removed. 310 */ 311 static void 312 notify_transaction_listeners(block_cache* cache, cache_transaction* transaction, 313 int32_t event) 314 { 315 bool isClosing = is_closing_event(event); 316 bool isWritten = is_written_event(event); 317 318 ListenerList::Iterator iterator = transaction->listeners.GetIterator(); 319 while (iterator.HasNext()) { 320 cache_listener* listener = iterator.Next(); 321 322 bool remove = (isClosing && !is_written_event(listener->events)) 323 || (isWritten && is_written_event(listener->events)); 324 if (remove) 325 iterator.Remove(); 326 327 if ((listener->events & event) != 0) 328 add_notification(cache, listener, event, remove); 329 else if (remove) 330 delete_notification(listener); 331 } 332 333 // This must work asynchronously in the kernel, but since we're not using 334 // most transaction events, we can do it here. 335 flush_pending_notifications(cache); 336 } 337 338 339 /*! Removes and deletes all listeners that are still monitoring this 340 transaction. 341 */ 342 static void 343 remove_transaction_listeners(block_cache* cache, cache_transaction* transaction) 344 { 345 ListenerList::Iterator iterator = transaction->listeners.GetIterator(); 346 while (iterator.HasNext()) { 347 cache_listener* listener = iterator.Next(); 348 iterator.Remove(); 349 350 delete_notification(listener); 351 } 352 } 353 354 355 static fssh_status_t 356 add_transaction_listener(block_cache* cache, cache_transaction* transaction, 357 int32_t events, fssh_transaction_notification_hook hookFunction, void* data) 358 { 359 ListenerList::Iterator iterator = transaction->listeners.GetIterator(); 360 while (iterator.HasNext()) { 361 cache_listener* listener = iterator.Next(); 362 363 if (listener->data == data && listener->hook == hookFunction) { 364 // this listener already exists, just update it 365 listener->events |= events; 366 return FSSH_B_OK; 367 } 368 } 369 370 cache_listener* listener = new(std::nothrow) cache_listener; 371 if (listener == NULL) 372 return FSSH_B_NO_MEMORY; 373 374 set_notification(transaction, *listener, events, hookFunction, data); 375 transaction->listeners.Add(listener); 376 return FSSH_B_OK; 377 } 378 379 380 // #pragma mark - private transaction 381 382 383 cache_transaction::cache_transaction() 384 { 385 num_blocks = 0; 386 main_num_blocks = 0; 387 sub_num_blocks = 0; 388 first_block = NULL; 389 notification_hook = NULL; 390 notification_data = NULL; 391 open = true; 392 has_sub_transaction = false; 393 } 394 395 396 static int 397 transaction_compare(void* _transaction, const void* _id) 398 { 399 cache_transaction* transaction = (cache_transaction*)_transaction; 400 const int32_t* id = (const int32_t*)_id; 401 402 return transaction->id - *id; 403 } 404 405 406 static uint32_t 407 transaction_hash(void* _transaction, const void* _id, uint32_t range) 408 { 409 cache_transaction* transaction = (cache_transaction*)_transaction; 410 const int32_t* id = (const int32_t*)_id; 411 412 if (transaction != NULL) 413 return transaction->id % range; 414 415 return (uint32_t)*id % range; 416 } 417 418 419 static void 420 delete_transaction(block_cache* cache, cache_transaction* transaction) 421 { 422 if (cache->last_transaction == transaction) 423 cache->last_transaction = NULL; 424 425 remove_transaction_listeners(cache, transaction); 426 delete transaction; 427 } 428 429 430 static cache_transaction* 431 lookup_transaction(block_cache* cache, int32_t id) 432 { 433 return (cache_transaction*)hash_lookup(cache->transaction_hash, &id); 434 } 435 436 437 // #pragma mark - cached_block 438 439 440 /*static*/ int 441 cached_block::Compare(void* _cacheEntry, const void* _block) 442 { 443 cached_block* cacheEntry = (cached_block*)_cacheEntry; 444 const fssh_off_t* block = (const fssh_off_t*)_block; 445 446 fssh_off_t diff = cacheEntry->block_number - *block; 447 if (diff > 0) 448 return 1; 449 450 return diff < 0 ? -1 : 0; 451 } 452 453 454 455 /*static*/ uint32_t 456 cached_block::Hash(void* _cacheEntry, const void* _block, uint32_t range) 457 { 458 cached_block* cacheEntry = (cached_block*)_cacheEntry; 459 const fssh_off_t* block = (const fssh_off_t*)_block; 460 461 if (cacheEntry != NULL) 462 return cacheEntry->block_number % range; 463 464 return (uint64_t)*block % range; 465 } 466 467 468 // #pragma mark - block_cache 469 470 471 block_cache::block_cache(int _fd, fssh_off_t numBlocks, fssh_size_t blockSize, 472 bool readOnly) 473 : 474 hash(NULL), 475 fd(_fd), 476 max_blocks(numBlocks), 477 block_size(blockSize), 478 allocated_block_count(0), 479 next_transaction_id(1), 480 last_transaction(NULL), 481 transaction_hash(NULL), 482 read_only(readOnly) 483 { 484 } 485 486 487 block_cache::~block_cache() 488 { 489 hash_uninit(transaction_hash); 490 hash_uninit(hash); 491 492 fssh_mutex_destroy(&lock); 493 } 494 495 496 fssh_status_t 497 block_cache::Init() 498 { 499 fssh_mutex_init(&lock, "block cache"); 500 if (lock.sem < FSSH_B_OK) 501 return lock.sem; 502 503 hash = hash_init(128, offsetof(cached_block, next), &cached_block::Compare, 504 &cached_block::Hash); 505 if (hash == NULL) 506 return FSSH_B_NO_MEMORY; 507 508 transaction_hash = hash_init(16, offsetof(cache_transaction, next), 509 &transaction_compare, &FSShell::transaction_hash); 510 if (transaction_hash == NULL) 511 return FSSH_B_NO_MEMORY; 512 513 return FSSH_B_OK; 514 } 515 516 517 void 518 block_cache::Free(void* buffer) 519 { 520 if (buffer == NULL) 521 return; 522 523 free(buffer); 524 } 525 526 527 void* 528 block_cache::Allocate() 529 { 530 return malloc(block_size); 531 } 532 533 534 void 535 block_cache::FreeBlock(cached_block* block) 536 { 537 Free(block->current_data); 538 539 if (block->original_data != NULL || block->parent_data != NULL) { 540 fssh_panic("block_cache::FreeBlock(): %" FSSH_B_PRIdOFF 541 ", original %p, parent %p\n", block->block_number, 542 block->original_data, block->parent_data); 543 } 544 545 #ifdef DEBUG_CHANGED 546 Free(block->compare); 547 #endif 548 549 delete block; 550 } 551 552 553 /*! Allocates a new block for \a blockNumber, ready for use */ 554 cached_block* 555 block_cache::NewBlock(fssh_off_t blockNumber) 556 { 557 cached_block* block = new(nothrow) cached_block; 558 if (block == NULL) { 559 FATAL(("could not allocate block!\n")); 560 return NULL; 561 } 562 563 // if we hit the limit of blocks to cache¸ try to free one or more 564 if (allocated_block_count >= kMaxBlockCount) { 565 RemoveUnusedBlocks(INT32_MAX, 566 allocated_block_count - kMaxBlockCount + 1); 567 } 568 569 block->current_data = Allocate(); 570 if (block->current_data == NULL) { 571 FATAL(("could not allocate block data!\n")); 572 delete block; 573 return NULL; 574 } 575 576 block->block_number = blockNumber; 577 block->ref_count = 0; 578 block->accessed = 0; 579 block->transaction_next = NULL; 580 block->transaction = block->previous_transaction = NULL; 581 block->original_data = NULL; 582 block->parent_data = NULL; 583 block->is_dirty = false; 584 block->unused = false; 585 block->discard = false; 586 #ifdef DEBUG_CHANGED 587 block->compare = NULL; 588 #endif 589 590 allocated_block_count++; 591 592 return block; 593 } 594 595 596 void 597 block_cache::RemoveUnusedBlocks(int32_t maxAccessed, int32_t count) 598 { 599 TRACE(("block_cache: remove up to %ld unused blocks\n", count)); 600 601 for (block_list::Iterator iterator = unused_blocks.GetIterator(); 602 cached_block *block = iterator.Next();) { 603 if (maxAccessed < block->accessed) 604 continue; 605 606 TRACE((" remove block %lld, accessed %ld times\n", 607 block->block_number, block->accessed)); 608 609 // this can only happen if no transactions are used 610 if (block->is_dirty && !block->discard) 611 write_cached_block(this, block, false); 612 613 // remove block from lists 614 iterator.Remove(); 615 RemoveBlock(block); 616 617 if (--count <= 0) 618 break; 619 } 620 } 621 622 623 void 624 block_cache::RemoveBlock(cached_block* block) 625 { 626 hash_remove(hash, block); 627 FreeBlock(block); 628 } 629 630 631 /*! Discards the block from a transaction (this method must not be called 632 for blocks not part of a transaction). 633 */ 634 void 635 block_cache::DiscardBlock(cached_block* block) 636 { 637 if (block->parent_data != NULL && block->parent_data != block->current_data) 638 Free(block->parent_data); 639 640 block->parent_data = NULL; 641 642 if (block->original_data != NULL) { 643 Free(block->original_data); 644 block->original_data = NULL; 645 } 646 647 RemoveBlock(block); 648 } 649 650 651 // #pragma mark - private block functions 652 653 654 /*! Removes a reference from the specified \a block. If this was the last 655 reference, the block is moved into the unused list. 656 In low memory situations, it will also free some blocks from that list, 657 but not necessarily the \a block it just released. 658 */ 659 static void 660 put_cached_block(block_cache* cache, cached_block* block) 661 { 662 #ifdef DEBUG_CHANGED 663 if (!block->is_dirty && block->compare != NULL 664 && memcmp(block->current_data, block->compare, cache->block_size)) { 665 fssh_dprintf("new block:\n"); 666 fssh_dump_block((const char*)block->current_data, 256, " "); 667 fssh_dprintf("unchanged block:\n"); 668 fssh_dump_block((const char*)block->compare, 256, " "); 669 write_cached_block(cache, block); 670 fssh_panic("block_cache: supposed to be clean block was changed!\n"); 671 672 cache->Free(block->compare); 673 block->compare = NULL; 674 } 675 #endif 676 677 if (--block->ref_count == 0 678 && block->transaction == NULL && block->previous_transaction == NULL) { 679 // This block is not used anymore, and not part of any transaction 680 if (block->discard) { 681 cache->RemoveBlock(block); 682 } else { 683 // put this block in the list of unused blocks 684 block->unused = true; 685 cache->unused_blocks.Add(block); 686 } 687 } 688 689 if (cache->allocated_block_count > kMaxBlockCount) { 690 cache->RemoveUnusedBlocks(INT32_MAX, 691 cache->allocated_block_count - kMaxBlockCount); 692 } 693 } 694 695 696 static void 697 put_cached_block(block_cache* cache, fssh_off_t blockNumber) 698 { 699 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 700 fssh_panic("put_cached_block: invalid block number %" FSSH_B_PRIdOFF 701 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 702 } 703 704 cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); 705 if (block != NULL) 706 put_cached_block(cache, block); 707 } 708 709 710 /*! Retrieves the block \a blockNumber from the hash table, if it's already 711 there, or reads it from the disk. 712 713 \param _allocated tells you whether or not a new block has been allocated 714 to satisfy your request. 715 \param readBlock if \c false, the block will not be read in case it was 716 not already in the cache. The block you retrieve may contain random 717 data. 718 */ 719 static fssh_status_t 720 get_cached_block(block_cache* cache, fssh_off_t blockNumber, bool* _allocated, 721 bool readBlock, cached_block** _block) 722 { 723 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 724 fssh_panic("get_cached_block: invalid block number %" FSSH_B_PRIdOFF 725 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 726 return FSSH_B_BAD_VALUE; 727 } 728 729 cached_block* block = (cached_block*)hash_lookup(cache->hash, 730 &blockNumber); 731 *_allocated = false; 732 733 if (block == NULL) { 734 // read block into cache 735 block = cache->NewBlock(blockNumber); 736 if (block == NULL) 737 return FSSH_B_NO_MEMORY; 738 739 hash_insert(cache->hash, block); 740 *_allocated = true; 741 } 742 743 if (*_allocated && readBlock) { 744 int32_t blockSize = cache->block_size; 745 746 if (fssh_read_pos(cache->fd, blockNumber * blockSize, block->current_data, 747 blockSize) < blockSize) { 748 cache->RemoveBlock(block); 749 FATAL(("could not read block %" FSSH_B_PRIdOFF "\n", blockNumber)); 750 return fssh_errno; 751 } 752 } 753 754 if (block->unused) { 755 //TRACE(("remove block %lld from unused\n", blockNumber)); 756 block->unused = false; 757 cache->unused_blocks.Remove(block); 758 } 759 760 block->ref_count++; 761 block->accessed++; 762 763 *_block = block; 764 return FSSH_B_OK; 765 } 766 767 768 /*! Returns the writable block data for the requested blockNumber. 769 If \a cleared is true, the block is not read from disk; an empty block 770 is returned. 771 772 This is the only method to insert a block into a transaction. It makes 773 sure that the previous block contents are preserved in that case. 774 */ 775 static fssh_status_t 776 get_writable_cached_block(block_cache* cache, fssh_off_t blockNumber, 777 int32_t transactionID, bool cleared, void** _block) 778 { 779 TRACE(("get_writable_cached_block(blockNumber = %lld, transaction = %d)\n", 780 blockNumber, transactionID)); 781 782 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 783 fssh_panic("get_writable_cached_block: invalid block number %" 784 FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, 785 cache->max_blocks - 1); 786 return FSSH_B_BAD_VALUE; 787 } 788 789 bool allocated; 790 cached_block* block; 791 fssh_status_t status = get_cached_block(cache, blockNumber, &allocated, 792 !cleared, &block); 793 if (status != FSSH_B_OK) 794 return status; 795 796 block->discard = false; 797 798 // if there is no transaction support, we just return the current block 799 if (transactionID == -1) { 800 if (cleared) 801 fssh_memset(block->current_data, 0, cache->block_size); 802 803 block->is_dirty = true; 804 // mark the block as dirty 805 806 *_block = block->current_data; 807 return FSSH_B_OK; 808 } 809 810 cache_transaction* transaction = block->transaction; 811 812 if (transaction != NULL && transaction->id != transactionID) { 813 // TODO: we have to wait here until the other transaction is done. 814 // Maybe we should even panic, since we can't prevent any deadlocks. 815 fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction->id); 816 put_cached_block(cache, block); 817 return FSSH_B_BAD_VALUE; 818 } 819 if (transaction == NULL && transactionID != -1) { 820 // get new transaction 821 transaction = lookup_transaction(cache, transactionID); 822 if (transaction == NULL) { 823 fssh_panic("get_writable_cached_block(): invalid transaction %d!\n", 824 (int)transactionID); 825 put_cached_block(cache, block); 826 return FSSH_B_BAD_VALUE; 827 } 828 if (!transaction->open) { 829 fssh_panic("get_writable_cached_block(): transaction already done!\n"); 830 put_cached_block(cache, block); 831 return FSSH_B_BAD_VALUE; 832 } 833 834 block->transaction = transaction; 835 836 // attach the block to the transaction block list 837 block->transaction_next = transaction->first_block; 838 transaction->first_block = block; 839 transaction->num_blocks++; 840 } 841 842 bool wasUnchanged = block->original_data == NULL 843 || block->previous_transaction != NULL; 844 845 if (!(allocated && cleared) && block->original_data == NULL) { 846 // we already have data, so we need to preserve it 847 block->original_data = cache->Allocate(); 848 if (block->original_data == NULL) { 849 FATAL(("could not allocate original_data\n")); 850 put_cached_block(cache, block); 851 return FSSH_B_NO_MEMORY; 852 } 853 854 fssh_memcpy(block->original_data, block->current_data, cache->block_size); 855 } 856 if (block->parent_data == block->current_data) { 857 // remember any previous contents for the parent transaction 858 block->parent_data = cache->Allocate(); 859 if (block->parent_data == NULL) { 860 // TODO: maybe we should just continue the current transaction in this case... 861 FATAL(("could not allocate parent\n")); 862 put_cached_block(cache, block); 863 return FSSH_B_NO_MEMORY; 864 } 865 866 fssh_memcpy(block->parent_data, block->current_data, cache->block_size); 867 transaction->sub_num_blocks++; 868 } else if (transaction != NULL && transaction->has_sub_transaction 869 && block->parent_data == NULL && wasUnchanged) 870 transaction->sub_num_blocks++; 871 872 if (cleared) 873 fssh_memset(block->current_data, 0, cache->block_size); 874 875 block->is_dirty = true; 876 877 *_block = block->current_data; 878 return FSSH_B_OK; 879 } 880 881 882 /*! Writes the specified \a block back to disk. It will always only write back 883 the oldest change of the block if it is part of more than one transaction. 884 It will automatically send out TRANSACTION_WRITTEN notices, as well as 885 delete transactions when they are no longer used, and \a deleteTransaction 886 is \c true. 887 */ 888 static fssh_status_t 889 write_cached_block(block_cache* cache, cached_block* block, 890 bool deleteTransaction) 891 { 892 cache_transaction* previous = block->previous_transaction; 893 int32_t blockSize = cache->block_size; 894 895 void* data = previous && block->original_data 896 ? block->original_data : block->current_data; 897 // we first need to write back changes from previous transactions 898 899 TRACE(("write_cached_block(block %lld)\n", block->block_number)); 900 901 fssh_ssize_t written = fssh_write_pos(cache->fd, block->block_number * blockSize, 902 data, blockSize); 903 904 if (written < blockSize) { 905 FATAL(("could not write back block %" FSSH_B_PRIdOFF " (%s)\n", 906 block->block_number, fssh_strerror(fssh_get_errno()))); 907 return FSSH_B_IO_ERROR; 908 } 909 910 if (data == block->current_data) 911 block->is_dirty = false; 912 913 if (previous != NULL) { 914 previous->blocks.Remove(block); 915 block->previous_transaction = NULL; 916 917 if (block->original_data != NULL && block->transaction == NULL) { 918 // This block is not part of a transaction, so it does not need 919 // its original pointer anymore. 920 cache->Free(block->original_data); 921 block->original_data = NULL; 922 } 923 924 // Has the previous transation been finished with that write? 925 if (--previous->num_blocks == 0) { 926 TRACE(("cache transaction %ld finished!\n", previous->id)); 927 928 notify_transaction_listeners(cache, previous, FSSH_TRANSACTION_WRITTEN); 929 930 if (deleteTransaction) { 931 hash_remove(cache->transaction_hash, previous); 932 delete_transaction(cache, previous); 933 } 934 } 935 } 936 if (block->transaction == NULL && block->ref_count == 0 && !block->unused) { 937 // the block is no longer used 938 block->unused = true; 939 cache->unused_blocks.Add(block); 940 } 941 942 return FSSH_B_OK; 943 } 944 945 946 /*! Waits until all pending notifications are carried out. 947 Safe to be called from the block writer/notifier thread. 948 You must not hold the \a cache lock when calling this function. 949 */ 950 static void 951 wait_for_notifications(block_cache* cache) 952 { 953 // TODO: nothing to wait for here. 954 } 955 956 957 fssh_status_t 958 block_cache_init() 959 { 960 fssh_mutex_init(&sNotificationsLock, "block cache notifications"); 961 return FSSH_B_OK; 962 } 963 964 965 } // namespace FSShell 966 967 968 // #pragma mark - public transaction API 969 970 971 using namespace FSShell; 972 973 974 int32_t 975 fssh_cache_start_transaction(void* _cache) 976 { 977 block_cache* cache = (block_cache*)_cache; 978 MutexLocker locker(&cache->lock); 979 980 if (cache->last_transaction && cache->last_transaction->open) { 981 fssh_panic("last transaction (%d) still open!\n", 982 (int)cache->last_transaction->id); 983 } 984 985 cache_transaction* transaction = new(nothrow) cache_transaction; 986 if (transaction == NULL) 987 return FSSH_B_NO_MEMORY; 988 989 transaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 990 cache->last_transaction = transaction; 991 992 TRACE(("cache_start_transaction(): id %d started\n", transaction->id)); 993 994 hash_insert(cache->transaction_hash, transaction); 995 996 return transaction->id; 997 } 998 999 1000 fssh_status_t 1001 fssh_cache_sync_transaction(void* _cache, int32_t id) 1002 { 1003 block_cache* cache = (block_cache*)_cache; 1004 MutexLocker locker(&cache->lock); 1005 fssh_status_t status = FSSH_B_ENTRY_NOT_FOUND; 1006 1007 TRACE(("cache_sync_transaction(id %d)\n", id)); 1008 1009 hash_iterator iterator; 1010 hash_open(cache->transaction_hash, &iterator); 1011 1012 cache_transaction* transaction; 1013 while ((transaction = (cache_transaction*)hash_next( 1014 cache->transaction_hash, &iterator)) != NULL) { 1015 // close all earlier transactions which haven't been closed yet 1016 1017 if (transaction->id <= id && !transaction->open) { 1018 // write back all of their remaining dirty blocks 1019 while (transaction->num_blocks > 0) { 1020 status = write_cached_block(cache, transaction->blocks.Head(), 1021 false); 1022 if (status != FSSH_B_OK) 1023 return status; 1024 } 1025 1026 hash_remove_current(cache->transaction_hash, &iterator); 1027 delete_transaction(cache, transaction); 1028 } 1029 } 1030 1031 hash_close(cache->transaction_hash, &iterator, false); 1032 locker.Unlock(); 1033 1034 wait_for_notifications(cache); 1035 // make sure that all pending FSSH_TRANSACTION_WRITTEN notifications 1036 // are handled after we return 1037 return FSSH_B_OK; 1038 } 1039 1040 1041 fssh_status_t 1042 fssh_cache_end_transaction(void* _cache, int32_t id, 1043 fssh_transaction_notification_hook hook, void* data) 1044 { 1045 block_cache* cache = (block_cache*)_cache; 1046 MutexLocker locker(&cache->lock); 1047 1048 TRACE(("cache_end_transaction(id = %d)\n", id)); 1049 1050 cache_transaction* transaction = lookup_transaction(cache, id); 1051 if (transaction == NULL) { 1052 fssh_panic("cache_end_transaction(): invalid transaction ID\n"); 1053 return FSSH_B_BAD_VALUE; 1054 } 1055 1056 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1057 1058 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1059 hook, data) != FSSH_B_OK) { 1060 return FSSH_B_NO_MEMORY; 1061 } 1062 1063 // iterate through all blocks and free the unchanged original contents 1064 1065 cached_block* block = transaction->first_block; 1066 cached_block* next; 1067 for (; block != NULL; block = next) { 1068 next = block->transaction_next; 1069 1070 if (block->previous_transaction != NULL) { 1071 // need to write back pending changes 1072 write_cached_block(cache, block); 1073 } 1074 if (block->discard) { 1075 // This block has been discarded in the transaction 1076 cache->DiscardBlock(block); 1077 transaction->num_blocks--; 1078 continue; 1079 } 1080 1081 if (block->original_data != NULL) { 1082 cache->Free(block->original_data); 1083 block->original_data = NULL; 1084 } 1085 if (transaction->has_sub_transaction) { 1086 if (block->parent_data != block->current_data) 1087 cache->Free(block->parent_data); 1088 block->parent_data = NULL; 1089 } 1090 1091 // move the block to the previous transaction list 1092 transaction->blocks.Add(block); 1093 1094 block->previous_transaction = transaction; 1095 block->transaction_next = NULL; 1096 block->transaction = NULL; 1097 } 1098 1099 transaction->open = false; 1100 return FSSH_B_OK; 1101 } 1102 1103 1104 fssh_status_t 1105 fssh_cache_abort_transaction(void* _cache, int32_t id) 1106 { 1107 block_cache* cache = (block_cache*)_cache; 1108 MutexLocker locker(&cache->lock); 1109 1110 TRACE(("cache_abort_transaction(id = %ld)\n", id)); 1111 1112 cache_transaction* transaction = lookup_transaction(cache, id); 1113 if (transaction == NULL) { 1114 fssh_panic("cache_abort_transaction(): invalid transaction ID\n"); 1115 return FSSH_B_BAD_VALUE; 1116 } 1117 1118 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1119 1120 // iterate through all blocks and restore their original contents 1121 1122 cached_block* block = transaction->first_block; 1123 cached_block* next; 1124 for (; block != NULL; block = next) { 1125 next = block->transaction_next; 1126 1127 if (block->original_data != NULL) { 1128 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %lld\n", 1129 transaction->id, block->block_number)); 1130 fssh_memcpy(block->current_data, block->original_data, cache->block_size); 1131 cache->Free(block->original_data); 1132 block->original_data = NULL; 1133 } 1134 if (transaction->has_sub_transaction) { 1135 if (block->parent_data != block->current_data) 1136 cache->Free(block->parent_data); 1137 block->parent_data = NULL; 1138 } 1139 1140 block->transaction_next = NULL; 1141 block->transaction = NULL; 1142 block->discard = false; 1143 } 1144 1145 hash_remove(cache->transaction_hash, transaction); 1146 delete_transaction(cache, transaction); 1147 return FSSH_B_OK; 1148 } 1149 1150 1151 /*! Acknowledges the current parent transaction, and starts a new transaction 1152 from its sub transaction. 1153 The new transaction also gets a new transaction ID. 1154 */ 1155 int32_t 1156 fssh_cache_detach_sub_transaction(void* _cache, int32_t id, 1157 fssh_transaction_notification_hook hook, void* data) 1158 { 1159 block_cache* cache = (block_cache*)_cache; 1160 MutexLocker locker(&cache->lock); 1161 1162 TRACE(("cache_detach_sub_transaction(id = %d)\n", id)); 1163 1164 cache_transaction* transaction = lookup_transaction(cache, id); 1165 if (transaction == NULL) { 1166 fssh_panic("cache_detach_sub_transaction(): invalid transaction ID\n"); 1167 return FSSH_B_BAD_VALUE; 1168 } 1169 if (!transaction->has_sub_transaction) 1170 return FSSH_B_BAD_VALUE; 1171 1172 // create a new transaction for the sub transaction 1173 cache_transaction* newTransaction = new(nothrow) cache_transaction; 1174 if (newTransaction == NULL) 1175 return FSSH_B_NO_MEMORY; 1176 1177 newTransaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 1178 1179 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1180 1181 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1182 hook, data) != FSSH_B_OK) { 1183 delete newTransaction; 1184 return FSSH_B_NO_MEMORY; 1185 } 1186 1187 // iterate through all blocks and free the unchanged original contents 1188 1189 cached_block* block = transaction->first_block; 1190 cached_block* last = NULL; 1191 cached_block* next; 1192 for (; block != NULL; block = next) { 1193 next = block->transaction_next; 1194 1195 if (block->previous_transaction != NULL) { 1196 // need to write back pending changes 1197 write_cached_block(cache, block); 1198 } 1199 if (block->discard) { 1200 cache->DiscardBlock(block); 1201 transaction->main_num_blocks--; 1202 continue; 1203 } 1204 1205 if (block->original_data != NULL && block->parent_data != NULL) { 1206 // free the original data if the parent data of the transaction 1207 // will be made current - but keep them otherwise 1208 cache->Free(block->original_data); 1209 block->original_data = NULL; 1210 } 1211 if (block->parent_data == NULL 1212 || block->parent_data != block->current_data) { 1213 // we need to move this block over to the new transaction 1214 block->original_data = block->parent_data; 1215 if (last == NULL) 1216 newTransaction->first_block = block; 1217 else 1218 last->transaction_next = block; 1219 1220 block->transaction = newTransaction; 1221 last = block; 1222 } else 1223 block->transaction = NULL; 1224 1225 if (block->parent_data != NULL) { 1226 // move the block to the previous transaction list 1227 transaction->blocks.Add(block); 1228 block->previous_transaction = transaction; 1229 block->parent_data = NULL; 1230 } 1231 1232 block->transaction_next = NULL; 1233 } 1234 1235 newTransaction->num_blocks = transaction->sub_num_blocks; 1236 1237 transaction->open = false; 1238 transaction->has_sub_transaction = false; 1239 transaction->num_blocks = transaction->main_num_blocks; 1240 transaction->sub_num_blocks = 0; 1241 1242 hash_insert(cache->transaction_hash, newTransaction); 1243 cache->last_transaction = newTransaction; 1244 1245 return newTransaction->id; 1246 } 1247 1248 1249 fssh_status_t 1250 fssh_cache_abort_sub_transaction(void* _cache, int32_t id) 1251 { 1252 block_cache* cache = (block_cache*)_cache; 1253 MutexLocker locker(&cache->lock); 1254 1255 TRACE(("cache_abort_sub_transaction(id = %ld)\n", id)); 1256 1257 cache_transaction* transaction = lookup_transaction(cache, id); 1258 if (transaction == NULL) { 1259 fssh_panic("cache_abort_sub_transaction(): invalid transaction ID\n"); 1260 return FSSH_B_BAD_VALUE; 1261 } 1262 if (!transaction->has_sub_transaction) 1263 return FSSH_B_BAD_VALUE; 1264 1265 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1266 1267 // revert all changes back to the version of the parent 1268 1269 cached_block* block = transaction->first_block; 1270 cached_block* next; 1271 for (; block != NULL; block = next) { 1272 next = block->transaction_next; 1273 1274 if (block->parent_data == NULL) { 1275 if (block->original_data != NULL) { 1276 // the parent transaction didn't change the block, but the sub 1277 // transaction did - we need to revert from the original data 1278 fssh_memcpy(block->current_data, block->original_data, 1279 cache->block_size); 1280 } 1281 } else if (block->parent_data != block->current_data) { 1282 // the block has been changed and must be restored 1283 TRACE(("cache_abort_sub_transaction(id = %ld): restored contents of block %lld\n", 1284 transaction->id, block->block_number)); 1285 fssh_memcpy(block->current_data, block->parent_data, 1286 cache->block_size); 1287 cache->Free(block->parent_data); 1288 } 1289 1290 block->parent_data = NULL; 1291 block->discard = false; 1292 } 1293 1294 // all subsequent changes will go into the main transaction 1295 transaction->has_sub_transaction = false; 1296 transaction->sub_num_blocks = 0; 1297 1298 return FSSH_B_OK; 1299 } 1300 1301 1302 fssh_status_t 1303 fssh_cache_start_sub_transaction(void* _cache, int32_t id) 1304 { 1305 block_cache* cache = (block_cache*)_cache; 1306 MutexLocker locker(&cache->lock); 1307 1308 TRACE(("cache_start_sub_transaction(id = %d)\n", id)); 1309 1310 cache_transaction* transaction = lookup_transaction(cache, id); 1311 if (transaction == NULL) { 1312 fssh_panic("cache_start_sub_transaction(): invalid transaction ID %d\n", (int)id); 1313 return FSSH_B_BAD_VALUE; 1314 } 1315 1316 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1317 1318 // move all changed blocks up to the parent 1319 1320 cached_block* block = transaction->first_block; 1321 cached_block* last = NULL; 1322 cached_block* next; 1323 for (; block != NULL; block = next) { 1324 next = block->transaction_next; 1325 1326 if (block->discard) { 1327 // This block has been discarded in the parent transaction 1328 if (last != NULL) 1329 last->transaction_next = next; 1330 else 1331 transaction->first_block = next; 1332 1333 cache->DiscardBlock(block); 1334 transaction->num_blocks--; 1335 continue; 1336 } 1337 if (transaction->has_sub_transaction 1338 && block->parent_data != NULL 1339 && block->parent_data != block->current_data) { 1340 // there already is an older sub transaction - we acknowledge 1341 // its changes and move its blocks up to the parent 1342 cache->Free(block->parent_data); 1343 } 1344 1345 // we "allocate" the parent data lazily, that means, we don't copy 1346 // the data (and allocate memory for it) until we need to 1347 block->parent_data = block->current_data; 1348 last = block; 1349 } 1350 1351 // all subsequent changes will go into the sub transaction 1352 transaction->has_sub_transaction = true; 1353 transaction->main_num_blocks = transaction->num_blocks; 1354 transaction->sub_num_blocks = 0; 1355 1356 return FSSH_B_OK; 1357 } 1358 1359 1360 /*! Adds a transaction listener that gets notified when the transaction 1361 is ended or aborted. 1362 The listener gets automatically removed in this case. 1363 */ 1364 fssh_status_t 1365 fssh_cache_add_transaction_listener(void* _cache, int32_t id, int32_t events, 1366 fssh_transaction_notification_hook hookFunction, void* data) 1367 { 1368 // TODO: this is currently not used in a critical context in BFS 1369 return FSSH_B_OK; 1370 } 1371 1372 1373 fssh_status_t 1374 fssh_cache_remove_transaction_listener(void* _cache, int32_t id, 1375 fssh_transaction_notification_hook hookFunction, void* data) 1376 { 1377 // TODO: this is currently not used in a critical context in BFS 1378 return FSSH_B_OK; 1379 } 1380 1381 1382 fssh_status_t 1383 fssh_cache_next_block_in_transaction(void* _cache, int32_t id, bool mainOnly, 1384 long* _cookie, fssh_off_t* _blockNumber, void** _data, 1385 void** _unchangedData) 1386 { 1387 cached_block* block = (cached_block*)*_cookie; 1388 block_cache* cache = (block_cache*)_cache; 1389 1390 MutexLocker locker(&cache->lock); 1391 1392 cache_transaction* transaction = lookup_transaction(cache, id); 1393 if (transaction == NULL || !transaction->open) 1394 return FSSH_B_BAD_VALUE; 1395 1396 if (block == NULL) 1397 block = transaction->first_block; 1398 else 1399 block = block->transaction_next; 1400 1401 if (transaction->has_sub_transaction) { 1402 if (mainOnly) { 1403 // find next block that the parent changed 1404 while (block != NULL && block->parent_data == NULL) 1405 block = block->transaction_next; 1406 } else { 1407 // find next non-discarded block 1408 while (block != NULL && block->discard) 1409 block = block->transaction_next; 1410 } 1411 } 1412 1413 if (block == NULL) 1414 return FSSH_B_ENTRY_NOT_FOUND; 1415 1416 if (_blockNumber) 1417 *_blockNumber = block->block_number; 1418 if (_data) 1419 *_data = mainOnly ? block->parent_data : block->current_data; 1420 if (_unchangedData) 1421 *_unchangedData = block->original_data; 1422 1423 *_cookie = (fssh_addr_t)block; 1424 return FSSH_B_OK; 1425 } 1426 1427 1428 int32_t 1429 fssh_cache_blocks_in_transaction(void* _cache, int32_t id) 1430 { 1431 block_cache* cache = (block_cache*)_cache; 1432 MutexLocker locker(&cache->lock); 1433 1434 cache_transaction* transaction = lookup_transaction(cache, id); 1435 if (transaction == NULL) 1436 return FSSH_B_BAD_VALUE; 1437 1438 return transaction->num_blocks; 1439 } 1440 1441 1442 int32_t 1443 fssh_cache_blocks_in_main_transaction(void* _cache, int32_t id) 1444 { 1445 block_cache* cache = (block_cache*)_cache; 1446 MutexLocker locker(&cache->lock); 1447 1448 cache_transaction* transaction = lookup_transaction(cache, id); 1449 if (transaction == NULL) 1450 return FSSH_B_BAD_VALUE; 1451 1452 return transaction->main_num_blocks; 1453 } 1454 1455 1456 int32_t 1457 fssh_cache_blocks_in_sub_transaction(void* _cache, int32_t id) 1458 { 1459 block_cache* cache = (block_cache*)_cache; 1460 MutexLocker locker(&cache->lock); 1461 1462 cache_transaction* transaction = lookup_transaction(cache, id); 1463 if (transaction == NULL) 1464 return FSSH_B_BAD_VALUE; 1465 1466 return transaction->sub_num_blocks; 1467 } 1468 1469 1470 bool 1471 fssh_cache_has_block_in_transaction(void* _cache, int32_t id, 1472 fssh_off_t blockNumber) 1473 { 1474 block_cache* cache = (block_cache*)_cache; 1475 MutexLocker locker(&cache->lock); 1476 1477 cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); 1478 1479 return (block != NULL && block->transaction != NULL 1480 && block->transaction->id == id); 1481 } 1482 1483 1484 // #pragma mark - public block cache API 1485 1486 1487 void 1488 fssh_block_cache_delete(void* _cache, bool allowWrites) 1489 { 1490 block_cache* cache = (block_cache*)_cache; 1491 1492 if (allowWrites) 1493 fssh_block_cache_sync(cache); 1494 1495 fssh_mutex_lock(&cache->lock); 1496 1497 // free all blocks 1498 1499 uint32_t cookie = 0; 1500 cached_block* block; 1501 while ((block = (cached_block*)hash_remove_first(cache->hash, 1502 &cookie)) != NULL) { 1503 cache->FreeBlock(block); 1504 } 1505 1506 // free all transactions (they will all be aborted) 1507 1508 cookie = 0; 1509 cache_transaction* transaction; 1510 while ((transaction = (cache_transaction*)hash_remove_first( 1511 cache->transaction_hash, &cookie)) != NULL) { 1512 delete transaction; 1513 } 1514 1515 delete cache; 1516 } 1517 1518 1519 void* 1520 fssh_block_cache_create(int fd, fssh_off_t numBlocks, fssh_size_t blockSize, bool readOnly) 1521 { 1522 block_cache* cache = new(std::nothrow) block_cache(fd, numBlocks, blockSize, 1523 readOnly); 1524 if (cache == NULL) 1525 return NULL; 1526 1527 if (cache->Init() != FSSH_B_OK) { 1528 delete cache; 1529 return NULL; 1530 } 1531 1532 return cache; 1533 } 1534 1535 1536 fssh_status_t 1537 fssh_block_cache_sync(void* _cache) 1538 { 1539 block_cache* cache = (block_cache*)_cache; 1540 1541 // we will sync all dirty blocks to disk that have a completed 1542 // transaction or no transaction only 1543 1544 MutexLocker locker(&cache->lock); 1545 hash_iterator iterator; 1546 hash_open(cache->hash, &iterator); 1547 1548 cached_block* block; 1549 while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) { 1550 if (block->previous_transaction != NULL 1551 || (block->transaction == NULL && block->is_dirty)) { 1552 fssh_status_t status = write_cached_block(cache, block); 1553 if (status != FSSH_B_OK) 1554 return status; 1555 } 1556 } 1557 1558 hash_close(cache->hash, &iterator, false); 1559 return FSSH_B_OK; 1560 } 1561 1562 1563 fssh_status_t 1564 fssh_block_cache_sync_etc(void* _cache, fssh_off_t blockNumber, 1565 fssh_size_t numBlocks) 1566 { 1567 block_cache* cache = (block_cache*)_cache; 1568 1569 // we will sync all dirty blocks to disk that have a completed 1570 // transaction or no transaction only 1571 1572 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 1573 fssh_panic("block_cache_sync_etc: invalid block number %" FSSH_B_PRIdOFF 1574 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 1575 return FSSH_B_BAD_VALUE; 1576 } 1577 1578 MutexLocker locker(&cache->lock); 1579 1580 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1581 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1582 &blockNumber); 1583 if (block == NULL) 1584 continue; 1585 1586 if (block->previous_transaction != NULL 1587 || (block->transaction == NULL && block->is_dirty)) { 1588 fssh_status_t status = write_cached_block(cache, block); 1589 if (status != FSSH_B_OK) 1590 return status; 1591 } 1592 } 1593 1594 return FSSH_B_OK; 1595 } 1596 1597 1598 void 1599 fssh_block_cache_discard(void* _cache, fssh_off_t blockNumber, 1600 fssh_size_t numBlocks) 1601 { 1602 block_cache* cache = (block_cache*)_cache; 1603 MutexLocker locker(&cache->lock); 1604 1605 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1606 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1607 &blockNumber); 1608 if (block == NULL) 1609 continue; 1610 1611 if (block->previous_transaction != NULL) 1612 write_cached_block(cache, block); 1613 1614 if (block->unused) { 1615 cache->unused_blocks.Remove(block); 1616 cache->RemoveBlock(block); 1617 } else { 1618 if (block->transaction != NULL && block->parent_data != NULL 1619 && block->parent_data != block->current_data) { 1620 fssh_panic("Discarded block %" FSSH_B_PRIdOFF " has already " 1621 "been changed in this transaction!", blockNumber); 1622 } 1623 1624 // mark it as discarded (in the current transaction only, if any) 1625 block->discard = true; 1626 } 1627 } 1628 } 1629 1630 1631 fssh_status_t 1632 fssh_block_cache_make_writable(void* _cache, fssh_off_t blockNumber, 1633 int32_t transaction) 1634 { 1635 block_cache* cache = (block_cache*)_cache; 1636 MutexLocker locker(&cache->lock); 1637 1638 if (cache->read_only) 1639 fssh_panic("tried to make block writable on a read-only cache!"); 1640 1641 // TODO: this can be done better! 1642 void* block; 1643 fssh_status_t status = get_writable_cached_block(cache, blockNumber, 1644 transaction, false, &block); 1645 if (status == FSSH_B_OK) { 1646 put_cached_block((block_cache*)_cache, blockNumber); 1647 return FSSH_B_OK; 1648 } 1649 1650 return status; 1651 } 1652 1653 1654 fssh_status_t 1655 fssh_block_cache_get_writable_etc(void* _cache, fssh_off_t blockNumber, 1656 int32_t transaction, void** _block) 1657 { 1658 block_cache* cache = (block_cache*)_cache; 1659 MutexLocker locker(&cache->lock); 1660 1661 TRACE(("block_cache_get_writable_etc(block = %lld, transaction = %ld)\n", 1662 blockNumber, transaction)); 1663 if (cache->read_only) 1664 fssh_panic("tried to get writable block on a read-only cache!"); 1665 1666 return get_writable_cached_block(cache, blockNumber, 1667 transaction, false, _block); 1668 } 1669 1670 1671 void* 1672 fssh_block_cache_get_writable(void* _cache, fssh_off_t blockNumber, 1673 int32_t transaction) 1674 { 1675 void* block; 1676 fssh_status_t status = fssh_block_cache_get_writable_etc(_cache, 1677 blockNumber, transaction, &block); 1678 if (status == FSSH_B_OK) 1679 return block; 1680 1681 return NULL; 1682 } 1683 1684 1685 void* 1686 fssh_block_cache_get_empty(void* _cache, fssh_off_t blockNumber, 1687 int32_t transaction) 1688 { 1689 block_cache* cache = (block_cache*)_cache; 1690 MutexLocker locker(&cache->lock); 1691 1692 TRACE(("block_cache_get_empty(block = %lld, transaction = %ld)\n", 1693 blockNumber, transaction)); 1694 if (cache->read_only) 1695 fssh_panic("tried to get empty writable block on a read-only cache!"); 1696 1697 void* block; 1698 if (get_writable_cached_block((block_cache*)_cache, blockNumber, 1699 transaction, true, &block) == FSSH_B_OK) 1700 return block; 1701 1702 return NULL; 1703 } 1704 1705 1706 fssh_status_t 1707 fssh_block_cache_get_etc(void* _cache, fssh_off_t blockNumber, const void** _block) 1708 { 1709 block_cache* cache = (block_cache*)_cache; 1710 MutexLocker locker(&cache->lock); 1711 bool allocated; 1712 1713 cached_block* block; 1714 fssh_status_t status = get_cached_block(cache, blockNumber, &allocated, 1715 true, &block); 1716 if (status != FSSH_B_OK) 1717 return status; 1718 1719 #ifdef DEBUG_CHANGED 1720 if (block->compare == NULL) 1721 block->compare = cache->Allocate(); 1722 if (block->compare != NULL) 1723 memcpy(block->compare, block->current_data, cache->block_size); 1724 #endif 1725 *_block = block->current_data; 1726 return FSSH_B_OK; 1727 } 1728 1729 1730 const void* 1731 fssh_block_cache_get(void* _cache, fssh_off_t blockNumber) 1732 { 1733 const void* block; 1734 if (fssh_block_cache_get_etc(_cache, blockNumber, &block) 1735 == FSSH_B_OK) 1736 return block; 1737 1738 return NULL; 1739 } 1740 1741 1742 /*! Changes the internal status of a writable block to \a dirty. This can be 1743 helpful in case you realize you don't need to change that block anymore 1744 for whatever reason. 1745 1746 Note, you must only use this function on blocks that were acquired 1747 writable! 1748 */ 1749 fssh_status_t 1750 fssh_block_cache_set_dirty(void* _cache, fssh_off_t blockNumber, bool dirty, 1751 int32_t transaction) 1752 { 1753 block_cache* cache = (block_cache*)_cache; 1754 MutexLocker locker(&cache->lock); 1755 1756 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1757 &blockNumber); 1758 if (block == NULL) 1759 return FSSH_B_BAD_VALUE; 1760 if (block->is_dirty == dirty) { 1761 // there is nothing to do for us 1762 return FSSH_B_OK; 1763 } 1764 1765 // TODO: not yet implemented 1766 if (dirty) 1767 fssh_panic("block_cache_set_dirty(): not yet implemented that way!\n"); 1768 1769 return FSSH_B_OK; 1770 } 1771 1772 1773 void 1774 fssh_block_cache_put(void* _cache, fssh_off_t blockNumber) 1775 { 1776 block_cache* cache = (block_cache*)_cache; 1777 MutexLocker locker(&cache->lock); 1778 1779 put_cached_block(cache, blockNumber); 1780 } 1781 1782 1783 fssh_status_t 1784 fssh_block_cache_prefetch(void* _cache, fssh_off_t blockNumber, fssh_size_t* _numBlocks) 1785 { 1786 *_numBlocks = 0; 1787 return FSSH_B_UNSUPPORTED; 1788 } 1789