1 /* 2 * Copyright 2004-2008, 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 = LONG_MAX, 128 int32_t count = LONG_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 } 393 394 395 static int 396 transaction_compare(void* _transaction, const void* _id) 397 { 398 cache_transaction* transaction = (cache_transaction*)_transaction; 399 const int32_t* id = (const int32_t*)_id; 400 401 return transaction->id - *id; 402 } 403 404 405 static uint32_t 406 transaction_hash(void* _transaction, const void* _id, uint32_t range) 407 { 408 cache_transaction* transaction = (cache_transaction*)_transaction; 409 const int32_t* id = (const int32_t*)_id; 410 411 if (transaction != NULL) 412 return transaction->id % range; 413 414 return (uint32_t)*id % range; 415 } 416 417 418 static void 419 delete_transaction(block_cache* cache, cache_transaction* transaction) 420 { 421 if (cache->last_transaction == transaction) 422 cache->last_transaction = NULL; 423 424 remove_transaction_listeners(cache, transaction); 425 delete transaction; 426 } 427 428 429 static cache_transaction* 430 lookup_transaction(block_cache* cache, int32_t id) 431 { 432 return (cache_transaction*)hash_lookup(cache->transaction_hash, &id); 433 } 434 435 436 // #pragma mark - cached_block 437 438 439 /*static*/ int 440 cached_block::Compare(void* _cacheEntry, const void* _block) 441 { 442 cached_block* cacheEntry = (cached_block*)_cacheEntry; 443 const fssh_off_t* block = (const fssh_off_t*)_block; 444 445 fssh_off_t diff = cacheEntry->block_number - *block; 446 if (diff > 0) 447 return 1; 448 449 return diff < 0 ? -1 : 0; 450 } 451 452 453 454 /*static*/ uint32_t 455 cached_block::Hash(void* _cacheEntry, const void* _block, uint32_t range) 456 { 457 cached_block* cacheEntry = (cached_block*)_cacheEntry; 458 const fssh_off_t* block = (const fssh_off_t*)_block; 459 460 if (cacheEntry != NULL) 461 return cacheEntry->block_number % range; 462 463 return (uint64_t)*block % range; 464 } 465 466 467 // #pragma mark - block_cache 468 469 470 block_cache::block_cache(int _fd, fssh_off_t numBlocks, fssh_size_t blockSize, 471 bool readOnly) 472 : 473 hash(NULL), 474 fd(_fd), 475 max_blocks(numBlocks), 476 block_size(blockSize), 477 allocated_block_count(0), 478 next_transaction_id(1), 479 last_transaction(NULL), 480 transaction_hash(NULL), 481 read_only(readOnly) 482 { 483 } 484 485 486 block_cache::~block_cache() 487 { 488 hash_uninit(transaction_hash); 489 hash_uninit(hash); 490 491 fssh_mutex_destroy(&lock); 492 } 493 494 495 fssh_status_t 496 block_cache::Init() 497 { 498 fssh_mutex_init(&lock, "block cache"); 499 if (lock.sem < FSSH_B_OK) 500 return lock.sem; 501 502 hash = hash_init(128, offsetof(cached_block, next), &cached_block::Compare, 503 &cached_block::Hash); 504 if (hash == NULL) 505 return FSSH_B_NO_MEMORY; 506 507 transaction_hash = hash_init(16, offsetof(cache_transaction, next), 508 &transaction_compare, &FSShell::transaction_hash); 509 if (transaction_hash == NULL) 510 return FSSH_B_NO_MEMORY; 511 512 return FSSH_B_OK; 513 } 514 515 516 void 517 block_cache::Free(void* buffer) 518 { 519 if (buffer == NULL) 520 return; 521 522 free(buffer); 523 } 524 525 526 void* 527 block_cache::Allocate() 528 { 529 return malloc(block_size); 530 } 531 532 533 void 534 block_cache::FreeBlock(cached_block* block) 535 { 536 Free(block->current_data); 537 538 if (block->original_data != NULL || block->parent_data != NULL) { 539 fssh_panic("block_cache::FreeBlock(): %" FSSH_B_PRIdOFF 540 ", original %p, parent %p\n", block->block_number, 541 block->original_data, block->parent_data); 542 } 543 544 #ifdef DEBUG_CHANGED 545 Free(block->compare); 546 #endif 547 548 delete block; 549 } 550 551 552 /*! Allocates a new block for \a blockNumber, ready for use */ 553 cached_block* 554 block_cache::NewBlock(fssh_off_t blockNumber) 555 { 556 cached_block* block = new(nothrow) cached_block; 557 if (block == NULL) { 558 FATAL(("could not allocate block!\n")); 559 return NULL; 560 } 561 562 // if we hit the limit of blocks to cache¸ try to free one or more 563 if (allocated_block_count >= kMaxBlockCount) { 564 RemoveUnusedBlocks(INT32_MAX, 565 allocated_block_count - kMaxBlockCount + 1); 566 } 567 568 block->current_data = Allocate(); 569 if (block->current_data == NULL) { 570 FATAL(("could not allocate block data!\n")); 571 delete block; 572 return NULL; 573 } 574 575 block->block_number = blockNumber; 576 block->ref_count = 0; 577 block->accessed = 0; 578 block->transaction_next = NULL; 579 block->transaction = block->previous_transaction = NULL; 580 block->original_data = NULL; 581 block->parent_data = NULL; 582 block->is_dirty = false; 583 block->unused = false; 584 block->discard = false; 585 #ifdef DEBUG_CHANGED 586 block->compare = NULL; 587 #endif 588 589 allocated_block_count++; 590 591 return block; 592 } 593 594 595 void 596 block_cache::RemoveUnusedBlocks(int32_t maxAccessed, int32_t count) 597 { 598 TRACE(("block_cache: remove up to %ld unused blocks\n", count)); 599 600 for (block_list::Iterator iterator = unused_blocks.GetIterator(); 601 cached_block *block = iterator.Next();) { 602 if (maxAccessed < block->accessed) 603 continue; 604 605 TRACE((" remove block %Ld, accessed %ld times\n", 606 block->block_number, block->accessed)); 607 608 // this can only happen if no transactions are used 609 if (block->is_dirty && !block->discard) 610 write_cached_block(this, block, false); 611 612 // remove block from lists 613 iterator.Remove(); 614 RemoveBlock(block); 615 616 if (--count <= 0) 617 break; 618 } 619 } 620 621 622 void 623 block_cache::RemoveBlock(cached_block* block) 624 { 625 hash_remove(hash, block); 626 FreeBlock(block); 627 } 628 629 630 /*! Discards the block from a transaction (this method must not be called 631 for blocks not part of a transaction). 632 */ 633 void 634 block_cache::DiscardBlock(cached_block* block) 635 { 636 if (block->parent_data != NULL && block->parent_data != block->current_data) 637 Free(block->parent_data); 638 639 block->parent_data = NULL; 640 641 if (block->original_data != NULL) { 642 Free(block->original_data); 643 block->original_data = NULL; 644 } 645 646 RemoveBlock(block); 647 } 648 649 650 // #pragma mark - private block functions 651 652 653 /*! Removes a reference from the specified \a block. If this was the last 654 reference, the block is moved into the unused list. 655 In low memory situations, it will also free some blocks from that list, 656 but not necessarily the \a block it just released. 657 */ 658 static void 659 put_cached_block(block_cache* cache, cached_block* block) 660 { 661 #ifdef DEBUG_CHANGED 662 if (!block->is_dirty && block->compare != NULL 663 && memcmp(block->current_data, block->compare, cache->block_size)) { 664 fssh_dprintf("new block:\n"); 665 fssh_dump_block((const char*)block->current_data, 256, " "); 666 fssh_dprintf("unchanged block:\n"); 667 fssh_dump_block((const char*)block->compare, 256, " "); 668 write_cached_block(cache, block); 669 fssh_panic("block_cache: supposed to be clean block was changed!\n"); 670 671 cache->Free(block->compare); 672 block->compare = NULL; 673 } 674 #endif 675 676 if (--block->ref_count == 0 677 && block->transaction == NULL && block->previous_transaction == NULL) { 678 // This block is not used anymore, and not part of any transaction 679 if (block->discard) { 680 cache->RemoveBlock(block); 681 } else { 682 // put this block in the list of unused blocks 683 block->unused = true; 684 cache->unused_blocks.Add(block); 685 } 686 } 687 688 if (cache->allocated_block_count > kMaxBlockCount) { 689 cache->RemoveUnusedBlocks(INT32_MAX, 690 cache->allocated_block_count - kMaxBlockCount); 691 } 692 } 693 694 695 static void 696 put_cached_block(block_cache* cache, fssh_off_t blockNumber) 697 { 698 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 699 fssh_panic("put_cached_block: invalid block number %" FSSH_B_PRIdOFF 700 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 701 } 702 703 cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber); 704 if (block != NULL) 705 put_cached_block(cache, block); 706 } 707 708 709 /*! Retrieves the block \a blockNumber from the hash table, if it's already 710 there, or reads it from the disk. 711 712 \param _allocated tells you whether or not a new block has been allocated 713 to satisfy your request. 714 \param readBlock if \c false, the block will not be read in case it was 715 not already in the cache. The block you retrieve may contain random 716 data. 717 */ 718 static cached_block* 719 get_cached_block(block_cache* cache, fssh_off_t blockNumber, bool* _allocated, 720 bool readBlock = true) 721 { 722 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 723 fssh_panic("get_cached_block: invalid block number %" FSSH_B_PRIdOFF 724 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 725 return NULL; 726 } 727 728 cached_block* block = (cached_block*)hash_lookup(cache->hash, 729 &blockNumber); 730 *_allocated = false; 731 732 if (block == NULL) { 733 // read block into cache 734 block = cache->NewBlock(blockNumber); 735 if (block == NULL) 736 return NULL; 737 738 hash_insert(cache->hash, block); 739 *_allocated = true; 740 } 741 742 if (*_allocated && readBlock) { 743 int32_t blockSize = cache->block_size; 744 745 if (fssh_read_pos(cache->fd, blockNumber * blockSize, block->current_data, 746 blockSize) < blockSize) { 747 cache->RemoveBlock(block); 748 FATAL(("could not read block %" FSSH_B_PRIdOFF "\n", blockNumber)); 749 return NULL; 750 } 751 } 752 753 if (block->unused) { 754 //TRACE(("remove block %Ld from unused\n", blockNumber)); 755 block->unused = false; 756 cache->unused_blocks.Remove(block); 757 } 758 759 block->ref_count++; 760 block->accessed++; 761 762 return block; 763 } 764 765 766 /*! Returns the writable block data for the requested blockNumber. 767 If \a cleared is true, the block is not read from disk; an empty block 768 is returned. 769 770 This is the only method to insert a block into a transaction. It makes 771 sure that the previous block contents are preserved in that case. 772 */ 773 static void* 774 get_writable_cached_block(block_cache* cache, fssh_off_t blockNumber, fssh_off_t base, 775 fssh_off_t length, int32_t transactionID, bool cleared) 776 { 777 TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %d)\n", 778 blockNumber, transactionID)); 779 780 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 781 fssh_panic("get_writable_cached_block: invalid block number %" 782 FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, 783 cache->max_blocks - 1); 784 } 785 786 bool allocated; 787 cached_block* block = get_cached_block(cache, blockNumber, &allocated, 788 !cleared); 789 if (block == NULL) 790 return NULL; 791 792 block->discard = false; 793 794 // if there is no transaction support, we just return the current block 795 if (transactionID == -1) { 796 if (cleared) 797 fssh_memset(block->current_data, 0, cache->block_size); 798 799 block->is_dirty = true; 800 // mark the block as dirty 801 802 return block->current_data; 803 } 804 805 cache_transaction* transaction = block->transaction; 806 807 if (transaction != NULL && transaction->id != transactionID) { 808 // TODO: we have to wait here until the other transaction is done. 809 // Maybe we should even panic, since we can't prevent any deadlocks. 810 fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction->id); 811 put_cached_block(cache, block); 812 return NULL; 813 } 814 if (transaction == NULL && transactionID != -1) { 815 // get new transaction 816 transaction = lookup_transaction(cache, transactionID); 817 if (transaction == NULL) { 818 fssh_panic("get_writable_cached_block(): invalid transaction %d!\n", 819 (int)transactionID); 820 put_cached_block(cache, block); 821 return NULL; 822 } 823 if (!transaction->open) { 824 fssh_panic("get_writable_cached_block(): transaction already done!\n"); 825 put_cached_block(cache, block); 826 return NULL; 827 } 828 829 block->transaction = transaction; 830 831 // attach the block to the transaction block list 832 block->transaction_next = transaction->first_block; 833 transaction->first_block = block; 834 transaction->num_blocks++; 835 } 836 837 bool wasUnchanged = block->original_data == NULL 838 || block->previous_transaction != NULL; 839 840 if (!(allocated && cleared) && block->original_data == NULL) { 841 // we already have data, so we need to preserve it 842 block->original_data = cache->Allocate(); 843 if (block->original_data == NULL) { 844 FATAL(("could not allocate original_data\n")); 845 put_cached_block(cache, block); 846 return NULL; 847 } 848 849 fssh_memcpy(block->original_data, block->current_data, cache->block_size); 850 } 851 if (block->parent_data == block->current_data) { 852 // remember any previous contents for the parent transaction 853 block->parent_data = cache->Allocate(); 854 if (block->parent_data == NULL) { 855 // TODO: maybe we should just continue the current transaction in this case... 856 FATAL(("could not allocate parent\n")); 857 put_cached_block(cache, block); 858 return NULL; 859 } 860 861 fssh_memcpy(block->parent_data, block->current_data, cache->block_size); 862 transaction->sub_num_blocks++; 863 } else if (transaction != NULL && transaction->has_sub_transaction 864 && block->parent_data == NULL && wasUnchanged) 865 transaction->sub_num_blocks++; 866 867 if (cleared) 868 fssh_memset(block->current_data, 0, cache->block_size); 869 870 block->is_dirty = true; 871 872 return block->current_data; 873 } 874 875 876 /*! Writes the specified \a block back to disk. It will always only write back 877 the oldest change of the block if it is part of more than one transaction. 878 It will automatically send out TRANSACTION_WRITTEN notices, as well as 879 delete transactions when they are no longer used, and \a deleteTransaction 880 is \c true. 881 */ 882 static fssh_status_t 883 write_cached_block(block_cache* cache, cached_block* block, 884 bool deleteTransaction) 885 { 886 cache_transaction* previous = block->previous_transaction; 887 int32_t blockSize = cache->block_size; 888 889 void* data = previous && block->original_data 890 ? block->original_data : block->current_data; 891 // we first need to write back changes from previous transactions 892 893 TRACE(("write_cached_block(block %Ld)\n", block->block_number)); 894 895 fssh_ssize_t written = fssh_write_pos(cache->fd, block->block_number * blockSize, 896 data, blockSize); 897 898 if (written < blockSize) { 899 FATAL(("could not write back block %" FSSH_B_PRIdOFF " (%s)\n", 900 block->block_number, fssh_strerror(fssh_get_errno()))); 901 return FSSH_B_IO_ERROR; 902 } 903 904 if (data == block->current_data) 905 block->is_dirty = false; 906 907 if (previous != NULL) { 908 previous->blocks.Remove(block); 909 block->previous_transaction = NULL; 910 911 if (block->original_data != NULL && block->transaction == NULL) { 912 // This block is not part of a transaction, so it does not need 913 // its original pointer anymore. 914 cache->Free(block->original_data); 915 block->original_data = NULL; 916 } 917 918 // Has the previous transation been finished with that write? 919 if (--previous->num_blocks == 0) { 920 TRACE(("cache transaction %ld finished!\n", previous->id)); 921 922 notify_transaction_listeners(cache, previous, FSSH_TRANSACTION_WRITTEN); 923 924 if (deleteTransaction) { 925 hash_remove(cache->transaction_hash, previous); 926 delete_transaction(cache, previous); 927 } 928 } 929 } 930 if (block->transaction == NULL && block->ref_count == 0 && !block->unused) { 931 // the block is no longer used 932 block->unused = true; 933 cache->unused_blocks.Add(block); 934 } 935 936 return FSSH_B_OK; 937 } 938 939 940 /*! Waits until all pending notifications are carried out. 941 Safe to be called from the block writer/notifier thread. 942 You must not hold the \a cache lock when calling this function. 943 */ 944 static void 945 wait_for_notifications(block_cache* cache) 946 { 947 // TODO: nothing to wait for here. 948 } 949 950 951 fssh_status_t 952 block_cache_init() 953 { 954 fssh_mutex_init(&sNotificationsLock, "block cache notifications"); 955 return FSSH_B_OK; 956 } 957 958 959 } // namespace FSShell 960 961 962 // #pragma mark - public transaction API 963 964 965 using namespace FSShell; 966 967 968 int32_t 969 fssh_cache_start_transaction(void* _cache) 970 { 971 block_cache* cache = (block_cache*)_cache; 972 MutexLocker locker(&cache->lock); 973 974 if (cache->last_transaction && cache->last_transaction->open) { 975 fssh_panic("last transaction (%d) still open!\n", 976 (int)cache->last_transaction->id); 977 } 978 979 cache_transaction* transaction = new(nothrow) cache_transaction; 980 if (transaction == NULL) 981 return FSSH_B_NO_MEMORY; 982 983 transaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 984 cache->last_transaction = transaction; 985 986 TRACE(("cache_start_transaction(): id %d started\n", transaction->id)); 987 988 hash_insert(cache->transaction_hash, transaction); 989 990 return transaction->id; 991 } 992 993 994 fssh_status_t 995 fssh_cache_sync_transaction(void* _cache, int32_t id) 996 { 997 block_cache* cache = (block_cache*)_cache; 998 MutexLocker locker(&cache->lock); 999 fssh_status_t status = FSSH_B_ENTRY_NOT_FOUND; 1000 1001 TRACE(("cache_sync_transaction(id %d)\n", id)); 1002 1003 hash_iterator iterator; 1004 hash_open(cache->transaction_hash, &iterator); 1005 1006 cache_transaction* transaction; 1007 while ((transaction = (cache_transaction*)hash_next( 1008 cache->transaction_hash, &iterator)) != NULL) { 1009 // close all earlier transactions which haven't been closed yet 1010 1011 if (transaction->id <= id && !transaction->open) { 1012 // write back all of their remaining dirty blocks 1013 while (transaction->num_blocks > 0) { 1014 status = write_cached_block(cache, transaction->blocks.Head(), 1015 false); 1016 if (status != FSSH_B_OK) 1017 return status; 1018 } 1019 1020 hash_remove_current(cache->transaction_hash, &iterator); 1021 delete_transaction(cache, transaction); 1022 } 1023 } 1024 1025 hash_close(cache->transaction_hash, &iterator, false); 1026 locker.Unlock(); 1027 1028 wait_for_notifications(cache); 1029 // make sure that all pending FSSH_TRANSACTION_WRITTEN notifications 1030 // are handled after we return 1031 return FSSH_B_OK; 1032 } 1033 1034 1035 fssh_status_t 1036 fssh_cache_end_transaction(void* _cache, int32_t id, 1037 fssh_transaction_notification_hook hook, void* data) 1038 { 1039 block_cache* cache = (block_cache*)_cache; 1040 MutexLocker locker(&cache->lock); 1041 1042 TRACE(("cache_end_transaction(id = %d)\n", id)); 1043 1044 cache_transaction* transaction = lookup_transaction(cache, id); 1045 if (transaction == NULL) { 1046 fssh_panic("cache_end_transaction(): invalid transaction ID\n"); 1047 return FSSH_B_BAD_VALUE; 1048 } 1049 1050 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1051 1052 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1053 hook, data) != FSSH_B_OK) { 1054 return FSSH_B_NO_MEMORY; 1055 } 1056 1057 // iterate through all blocks and free the unchanged original contents 1058 1059 cached_block* block = transaction->first_block; 1060 cached_block* next; 1061 for (; block != NULL; block = next) { 1062 next = block->transaction_next; 1063 1064 if (block->previous_transaction != NULL) { 1065 // need to write back pending changes 1066 write_cached_block(cache, block); 1067 } 1068 if (block->discard) { 1069 // This block has been discarded in the transaction 1070 cache->DiscardBlock(block); 1071 transaction->num_blocks--; 1072 continue; 1073 } 1074 1075 if (block->original_data != NULL) { 1076 cache->Free(block->original_data); 1077 block->original_data = NULL; 1078 } 1079 if (transaction->has_sub_transaction) { 1080 if (block->parent_data != block->current_data) 1081 cache->Free(block->parent_data); 1082 block->parent_data = NULL; 1083 } 1084 1085 // move the block to the previous transaction list 1086 transaction->blocks.Add(block); 1087 1088 block->previous_transaction = transaction; 1089 block->transaction_next = NULL; 1090 block->transaction = NULL; 1091 } 1092 1093 transaction->open = false; 1094 return FSSH_B_OK; 1095 } 1096 1097 1098 fssh_status_t 1099 fssh_cache_abort_transaction(void* _cache, int32_t id) 1100 { 1101 block_cache* cache = (block_cache*)_cache; 1102 MutexLocker locker(&cache->lock); 1103 1104 TRACE(("cache_abort_transaction(id = %ld)\n", id)); 1105 1106 cache_transaction* transaction = lookup_transaction(cache, id); 1107 if (transaction == NULL) { 1108 fssh_panic("cache_abort_transaction(): invalid transaction ID\n"); 1109 return FSSH_B_BAD_VALUE; 1110 } 1111 1112 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1113 1114 // iterate through all blocks and restore their original contents 1115 1116 cached_block* block = transaction->first_block; 1117 cached_block* next; 1118 for (; block != NULL; block = next) { 1119 next = block->transaction_next; 1120 1121 if (block->original_data != NULL) { 1122 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %Ld\n", 1123 transaction->id, block->block_number)); 1124 fssh_memcpy(block->current_data, block->original_data, cache->block_size); 1125 cache->Free(block->original_data); 1126 block->original_data = NULL; 1127 } 1128 if (transaction->has_sub_transaction) { 1129 if (block->parent_data != block->current_data) 1130 cache->Free(block->parent_data); 1131 block->parent_data = NULL; 1132 } 1133 1134 block->transaction_next = NULL; 1135 block->transaction = NULL; 1136 block->discard = false; 1137 } 1138 1139 hash_remove(cache->transaction_hash, transaction); 1140 delete_transaction(cache, transaction); 1141 return FSSH_B_OK; 1142 } 1143 1144 1145 /*! Acknowledges the current parent transaction, and starts a new transaction 1146 from its sub transaction. 1147 The new transaction also gets a new transaction ID. 1148 */ 1149 int32_t 1150 fssh_cache_detach_sub_transaction(void* _cache, int32_t id, 1151 fssh_transaction_notification_hook hook, void* data) 1152 { 1153 block_cache* cache = (block_cache*)_cache; 1154 MutexLocker locker(&cache->lock); 1155 1156 TRACE(("cache_detach_sub_transaction(id = %d)\n", id)); 1157 1158 cache_transaction* transaction = lookup_transaction(cache, id); 1159 if (transaction == NULL) { 1160 fssh_panic("cache_detach_sub_transaction(): invalid transaction ID\n"); 1161 return FSSH_B_BAD_VALUE; 1162 } 1163 if (!transaction->has_sub_transaction) 1164 return FSSH_B_BAD_VALUE; 1165 1166 // create a new transaction for the sub transaction 1167 cache_transaction* newTransaction = new(nothrow) cache_transaction; 1168 if (transaction == NULL) 1169 return FSSH_B_NO_MEMORY; 1170 1171 newTransaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 1172 1173 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1174 1175 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1176 hook, data) != FSSH_B_OK) { 1177 delete newTransaction; 1178 return FSSH_B_NO_MEMORY; 1179 } 1180 1181 // iterate through all blocks and free the unchanged original contents 1182 1183 cached_block* block = transaction->first_block; 1184 cached_block* last = NULL; 1185 cached_block* next; 1186 for (; block != NULL; block = next) { 1187 next = block->transaction_next; 1188 1189 if (block->previous_transaction != NULL) { 1190 // need to write back pending changes 1191 write_cached_block(cache, block); 1192 } 1193 if (block->discard) { 1194 cache->DiscardBlock(block); 1195 transaction->main_num_blocks--; 1196 continue; 1197 } 1198 1199 if (block->original_data != NULL && block->parent_data != NULL) { 1200 // free the original data if the parent data of the transaction 1201 // will be made current - but keep them otherwise 1202 cache->Free(block->original_data); 1203 block->original_data = NULL; 1204 } 1205 if (block->parent_data == NULL 1206 || block->parent_data != block->current_data) { 1207 // we need to move this block over to the new transaction 1208 block->original_data = block->parent_data; 1209 if (last == NULL) 1210 newTransaction->first_block = block; 1211 else 1212 last->transaction_next = block; 1213 1214 block->transaction = newTransaction; 1215 last = block; 1216 } else 1217 block->transaction = NULL; 1218 1219 if (block->parent_data != NULL) { 1220 // move the block to the previous transaction list 1221 transaction->blocks.Add(block); 1222 block->previous_transaction = transaction; 1223 block->parent_data = NULL; 1224 } 1225 1226 block->transaction_next = NULL; 1227 } 1228 1229 newTransaction->num_blocks = transaction->sub_num_blocks; 1230 1231 transaction->open = false; 1232 transaction->has_sub_transaction = false; 1233 transaction->num_blocks = transaction->main_num_blocks; 1234 transaction->sub_num_blocks = 0; 1235 1236 hash_insert(cache->transaction_hash, newTransaction); 1237 cache->last_transaction = newTransaction; 1238 1239 return newTransaction->id; 1240 } 1241 1242 1243 fssh_status_t 1244 fssh_cache_abort_sub_transaction(void* _cache, int32_t id) 1245 { 1246 block_cache* cache = (block_cache*)_cache; 1247 MutexLocker locker(&cache->lock); 1248 1249 TRACE(("cache_abort_sub_transaction(id = %ld)\n", id)); 1250 1251 cache_transaction* transaction = lookup_transaction(cache, id); 1252 if (transaction == NULL) { 1253 fssh_panic("cache_abort_sub_transaction(): invalid transaction ID\n"); 1254 return FSSH_B_BAD_VALUE; 1255 } 1256 if (!transaction->has_sub_transaction) 1257 return FSSH_B_BAD_VALUE; 1258 1259 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1260 1261 // revert all changes back to the version of the parent 1262 1263 cached_block* block = transaction->first_block; 1264 cached_block* next; 1265 for (; block != NULL; block = next) { 1266 next = block->transaction_next; 1267 1268 if (block->parent_data == NULL) { 1269 if (block->original_data != NULL) { 1270 // the parent transaction didn't change the block, but the sub 1271 // transaction did - we need to revert from the original data 1272 fssh_memcpy(block->current_data, block->original_data, 1273 cache->block_size); 1274 } 1275 } else if (block->parent_data != block->current_data) { 1276 // the block has been changed and must be restored 1277 TRACE(("cache_abort_sub_transaction(id = %ld): restored contents of block %Ld\n", 1278 transaction->id, block->block_number)); 1279 fssh_memcpy(block->current_data, block->parent_data, 1280 cache->block_size); 1281 cache->Free(block->parent_data); 1282 } 1283 1284 block->parent_data = NULL; 1285 block->discard = false; 1286 } 1287 1288 // all subsequent changes will go into the main transaction 1289 transaction->has_sub_transaction = false; 1290 transaction->sub_num_blocks = 0; 1291 1292 return FSSH_B_OK; 1293 } 1294 1295 1296 fssh_status_t 1297 fssh_cache_start_sub_transaction(void* _cache, int32_t id) 1298 { 1299 block_cache* cache = (block_cache*)_cache; 1300 MutexLocker locker(&cache->lock); 1301 1302 TRACE(("cache_start_sub_transaction(id = %d)\n", id)); 1303 1304 cache_transaction* transaction = lookup_transaction(cache, id); 1305 if (transaction == NULL) { 1306 fssh_panic("cache_start_sub_transaction(): invalid transaction ID %d\n", (int)id); 1307 return FSSH_B_BAD_VALUE; 1308 } 1309 1310 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1311 1312 // move all changed blocks up to the parent 1313 1314 cached_block* block = transaction->first_block; 1315 cached_block* last = NULL; 1316 cached_block* next; 1317 for (; block != NULL; block = next) { 1318 next = block->transaction_next; 1319 1320 if (block->discard) { 1321 // This block has been discarded in the parent transaction 1322 if (last != NULL) 1323 last->transaction_next = next; 1324 else 1325 transaction->first_block = next; 1326 1327 cache->DiscardBlock(block); 1328 transaction->num_blocks--; 1329 continue; 1330 } 1331 if (transaction->has_sub_transaction 1332 && block->parent_data != NULL 1333 && block->parent_data != block->current_data) { 1334 // there already is an older sub transaction - we acknowledge 1335 // its changes and move its blocks up to the parent 1336 cache->Free(block->parent_data); 1337 } 1338 1339 // we "allocate" the parent data lazily, that means, we don't copy 1340 // the data (and allocate memory for it) until we need to 1341 block->parent_data = block->current_data; 1342 last = block; 1343 } 1344 1345 // all subsequent changes will go into the sub transaction 1346 transaction->has_sub_transaction = true; 1347 transaction->main_num_blocks = transaction->num_blocks; 1348 transaction->sub_num_blocks = 0; 1349 1350 return FSSH_B_OK; 1351 } 1352 1353 1354 /*! Adds a transaction listener that gets notified when the transaction 1355 is ended or aborted. 1356 The listener gets automatically removed in this case. 1357 */ 1358 fssh_status_t 1359 fssh_cache_add_transaction_listener(void* _cache, int32_t id, int32_t events, 1360 fssh_transaction_notification_hook hookFunction, void* data) 1361 { 1362 // TODO: this is currently not used in a critical context in BFS 1363 return FSSH_B_OK; 1364 } 1365 1366 1367 fssh_status_t 1368 fssh_cache_remove_transaction_listener(void* _cache, int32_t id, 1369 fssh_transaction_notification_hook hookFunction, void* data) 1370 { 1371 // TODO: this is currently not used in a critical context in BFS 1372 return FSSH_B_OK; 1373 } 1374 1375 1376 fssh_status_t 1377 fssh_cache_next_block_in_transaction(void* _cache, int32_t id, bool mainOnly, 1378 long* _cookie, fssh_off_t* _blockNumber, void** _data, 1379 void** _unchangedData) 1380 { 1381 cached_block* block = (cached_block*)*_cookie; 1382 block_cache* cache = (block_cache*)_cache; 1383 1384 MutexLocker locker(&cache->lock); 1385 1386 cache_transaction* transaction = lookup_transaction(cache, id); 1387 if (transaction == NULL || !transaction->open) 1388 return FSSH_B_BAD_VALUE; 1389 1390 if (block == NULL) 1391 block = transaction->first_block; 1392 else 1393 block = block->transaction_next; 1394 1395 if (transaction->has_sub_transaction) { 1396 if (mainOnly) { 1397 // find next block that the parent changed 1398 while (block != NULL && block->parent_data == NULL) 1399 block = block->transaction_next; 1400 } else { 1401 // find next non-discarded block 1402 while (block != NULL && block->discard) 1403 block = block->transaction_next; 1404 } 1405 } 1406 1407 if (block == NULL) 1408 return FSSH_B_ENTRY_NOT_FOUND; 1409 1410 if (_blockNumber) 1411 *_blockNumber = block->block_number; 1412 if (_data) 1413 *_data = mainOnly ? block->parent_data : block->current_data; 1414 if (_unchangedData) 1415 *_unchangedData = block->original_data; 1416 1417 *_cookie = (fssh_addr_t)block; 1418 return FSSH_B_OK; 1419 } 1420 1421 1422 int32_t 1423 fssh_cache_blocks_in_transaction(void* _cache, int32_t id) 1424 { 1425 block_cache* cache = (block_cache*)_cache; 1426 MutexLocker locker(&cache->lock); 1427 1428 cache_transaction* transaction = lookup_transaction(cache, id); 1429 if (transaction == NULL) 1430 return FSSH_B_BAD_VALUE; 1431 1432 return transaction->num_blocks; 1433 } 1434 1435 1436 int32_t 1437 fssh_cache_blocks_in_main_transaction(void* _cache, int32_t id) 1438 { 1439 block_cache* cache = (block_cache*)_cache; 1440 MutexLocker locker(&cache->lock); 1441 1442 cache_transaction* transaction = lookup_transaction(cache, id); 1443 if (transaction == NULL) 1444 return FSSH_B_BAD_VALUE; 1445 1446 return transaction->main_num_blocks; 1447 } 1448 1449 1450 int32_t 1451 fssh_cache_blocks_in_sub_transaction(void* _cache, int32_t id) 1452 { 1453 block_cache* cache = (block_cache*)_cache; 1454 MutexLocker locker(&cache->lock); 1455 1456 cache_transaction* transaction = lookup_transaction(cache, id); 1457 if (transaction == NULL) 1458 return FSSH_B_BAD_VALUE; 1459 1460 return transaction->sub_num_blocks; 1461 } 1462 1463 1464 // #pragma mark - public block cache API 1465 1466 1467 void 1468 fssh_block_cache_delete(void* _cache, bool allowWrites) 1469 { 1470 block_cache* cache = (block_cache*)_cache; 1471 1472 if (allowWrites) 1473 fssh_block_cache_sync(cache); 1474 1475 fssh_mutex_lock(&cache->lock); 1476 1477 // free all blocks 1478 1479 uint32_t cookie = 0; 1480 cached_block* block; 1481 while ((block = (cached_block*)hash_remove_first(cache->hash, 1482 &cookie)) != NULL) { 1483 cache->FreeBlock(block); 1484 } 1485 1486 // free all transactions (they will all be aborted) 1487 1488 cookie = 0; 1489 cache_transaction* transaction; 1490 while ((transaction = (cache_transaction*)hash_remove_first( 1491 cache->transaction_hash, &cookie)) != NULL) { 1492 delete transaction; 1493 } 1494 1495 delete cache; 1496 } 1497 1498 1499 void* 1500 fssh_block_cache_create(int fd, fssh_off_t numBlocks, fssh_size_t blockSize, bool readOnly) 1501 { 1502 block_cache* cache = new(std::nothrow) block_cache(fd, numBlocks, blockSize, 1503 readOnly); 1504 if (cache == NULL) 1505 return NULL; 1506 1507 if (cache->Init() != FSSH_B_OK) { 1508 delete cache; 1509 return NULL; 1510 } 1511 1512 return cache; 1513 } 1514 1515 1516 fssh_status_t 1517 fssh_block_cache_sync(void* _cache) 1518 { 1519 block_cache* cache = (block_cache*)_cache; 1520 1521 // we will sync all dirty blocks to disk that have a completed 1522 // transaction or no transaction only 1523 1524 MutexLocker locker(&cache->lock); 1525 hash_iterator iterator; 1526 hash_open(cache->hash, &iterator); 1527 1528 cached_block* block; 1529 while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) { 1530 if (block->previous_transaction != NULL 1531 || (block->transaction == NULL && block->is_dirty)) { 1532 fssh_status_t status = write_cached_block(cache, block); 1533 if (status != FSSH_B_OK) 1534 return status; 1535 } 1536 } 1537 1538 hash_close(cache->hash, &iterator, false); 1539 return FSSH_B_OK; 1540 } 1541 1542 1543 fssh_status_t 1544 fssh_block_cache_sync_etc(void* _cache, fssh_off_t blockNumber, 1545 fssh_size_t numBlocks) 1546 { 1547 block_cache* cache = (block_cache*)_cache; 1548 1549 // we will sync all dirty blocks to disk that have a completed 1550 // transaction or no transaction only 1551 1552 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 1553 fssh_panic("block_cache_sync_etc: invalid block number %" FSSH_B_PRIdOFF 1554 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 1555 return FSSH_B_BAD_VALUE; 1556 } 1557 1558 MutexLocker locker(&cache->lock); 1559 1560 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1561 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1562 &blockNumber); 1563 if (block == NULL) 1564 continue; 1565 1566 if (block->previous_transaction != NULL 1567 || (block->transaction == NULL && block->is_dirty)) { 1568 fssh_status_t status = write_cached_block(cache, block); 1569 if (status != FSSH_B_OK) 1570 return status; 1571 } 1572 } 1573 1574 return FSSH_B_OK; 1575 } 1576 1577 1578 void 1579 fssh_block_cache_discard(void* _cache, fssh_off_t blockNumber, 1580 fssh_size_t numBlocks) 1581 { 1582 block_cache* cache = (block_cache*)_cache; 1583 MutexLocker locker(&cache->lock); 1584 1585 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1586 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1587 &blockNumber); 1588 if (block == NULL) 1589 continue; 1590 1591 if (block->previous_transaction != NULL) 1592 write_cached_block(cache, block); 1593 1594 if (block->unused) { 1595 cache->unused_blocks.Remove(block); 1596 cache->RemoveBlock(block); 1597 } else { 1598 if (block->transaction != NULL && block->parent_data != NULL 1599 && block->parent_data != block->current_data) { 1600 fssh_panic("Discarded block %" FSSH_B_PRIdOFF " has already " 1601 "been changed in this transaction!", blockNumber); 1602 } 1603 1604 // mark it as discarded (in the current transaction only, if any) 1605 block->discard = true; 1606 } 1607 } 1608 } 1609 1610 1611 fssh_status_t 1612 fssh_block_cache_make_writable(void* _cache, fssh_off_t blockNumber, 1613 int32_t transaction) 1614 { 1615 block_cache* cache = (block_cache*)_cache; 1616 MutexLocker locker(&cache->lock); 1617 1618 if (cache->read_only) 1619 fssh_panic("tried to make block writable on a read-only cache!"); 1620 1621 // TODO: this can be done better! 1622 void* block = get_writable_cached_block(cache, blockNumber, 1623 blockNumber, 1, transaction, false); 1624 if (block != NULL) { 1625 put_cached_block((block_cache*)_cache, blockNumber); 1626 return FSSH_B_OK; 1627 } 1628 1629 return FSSH_B_ERROR; 1630 } 1631 1632 1633 void* 1634 fssh_block_cache_get_writable_etc(void* _cache, fssh_off_t blockNumber, fssh_off_t base, 1635 fssh_off_t length, int32_t transaction) 1636 { 1637 block_cache* cache = (block_cache*)_cache; 1638 MutexLocker locker(&cache->lock); 1639 1640 TRACE(("block_cache_get_writable_etc(block = %Ld, transaction = %ld)\n", 1641 blockNumber, transaction)); 1642 if (cache->read_only) 1643 fssh_panic("tried to get writable block on a read-only cache!"); 1644 1645 return get_writable_cached_block(cache, blockNumber, base, length, 1646 transaction, false); 1647 } 1648 1649 1650 void* 1651 fssh_block_cache_get_writable(void* _cache, fssh_off_t blockNumber, 1652 int32_t transaction) 1653 { 1654 return fssh_block_cache_get_writable_etc(_cache, blockNumber, 1655 blockNumber, 1, transaction); 1656 } 1657 1658 1659 void* 1660 fssh_block_cache_get_empty(void* _cache, fssh_off_t blockNumber, 1661 int32_t transaction) 1662 { 1663 block_cache* cache = (block_cache*)_cache; 1664 MutexLocker locker(&cache->lock); 1665 1666 TRACE(("block_cache_get_empty(block = %Ld, transaction = %ld)\n", 1667 blockNumber, transaction)); 1668 if (cache->read_only) 1669 fssh_panic("tried to get empty writable block on a read-only cache!"); 1670 1671 return get_writable_cached_block((block_cache*)_cache, blockNumber, 1672 blockNumber, 1, transaction, true); 1673 } 1674 1675 1676 const void* 1677 fssh_block_cache_get_etc(void* _cache, fssh_off_t blockNumber, fssh_off_t base, 1678 fssh_off_t length) 1679 { 1680 block_cache* cache = (block_cache*)_cache; 1681 MutexLocker locker(&cache->lock); 1682 bool allocated; 1683 1684 cached_block* block = get_cached_block(cache, blockNumber, &allocated); 1685 if (block == NULL) 1686 return NULL; 1687 1688 #ifdef DEBUG_CHANGED 1689 if (block->compare == NULL) 1690 block->compare = cache->Allocate(); 1691 if (block->compare != NULL) 1692 memcpy(block->compare, block->current_data, cache->block_size); 1693 #endif 1694 return block->current_data; 1695 } 1696 1697 1698 const void* 1699 fssh_block_cache_get(void* _cache, fssh_off_t blockNumber) 1700 { 1701 return fssh_block_cache_get_etc(_cache, blockNumber, blockNumber, 1); 1702 } 1703 1704 1705 /*! Changes the internal status of a writable block to \a dirty. This can be 1706 helpful in case you realize you don't need to change that block anymore 1707 for whatever reason. 1708 1709 Note, you must only use this function on blocks that were acquired 1710 writable! 1711 */ 1712 fssh_status_t 1713 fssh_block_cache_set_dirty(void* _cache, fssh_off_t blockNumber, bool dirty, 1714 int32_t transaction) 1715 { 1716 block_cache* cache = (block_cache*)_cache; 1717 MutexLocker locker(&cache->lock); 1718 1719 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1720 &blockNumber); 1721 if (block == NULL) 1722 return FSSH_B_BAD_VALUE; 1723 if (block->is_dirty == dirty) { 1724 // there is nothing to do for us 1725 return FSSH_B_OK; 1726 } 1727 1728 // TODO: not yet implemented 1729 if (dirty) 1730 fssh_panic("block_cache_set_dirty(): not yet implemented that way!\n"); 1731 1732 return FSSH_B_OK; 1733 } 1734 1735 1736 void 1737 fssh_block_cache_put(void* _cache, fssh_off_t blockNumber) 1738 { 1739 block_cache* cache = (block_cache*)_cache; 1740 MutexLocker locker(&cache->lock); 1741 1742 put_cached_block(cache, blockNumber); 1743 } 1744 1745