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