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 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 %Ld, 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 cached_block* 720 get_cached_block(block_cache* cache, fssh_off_t blockNumber, bool* _allocated, 721 bool readBlock = true) 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 NULL; 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 NULL; 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 NULL; 751 } 752 } 753 754 if (block->unused) { 755 //TRACE(("remove block %Ld 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 return block; 764 } 765 766 767 /*! Returns the writable block data for the requested blockNumber. 768 If \a cleared is true, the block is not read from disk; an empty block 769 is returned. 770 771 This is the only method to insert a block into a transaction. It makes 772 sure that the previous block contents are preserved in that case. 773 */ 774 static void* 775 get_writable_cached_block(block_cache* cache, fssh_off_t blockNumber, fssh_off_t base, 776 fssh_off_t length, int32_t transactionID, bool cleared) 777 { 778 TRACE(("get_writable_cached_block(blockNumber = %Ld, transaction = %d)\n", 779 blockNumber, transactionID)); 780 781 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 782 fssh_panic("get_writable_cached_block: invalid block number %" 783 FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber, 784 cache->max_blocks - 1); 785 } 786 787 bool allocated; 788 cached_block* block = get_cached_block(cache, blockNumber, &allocated, 789 !cleared); 790 if (block == NULL) 791 return NULL; 792 793 block->discard = false; 794 795 // if there is no transaction support, we just return the current block 796 if (transactionID == -1) { 797 if (cleared) 798 fssh_memset(block->current_data, 0, cache->block_size); 799 800 block->is_dirty = true; 801 // mark the block as dirty 802 803 return block->current_data; 804 } 805 806 cache_transaction* transaction = block->transaction; 807 808 if (transaction != NULL && transaction->id != transactionID) { 809 // TODO: we have to wait here until the other transaction is done. 810 // Maybe we should even panic, since we can't prevent any deadlocks. 811 fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction->id); 812 put_cached_block(cache, block); 813 return NULL; 814 } 815 if (transaction == NULL && transactionID != -1) { 816 // get new transaction 817 transaction = lookup_transaction(cache, transactionID); 818 if (transaction == NULL) { 819 fssh_panic("get_writable_cached_block(): invalid transaction %d!\n", 820 (int)transactionID); 821 put_cached_block(cache, block); 822 return NULL; 823 } 824 if (!transaction->open) { 825 fssh_panic("get_writable_cached_block(): transaction already done!\n"); 826 put_cached_block(cache, block); 827 return NULL; 828 } 829 830 block->transaction = transaction; 831 832 // attach the block to the transaction block list 833 block->transaction_next = transaction->first_block; 834 transaction->first_block = block; 835 transaction->num_blocks++; 836 } 837 838 bool wasUnchanged = block->original_data == NULL 839 || block->previous_transaction != NULL; 840 841 if (!(allocated && cleared) && block->original_data == NULL) { 842 // we already have data, so we need to preserve it 843 block->original_data = cache->Allocate(); 844 if (block->original_data == NULL) { 845 FATAL(("could not allocate original_data\n")); 846 put_cached_block(cache, block); 847 return NULL; 848 } 849 850 fssh_memcpy(block->original_data, block->current_data, cache->block_size); 851 } 852 if (block->parent_data == block->current_data) { 853 // remember any previous contents for the parent transaction 854 block->parent_data = cache->Allocate(); 855 if (block->parent_data == NULL) { 856 // TODO: maybe we should just continue the current transaction in this case... 857 FATAL(("could not allocate parent\n")); 858 put_cached_block(cache, block); 859 return NULL; 860 } 861 862 fssh_memcpy(block->parent_data, block->current_data, cache->block_size); 863 transaction->sub_num_blocks++; 864 } else if (transaction != NULL && transaction->has_sub_transaction 865 && block->parent_data == NULL && wasUnchanged) 866 transaction->sub_num_blocks++; 867 868 if (cleared) 869 fssh_memset(block->current_data, 0, cache->block_size); 870 871 block->is_dirty = true; 872 873 return block->current_data; 874 } 875 876 877 /*! Writes the specified \a block back to disk. It will always only write back 878 the oldest change of the block if it is part of more than one transaction. 879 It will automatically send out TRANSACTION_WRITTEN notices, as well as 880 delete transactions when they are no longer used, and \a deleteTransaction 881 is \c true. 882 */ 883 static fssh_status_t 884 write_cached_block(block_cache* cache, cached_block* block, 885 bool deleteTransaction) 886 { 887 cache_transaction* previous = block->previous_transaction; 888 int32_t blockSize = cache->block_size; 889 890 void* data = previous && block->original_data 891 ? block->original_data : block->current_data; 892 // we first need to write back changes from previous transactions 893 894 TRACE(("write_cached_block(block %Ld)\n", block->block_number)); 895 896 fssh_ssize_t written = fssh_write_pos(cache->fd, block->block_number * blockSize, 897 data, blockSize); 898 899 if (written < blockSize) { 900 FATAL(("could not write back block %" FSSH_B_PRIdOFF " (%s)\n", 901 block->block_number, fssh_strerror(fssh_get_errno()))); 902 return FSSH_B_IO_ERROR; 903 } 904 905 if (data == block->current_data) 906 block->is_dirty = false; 907 908 if (previous != NULL) { 909 previous->blocks.Remove(block); 910 block->previous_transaction = NULL; 911 912 if (block->original_data != NULL && block->transaction == NULL) { 913 // This block is not part of a transaction, so it does not need 914 // its original pointer anymore. 915 cache->Free(block->original_data); 916 block->original_data = NULL; 917 } 918 919 // Has the previous transation been finished with that write? 920 if (--previous->num_blocks == 0) { 921 TRACE(("cache transaction %ld finished!\n", previous->id)); 922 923 notify_transaction_listeners(cache, previous, FSSH_TRANSACTION_WRITTEN); 924 925 if (deleteTransaction) { 926 hash_remove(cache->transaction_hash, previous); 927 delete_transaction(cache, previous); 928 } 929 } 930 } 931 if (block->transaction == NULL && block->ref_count == 0 && !block->unused) { 932 // the block is no longer used 933 block->unused = true; 934 cache->unused_blocks.Add(block); 935 } 936 937 return FSSH_B_OK; 938 } 939 940 941 /*! Waits until all pending notifications are carried out. 942 Safe to be called from the block writer/notifier thread. 943 You must not hold the \a cache lock when calling this function. 944 */ 945 static void 946 wait_for_notifications(block_cache* cache) 947 { 948 // TODO: nothing to wait for here. 949 } 950 951 952 fssh_status_t 953 block_cache_init() 954 { 955 fssh_mutex_init(&sNotificationsLock, "block cache notifications"); 956 return FSSH_B_OK; 957 } 958 959 960 } // namespace FSShell 961 962 963 // #pragma mark - public transaction API 964 965 966 using namespace FSShell; 967 968 969 int32_t 970 fssh_cache_start_transaction(void* _cache) 971 { 972 block_cache* cache = (block_cache*)_cache; 973 MutexLocker locker(&cache->lock); 974 975 if (cache->last_transaction && cache->last_transaction->open) { 976 fssh_panic("last transaction (%d) still open!\n", 977 (int)cache->last_transaction->id); 978 } 979 980 cache_transaction* transaction = new(nothrow) cache_transaction; 981 if (transaction == NULL) 982 return FSSH_B_NO_MEMORY; 983 984 transaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 985 cache->last_transaction = transaction; 986 987 TRACE(("cache_start_transaction(): id %d started\n", transaction->id)); 988 989 hash_insert(cache->transaction_hash, transaction); 990 991 return transaction->id; 992 } 993 994 995 fssh_status_t 996 fssh_cache_sync_transaction(void* _cache, int32_t id) 997 { 998 block_cache* cache = (block_cache*)_cache; 999 MutexLocker locker(&cache->lock); 1000 fssh_status_t status = FSSH_B_ENTRY_NOT_FOUND; 1001 1002 TRACE(("cache_sync_transaction(id %d)\n", id)); 1003 1004 hash_iterator iterator; 1005 hash_open(cache->transaction_hash, &iterator); 1006 1007 cache_transaction* transaction; 1008 while ((transaction = (cache_transaction*)hash_next( 1009 cache->transaction_hash, &iterator)) != NULL) { 1010 // close all earlier transactions which haven't been closed yet 1011 1012 if (transaction->id <= id && !transaction->open) { 1013 // write back all of their remaining dirty blocks 1014 while (transaction->num_blocks > 0) { 1015 status = write_cached_block(cache, transaction->blocks.Head(), 1016 false); 1017 if (status != FSSH_B_OK) 1018 return status; 1019 } 1020 1021 hash_remove_current(cache->transaction_hash, &iterator); 1022 delete_transaction(cache, transaction); 1023 } 1024 } 1025 1026 hash_close(cache->transaction_hash, &iterator, false); 1027 locker.Unlock(); 1028 1029 wait_for_notifications(cache); 1030 // make sure that all pending FSSH_TRANSACTION_WRITTEN notifications 1031 // are handled after we return 1032 return FSSH_B_OK; 1033 } 1034 1035 1036 fssh_status_t 1037 fssh_cache_end_transaction(void* _cache, int32_t id, 1038 fssh_transaction_notification_hook hook, void* data) 1039 { 1040 block_cache* cache = (block_cache*)_cache; 1041 MutexLocker locker(&cache->lock); 1042 1043 TRACE(("cache_end_transaction(id = %d)\n", id)); 1044 1045 cache_transaction* transaction = lookup_transaction(cache, id); 1046 if (transaction == NULL) { 1047 fssh_panic("cache_end_transaction(): invalid transaction ID\n"); 1048 return FSSH_B_BAD_VALUE; 1049 } 1050 1051 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1052 1053 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1054 hook, data) != FSSH_B_OK) { 1055 return FSSH_B_NO_MEMORY; 1056 } 1057 1058 // iterate through all blocks and free the unchanged original contents 1059 1060 cached_block* block = transaction->first_block; 1061 cached_block* next; 1062 for (; block != NULL; block = next) { 1063 next = block->transaction_next; 1064 1065 if (block->previous_transaction != NULL) { 1066 // need to write back pending changes 1067 write_cached_block(cache, block); 1068 } 1069 if (block->discard) { 1070 // This block has been discarded in the transaction 1071 cache->DiscardBlock(block); 1072 transaction->num_blocks--; 1073 continue; 1074 } 1075 1076 if (block->original_data != NULL) { 1077 cache->Free(block->original_data); 1078 block->original_data = NULL; 1079 } 1080 if (transaction->has_sub_transaction) { 1081 if (block->parent_data != block->current_data) 1082 cache->Free(block->parent_data); 1083 block->parent_data = NULL; 1084 } 1085 1086 // move the block to the previous transaction list 1087 transaction->blocks.Add(block); 1088 1089 block->previous_transaction = transaction; 1090 block->transaction_next = NULL; 1091 block->transaction = NULL; 1092 } 1093 1094 transaction->open = false; 1095 return FSSH_B_OK; 1096 } 1097 1098 1099 fssh_status_t 1100 fssh_cache_abort_transaction(void* _cache, int32_t id) 1101 { 1102 block_cache* cache = (block_cache*)_cache; 1103 MutexLocker locker(&cache->lock); 1104 1105 TRACE(("cache_abort_transaction(id = %ld)\n", id)); 1106 1107 cache_transaction* transaction = lookup_transaction(cache, id); 1108 if (transaction == NULL) { 1109 fssh_panic("cache_abort_transaction(): invalid transaction ID\n"); 1110 return FSSH_B_BAD_VALUE; 1111 } 1112 1113 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1114 1115 // iterate through all blocks and restore their original contents 1116 1117 cached_block* block = transaction->first_block; 1118 cached_block* next; 1119 for (; block != NULL; block = next) { 1120 next = block->transaction_next; 1121 1122 if (block->original_data != NULL) { 1123 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %Ld\n", 1124 transaction->id, block->block_number)); 1125 fssh_memcpy(block->current_data, block->original_data, cache->block_size); 1126 cache->Free(block->original_data); 1127 block->original_data = NULL; 1128 } 1129 if (transaction->has_sub_transaction) { 1130 if (block->parent_data != block->current_data) 1131 cache->Free(block->parent_data); 1132 block->parent_data = NULL; 1133 } 1134 1135 block->transaction_next = NULL; 1136 block->transaction = NULL; 1137 block->discard = false; 1138 } 1139 1140 hash_remove(cache->transaction_hash, transaction); 1141 delete_transaction(cache, transaction); 1142 return FSSH_B_OK; 1143 } 1144 1145 1146 /*! Acknowledges the current parent transaction, and starts a new transaction 1147 from its sub transaction. 1148 The new transaction also gets a new transaction ID. 1149 */ 1150 int32_t 1151 fssh_cache_detach_sub_transaction(void* _cache, int32_t id, 1152 fssh_transaction_notification_hook hook, void* data) 1153 { 1154 block_cache* cache = (block_cache*)_cache; 1155 MutexLocker locker(&cache->lock); 1156 1157 TRACE(("cache_detach_sub_transaction(id = %d)\n", id)); 1158 1159 cache_transaction* transaction = lookup_transaction(cache, id); 1160 if (transaction == NULL) { 1161 fssh_panic("cache_detach_sub_transaction(): invalid transaction ID\n"); 1162 return FSSH_B_BAD_VALUE; 1163 } 1164 if (!transaction->has_sub_transaction) 1165 return FSSH_B_BAD_VALUE; 1166 1167 // create a new transaction for the sub transaction 1168 cache_transaction* newTransaction = new(nothrow) cache_transaction; 1169 if (transaction == NULL) 1170 return FSSH_B_NO_MEMORY; 1171 1172 newTransaction->id = fssh_atomic_add(&cache->next_transaction_id, 1); 1173 1174 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1175 1176 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN, 1177 hook, data) != FSSH_B_OK) { 1178 delete newTransaction; 1179 return FSSH_B_NO_MEMORY; 1180 } 1181 1182 // iterate through all blocks and free the unchanged original contents 1183 1184 cached_block* block = transaction->first_block; 1185 cached_block* last = NULL; 1186 cached_block* next; 1187 for (; block != NULL; block = next) { 1188 next = block->transaction_next; 1189 1190 if (block->previous_transaction != NULL) { 1191 // need to write back pending changes 1192 write_cached_block(cache, block); 1193 } 1194 if (block->discard) { 1195 cache->DiscardBlock(block); 1196 transaction->main_num_blocks--; 1197 continue; 1198 } 1199 1200 if (block->original_data != NULL && block->parent_data != NULL) { 1201 // free the original data if the parent data of the transaction 1202 // will be made current - but keep them otherwise 1203 cache->Free(block->original_data); 1204 block->original_data = NULL; 1205 } 1206 if (block->parent_data == NULL 1207 || block->parent_data != block->current_data) { 1208 // we need to move this block over to the new transaction 1209 block->original_data = block->parent_data; 1210 if (last == NULL) 1211 newTransaction->first_block = block; 1212 else 1213 last->transaction_next = block; 1214 1215 block->transaction = newTransaction; 1216 last = block; 1217 } else 1218 block->transaction = NULL; 1219 1220 if (block->parent_data != NULL) { 1221 // move the block to the previous transaction list 1222 transaction->blocks.Add(block); 1223 block->previous_transaction = transaction; 1224 block->parent_data = NULL; 1225 } 1226 1227 block->transaction_next = NULL; 1228 } 1229 1230 newTransaction->num_blocks = transaction->sub_num_blocks; 1231 1232 transaction->open = false; 1233 transaction->has_sub_transaction = false; 1234 transaction->num_blocks = transaction->main_num_blocks; 1235 transaction->sub_num_blocks = 0; 1236 1237 hash_insert(cache->transaction_hash, newTransaction); 1238 cache->last_transaction = newTransaction; 1239 1240 return newTransaction->id; 1241 } 1242 1243 1244 fssh_status_t 1245 fssh_cache_abort_sub_transaction(void* _cache, int32_t id) 1246 { 1247 block_cache* cache = (block_cache*)_cache; 1248 MutexLocker locker(&cache->lock); 1249 1250 TRACE(("cache_abort_sub_transaction(id = %ld)\n", id)); 1251 1252 cache_transaction* transaction = lookup_transaction(cache, id); 1253 if (transaction == NULL) { 1254 fssh_panic("cache_abort_sub_transaction(): invalid transaction ID\n"); 1255 return FSSH_B_BAD_VALUE; 1256 } 1257 if (!transaction->has_sub_transaction) 1258 return FSSH_B_BAD_VALUE; 1259 1260 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED); 1261 1262 // revert all changes back to the version of the parent 1263 1264 cached_block* block = transaction->first_block; 1265 cached_block* next; 1266 for (; block != NULL; block = next) { 1267 next = block->transaction_next; 1268 1269 if (block->parent_data == NULL) { 1270 if (block->original_data != NULL) { 1271 // the parent transaction didn't change the block, but the sub 1272 // transaction did - we need to revert from the original data 1273 fssh_memcpy(block->current_data, block->original_data, 1274 cache->block_size); 1275 } 1276 } else if (block->parent_data != block->current_data) { 1277 // the block has been changed and must be restored 1278 TRACE(("cache_abort_sub_transaction(id = %ld): restored contents of block %Ld\n", 1279 transaction->id, block->block_number)); 1280 fssh_memcpy(block->current_data, block->parent_data, 1281 cache->block_size); 1282 cache->Free(block->parent_data); 1283 } 1284 1285 block->parent_data = NULL; 1286 block->discard = false; 1287 } 1288 1289 // all subsequent changes will go into the main transaction 1290 transaction->has_sub_transaction = false; 1291 transaction->sub_num_blocks = 0; 1292 1293 return FSSH_B_OK; 1294 } 1295 1296 1297 fssh_status_t 1298 fssh_cache_start_sub_transaction(void* _cache, int32_t id) 1299 { 1300 block_cache* cache = (block_cache*)_cache; 1301 MutexLocker locker(&cache->lock); 1302 1303 TRACE(("cache_start_sub_transaction(id = %d)\n", id)); 1304 1305 cache_transaction* transaction = lookup_transaction(cache, id); 1306 if (transaction == NULL) { 1307 fssh_panic("cache_start_sub_transaction(): invalid transaction ID %d\n", (int)id); 1308 return FSSH_B_BAD_VALUE; 1309 } 1310 1311 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED); 1312 1313 // move all changed blocks up to the parent 1314 1315 cached_block* block = transaction->first_block; 1316 cached_block* last = NULL; 1317 cached_block* next; 1318 for (; block != NULL; block = next) { 1319 next = block->transaction_next; 1320 1321 if (block->discard) { 1322 // This block has been discarded in the parent transaction 1323 if (last != NULL) 1324 last->transaction_next = next; 1325 else 1326 transaction->first_block = next; 1327 1328 cache->DiscardBlock(block); 1329 transaction->num_blocks--; 1330 continue; 1331 } 1332 if (transaction->has_sub_transaction 1333 && block->parent_data != NULL 1334 && block->parent_data != block->current_data) { 1335 // there already is an older sub transaction - we acknowledge 1336 // its changes and move its blocks up to the parent 1337 cache->Free(block->parent_data); 1338 } 1339 1340 // we "allocate" the parent data lazily, that means, we don't copy 1341 // the data (and allocate memory for it) until we need to 1342 block->parent_data = block->current_data; 1343 last = block; 1344 } 1345 1346 // all subsequent changes will go into the sub transaction 1347 transaction->has_sub_transaction = true; 1348 transaction->main_num_blocks = transaction->num_blocks; 1349 transaction->sub_num_blocks = 0; 1350 1351 return FSSH_B_OK; 1352 } 1353 1354 1355 /*! Adds a transaction listener that gets notified when the transaction 1356 is ended or aborted. 1357 The listener gets automatically removed in this case. 1358 */ 1359 fssh_status_t 1360 fssh_cache_add_transaction_listener(void* _cache, int32_t id, int32_t events, 1361 fssh_transaction_notification_hook hookFunction, void* data) 1362 { 1363 // TODO: this is currently not used in a critical context in BFS 1364 return FSSH_B_OK; 1365 } 1366 1367 1368 fssh_status_t 1369 fssh_cache_remove_transaction_listener(void* _cache, int32_t id, 1370 fssh_transaction_notification_hook hookFunction, void* data) 1371 { 1372 // TODO: this is currently not used in a critical context in BFS 1373 return FSSH_B_OK; 1374 } 1375 1376 1377 fssh_status_t 1378 fssh_cache_next_block_in_transaction(void* _cache, int32_t id, bool mainOnly, 1379 long* _cookie, fssh_off_t* _blockNumber, void** _data, 1380 void** _unchangedData) 1381 { 1382 cached_block* block = (cached_block*)*_cookie; 1383 block_cache* cache = (block_cache*)_cache; 1384 1385 MutexLocker locker(&cache->lock); 1386 1387 cache_transaction* transaction = lookup_transaction(cache, id); 1388 if (transaction == NULL || !transaction->open) 1389 return FSSH_B_BAD_VALUE; 1390 1391 if (block == NULL) 1392 block = transaction->first_block; 1393 else 1394 block = block->transaction_next; 1395 1396 if (transaction->has_sub_transaction) { 1397 if (mainOnly) { 1398 // find next block that the parent changed 1399 while (block != NULL && block->parent_data == NULL) 1400 block = block->transaction_next; 1401 } else { 1402 // find next non-discarded block 1403 while (block != NULL && block->discard) 1404 block = block->transaction_next; 1405 } 1406 } 1407 1408 if (block == NULL) 1409 return FSSH_B_ENTRY_NOT_FOUND; 1410 1411 if (_blockNumber) 1412 *_blockNumber = block->block_number; 1413 if (_data) 1414 *_data = mainOnly ? block->parent_data : block->current_data; 1415 if (_unchangedData) 1416 *_unchangedData = block->original_data; 1417 1418 *_cookie = (fssh_addr_t)block; 1419 return FSSH_B_OK; 1420 } 1421 1422 1423 int32_t 1424 fssh_cache_blocks_in_transaction(void* _cache, int32_t id) 1425 { 1426 block_cache* cache = (block_cache*)_cache; 1427 MutexLocker locker(&cache->lock); 1428 1429 cache_transaction* transaction = lookup_transaction(cache, id); 1430 if (transaction == NULL) 1431 return FSSH_B_BAD_VALUE; 1432 1433 return transaction->num_blocks; 1434 } 1435 1436 1437 int32_t 1438 fssh_cache_blocks_in_main_transaction(void* _cache, int32_t id) 1439 { 1440 block_cache* cache = (block_cache*)_cache; 1441 MutexLocker locker(&cache->lock); 1442 1443 cache_transaction* transaction = lookup_transaction(cache, id); 1444 if (transaction == NULL) 1445 return FSSH_B_BAD_VALUE; 1446 1447 return transaction->main_num_blocks; 1448 } 1449 1450 1451 int32_t 1452 fssh_cache_blocks_in_sub_transaction(void* _cache, int32_t id) 1453 { 1454 block_cache* cache = (block_cache*)_cache; 1455 MutexLocker locker(&cache->lock); 1456 1457 cache_transaction* transaction = lookup_transaction(cache, id); 1458 if (transaction == NULL) 1459 return FSSH_B_BAD_VALUE; 1460 1461 return transaction->sub_num_blocks; 1462 } 1463 1464 1465 // #pragma mark - public block cache API 1466 1467 1468 void 1469 fssh_block_cache_delete(void* _cache, bool allowWrites) 1470 { 1471 block_cache* cache = (block_cache*)_cache; 1472 1473 if (allowWrites) 1474 fssh_block_cache_sync(cache); 1475 1476 fssh_mutex_lock(&cache->lock); 1477 1478 // free all blocks 1479 1480 uint32_t cookie = 0; 1481 cached_block* block; 1482 while ((block = (cached_block*)hash_remove_first(cache->hash, 1483 &cookie)) != NULL) { 1484 cache->FreeBlock(block); 1485 } 1486 1487 // free all transactions (they will all be aborted) 1488 1489 cookie = 0; 1490 cache_transaction* transaction; 1491 while ((transaction = (cache_transaction*)hash_remove_first( 1492 cache->transaction_hash, &cookie)) != NULL) { 1493 delete transaction; 1494 } 1495 1496 delete cache; 1497 } 1498 1499 1500 void* 1501 fssh_block_cache_create(int fd, fssh_off_t numBlocks, fssh_size_t blockSize, bool readOnly) 1502 { 1503 block_cache* cache = new(std::nothrow) block_cache(fd, numBlocks, blockSize, 1504 readOnly); 1505 if (cache == NULL) 1506 return NULL; 1507 1508 if (cache->Init() != FSSH_B_OK) { 1509 delete cache; 1510 return NULL; 1511 } 1512 1513 return cache; 1514 } 1515 1516 1517 fssh_status_t 1518 fssh_block_cache_sync(void* _cache) 1519 { 1520 block_cache* cache = (block_cache*)_cache; 1521 1522 // we will sync all dirty blocks to disk that have a completed 1523 // transaction or no transaction only 1524 1525 MutexLocker locker(&cache->lock); 1526 hash_iterator iterator; 1527 hash_open(cache->hash, &iterator); 1528 1529 cached_block* block; 1530 while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) { 1531 if (block->previous_transaction != NULL 1532 || (block->transaction == NULL && block->is_dirty)) { 1533 fssh_status_t status = write_cached_block(cache, block); 1534 if (status != FSSH_B_OK) 1535 return status; 1536 } 1537 } 1538 1539 hash_close(cache->hash, &iterator, false); 1540 return FSSH_B_OK; 1541 } 1542 1543 1544 fssh_status_t 1545 fssh_block_cache_sync_etc(void* _cache, fssh_off_t blockNumber, 1546 fssh_size_t numBlocks) 1547 { 1548 block_cache* cache = (block_cache*)_cache; 1549 1550 // we will sync all dirty blocks to disk that have a completed 1551 // transaction or no transaction only 1552 1553 if (blockNumber < 0 || blockNumber >= cache->max_blocks) { 1554 fssh_panic("block_cache_sync_etc: invalid block number %" FSSH_B_PRIdOFF 1555 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1); 1556 return FSSH_B_BAD_VALUE; 1557 } 1558 1559 MutexLocker locker(&cache->lock); 1560 1561 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1562 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1563 &blockNumber); 1564 if (block == NULL) 1565 continue; 1566 1567 if (block->previous_transaction != NULL 1568 || (block->transaction == NULL && block->is_dirty)) { 1569 fssh_status_t status = write_cached_block(cache, block); 1570 if (status != FSSH_B_OK) 1571 return status; 1572 } 1573 } 1574 1575 return FSSH_B_OK; 1576 } 1577 1578 1579 void 1580 fssh_block_cache_discard(void* _cache, fssh_off_t blockNumber, 1581 fssh_size_t numBlocks) 1582 { 1583 block_cache* cache = (block_cache*)_cache; 1584 MutexLocker locker(&cache->lock); 1585 1586 for (; numBlocks > 0; numBlocks--, blockNumber++) { 1587 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1588 &blockNumber); 1589 if (block == NULL) 1590 continue; 1591 1592 if (block->previous_transaction != NULL) 1593 write_cached_block(cache, block); 1594 1595 if (block->unused) { 1596 cache->unused_blocks.Remove(block); 1597 cache->RemoveBlock(block); 1598 } else { 1599 if (block->transaction != NULL && block->parent_data != NULL 1600 && block->parent_data != block->current_data) { 1601 fssh_panic("Discarded block %" FSSH_B_PRIdOFF " has already " 1602 "been changed in this transaction!", blockNumber); 1603 } 1604 1605 // mark it as discarded (in the current transaction only, if any) 1606 block->discard = true; 1607 } 1608 } 1609 } 1610 1611 1612 fssh_status_t 1613 fssh_block_cache_make_writable(void* _cache, fssh_off_t blockNumber, 1614 int32_t transaction) 1615 { 1616 block_cache* cache = (block_cache*)_cache; 1617 MutexLocker locker(&cache->lock); 1618 1619 if (cache->read_only) 1620 fssh_panic("tried to make block writable on a read-only cache!"); 1621 1622 // TODO: this can be done better! 1623 void* block = get_writable_cached_block(cache, blockNumber, 1624 blockNumber, 1, transaction, false); 1625 if (block != NULL) { 1626 put_cached_block((block_cache*)_cache, blockNumber); 1627 return FSSH_B_OK; 1628 } 1629 1630 return FSSH_B_ERROR; 1631 } 1632 1633 1634 void* 1635 fssh_block_cache_get_writable_etc(void* _cache, fssh_off_t blockNumber, fssh_off_t base, 1636 fssh_off_t length, int32_t transaction) 1637 { 1638 block_cache* cache = (block_cache*)_cache; 1639 MutexLocker locker(&cache->lock); 1640 1641 TRACE(("block_cache_get_writable_etc(block = %Ld, transaction = %ld)\n", 1642 blockNumber, transaction)); 1643 if (cache->read_only) 1644 fssh_panic("tried to get writable block on a read-only cache!"); 1645 1646 return get_writable_cached_block(cache, blockNumber, base, length, 1647 transaction, false); 1648 } 1649 1650 1651 void* 1652 fssh_block_cache_get_writable(void* _cache, fssh_off_t blockNumber, 1653 int32_t transaction) 1654 { 1655 return fssh_block_cache_get_writable_etc(_cache, blockNumber, 1656 blockNumber, 1, transaction); 1657 } 1658 1659 1660 void* 1661 fssh_block_cache_get_empty(void* _cache, fssh_off_t blockNumber, 1662 int32_t transaction) 1663 { 1664 block_cache* cache = (block_cache*)_cache; 1665 MutexLocker locker(&cache->lock); 1666 1667 TRACE(("block_cache_get_empty(block = %Ld, transaction = %ld)\n", 1668 blockNumber, transaction)); 1669 if (cache->read_only) 1670 fssh_panic("tried to get empty writable block on a read-only cache!"); 1671 1672 return get_writable_cached_block((block_cache*)_cache, blockNumber, 1673 blockNumber, 1, transaction, true); 1674 } 1675 1676 1677 const void* 1678 fssh_block_cache_get_etc(void* _cache, fssh_off_t blockNumber, fssh_off_t base, 1679 fssh_off_t length) 1680 { 1681 block_cache* cache = (block_cache*)_cache; 1682 MutexLocker locker(&cache->lock); 1683 bool allocated; 1684 1685 cached_block* block = get_cached_block(cache, blockNumber, &allocated); 1686 if (block == NULL) 1687 return NULL; 1688 1689 #ifdef DEBUG_CHANGED 1690 if (block->compare == NULL) 1691 block->compare = cache->Allocate(); 1692 if (block->compare != NULL) 1693 memcpy(block->compare, block->current_data, cache->block_size); 1694 #endif 1695 return block->current_data; 1696 } 1697 1698 1699 const void* 1700 fssh_block_cache_get(void* _cache, fssh_off_t blockNumber) 1701 { 1702 return fssh_block_cache_get_etc(_cache, blockNumber, blockNumber, 1); 1703 } 1704 1705 1706 /*! Changes the internal status of a writable block to \a dirty. This can be 1707 helpful in case you realize you don't need to change that block anymore 1708 for whatever reason. 1709 1710 Note, you must only use this function on blocks that were acquired 1711 writable! 1712 */ 1713 fssh_status_t 1714 fssh_block_cache_set_dirty(void* _cache, fssh_off_t blockNumber, bool dirty, 1715 int32_t transaction) 1716 { 1717 block_cache* cache = (block_cache*)_cache; 1718 MutexLocker locker(&cache->lock); 1719 1720 cached_block* block = (cached_block*)hash_lookup(cache->hash, 1721 &blockNumber); 1722 if (block == NULL) 1723 return FSSH_B_BAD_VALUE; 1724 if (block->is_dirty == dirty) { 1725 // there is nothing to do for us 1726 return FSSH_B_OK; 1727 } 1728 1729 // TODO: not yet implemented 1730 if (dirty) 1731 fssh_panic("block_cache_set_dirty(): not yet implemented that way!\n"); 1732 1733 return FSSH_B_OK; 1734 } 1735 1736 1737 void 1738 fssh_block_cache_put(void* _cache, fssh_off_t blockNumber) 1739 { 1740 block_cache* cache = (block_cache*)_cache; 1741 MutexLocker locker(&cache->lock); 1742 1743 put_cached_block(cache, blockNumber); 1744 } 1745 1746