xref: /haiku/src/tools/fs_shell/block_cache.cpp (revision 9bb1816c14e5b8f360ee3213c831c535680fc755)
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(&notification->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 &notification, 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(&notification->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