1 /*
2 * Copyright 2004-2020, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include <new>
8 #include <stdlib.h>
9
10 #include "DoublyLinkedList.h"
11 #include "fssh_atomic.h"
12 #include "fssh_errno.h"
13 #include "fssh_fs_cache.h"
14 #include "fssh_kernel_export.h"
15 #include "fssh_lock.h"
16 #include "fssh_string.h"
17 #include "fssh_unistd.h"
18 #include "hash.h"
19 #include "vfs.h"
20
21 // TODO: this is a naive but growing implementation to test the API:
22 // 1) block reading/writing is not at all optimized for speed, it will
23 // just read and write single blocks.
24 // 2) the locking could be improved; getting a block should not need to
25 // wait for blocks to be written
26 // TODO: the retrieval/copy of the original data could be delayed until the
27 // new data must be written, ie. in low memory situations.
28
29 //#define TRACE_BLOCK_CACHE
30 #ifdef TRACE_BLOCK_CACHE
31 # define TRACE(x) fssh_dprintf x
32 #else
33 # define TRACE(x) ;
34 #endif
35
36 using std::nothrow;
37
38 // This macro is used for fatal situations that are acceptable in a running
39 // system, like out of memory situations - should only panic for debugging.
40 #define FATAL(x) fssh_panic x
41
42 #undef offsetof
43 #define offsetof(struct, member) 0
44 // TODO: I don't know why the offsetof() macro doesn't work in this context,
45 // but (0) is okay here...
46
47 namespace FSShell {
48
49 struct hash_table;
50 struct vm_page;
51
52
53 //#define DEBUG_CHANGED
54 #undef DEBUG_CHANGED
55
56
57 struct cache_transaction;
58 struct cached_block;
59 struct block_cache;
60 typedef DoublyLinkedListLink<cached_block> block_link;
61
62 struct cached_block {
63 cached_block* next; // next in hash
64 cached_block* transaction_next;
65 block_link link;
66 fssh_off_t block_number;
67 void* current_data;
68 void* original_data;
69 void* parent_data;
70 #ifdef DEBUG_CHANGED
71 void *compare;
72 #endif
73 int32_t ref_count;
74 int32_t accessed;
75 bool busy : 1;
76 bool is_writing : 1;
77 bool is_dirty : 1;
78 bool unused : 1;
79 bool discard : 1;
80 cache_transaction* transaction;
81 cache_transaction* previous_transaction;
82
83 static int Compare(void* _cacheEntry, const void* _block);
84 static uint32_t Hash(void* _cacheEntry, const void* _block, uint32_t range);
85 };
86
87 typedef DoublyLinkedList<cached_block,
88 DoublyLinkedListMemberGetLink<cached_block,
89 &cached_block::link> > block_list;
90
91 struct cache_notification : DoublyLinkedListLinkImpl<cache_notification> {
92 int32_t transaction_id;
93 int32_t events_pending;
94 int32_t events;
95 fssh_transaction_notification_hook hook;
96 void* data;
97 bool delete_after_event;
98 };
99
100 typedef DoublyLinkedList<cache_notification> NotificationList;
101
102 struct block_cache {
103 hash_table* hash;
104 fssh_mutex lock;
105 int fd;
106 fssh_off_t max_blocks;
107 fssh_size_t block_size;
108 int32_t allocated_block_count;
109 int32_t next_transaction_id;
110 cache_transaction* last_transaction;
111 hash_table* transaction_hash;
112
113 block_list unused_blocks;
114
115 bool read_only;
116
117 NotificationList pending_notifications;
118
119 block_cache(int fd, fssh_off_t numBlocks,
120 fssh_size_t blockSize, bool readOnly);
121 ~block_cache();
122
123 fssh_status_t Init();
124
125 void Free(void* buffer);
126 void* Allocate();
127 void RemoveUnusedBlocks(int32_t maxAccessed = INT32_MAX,
128 int32_t count = INT32_MAX);
129 void RemoveBlock(cached_block* block);
130 void DiscardBlock(cached_block* block);
131 void FreeBlock(cached_block* block);
132 cached_block* NewBlock(fssh_off_t blockNumber);
133 };
134
135 static const int32_t kMaxBlockCount = 1024;
136
137 struct cache_listener;
138 typedef DoublyLinkedListLink<cache_listener> listener_link;
139
140 struct cache_listener : cache_notification {
141 listener_link link;
142 };
143
144 typedef DoublyLinkedList<cache_listener,
145 DoublyLinkedListMemberGetLink<cache_listener,
146 &cache_listener::link> > ListenerList;
147
148 struct cache_transaction {
149 cache_transaction();
150
151 cache_transaction* next;
152 int32_t id;
153 int32_t num_blocks;
154 int32_t main_num_blocks;
155 int32_t sub_num_blocks;
156 cached_block* first_block;
157 block_list blocks;
158 fssh_transaction_notification_hook notification_hook;
159 void* notification_data;
160 ListenerList listeners;
161 bool open;
162 bool has_sub_transaction;
163 };
164
165
166 static fssh_status_t write_cached_block(block_cache* cache, cached_block* block,
167 bool deleteTransaction = true);
168
169
170 static fssh_mutex sNotificationsLock;
171
172
173 // #pragma mark - notifications/listener
174
175
176 /*! Checks whether or not this is an event that closes a transaction. */
177 static inline bool
is_closing_event(int32_t event)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
is_written_event(int32_t event)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
get_next_pending_event(cache_notification * notification,int32_t * _event)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
flush_pending_notifications(block_cache * cache)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
set_notification(cache_transaction * transaction,cache_notification & notification,int32_t events,fssh_transaction_notification_hook hook,void * data)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
delete_notification(cache_notification * notification)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
add_notification(block_cache * cache,cache_notification * notification,int32_t event,bool deleteNotification)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
notify_transaction_listeners(block_cache * cache,cache_transaction * transaction,int32_t event)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
remove_transaction_listeners(block_cache * cache,cache_transaction * transaction)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
add_transaction_listener(block_cache * cache,cache_transaction * transaction,int32_t events,fssh_transaction_notification_hook hookFunction,void * data)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
cache_transaction()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
transaction_compare(void * _transaction,const void * _id)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
transaction_hash(void * _transaction,const void * _id,uint32_t range)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
delete_transaction(block_cache * cache,cache_transaction * transaction)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*
lookup_transaction(block_cache * cache,int32_t id)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
Compare(void * _cacheEntry,const void * _block)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
Hash(void * _cacheEntry,const void * _block,uint32_t range)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
block_cache(int _fd,fssh_off_t numBlocks,fssh_size_t blockSize,bool readOnly)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
~block_cache()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
Init()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
Free(void * buffer)518 block_cache::Free(void* buffer)
519 {
520 if (buffer == NULL)
521 return;
522
523 free(buffer);
524 }
525
526
527 void*
Allocate()528 block_cache::Allocate()
529 {
530 return malloc(block_size);
531 }
532
533
534 void
FreeBlock(cached_block * block)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*
NewBlock(fssh_off_t blockNumber)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
RemoveUnusedBlocks(int32_t maxAccessed,int32_t count)597 block_cache::RemoveUnusedBlocks(int32_t maxAccessed, int32_t count)
598 {
599 TRACE(("block_cache: remove up to %ld unused blocks\n", count));
600
601 for (block_list::Iterator iterator = unused_blocks.GetIterator();
602 cached_block *block = iterator.Next();) {
603 if (maxAccessed < block->accessed)
604 continue;
605
606 TRACE((" remove block %lld, accessed %ld times\n",
607 block->block_number, block->accessed));
608
609 // this can only happen if no transactions are used
610 if (block->is_dirty && !block->discard)
611 write_cached_block(this, block, false);
612
613 // remove block from lists
614 iterator.Remove();
615 RemoveBlock(block);
616
617 if (--count <= 0)
618 break;
619 }
620 }
621
622
623 void
RemoveBlock(cached_block * block)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
DiscardBlock(cached_block * block)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
put_cached_block(block_cache * cache,cached_block * block)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
put_cached_block(block_cache * cache,fssh_off_t blockNumber)697 put_cached_block(block_cache* cache, fssh_off_t blockNumber)
698 {
699 if (blockNumber < 0 || blockNumber >= cache->max_blocks) {
700 fssh_panic("put_cached_block: invalid block number %" FSSH_B_PRIdOFF
701 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1);
702 }
703
704 cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber);
705 if (block != NULL)
706 put_cached_block(cache, block);
707 }
708
709
710 /*! Retrieves the block \a blockNumber from the hash table, if it's already
711 there, or reads it from the disk.
712
713 \param _allocated tells you whether or not a new block has been allocated
714 to satisfy your request.
715 \param readBlock if \c false, the block will not be read in case it was
716 not already in the cache. The block you retrieve may contain random
717 data.
718 */
719 static fssh_status_t
get_cached_block(block_cache * cache,fssh_off_t blockNumber,bool * _allocated,bool readBlock,cached_block ** _block)720 get_cached_block(block_cache* cache, fssh_off_t blockNumber, bool* _allocated,
721 bool readBlock, cached_block** _block)
722 {
723 if (blockNumber < 0 || blockNumber >= cache->max_blocks) {
724 fssh_panic("get_cached_block: invalid block number %" FSSH_B_PRIdOFF
725 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1);
726 return FSSH_B_BAD_VALUE;
727 }
728
729 cached_block* block = (cached_block*)hash_lookup(cache->hash,
730 &blockNumber);
731 *_allocated = false;
732
733 if (block == NULL) {
734 // read block into cache
735 block = cache->NewBlock(blockNumber);
736 if (block == NULL)
737 return FSSH_B_NO_MEMORY;
738
739 hash_insert(cache->hash, block);
740 *_allocated = true;
741 }
742
743 if (*_allocated && readBlock) {
744 int32_t blockSize = cache->block_size;
745
746 if (fssh_read_pos(cache->fd, blockNumber * blockSize, block->current_data,
747 blockSize) < blockSize) {
748 cache->RemoveBlock(block);
749 FATAL(("could not read block %" FSSH_B_PRIdOFF "\n", blockNumber));
750 return fssh_errno;
751 }
752 }
753
754 if (block->unused) {
755 //TRACE(("remove block %lld from unused\n", blockNumber));
756 block->unused = false;
757 cache->unused_blocks.Remove(block);
758 }
759
760 block->ref_count++;
761 block->accessed++;
762
763 *_block = block;
764 return FSSH_B_OK;
765 }
766
767
768 /*! Returns the writable block data for the requested blockNumber.
769 If \a cleared is true, the block is not read from disk; an empty block
770 is returned.
771
772 This is the only method to insert a block into a transaction. It makes
773 sure that the previous block contents are preserved in that case.
774 */
775 static fssh_status_t
get_writable_cached_block(block_cache * cache,fssh_off_t blockNumber,int32_t transactionID,bool cleared,void ** _block)776 get_writable_cached_block(block_cache* cache, fssh_off_t blockNumber,
777 int32_t transactionID, bool cleared, void** _block)
778 {
779 TRACE(("get_writable_cached_block(blockNumber = %lld, transaction = %d)\n",
780 blockNumber, transactionID));
781
782 if (blockNumber < 0 || blockNumber >= cache->max_blocks) {
783 fssh_panic("get_writable_cached_block: invalid block number %"
784 FSSH_B_PRIdOFF " (max %" FSSH_B_PRIdOFF ")", blockNumber,
785 cache->max_blocks - 1);
786 return FSSH_B_BAD_VALUE;
787 }
788
789 bool allocated;
790 cached_block* block;
791 fssh_status_t status = get_cached_block(cache, blockNumber, &allocated,
792 !cleared, &block);
793 if (status != FSSH_B_OK)
794 return status;
795
796 block->discard = false;
797
798 // if there is no transaction support, we just return the current block
799 if (transactionID == -1) {
800 if (cleared)
801 fssh_memset(block->current_data, 0, cache->block_size);
802
803 block->is_dirty = true;
804 // mark the block as dirty
805
806 *_block = block->current_data;
807 return FSSH_B_OK;
808 }
809
810 cache_transaction* transaction = block->transaction;
811
812 if (transaction != NULL && transaction->id != transactionID) {
813 // TODO: we have to wait here until the other transaction is done.
814 // Maybe we should even panic, since we can't prevent any deadlocks.
815 fssh_panic("get_writable_cached_block(): asked to get busy writable block (transaction %d)\n", (int)transaction->id);
816 put_cached_block(cache, block);
817 return FSSH_B_BAD_VALUE;
818 }
819 if (transaction == NULL && transactionID != -1) {
820 // get new transaction
821 transaction = lookup_transaction(cache, transactionID);
822 if (transaction == NULL) {
823 fssh_panic("get_writable_cached_block(): invalid transaction %d!\n",
824 (int)transactionID);
825 put_cached_block(cache, block);
826 return FSSH_B_BAD_VALUE;
827 }
828 if (!transaction->open) {
829 fssh_panic("get_writable_cached_block(): transaction already done!\n");
830 put_cached_block(cache, block);
831 return FSSH_B_BAD_VALUE;
832 }
833
834 block->transaction = transaction;
835
836 // attach the block to the transaction block list
837 block->transaction_next = transaction->first_block;
838 transaction->first_block = block;
839 transaction->num_blocks++;
840 }
841
842 bool wasUnchanged = block->original_data == NULL
843 || block->previous_transaction != NULL;
844
845 if (!(allocated && cleared) && block->original_data == NULL) {
846 // we already have data, so we need to preserve it
847 block->original_data = cache->Allocate();
848 if (block->original_data == NULL) {
849 FATAL(("could not allocate original_data\n"));
850 put_cached_block(cache, block);
851 return FSSH_B_NO_MEMORY;
852 }
853
854 fssh_memcpy(block->original_data, block->current_data, cache->block_size);
855 }
856 if (block->parent_data == block->current_data) {
857 // remember any previous contents for the parent transaction
858 block->parent_data = cache->Allocate();
859 if (block->parent_data == NULL) {
860 // TODO: maybe we should just continue the current transaction in this case...
861 FATAL(("could not allocate parent\n"));
862 put_cached_block(cache, block);
863 return FSSH_B_NO_MEMORY;
864 }
865
866 fssh_memcpy(block->parent_data, block->current_data, cache->block_size);
867 transaction->sub_num_blocks++;
868 } else if (transaction != NULL && transaction->has_sub_transaction
869 && block->parent_data == NULL && wasUnchanged)
870 transaction->sub_num_blocks++;
871
872 if (cleared)
873 fssh_memset(block->current_data, 0, cache->block_size);
874
875 block->is_dirty = true;
876
877 *_block = block->current_data;
878 return FSSH_B_OK;
879 }
880
881
882 /*! Writes the specified \a block back to disk. It will always only write back
883 the oldest change of the block if it is part of more than one transaction.
884 It will automatically send out TRANSACTION_WRITTEN notices, as well as
885 delete transactions when they are no longer used, and \a deleteTransaction
886 is \c true.
887 */
888 static fssh_status_t
write_cached_block(block_cache * cache,cached_block * block,bool deleteTransaction)889 write_cached_block(block_cache* cache, cached_block* block,
890 bool deleteTransaction)
891 {
892 cache_transaction* previous = block->previous_transaction;
893 int32_t blockSize = cache->block_size;
894
895 void* data = previous && block->original_data
896 ? block->original_data : block->current_data;
897 // we first need to write back changes from previous transactions
898
899 TRACE(("write_cached_block(block %lld)\n", block->block_number));
900
901 fssh_ssize_t written = fssh_write_pos(cache->fd, block->block_number * blockSize,
902 data, blockSize);
903
904 if (written < blockSize) {
905 FATAL(("could not write back block %" FSSH_B_PRIdOFF " (%s)\n",
906 block->block_number, fssh_strerror(fssh_get_errno())));
907 return FSSH_B_IO_ERROR;
908 }
909
910 if (data == block->current_data)
911 block->is_dirty = false;
912
913 if (previous != NULL) {
914 previous->blocks.Remove(block);
915 block->previous_transaction = NULL;
916
917 if (block->original_data != NULL && block->transaction == NULL) {
918 // This block is not part of a transaction, so it does not need
919 // its original pointer anymore.
920 cache->Free(block->original_data);
921 block->original_data = NULL;
922 }
923
924 // Has the previous transation been finished with that write?
925 if (--previous->num_blocks == 0) {
926 TRACE(("cache transaction %ld finished!\n", previous->id));
927
928 notify_transaction_listeners(cache, previous, FSSH_TRANSACTION_WRITTEN);
929
930 if (deleteTransaction) {
931 hash_remove(cache->transaction_hash, previous);
932 delete_transaction(cache, previous);
933 }
934 }
935 }
936 if (block->transaction == NULL && block->ref_count == 0 && !block->unused) {
937 // the block is no longer used
938 block->unused = true;
939 cache->unused_blocks.Add(block);
940 }
941
942 return FSSH_B_OK;
943 }
944
945
946 /*! Waits until all pending notifications are carried out.
947 Safe to be called from the block writer/notifier thread.
948 You must not hold the \a cache lock when calling this function.
949 */
950 static void
wait_for_notifications(block_cache * cache)951 wait_for_notifications(block_cache* cache)
952 {
953 // TODO: nothing to wait for here.
954 }
955
956
957 fssh_status_t
block_cache_init()958 block_cache_init()
959 {
960 fssh_mutex_init(&sNotificationsLock, "block cache notifications");
961 return FSSH_B_OK;
962 }
963
964
965 } // namespace FSShell
966
967
968 // #pragma mark - public transaction API
969
970
971 using namespace FSShell;
972
973
974 int32_t
fssh_cache_start_transaction(void * _cache)975 fssh_cache_start_transaction(void* _cache)
976 {
977 block_cache* cache = (block_cache*)_cache;
978 MutexLocker locker(&cache->lock);
979
980 if (cache->last_transaction && cache->last_transaction->open) {
981 fssh_panic("last transaction (%d) still open!\n",
982 (int)cache->last_transaction->id);
983 }
984
985 cache_transaction* transaction = new(nothrow) cache_transaction;
986 if (transaction == NULL)
987 return FSSH_B_NO_MEMORY;
988
989 transaction->id = fssh_atomic_add(&cache->next_transaction_id, 1);
990 cache->last_transaction = transaction;
991
992 TRACE(("cache_start_transaction(): id %d started\n", transaction->id));
993
994 hash_insert(cache->transaction_hash, transaction);
995
996 return transaction->id;
997 }
998
999
1000 fssh_status_t
fssh_cache_sync_transaction(void * _cache,int32_t id)1001 fssh_cache_sync_transaction(void* _cache, int32_t id)
1002 {
1003 block_cache* cache = (block_cache*)_cache;
1004 MutexLocker locker(&cache->lock);
1005 fssh_status_t status = FSSH_B_ENTRY_NOT_FOUND;
1006
1007 TRACE(("cache_sync_transaction(id %d)\n", id));
1008
1009 hash_iterator iterator;
1010 hash_open(cache->transaction_hash, &iterator);
1011
1012 cache_transaction* transaction;
1013 while ((transaction = (cache_transaction*)hash_next(
1014 cache->transaction_hash, &iterator)) != NULL) {
1015 // close all earlier transactions which haven't been closed yet
1016
1017 if (transaction->id <= id && !transaction->open) {
1018 // write back all of their remaining dirty blocks
1019 while (transaction->num_blocks > 0) {
1020 status = write_cached_block(cache, transaction->blocks.Head(),
1021 false);
1022 if (status != FSSH_B_OK)
1023 return status;
1024 }
1025
1026 hash_remove_current(cache->transaction_hash, &iterator);
1027 delete_transaction(cache, transaction);
1028 }
1029 }
1030
1031 hash_close(cache->transaction_hash, &iterator, false);
1032 locker.Unlock();
1033
1034 wait_for_notifications(cache);
1035 // make sure that all pending FSSH_TRANSACTION_WRITTEN notifications
1036 // are handled after we return
1037 return FSSH_B_OK;
1038 }
1039
1040
1041 fssh_status_t
fssh_cache_end_transaction(void * _cache,int32_t id,fssh_transaction_notification_hook hook,void * data)1042 fssh_cache_end_transaction(void* _cache, int32_t id,
1043 fssh_transaction_notification_hook hook, void* data)
1044 {
1045 block_cache* cache = (block_cache*)_cache;
1046 MutexLocker locker(&cache->lock);
1047
1048 TRACE(("cache_end_transaction(id = %d)\n", id));
1049
1050 cache_transaction* transaction = lookup_transaction(cache, id);
1051 if (transaction == NULL) {
1052 fssh_panic("cache_end_transaction(): invalid transaction ID\n");
1053 return FSSH_B_BAD_VALUE;
1054 }
1055
1056 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED);
1057
1058 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN,
1059 hook, data) != FSSH_B_OK) {
1060 return FSSH_B_NO_MEMORY;
1061 }
1062
1063 // iterate through all blocks and free the unchanged original contents
1064
1065 cached_block* block = transaction->first_block;
1066 cached_block* next;
1067 for (; block != NULL; block = next) {
1068 next = block->transaction_next;
1069
1070 if (block->previous_transaction != NULL) {
1071 // need to write back pending changes
1072 write_cached_block(cache, block);
1073 }
1074 if (block->discard) {
1075 // This block has been discarded in the transaction
1076 cache->DiscardBlock(block);
1077 transaction->num_blocks--;
1078 continue;
1079 }
1080
1081 if (block->original_data != NULL) {
1082 cache->Free(block->original_data);
1083 block->original_data = NULL;
1084 }
1085 if (transaction->has_sub_transaction) {
1086 if (block->parent_data != block->current_data)
1087 cache->Free(block->parent_data);
1088 block->parent_data = NULL;
1089 }
1090
1091 // move the block to the previous transaction list
1092 transaction->blocks.Add(block);
1093
1094 block->previous_transaction = transaction;
1095 block->transaction_next = NULL;
1096 block->transaction = NULL;
1097 }
1098
1099 transaction->open = false;
1100 return FSSH_B_OK;
1101 }
1102
1103
1104 fssh_status_t
fssh_cache_abort_transaction(void * _cache,int32_t id)1105 fssh_cache_abort_transaction(void* _cache, int32_t id)
1106 {
1107 block_cache* cache = (block_cache*)_cache;
1108 MutexLocker locker(&cache->lock);
1109
1110 TRACE(("cache_abort_transaction(id = %ld)\n", id));
1111
1112 cache_transaction* transaction = lookup_transaction(cache, id);
1113 if (transaction == NULL) {
1114 fssh_panic("cache_abort_transaction(): invalid transaction ID\n");
1115 return FSSH_B_BAD_VALUE;
1116 }
1117
1118 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED);
1119
1120 // iterate through all blocks and restore their original contents
1121
1122 cached_block* block = transaction->first_block;
1123 cached_block* next;
1124 for (; block != NULL; block = next) {
1125 next = block->transaction_next;
1126
1127 if (block->original_data != NULL) {
1128 TRACE(("cache_abort_transaction(id = %ld): restored contents of block %lld\n",
1129 transaction->id, block->block_number));
1130 fssh_memcpy(block->current_data, block->original_data, cache->block_size);
1131 cache->Free(block->original_data);
1132 block->original_data = NULL;
1133 }
1134 if (transaction->has_sub_transaction) {
1135 if (block->parent_data != block->current_data)
1136 cache->Free(block->parent_data);
1137 block->parent_data = NULL;
1138 }
1139
1140 block->transaction_next = NULL;
1141 block->transaction = NULL;
1142 block->discard = false;
1143 }
1144
1145 hash_remove(cache->transaction_hash, transaction);
1146 delete_transaction(cache, transaction);
1147 return FSSH_B_OK;
1148 }
1149
1150
1151 /*! Acknowledges the current parent transaction, and starts a new transaction
1152 from its sub transaction.
1153 The new transaction also gets a new transaction ID.
1154 */
1155 int32_t
fssh_cache_detach_sub_transaction(void * _cache,int32_t id,fssh_transaction_notification_hook hook,void * data)1156 fssh_cache_detach_sub_transaction(void* _cache, int32_t id,
1157 fssh_transaction_notification_hook hook, void* data)
1158 {
1159 block_cache* cache = (block_cache*)_cache;
1160 MutexLocker locker(&cache->lock);
1161
1162 TRACE(("cache_detach_sub_transaction(id = %d)\n", id));
1163
1164 cache_transaction* transaction = lookup_transaction(cache, id);
1165 if (transaction == NULL) {
1166 fssh_panic("cache_detach_sub_transaction(): invalid transaction ID\n");
1167 return FSSH_B_BAD_VALUE;
1168 }
1169 if (!transaction->has_sub_transaction)
1170 return FSSH_B_BAD_VALUE;
1171
1172 // create a new transaction for the sub transaction
1173 cache_transaction* newTransaction = new(nothrow) cache_transaction;
1174 if (newTransaction == NULL)
1175 return FSSH_B_NO_MEMORY;
1176
1177 newTransaction->id = fssh_atomic_add(&cache->next_transaction_id, 1);
1178
1179 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED);
1180
1181 if (add_transaction_listener(cache, transaction, FSSH_TRANSACTION_WRITTEN,
1182 hook, data) != FSSH_B_OK) {
1183 delete newTransaction;
1184 return FSSH_B_NO_MEMORY;
1185 }
1186
1187 // iterate through all blocks and free the unchanged original contents
1188
1189 cached_block* block = transaction->first_block;
1190 cached_block* last = NULL;
1191 cached_block* next;
1192 for (; block != NULL; block = next) {
1193 next = block->transaction_next;
1194
1195 if (block->previous_transaction != NULL) {
1196 // need to write back pending changes
1197 write_cached_block(cache, block);
1198 }
1199 if (block->discard) {
1200 cache->DiscardBlock(block);
1201 transaction->main_num_blocks--;
1202 continue;
1203 }
1204
1205 if (block->original_data != NULL && block->parent_data != NULL) {
1206 // free the original data if the parent data of the transaction
1207 // will be made current - but keep them otherwise
1208 cache->Free(block->original_data);
1209 block->original_data = NULL;
1210 }
1211 if (block->parent_data == NULL
1212 || block->parent_data != block->current_data) {
1213 // we need to move this block over to the new transaction
1214 block->original_data = block->parent_data;
1215 if (last == NULL)
1216 newTransaction->first_block = block;
1217 else
1218 last->transaction_next = block;
1219
1220 block->transaction = newTransaction;
1221 last = block;
1222 } else
1223 block->transaction = NULL;
1224
1225 if (block->parent_data != NULL) {
1226 // move the block to the previous transaction list
1227 transaction->blocks.Add(block);
1228 block->previous_transaction = transaction;
1229 block->parent_data = NULL;
1230 }
1231
1232 block->transaction_next = NULL;
1233 }
1234
1235 newTransaction->num_blocks = transaction->sub_num_blocks;
1236
1237 transaction->open = false;
1238 transaction->has_sub_transaction = false;
1239 transaction->num_blocks = transaction->main_num_blocks;
1240 transaction->sub_num_blocks = 0;
1241
1242 hash_insert(cache->transaction_hash, newTransaction);
1243 cache->last_transaction = newTransaction;
1244
1245 return newTransaction->id;
1246 }
1247
1248
1249 fssh_status_t
fssh_cache_abort_sub_transaction(void * _cache,int32_t id)1250 fssh_cache_abort_sub_transaction(void* _cache, int32_t id)
1251 {
1252 block_cache* cache = (block_cache*)_cache;
1253 MutexLocker locker(&cache->lock);
1254
1255 TRACE(("cache_abort_sub_transaction(id = %ld)\n", id));
1256
1257 cache_transaction* transaction = lookup_transaction(cache, id);
1258 if (transaction == NULL) {
1259 fssh_panic("cache_abort_sub_transaction(): invalid transaction ID\n");
1260 return FSSH_B_BAD_VALUE;
1261 }
1262 if (!transaction->has_sub_transaction)
1263 return FSSH_B_BAD_VALUE;
1264
1265 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ABORTED);
1266
1267 // revert all changes back to the version of the parent
1268
1269 cached_block* block = transaction->first_block;
1270 cached_block* next;
1271 for (; block != NULL; block = next) {
1272 next = block->transaction_next;
1273
1274 if (block->parent_data == NULL) {
1275 if (block->original_data != NULL) {
1276 // the parent transaction didn't change the block, but the sub
1277 // transaction did - we need to revert from the original data
1278 fssh_memcpy(block->current_data, block->original_data,
1279 cache->block_size);
1280 }
1281 } else if (block->parent_data != block->current_data) {
1282 // the block has been changed and must be restored
1283 TRACE(("cache_abort_sub_transaction(id = %ld): restored contents of block %lld\n",
1284 transaction->id, block->block_number));
1285 fssh_memcpy(block->current_data, block->parent_data,
1286 cache->block_size);
1287 cache->Free(block->parent_data);
1288 }
1289
1290 block->parent_data = NULL;
1291 block->discard = false;
1292 }
1293
1294 // all subsequent changes will go into the main transaction
1295 transaction->has_sub_transaction = false;
1296 transaction->sub_num_blocks = 0;
1297
1298 return FSSH_B_OK;
1299 }
1300
1301
1302 fssh_status_t
fssh_cache_start_sub_transaction(void * _cache,int32_t id)1303 fssh_cache_start_sub_transaction(void* _cache, int32_t id)
1304 {
1305 block_cache* cache = (block_cache*)_cache;
1306 MutexLocker locker(&cache->lock);
1307
1308 TRACE(("cache_start_sub_transaction(id = %d)\n", id));
1309
1310 cache_transaction* transaction = lookup_transaction(cache, id);
1311 if (transaction == NULL) {
1312 fssh_panic("cache_start_sub_transaction(): invalid transaction ID %d\n", (int)id);
1313 return FSSH_B_BAD_VALUE;
1314 }
1315
1316 notify_transaction_listeners(cache, transaction, FSSH_TRANSACTION_ENDED);
1317
1318 // move all changed blocks up to the parent
1319
1320 cached_block* block = transaction->first_block;
1321 cached_block* last = NULL;
1322 cached_block* next;
1323 for (; block != NULL; block = next) {
1324 next = block->transaction_next;
1325
1326 if (block->discard) {
1327 // This block has been discarded in the parent transaction
1328 if (last != NULL)
1329 last->transaction_next = next;
1330 else
1331 transaction->first_block = next;
1332
1333 cache->DiscardBlock(block);
1334 transaction->num_blocks--;
1335 continue;
1336 }
1337 if (transaction->has_sub_transaction
1338 && block->parent_data != NULL
1339 && block->parent_data != block->current_data) {
1340 // there already is an older sub transaction - we acknowledge
1341 // its changes and move its blocks up to the parent
1342 cache->Free(block->parent_data);
1343 }
1344
1345 // we "allocate" the parent data lazily, that means, we don't copy
1346 // the data (and allocate memory for it) until we need to
1347 block->parent_data = block->current_data;
1348 last = block;
1349 }
1350
1351 // all subsequent changes will go into the sub transaction
1352 transaction->has_sub_transaction = true;
1353 transaction->main_num_blocks = transaction->num_blocks;
1354 transaction->sub_num_blocks = 0;
1355
1356 return FSSH_B_OK;
1357 }
1358
1359
1360 /*! Adds a transaction listener that gets notified when the transaction
1361 is ended or aborted.
1362 The listener gets automatically removed in this case.
1363 */
1364 fssh_status_t
fssh_cache_add_transaction_listener(void * _cache,int32_t id,int32_t events,fssh_transaction_notification_hook hookFunction,void * data)1365 fssh_cache_add_transaction_listener(void* _cache, int32_t id, int32_t events,
1366 fssh_transaction_notification_hook hookFunction, void* data)
1367 {
1368 // TODO: this is currently not used in a critical context in BFS
1369 return FSSH_B_OK;
1370 }
1371
1372
1373 fssh_status_t
fssh_cache_remove_transaction_listener(void * _cache,int32_t id,fssh_transaction_notification_hook hookFunction,void * data)1374 fssh_cache_remove_transaction_listener(void* _cache, int32_t id,
1375 fssh_transaction_notification_hook hookFunction, void* data)
1376 {
1377 // TODO: this is currently not used in a critical context in BFS
1378 return FSSH_B_OK;
1379 }
1380
1381
1382 fssh_status_t
fssh_cache_next_block_in_transaction(void * _cache,int32_t id,bool mainOnly,long * _cookie,fssh_off_t * _blockNumber,void ** _data,void ** _unchangedData)1383 fssh_cache_next_block_in_transaction(void* _cache, int32_t id, bool mainOnly,
1384 long* _cookie, fssh_off_t* _blockNumber, void** _data,
1385 void** _unchangedData)
1386 {
1387 cached_block* block = (cached_block*)*_cookie;
1388 block_cache* cache = (block_cache*)_cache;
1389
1390 MutexLocker locker(&cache->lock);
1391
1392 cache_transaction* transaction = lookup_transaction(cache, id);
1393 if (transaction == NULL || !transaction->open)
1394 return FSSH_B_BAD_VALUE;
1395
1396 if (block == NULL)
1397 block = transaction->first_block;
1398 else
1399 block = block->transaction_next;
1400
1401 if (transaction->has_sub_transaction) {
1402 if (mainOnly) {
1403 // find next block that the parent changed
1404 while (block != NULL && block->parent_data == NULL)
1405 block = block->transaction_next;
1406 } else {
1407 // find next non-discarded block
1408 while (block != NULL && block->discard)
1409 block = block->transaction_next;
1410 }
1411 }
1412
1413 if (block == NULL)
1414 return FSSH_B_ENTRY_NOT_FOUND;
1415
1416 if (_blockNumber)
1417 *_blockNumber = block->block_number;
1418 if (_data)
1419 *_data = mainOnly ? block->parent_data : block->current_data;
1420 if (_unchangedData)
1421 *_unchangedData = block->original_data;
1422
1423 *_cookie = (fssh_addr_t)block;
1424 return FSSH_B_OK;
1425 }
1426
1427
1428 int32_t
fssh_cache_blocks_in_transaction(void * _cache,int32_t id)1429 fssh_cache_blocks_in_transaction(void* _cache, int32_t id)
1430 {
1431 block_cache* cache = (block_cache*)_cache;
1432 MutexLocker locker(&cache->lock);
1433
1434 cache_transaction* transaction = lookup_transaction(cache, id);
1435 if (transaction == NULL)
1436 return FSSH_B_BAD_VALUE;
1437
1438 return transaction->num_blocks;
1439 }
1440
1441
1442 int32_t
fssh_cache_blocks_in_main_transaction(void * _cache,int32_t id)1443 fssh_cache_blocks_in_main_transaction(void* _cache, int32_t id)
1444 {
1445 block_cache* cache = (block_cache*)_cache;
1446 MutexLocker locker(&cache->lock);
1447
1448 cache_transaction* transaction = lookup_transaction(cache, id);
1449 if (transaction == NULL)
1450 return FSSH_B_BAD_VALUE;
1451
1452 return transaction->main_num_blocks;
1453 }
1454
1455
1456 int32_t
fssh_cache_blocks_in_sub_transaction(void * _cache,int32_t id)1457 fssh_cache_blocks_in_sub_transaction(void* _cache, int32_t id)
1458 {
1459 block_cache* cache = (block_cache*)_cache;
1460 MutexLocker locker(&cache->lock);
1461
1462 cache_transaction* transaction = lookup_transaction(cache, id);
1463 if (transaction == NULL)
1464 return FSSH_B_BAD_VALUE;
1465
1466 return transaction->sub_num_blocks;
1467 }
1468
1469
1470 bool
fssh_cache_has_block_in_transaction(void * _cache,int32_t id,fssh_off_t blockNumber)1471 fssh_cache_has_block_in_transaction(void* _cache, int32_t id,
1472 fssh_off_t blockNumber)
1473 {
1474 block_cache* cache = (block_cache*)_cache;
1475 MutexLocker locker(&cache->lock);
1476
1477 cached_block* block = (cached_block*)hash_lookup(cache->hash, &blockNumber);
1478
1479 return (block != NULL && block->transaction != NULL
1480 && block->transaction->id == id);
1481 }
1482
1483
1484 // #pragma mark - public block cache API
1485
1486
1487 void
fssh_block_cache_delete(void * _cache,bool allowWrites)1488 fssh_block_cache_delete(void* _cache, bool allowWrites)
1489 {
1490 block_cache* cache = (block_cache*)_cache;
1491
1492 if (allowWrites)
1493 fssh_block_cache_sync(cache);
1494
1495 fssh_mutex_lock(&cache->lock);
1496
1497 // free all blocks
1498
1499 uint32_t cookie = 0;
1500 cached_block* block;
1501 while ((block = (cached_block*)hash_remove_first(cache->hash,
1502 &cookie)) != NULL) {
1503 cache->FreeBlock(block);
1504 }
1505
1506 // free all transactions (they will all be aborted)
1507
1508 cookie = 0;
1509 cache_transaction* transaction;
1510 while ((transaction = (cache_transaction*)hash_remove_first(
1511 cache->transaction_hash, &cookie)) != NULL) {
1512 delete transaction;
1513 }
1514
1515 delete cache;
1516 }
1517
1518
1519 void*
fssh_block_cache_create(int fd,fssh_off_t numBlocks,fssh_size_t blockSize,bool readOnly)1520 fssh_block_cache_create(int fd, fssh_off_t numBlocks, fssh_size_t blockSize, bool readOnly)
1521 {
1522 block_cache* cache = new(std::nothrow) block_cache(fd, numBlocks, blockSize,
1523 readOnly);
1524 if (cache == NULL)
1525 return NULL;
1526
1527 if (cache->Init() != FSSH_B_OK) {
1528 delete cache;
1529 return NULL;
1530 }
1531
1532 return cache;
1533 }
1534
1535
1536 fssh_status_t
fssh_block_cache_sync(void * _cache)1537 fssh_block_cache_sync(void* _cache)
1538 {
1539 block_cache* cache = (block_cache*)_cache;
1540
1541 // we will sync all dirty blocks to disk that have a completed
1542 // transaction or no transaction only
1543
1544 MutexLocker locker(&cache->lock);
1545 hash_iterator iterator;
1546 hash_open(cache->hash, &iterator);
1547
1548 cached_block* block;
1549 while ((block = (cached_block*)hash_next(cache->hash, &iterator)) != NULL) {
1550 if (block->previous_transaction != NULL
1551 || (block->transaction == NULL && block->is_dirty)) {
1552 fssh_status_t status = write_cached_block(cache, block);
1553 if (status != FSSH_B_OK)
1554 return status;
1555 }
1556 }
1557
1558 hash_close(cache->hash, &iterator, false);
1559 return FSSH_B_OK;
1560 }
1561
1562
1563 fssh_status_t
fssh_block_cache_sync_etc(void * _cache,fssh_off_t blockNumber,fssh_size_t numBlocks)1564 fssh_block_cache_sync_etc(void* _cache, fssh_off_t blockNumber,
1565 fssh_size_t numBlocks)
1566 {
1567 block_cache* cache = (block_cache*)_cache;
1568
1569 // we will sync all dirty blocks to disk that have a completed
1570 // transaction or no transaction only
1571
1572 if (blockNumber < 0 || blockNumber >= cache->max_blocks) {
1573 fssh_panic("block_cache_sync_etc: invalid block number %" FSSH_B_PRIdOFF
1574 " (max %" FSSH_B_PRIdOFF ")", blockNumber, cache->max_blocks - 1);
1575 return FSSH_B_BAD_VALUE;
1576 }
1577
1578 MutexLocker locker(&cache->lock);
1579
1580 for (; numBlocks > 0; numBlocks--, blockNumber++) {
1581 cached_block* block = (cached_block*)hash_lookup(cache->hash,
1582 &blockNumber);
1583 if (block == NULL)
1584 continue;
1585
1586 if (block->previous_transaction != NULL
1587 || (block->transaction == NULL && block->is_dirty)) {
1588 fssh_status_t status = write_cached_block(cache, block);
1589 if (status != FSSH_B_OK)
1590 return status;
1591 }
1592 }
1593
1594 return FSSH_B_OK;
1595 }
1596
1597
1598 void
fssh_block_cache_discard(void * _cache,fssh_off_t blockNumber,fssh_size_t numBlocks)1599 fssh_block_cache_discard(void* _cache, fssh_off_t blockNumber,
1600 fssh_size_t numBlocks)
1601 {
1602 block_cache* cache = (block_cache*)_cache;
1603 MutexLocker locker(&cache->lock);
1604
1605 for (; numBlocks > 0; numBlocks--, blockNumber++) {
1606 cached_block* block = (cached_block*)hash_lookup(cache->hash,
1607 &blockNumber);
1608 if (block == NULL)
1609 continue;
1610
1611 if (block->previous_transaction != NULL)
1612 write_cached_block(cache, block);
1613
1614 if (block->unused) {
1615 cache->unused_blocks.Remove(block);
1616 cache->RemoveBlock(block);
1617 } else {
1618 if (block->transaction != NULL && block->parent_data != NULL
1619 && block->parent_data != block->current_data) {
1620 fssh_panic("Discarded block %" FSSH_B_PRIdOFF " has already "
1621 "been changed in this transaction!", blockNumber);
1622 }
1623
1624 // mark it as discarded (in the current transaction only, if any)
1625 block->discard = true;
1626 }
1627 }
1628 }
1629
1630
1631 fssh_status_t
fssh_block_cache_make_writable(void * _cache,fssh_off_t blockNumber,int32_t transaction)1632 fssh_block_cache_make_writable(void* _cache, fssh_off_t blockNumber,
1633 int32_t transaction)
1634 {
1635 block_cache* cache = (block_cache*)_cache;
1636 MutexLocker locker(&cache->lock);
1637
1638 if (cache->read_only)
1639 fssh_panic("tried to make block writable on a read-only cache!");
1640
1641 // TODO: this can be done better!
1642 void* block;
1643 fssh_status_t status = get_writable_cached_block(cache, blockNumber,
1644 transaction, false, &block);
1645 if (status == FSSH_B_OK) {
1646 put_cached_block((block_cache*)_cache, blockNumber);
1647 return FSSH_B_OK;
1648 }
1649
1650 return status;
1651 }
1652
1653
1654 fssh_status_t
fssh_block_cache_get_writable_etc(void * _cache,fssh_off_t blockNumber,int32_t transaction,void ** _block)1655 fssh_block_cache_get_writable_etc(void* _cache, fssh_off_t blockNumber,
1656 int32_t transaction, void** _block)
1657 {
1658 block_cache* cache = (block_cache*)_cache;
1659 MutexLocker locker(&cache->lock);
1660
1661 TRACE(("block_cache_get_writable_etc(block = %lld, transaction = %ld)\n",
1662 blockNumber, transaction));
1663 if (cache->read_only)
1664 fssh_panic("tried to get writable block on a read-only cache!");
1665
1666 return get_writable_cached_block(cache, blockNumber,
1667 transaction, false, _block);
1668 }
1669
1670
1671 void*
fssh_block_cache_get_writable(void * _cache,fssh_off_t blockNumber,int32_t transaction)1672 fssh_block_cache_get_writable(void* _cache, fssh_off_t blockNumber,
1673 int32_t transaction)
1674 {
1675 void* block;
1676 fssh_status_t status = fssh_block_cache_get_writable_etc(_cache,
1677 blockNumber, transaction, &block);
1678 if (status == FSSH_B_OK)
1679 return block;
1680
1681 return NULL;
1682 }
1683
1684
1685 void*
fssh_block_cache_get_empty(void * _cache,fssh_off_t blockNumber,int32_t transaction)1686 fssh_block_cache_get_empty(void* _cache, fssh_off_t blockNumber,
1687 int32_t transaction)
1688 {
1689 block_cache* cache = (block_cache*)_cache;
1690 MutexLocker locker(&cache->lock);
1691
1692 TRACE(("block_cache_get_empty(block = %lld, transaction = %ld)\n",
1693 blockNumber, transaction));
1694 if (cache->read_only)
1695 fssh_panic("tried to get empty writable block on a read-only cache!");
1696
1697 void* block;
1698 if (get_writable_cached_block((block_cache*)_cache, blockNumber,
1699 transaction, true, &block) == FSSH_B_OK)
1700 return block;
1701
1702 return NULL;
1703 }
1704
1705
1706 fssh_status_t
fssh_block_cache_get_etc(void * _cache,fssh_off_t blockNumber,const void ** _block)1707 fssh_block_cache_get_etc(void* _cache, fssh_off_t blockNumber, const void** _block)
1708 {
1709 block_cache* cache = (block_cache*)_cache;
1710 MutexLocker locker(&cache->lock);
1711 bool allocated;
1712
1713 cached_block* block;
1714 fssh_status_t status = get_cached_block(cache, blockNumber, &allocated,
1715 true, &block);
1716 if (status != FSSH_B_OK)
1717 return status;
1718
1719 #ifdef DEBUG_CHANGED
1720 if (block->compare == NULL)
1721 block->compare = cache->Allocate();
1722 if (block->compare != NULL)
1723 memcpy(block->compare, block->current_data, cache->block_size);
1724 #endif
1725 *_block = block->current_data;
1726 return FSSH_B_OK;
1727 }
1728
1729
1730 const void*
fssh_block_cache_get(void * _cache,fssh_off_t blockNumber)1731 fssh_block_cache_get(void* _cache, fssh_off_t blockNumber)
1732 {
1733 const void* block;
1734 if (fssh_block_cache_get_etc(_cache, blockNumber, &block)
1735 == FSSH_B_OK)
1736 return block;
1737
1738 return NULL;
1739 }
1740
1741
1742 /*! Changes the internal status of a writable block to \a dirty. This can be
1743 helpful in case you realize you don't need to change that block anymore
1744 for whatever reason.
1745
1746 Note, you must only use this function on blocks that were acquired
1747 writable!
1748 */
1749 fssh_status_t
fssh_block_cache_set_dirty(void * _cache,fssh_off_t blockNumber,bool dirty,int32_t transaction)1750 fssh_block_cache_set_dirty(void* _cache, fssh_off_t blockNumber, bool dirty,
1751 int32_t transaction)
1752 {
1753 block_cache* cache = (block_cache*)_cache;
1754 MutexLocker locker(&cache->lock);
1755
1756 cached_block* block = (cached_block*)hash_lookup(cache->hash,
1757 &blockNumber);
1758 if (block == NULL)
1759 return FSSH_B_BAD_VALUE;
1760 if (block->is_dirty == dirty) {
1761 // there is nothing to do for us
1762 return FSSH_B_OK;
1763 }
1764
1765 // TODO: not yet implemented
1766 if (dirty)
1767 fssh_panic("block_cache_set_dirty(): not yet implemented that way!\n");
1768
1769 return FSSH_B_OK;
1770 }
1771
1772
1773 void
fssh_block_cache_put(void * _cache,fssh_off_t blockNumber)1774 fssh_block_cache_put(void* _cache, fssh_off_t blockNumber)
1775 {
1776 block_cache* cache = (block_cache*)_cache;
1777 MutexLocker locker(&cache->lock);
1778
1779 put_cached_block(cache, blockNumber);
1780 }
1781
1782
1783 fssh_status_t
fssh_block_cache_prefetch(void * _cache,fssh_off_t blockNumber,fssh_size_t * _numBlocks)1784 fssh_block_cache_prefetch(void* _cache, fssh_off_t blockNumber, fssh_size_t* _numBlocks)
1785 {
1786 *_numBlocks = 0;
1787 return FSSH_B_UNSUPPORTED;
1788 }
1789