xref: /haiku/src/add-ons/kernel/file_systems/bfs/Journal.cpp (revision cbe35e2031cb2bfb757422f35006bb9bd382bed1)
1 /* Journal - transaction and logging
2  *
3  * Copyright 2001-2005, Axel Dörfler, axeld@pinc-software.de.
4  * This file may be used under the terms of the MIT License.
5  */
6 
7 
8 #include "Journal.h"
9 #include "Inode.h"
10 #include "Debug.h"
11 
12 #include <Drivers.h>
13 #include <util/kernel_cpp.h>
14 #include <util/Stack.h>
15 #include <errno.h>
16 
17 
18 struct run_array {
19 	int32		count;
20 	union {
21 		int32	max_runs;
22 		int32	block_count;
23 	};
24 	block_run	runs[0];
25 
26 	int32 CountRuns() const { return BFS_ENDIAN_TO_HOST_INT32(count); }
27 	int32 MaxRuns() const { return BFS_ENDIAN_TO_HOST_INT32(max_runs) - 1; }
28 		// that -1 accounts for an off-by-one error in Be's BFS implementation
29 	const block_run &RunAt(int32 i) const { return runs[i]; }
30 
31 	static int32 MaxRuns(int32 blockSize)
32 		{ return (blockSize - sizeof(run_array)) / sizeof(block_run); }
33 };
34 
35 class RunArrays {
36 	public:
37 		RunArrays(Journal *journal);
38 		~RunArrays();
39 
40 		uint32 Length() const { return fLength; }
41 
42 		status_t Insert(off_t blockNumber);
43 
44 		run_array *ArrayAt(int32 i) { return fArrays.Array()[i]; }
45 		int32 CountArrays() const { return fArrays.CountItems(); }
46 
47 		int32 MaxArrayLength();
48 		void PrepareForWriting();
49 
50 	private:
51 		status_t _AddArray();
52 		bool _ContainsRun(block_run &run);
53 		bool _AddRun(block_run &run);
54 
55 		Journal		*fJournal;
56 		uint32		fLength;
57 		Stack<run_array *> fArrays;
58 		run_array	*fLastArray;
59 };
60 
61 class LogEntry : public DoublyLinkedListLinkImpl<LogEntry> {
62 	public:
63 		LogEntry(Journal *journal, uint32 logStart, uint32 length);
64 		~LogEntry();
65 
66 		uint32 Start() const { return fStart; }
67 		uint32 Length() const { return fLength; }
68 
69 		Journal *GetJournal() { return fJournal; }
70 
71 	private:
72 		Journal		*fJournal;
73 		uint32		fStart;
74 		uint32		fLength;
75 };
76 
77 
78 //	#pragma mark -
79 
80 
81 static void
82 add_to_iovec(iovec *vecs, int32 &index, int32 max, const void *address, size_t size)
83 {
84 	if (index > 0
85 		&& (addr_t)vecs[index - 1].iov_base + vecs[index - 1].iov_len == (addr_t)address) {
86 		// the iovec can be combined with the previous one
87 		vecs[index - 1].iov_len += size;
88 		return;
89 	}
90 
91 	if (index == max)
92 		panic("no more space for iovecs!");
93 
94 	// we need to start a new iovec
95 	vecs[index].iov_base = const_cast<void *>(address);
96 	vecs[index].iov_len = size;
97 	index++;
98 }
99 
100 
101 //	#pragma mark -
102 
103 
104 LogEntry::LogEntry(Journal *journal, uint32 start, uint32 length)
105 	:
106 	fJournal(journal),
107 	fStart(start),
108 	fLength(length)
109 {
110 }
111 
112 
113 LogEntry::~LogEntry()
114 {
115 }
116 
117 
118 //	#pragma mark -
119 
120 
121 RunArrays::RunArrays(Journal *journal)
122 	:
123 	fJournal(journal),
124 	fLength(0),
125 	fArrays(),
126 	fLastArray(NULL)
127 {
128 }
129 
130 
131 RunArrays::~RunArrays()
132 {
133 	run_array *array;
134 	while (fArrays.Pop(&array))
135 		free(array);
136 }
137 
138 
139 bool
140 RunArrays::_ContainsRun(block_run &run)
141 {
142 	for (int32 i = 0; i < CountArrays(); i++) {
143 		run_array *array = ArrayAt(i);
144 
145 		for (int32 j = 0; j < array->CountRuns(); j++) {
146 			block_run &arrayRun = array->runs[j];
147 			if (run.AllocationGroup() != arrayRun.AllocationGroup())
148 				continue;
149 
150 			if (run.Start() >= arrayRun.Start()
151 				&& run.Start() + run.Length() <= arrayRun.Start() + arrayRun.Length())
152 				return true;
153 		}
154 	}
155 
156 	return false;
157 }
158 
159 
160 /**	Adds the specified block_run into the array.
161  *	Note: it doesn't support overlapping - it must only be used
162  *	with block_runs of length 1!
163  */
164 
165 bool
166 RunArrays::_AddRun(block_run &run)
167 {
168 	ASSERT(run.length == 1);
169 
170 	// Be's BFS log replay routine can only deal with block_runs of size 1
171 	// A pity, isn't it? Too sad we have to be compatible.
172 #if 0
173 	// search for an existing adjacent block_run
174 	// ToDo: this could be improved by sorting and a binary search
175 
176 	for (int32 i = 0; i < CountArrays(); i++) {
177 		run_array *array = ArrayAt(i);
178 
179 		for (int32 j = 0; j < array->CountRuns(); j++) {
180 			block_run &arrayRun = array->runs[j];
181 			if (run.AllocationGroup() != arrayRun.AllocationGroup())
182 				continue;
183 
184 			if (run.Start() == arrayRun.Start() + arrayRun.Length()) {
185 				// matches the end
186 				arrayRun.length = HOST_ENDIAN_TO_BFS_INT16(arrayRun.Length() + 1);
187 				array->block_count++;
188 				fLength++;
189 				return true;
190 			} else if (run.start + 1 == arrayRun.start) {
191 				// matches the start
192 				arrayRun.start = run.start;
193 				arrayRun.length = HOST_ENDIAN_TO_BFS_INT16(arrayRun.Length() + 1);
194 				array->block_count++;
195 				fLength++;
196 				return true;
197 			}
198 		}
199 	}
200 #endif
201 
202 	// no entry found, add new to the last array
203 
204 	if (fLastArray == NULL || fLastArray->CountRuns() == fLastArray->MaxRuns())
205 		return false;
206 
207 	fLastArray->runs[fLastArray->CountRuns()] = run;
208 	fLastArray->count = HOST_ENDIAN_TO_BFS_INT16(fLastArray->CountRuns() + 1);
209 	fLastArray->block_count++;
210 	fLength++;
211 	return true;
212 }
213 
214 
215 status_t
216 RunArrays::_AddArray()
217 {
218 	int32 blockSize = fJournal->GetVolume()->BlockSize();
219 	run_array *array = (run_array *)malloc(blockSize);
220 	if (array == NULL)
221 		return B_NO_MEMORY;
222 
223 	if (fArrays.Push(array) != B_OK) {
224 		free(array);
225 		return B_NO_MEMORY;
226 	}
227 
228 	memset(array, 0, blockSize);
229 	array->block_count = 1;
230 	fLastArray = array;
231 	fLength++;
232 
233 	return B_OK;
234 }
235 
236 
237 status_t
238 RunArrays::Insert(off_t blockNumber)
239 {
240 	Volume *volume = fJournal->GetVolume();
241 	block_run run = volume->ToBlockRun(blockNumber);
242 
243 	if (fLastArray != NULL) {
244 		// check if the block is already in the array
245 		if (_ContainsRun(run))
246 			return B_OK;
247 	}
248 
249 	// insert block into array
250 
251 	if (!_AddRun(run)) {
252 		// array is full
253 		if (_AddArray() != B_OK)
254 			return B_NO_MEMORY;
255 
256 		// insert entry manually, because _AddRun() would search the
257 		// all arrays again for a free spot
258 		fLastArray->runs[0] = run;
259 		fLastArray->count = HOST_ENDIAN_TO_BFS_INT16(1);
260 		fLastArray->block_count++;
261 		fLength++;
262 	}
263 
264 	return B_OK;
265 }
266 
267 
268 int32
269 RunArrays::MaxArrayLength()
270 {
271 	int32 max = 0;
272 	for (int32 i = 0; i < CountArrays(); i++) {
273 		if (ArrayAt(i)->block_count > max)
274 			max = ArrayAt(i)->block_count;
275 	}
276 
277 	return max;
278 }
279 
280 
281 void
282 RunArrays::PrepareForWriting()
283 {
284 	int32 blockSize = fJournal->GetVolume()->BlockSize();
285 
286 	for (int32 i = 0; i < CountArrays(); i++) {
287 		ArrayAt(i)->max_runs = HOST_ENDIAN_TO_BFS_INT32(run_array::MaxRuns(blockSize));
288 	}
289 }
290 
291 
292 //	#pragma mark -
293 
294 
295 Journal::Journal(Volume *volume)
296 	:
297 	fVolume(volume),
298 	fLock("bfs journal"),
299 	fOwner(NULL),
300 	fLogSize(volume->Log().length),
301 	fMaxTransactionSize(fLogSize / 4 - 5),
302 	fUsed(0),
303 	fUnwrittenTransactions(0)
304 {
305 	if (fMaxTransactionSize > fLogSize / 2)
306 		fMaxTransactionSize = fLogSize / 2 - 5;
307 }
308 
309 
310 Journal::~Journal()
311 {
312 	FlushLogAndBlocks();
313 }
314 
315 
316 status_t
317 Journal::InitCheck()
318 {
319 	if (fVolume->LogStart() != fVolume->LogEnd()) {
320 		if (fVolume->SuperBlock().flags != SUPER_BLOCK_DISK_DIRTY)
321 			FATAL(("log_start and log_end differ, but disk is marked clean - trying to replay log...\n"));
322 
323 		return ReplayLog();
324 	}
325 
326 	return B_OK;
327 }
328 
329 
330 status_t
331 Journal::_CheckRunArray(const run_array *array)
332 {
333 	int32 maxRuns = run_array::MaxRuns(fVolume->BlockSize());
334 	if (array->MaxRuns() != maxRuns
335 		|| array->CountRuns() > maxRuns
336 		|| array->CountRuns() <= 0) {
337 		FATAL(("Log entry has broken header!\n"));
338 		return B_ERROR;
339 	}
340 
341 	for (int32 i = 0; i < array->CountRuns(); i++) {
342 		if (fVolume->ValidateBlockRun(array->RunAt(i)) != B_OK)
343 			return B_ERROR;
344 	}
345 
346 	PRINT(("Log entry has %ld entries (%Ld)\n", array->CountRuns()));
347 	return B_OK;
348 }
349 
350 
351 /**	Replays an entry in the log.
352  *	\a _start points to the entry in the log, and will be bumped to the next
353  *	one if replaying succeeded.
354  */
355 
356 status_t
357 Journal::_ReplayRunArray(int32 *_start)
358 {
359 	PRINT(("ReplayRunArray(start = %ld)\n", *_start));
360 
361 	off_t logOffset = fVolume->ToBlock(fVolume->Log());
362 	off_t blockNumber = *_start % fLogSize;
363 	int32 blockSize = fVolume->BlockSize();
364 	int32 count = 1;
365 
366 	CachedBlock cachedArray(fVolume);
367 
368 	const run_array *array = (const run_array *)cachedArray.SetTo(logOffset + blockNumber);
369 	if (array == NULL)
370 		return B_IO_ERROR;
371 
372 	if (_CheckRunArray(array) < B_OK)
373 		return B_BAD_DATA;
374 
375 	blockNumber = (blockNumber + 1) % fLogSize;
376 
377 	CachedBlock cached(fVolume);
378 	for (int32 index = 0; index < array->CountRuns(); index++) {
379 		const block_run &run = array->RunAt(index);
380 		PRINT(("replay block run %lu:%u:%u in log at %Ld!\n", run.AllocationGroup(),
381 			run.Start(), run.Length(), blockNumber));
382 
383 		off_t offset = fVolume->ToOffset(run);
384 		for (int32 i = 0; i < run.Length(); i++) {
385 			const uint8 *data = cached.SetTo(logOffset + blockNumber);
386 			if (data == NULL)
387 				RETURN_ERROR(B_IO_ERROR);
388 
389 			ssize_t written = write_pos(fVolume->Device(),
390 				offset + (i * blockSize), data, blockSize);
391 			if (written != blockSize)
392 				RETURN_ERROR(B_IO_ERROR);
393 
394 			blockNumber = (blockNumber + 1) % fLogSize;
395 			count++;
396 		}
397 	}
398 
399 	*_start += count;
400 	return B_OK;
401 }
402 
403 
404 /**	Replays all log entries - this will put the disk into a
405  *	consistent and clean state, if it was not correctly unmounted
406  *	before.
407  *	This method is called by Journal::InitCheck() if the log start
408  *	and end pointer don't match.
409  */
410 
411 status_t
412 Journal::ReplayLog()
413 {
414 	INFORM(("Replay log, disk was not correctly unmounted...\n"));
415 
416 	int32 start = fVolume->LogStart();
417 	int32 lastStart = -1;
418 	while (true) {
419 		// stop if the log is completely flushed
420 		if (start == fVolume->LogEnd())
421 			break;
422 
423 		if (start == lastStart) {
424 			// strange, flushing the log hasn't changed the log_start pointer
425 			return B_ERROR;
426 		}
427 		lastStart = start;
428 
429 		status_t status = _ReplayRunArray(&start);
430 		if (status < B_OK) {
431 			FATAL(("replaying log entry from %ld failed: %s\n", start, strerror(status)));
432 			return B_ERROR;
433 		}
434 		start = start % fLogSize;
435 	}
436 
437 	PRINT(("replaying worked fine!\n"));
438 	fVolume->SuperBlock().log_start = fVolume->LogEnd();
439 	fVolume->LogStart() = fVolume->LogEnd();
440 	fVolume->SuperBlock().flags = SUPER_BLOCK_DISK_CLEAN;
441 
442 	return fVolume->WriteSuperBlock();
443 }
444 
445 
446 /**	This is a callback function that is called by the cache, whenever
447  *	a block is flushed to disk that was updated as part of a transaction.
448  *	This is necessary to keep track of completed transactions, to be
449  *	able to update the log start pointer.
450  */
451 
452 void
453 Journal::_blockNotify(int32 transactionID, void *arg)
454 {
455 	LogEntry *logEntry = (LogEntry *)arg;
456 
457 	PRINT(("Log entry %p has been finished, transaction ID = %ld\n", logEntry, transactionID));
458 
459 	Journal *journal = logEntry->GetJournal();
460 	disk_super_block &superBlock = journal->fVolume->SuperBlock();
461 	bool update = false;
462 
463 	// Set log_start pointer if possible...
464 
465 	journal->fEntriesLock.Lock();
466 
467 	if (logEntry == journal->fEntries.First()) {
468 		LogEntry *next = journal->fEntries.GetNext(logEntry);
469 		if (next != NULL) {
470 			int32 length = next->Start() - logEntry->Start();
471 				// log entries inbetween could have been already released, so
472 				// we can't just use LogEntry::Length() here
473 			superBlock.log_start = (superBlock.log_start + length) % journal->fLogSize;
474 		} else
475 			superBlock.log_start = journal->fVolume->LogEnd();
476 
477 		update = true;
478 	}
479 
480 	journal->fUsed -= logEntry->Length();
481 	journal->fEntries.Remove(logEntry);
482 	journal->fEntriesLock.Unlock();
483 
484 	delete logEntry;
485 
486 	// update the super block, and change the disk's state, if necessary
487 
488 	if (update) {
489 		journal->fVolume->LogStart() = superBlock.log_start;
490 
491 		if (superBlock.log_start == superBlock.log_end)
492 			superBlock.flags = SUPER_BLOCK_DISK_CLEAN;
493 
494 		status_t status = journal->fVolume->WriteSuperBlock();
495 		if (status != B_OK) {
496 			FATAL(("blockNotify: could not write back super block: %s\n",
497 				strerror(status)));
498 		}
499 	}
500 }
501 
502 
503 status_t
504 Journal::_WriteTransactionToLog()
505 {
506 	// ToDo: in case of a failure, we need a backup plan like writing all
507 	//	changed blocks back to disk immediately
508 
509 	fUnwrittenTransactions = 0;
510 
511 	int32 blockShift = fVolume->BlockShift();
512 	off_t logOffset = fVolume->ToBlock(fVolume->Log()) << blockShift;
513 	off_t logStart = fVolume->LogEnd();
514 	off_t logPosition = logStart % fLogSize;
515 	status_t status;
516 
517 	// create run_array structures for all changed blocks
518 
519 	RunArrays runArrays(this);
520 
521 	uint32 cookie = 0;
522 	off_t blockNumber;
523 	while (cache_next_block_in_transaction(fVolume->BlockCache(), fTransactionID,
524 			&cookie, &blockNumber, NULL, NULL) == B_OK) {
525 		status = runArrays.Insert(blockNumber);
526 		if (status < B_OK) {
527 			FATAL(("filling log entry failed!"));
528 			return status;
529 		}
530 	}
531 
532 	if (runArrays.Length() == 0) {
533 		// nothing has changed during this transaction
534 		cache_end_transaction(fVolume->BlockCache(), fTransactionID, NULL, NULL);
535 		return B_OK;
536 	}
537 
538 	// Make sure there is enough space in the log.
539 	// If that fails for whatever reason, panic!
540 	// ToDo:
541 /*	force_cache_flush(fVolume->Device(), false);
542 	int32 tries = fLogSize / 2 + 1;
543 	while (TransactionSize() > FreeLogBlocks() && tries-- > 0)
544 		force_cache_flush(fVolume->Device(), true);
545 
546 	if (tries <= 0) {
547 		fVolume->Panic();
548 		return B_BAD_DATA;
549 	}
550 */
551 
552 	// Write log entries to disk
553 
554 	int32 maxVecs = runArrays.MaxArrayLength();
555 
556 	iovec *vecs = (iovec *)malloc(sizeof(iovec) * maxVecs);
557 	if (vecs == NULL) {
558 		// ToDo: write back log entries directly?
559 		return B_NO_MEMORY;
560 	}
561 
562 	runArrays.PrepareForWriting();
563 
564 	for (int32 k = 0; k < runArrays.CountArrays(); k++) {
565 		run_array *array = runArrays.ArrayAt(k);
566 		int32 index = 0, count = 1;
567 		int32 wrap = fLogSize - logStart;
568 
569 		add_to_iovec(vecs, index, maxVecs, (void *)array, fVolume->BlockSize());
570 
571 		// add block runs
572 
573 		for (int32 i = 0; i < array->CountRuns(); i++) {
574 			const block_run &run = array->RunAt(i);
575 			off_t blockNumber = fVolume->ToBlock(run);
576 
577 			for (int32 j = 0; j < run.Length(); j++) {
578 				if (count >= wrap) {
579 					// we need to write back the first half of the entry directly
580 					logPosition = logStart + count;
581 					if (writev_pos(fVolume->Device(), logOffset
582 						+ (logStart << blockShift), vecs, index) < 0)
583 						FATAL(("could not write log area!\n"));
584 
585 					logStart = 0;
586 					wrap = fLogSize;
587 					count = 0;
588 					index = 0;
589 				}
590 
591 				// make blocks available in the cache
592 				const void *data;
593 				if (j == 0) {
594 					data = block_cache_get_etc(fVolume->BlockCache(), blockNumber,
595 						blockNumber, run.Length());
596 				} else
597 					data = block_cache_get(fVolume->BlockCache(), blockNumber + j);
598 
599 				if (data == NULL)
600 					return B_IO_ERROR;
601 
602 				add_to_iovec(vecs, index, maxVecs, data, fVolume->BlockSize());
603 				count++;
604 			}
605 		}
606 
607 		// write back log entry
608 		if (count > 0) {
609 			logPosition = logStart + count;
610 			if (writev_pos(fVolume->Device(), logOffset + (logStart << blockShift),
611 				vecs, index) < 0)
612 				FATAL(("could not write log area: %s!\n", strerror(errno)));
613 		}
614 
615 		// release blocks again
616 		for (int32 i = 0; i < array->CountRuns(); i++) {
617 			const block_run &run = array->RunAt(i);
618 			off_t blockNumber = fVolume->ToBlock(run);
619 
620 			for (int32 j = 0; j < run.Length(); j++) {
621 				block_cache_put(fVolume->BlockCache(), blockNumber + j);
622 			}
623 		}
624 	}
625 
626 	LogEntry *logEntry = new LogEntry(this, fVolume->LogEnd(), runArrays.Length());
627 	if (logEntry == NULL) {
628 		FATAL(("no memory to allocate log entries!"));
629 		return B_NO_MEMORY;
630 	}
631 
632 	// Update the log end pointer in the super block
633 
634 	fVolume->SuperBlock().flags = SUPER_BLOCK_DISK_DIRTY;
635 	fVolume->SuperBlock().log_end = logPosition;
636 	fVolume->LogEnd() = logPosition;
637 
638 	status = fVolume->WriteSuperBlock();
639 
640 	// We need to flush the drives own cache here to ensure
641 	// disk consistency.
642 	// If that call fails, we can't do anything about it anyway
643 	ioctl(fVolume->Device(), B_FLUSH_DRIVE_CACHE);
644 
645 	// at this point, we can finally end the transaction - we're in
646 	// a guaranteed valid state
647 
648 	fEntriesLock.Lock();
649 	fEntries.Add(logEntry);
650 	fUsed += logEntry->Length();
651 	fEntriesLock.Unlock();
652 
653 	cache_end_transaction(fVolume->BlockCache(), fTransactionID, _blockNotify, logEntry);
654 
655 	// If the log goes to the next round (the log is written as a
656 	// circular buffer), all blocks will be flushed out which is
657 	// possible because we don't have any locked blocks at this
658 	// point.
659 	if (logPosition < logStart)
660 		fVolume->FlushDevice();
661 
662 	return status;
663 }
664 
665 
666 status_t
667 Journal::FlushLogAndBlocks()
668 {
669 	status_t status = fLock.Lock();
670 	if (status != B_OK)
671 		return status;
672 
673 	if (fLock.OwnerCount() > 1) {
674 		// whoa, FlushLogAndBlocks() was called from inside a transaction
675 		fLock.Unlock();
676 		return B_OK;
677 	}
678 
679 	// write the current log entry to disk
680 
681 	if (fUnwrittenTransactions != 0 && _TransactionSize() != 0) {
682 		status = _WriteTransactionToLog();
683 		if (status < B_OK)
684 			FATAL(("writing current log entry failed: %s\n", strerror(status)));
685 	}
686 
687 	status = fVolume->FlushDevice();
688 
689 	fLock.Unlock();
690 	return status;
691 }
692 
693 
694 status_t
695 Journal::Lock(Transaction *owner)
696 {
697 	status_t status = fLock.Lock();
698 	if (status != B_OK)
699 		return status;
700 
701 /*	ToDo:
702 	// if the last transaction is older than 2 secs, start a new one
703 	if (fTransactionsInEntry != 0 && system_time() - fTimestamp > 2000000L)
704 		WriteLogEntry();
705 */
706 
707 	if (fLock.OwnerCount() > 1) {
708 		// we'll just use the current transaction again
709 		return B_OK;
710 	}
711 
712 	fOwner = owner;
713 
714 	// ToDo: we need a way to find out how big the current transaction is;
715 	//	we need to be able to either detach the latest sub transaction on
716 	//	demand, as well as having some kind of fall back plan in case the
717 	//	sub transaction itself grows bigger than the log.
718 	//	For that, it would be nice to have some call-back interface in the
719 	//	cache transaction API...
720 
721 	if (fUnwrittenTransactions > 0) {
722 		// start a sub transaction
723 		cache_start_sub_transaction(fVolume->BlockCache(), fTransactionID);
724 	} else
725 		fTransactionID = cache_start_transaction(fVolume->BlockCache());
726 
727 	if (fTransactionID < B_OK) {
728 		fLock.Unlock();
729 		return fTransactionID;
730 	}
731 
732 	return B_OK;
733 }
734 
735 
736 void
737 Journal::Unlock(Transaction *owner, bool success)
738 {
739 	if (fLock.OwnerCount() == 1) {
740 		// we only end the transaction if we would really unlock it
741 		// ToDo: what about failing transactions that do not unlock?
742 		_TransactionDone(success);
743 
744 		fTimestamp = system_time();
745 		fOwner = NULL;
746 	}
747 
748 	fLock.Unlock();
749 }
750 
751 
752 uint32
753 Journal::_TransactionSize() const
754 {
755 	int32 count = cache_blocks_in_transaction(fVolume->BlockCache(), fTransactionID);
756 	if (count < 0)
757 		return 0;
758 
759 	return count;
760 }
761 
762 
763 status_t
764 Journal::_TransactionDone(bool success)
765 {
766 	if (!success) {
767 		if (_HasSubTransaction())
768 			cache_abort_sub_transaction(fVolume->BlockCache(), fTransactionID);
769 		else
770 			cache_abort_transaction(fVolume->BlockCache(), fTransactionID);
771 
772 		return B_OK;
773 	}
774 
775 	// Up to a maximum size, we will just batch several
776 	// transactions together to improve speed
777 	if (_TransactionSize() < fMaxTransactionSize) {
778 		fUnwrittenTransactions++;
779 		return B_OK;
780 	}
781 
782 	return _WriteTransactionToLog();
783 }
784 
785 
786 status_t
787 Journal::LogBlocks(off_t blockNumber, const uint8 *buffer, size_t numBlocks)
788 {
789 	panic("LogBlocks() called!\n");
790 #if 0
791 	// ToDo: that's for now - we should change the log file size here
792 	if (TransactionSize() + numBlocks + 1 > fLogSize)
793 		return B_DEVICE_FULL;
794 
795 	int32 blockSize = fVolume->BlockSize();
796 
797 	for (;numBlocks-- > 0; blockNumber++, buffer += blockSize) {
798 		if (fArray.Find(blockNumber) >= 0) {
799 			// The block is already in the log, so just update its data
800 			// Note, this is only necessary if this method is called with a buffer
801 			// different from the cached block buffer - which is unlikely but
802 			// we'll make sure this way (costs one cache lookup, though).
803 			// ToDo:
804 /*			status_t status = cached_write(fVolume->Device(), blockNumber, buffer, 1, blockSize);
805 			if (status < B_OK)
806 				return status;
807 */
808 			continue;
809 		}
810 
811 		// Insert the block into the transaction's array, and write the changes
812 		// back into the locked cache buffer
813 		fArray.Insert(blockNumber);
814 
815 		// ToDo:
816 /*		status_t status = cached_write_locked(fVolume->Device(), blockNumber, buffer, 1, blockSize);
817 		if (status < B_OK)
818 			return status;
819 */	}
820 
821 	// ToDo:
822 	// If necessary, flush the log, so that we have enough space for this transaction
823 /*	if (TransactionSize() > FreeLogBlocks())
824 		force_cache_flush(fVolume->Device(), true);
825 */
826 #endif
827 	return B_OK;
828 }
829 
830 
831 //	#pragma mark -
832 
833 
834 status_t
835 Transaction::Start(Volume *volume, off_t refBlock)
836 {
837 	// has it already been started?
838 	if (fJournal != NULL)
839 		return B_OK;
840 
841 	fJournal = volume->GetJournal(refBlock);
842 	if (fJournal != NULL && fJournal->Lock(this) == B_OK)
843 		return B_OK;
844 
845 	fJournal = NULL;
846 	return B_ERROR;
847 }
848 
849