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