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