1/*
2 * Copyright 2010, Haiku Inc. All rights reserved.
3 * Copyright 2001-2010, Axel D��rfler, axeld@pinc-software.de.
4 * This file may be used under the terms of the MIT License.
5 *
6 * Authors:
7 *		Janito V. Ferreira Filho
8 */
9
10
11#include "Journal.h"
12
13#include <new>
14#include <string.h>
15#include <unistd.h>
16
17#include <fs_cache.h>
18
19#include "CachedBlock.h"
20#include "CRCTable.h"
21#include "HashRevokeManager.h"
22
23
24//#define TRACE_EXT2
25#ifdef TRACE_EXT2
26#	define TRACE(x...) dprintf("\33[34mext2:\33[0m " x)
27#else
28#	define TRACE(x...) ;
29#endif
30#define ERROR(x...) dprintf("\33[34mext2:\33[0m " x)
31#define WARN(x...) dprintf("\33[34mext2:\33[0m " x)
32
33
34class LogEntry : public DoublyLinkedListLinkImpl<LogEntry> {
35public:
36							LogEntry(Journal* journal, uint32 logStart,
37								uint32 length);
38							~LogEntry();
39
40			uint32			Start() const { return fStart; }
41			uint32			CommitID() const { return fCommitID; }
42
43			Journal*		GetJournal() { return fJournal; }
44
45private:
46			Journal*		fJournal;
47			uint32			fStart;
48			uint32			fCommitID;
49};
50
51
52LogEntry::LogEntry(Journal* journal, uint32 logStart, uint32 commitID)
53	:
54	fJournal(journal),
55	fStart(logStart),
56	fCommitID(commitID)
57{
58}
59
60
61LogEntry::~LogEntry()
62{
63}
64
65
66void
67JournalHeader::MakeDescriptor(uint32 sequence)
68{
69	this->magic = B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC);
70	this->sequence = B_HOST_TO_BENDIAN_INT32(sequence);
71	this->block_type = B_HOST_TO_BENDIAN_INT32(JOURNAL_DESCRIPTOR_BLOCK);
72}
73
74
75void
76JournalHeader::MakeCommit(uint32 sequence)
77{
78	this->magic = B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC);
79	this->sequence = B_HOST_TO_BENDIAN_INT32(sequence);
80	this->block_type = B_HOST_TO_BENDIAN_INT32(JOURNAL_COMMIT_BLOCK);
81}
82
83
84Journal::Journal(Volume* fsVolume, Volume* jVolume)
85	:
86	fJournalVolume(jVolume),
87	fJournalBlockCache(jVolume->BlockCache()),
88	fFilesystemVolume(fsVolume),
89	fFilesystemBlockCache(fsVolume->BlockCache()),
90	fRevokeManager(NULL),
91	fInitStatus(B_OK),
92	fBlockSize(sizeof(JournalSuperBlock)),
93	fFirstCommitID(0),
94	fFirstCacheCommitID(0),
95	fFirstLogBlock(1),
96	fLogSize(0),
97	fVersion(0),
98	fLogStart(0),
99	fLogEnd(0),
100	fFreeBlocks(0),
101	fMaxTransactionSize(0),
102	fCurrentCommitID(0),
103	fHasSubTransaction(false),
104	fSeparateSubTransactions(false),
105	fUnwrittenTransactions(0),
106	fTransactionID(0)
107{
108	recursive_lock_init(&fLock, "ext2 journal");
109	mutex_init(&fLogEntriesLock, "ext2 journal log entries");
110
111	HashRevokeManager* revokeManager = new(std::nothrow) HashRevokeManager(
112		fsVolume->Has64bitFeature());
113	TRACE("Journal::Journal(): Allocated a hash revoke manager at %p\n",
114		revokeManager);
115
116	if (revokeManager == NULL)
117		fInitStatus = B_NO_MEMORY;
118	else {
119		fInitStatus = revokeManager->Init();
120
121		if (fInitStatus == B_OK) {
122			fRevokeManager = revokeManager;
123			fInitStatus = _LoadSuperBlock();
124		} else
125			delete revokeManager;
126	}
127}
128
129
130Journal::Journal()
131	:
132	fJournalVolume(NULL),
133	fJournalBlockCache(NULL),
134	fFilesystemVolume(NULL),
135	fFilesystemBlockCache(NULL),
136	fOwner(NULL),
137	fRevokeManager(NULL),
138	fInitStatus(B_OK),
139	fBlockSize(sizeof(JournalSuperBlock)),
140	fFirstCommitID(0),
141	fFirstCacheCommitID(0),
142	fFirstLogBlock(1),
143	fLogSize(0),
144	fVersion(0),
145	fIsStarted(false),
146	fLogStart(0),
147	fLogEnd(0),
148	fFreeBlocks(0),
149	fMaxTransactionSize(0),
150	fCurrentCommitID(0),
151	fHasSubTransaction(false),
152	fSeparateSubTransactions(false),
153	fUnwrittenTransactions(0),
154	fTransactionID(0),
155	fChecksumEnabled(false),
156	fChecksumV3Enabled(false),
157	fFeature64bits(false)
158{
159	recursive_lock_init(&fLock, "ext2 journal");
160	mutex_init(&fLogEntriesLock, "ext2 journal log entries");
161}
162
163
164Journal::~Journal()
165{
166	TRACE("Journal destructor.\n");
167
168	TRACE("Journal::~Journal(): Attempting to delete revoke manager at %p\n",
169		fRevokeManager);
170	delete fRevokeManager;
171
172	recursive_lock_destroy(&fLock);
173	mutex_destroy(&fLogEntriesLock);
174}
175
176
177status_t
178Journal::InitCheck()
179{
180	return fInitStatus;
181}
182
183
184status_t
185Journal::Uninit()
186{
187	if (!fIsStarted)
188		return B_OK;
189
190	status_t status = FlushLogAndBlocks();
191
192	if (status == B_OK) {
193		// Mark journal as clean
194		fLogStart = 0;
195		status = _SaveSuperBlock();
196	}
197
198	fIsStarted = false;
199
200	return status;
201}
202
203
204/*virtual*/ status_t
205Journal::StartLog()
206{
207	fLogStart = fFirstLogBlock;
208	fLogEnd = fFirstLogBlock;
209	fFreeBlocks = 0;
210	fIsStarted = true;
211
212	fCurrentCommitID = fFirstCommitID;
213
214	return _SaveSuperBlock();
215}
216
217
218status_t
219Journal::RestartLog()
220{
221	fFirstCommitID = 1;
222
223	return B_OK;
224}
225
226
227/*virtual*/ status_t
228Journal::Lock(Transaction* owner, bool separateSubTransactions)
229{
230	TRACE("Journal::Lock()\n");
231	status_t status = recursive_lock_lock(&fLock);
232	if (status != B_OK)
233		return status;
234
235	TRACE("Journal::Lock(): Aquired lock\n");
236
237	if (!fSeparateSubTransactions && recursive_lock_get_recursion(&fLock) > 1) {
238		// reuse current transaction
239		TRACE("Journal::Lock(): Reusing current transaction\n");
240		return B_OK;
241	}
242
243	if (separateSubTransactions)
244		fSeparateSubTransactions = true;
245
246	if (owner != NULL)
247		owner->SetParent(fOwner);
248
249	fOwner = owner;
250
251	if (fOwner != NULL) {
252		if (fUnwrittenTransactions > 0) {
253			// start a sub transaction
254			TRACE("Journal::Lock(): Starting sub transaction\n");
255			cache_start_sub_transaction(fFilesystemBlockCache, fTransactionID);
256			fHasSubTransaction = true;
257		} else {
258			TRACE("Journal::Lock(): Starting new transaction\n");
259			fTransactionID = cache_start_transaction(fFilesystemBlockCache);
260		}
261
262		if (fTransactionID < B_OK) {
263			recursive_lock_unlock(&fLock);
264			return fTransactionID;
265		}
266
267		cache_add_transaction_listener(fFilesystemBlockCache, fTransactionID,
268			TRANSACTION_IDLE, _TransactionIdle, this);
269	}
270
271	return B_OK;
272}
273
274
275/*virtual*/ status_t
276Journal::Unlock(Transaction* owner, bool success)
277{
278	TRACE("Journal::Unlock(): Lock recursion: %" B_PRId32 "\n",
279		recursive_lock_get_recursion(&fLock));
280	if (fSeparateSubTransactions
281		|| recursive_lock_get_recursion(&fLock) == 1) {
282		// we only end the transaction if we unlock it
283		if (owner != NULL) {
284			TRACE("Journal::Unlock(): Calling _TransactionDone\n");
285			status_t status = _TransactionDone(success);
286			if (status != B_OK)
287				return status;
288
289			TRACE("Journal::Unlock(): Returned from _TransactionDone\n");
290			bool separateSubTransactions = fSeparateSubTransactions;
291			fSeparateSubTransactions = true;
292			TRACE("Journal::Unlock(): Notifying listeners for: %p\n", owner);
293			owner->NotifyListeners(success);
294			TRACE("Journal::Unlock(): Done notifying listeners\n");
295			fSeparateSubTransactions = separateSubTransactions;
296
297			fOwner = owner->Parent();
298		} else
299			fOwner = NULL;
300
301		if (fSeparateSubTransactions
302			&& recursive_lock_get_recursion(&fLock) == 1)
303			fSeparateSubTransactions = false;
304	} else
305		owner->MoveListenersTo(fOwner);
306
307	TRACE("Journal::Unlock(): Unlocking the lock\n");
308
309	recursive_lock_unlock(&fLock);
310	return B_OK;
311}
312
313
314status_t
315Journal::MapBlock(off_t logical, fsblock_t& physical)
316{
317	TRACE("Journal::MapBlock()\n");
318	physical = logical;
319
320	return B_OK;
321}
322
323
324inline uint32
325Journal::FreeLogBlocks() const
326{
327	TRACE("Journal::FreeLogBlocks(): start: %" B_PRIu32 ", end: %" B_PRIu32
328		", size: %" B_PRIu32 "\n", fLogStart, fLogEnd, fLogSize);
329	return fLogStart <= fLogEnd
330		? fLogSize - fLogEnd + fLogStart - 1
331		: fLogStart - fLogEnd;
332}
333
334
335status_t
336Journal::FlushLogAndBlocks()
337{
338	return _FlushLog(true, true);
339}
340
341
342int32
343Journal::TransactionID() const
344{
345	return fTransactionID;
346}
347
348
349status_t
350Journal::_WritePartialTransactionToLog(JournalHeader* descriptorBlock,
351	bool detached, uint8** _escapedData, uint32 &logBlock, off_t& blockNumber,
352	long& cookie, ArrayDeleter<uint8>& escapedDataDeleter, uint32& blockCount,
353	bool& finished)
354{
355	TRACE("Journal::_WritePartialTransactionToLog()\n");
356
357	uint32 descriptorBlockPos = logBlock;
358	uint8* escapedData = *_escapedData;
359
360	JournalBlockTag* tag = (JournalBlockTag*)descriptorBlock->data;
361	JournalBlockTag* lastTag = (JournalBlockTag*)((uint8*)descriptorBlock
362		+ fBlockSize - sizeof(JournalHeader));
363
364	finished = false;
365	status_t status = B_OK;
366
367	while (tag < lastTag && status == B_OK) {
368		tag->SetBlockNumber(blockNumber);
369		tag->SetFlags(0);
370
371		CachedBlock data(fFilesystemVolume);
372		const JournalHeader* blockData = (JournalHeader*)data.SetTo(
373			blockNumber);
374		if (blockData == NULL) {
375			panic("Got a NULL pointer while iterating through transaction "
376				"blocks.\n");
377			return B_ERROR;
378		}
379
380		void* finalData;
381
382		if (blockData->CheckMagic()) {
383			// The journaled block starts with the magic value
384			// We must remove it to prevent confusion
385			TRACE("Journal::_WritePartialTransactionToLog(): Block starts with "
386				"magic number. Escaping it\n");
387			tag->SetEscapedFlag();
388
389			if (escapedData == NULL) {
390				TRACE("Journal::_WritePartialTransactionToLog(): Allocating "
391					"space for escaped block (%" B_PRIu32 ")\n", fBlockSize);
392				escapedData = new(std::nothrow) uint8[fBlockSize];
393				if (escapedData == NULL) {
394					TRACE("Journal::_WritePartialTransactionToLof(): Failed to "
395						"allocate buffer for escaped data block\n");
396					return B_NO_MEMORY;
397				}
398				escapedDataDeleter.SetTo(escapedData);
399				*_escapedData = escapedData;
400
401				((int32*)escapedData)[0] = 0; // Remove magic
402			}
403
404			memcpy(escapedData + 4, blockData->data, fBlockSize - 4);
405			finalData = escapedData;
406		} else
407			finalData = (void*)blockData;
408
409		// TODO: use iovecs?
410
411		logBlock = _WrapAroundLog(logBlock + 1);
412
413		fsblock_t physicalBlock;
414		status = MapBlock(logBlock, physicalBlock);
415		if (status != B_OK)
416			return status;
417
418		off_t logOffset = physicalBlock * fBlockSize;
419
420		TRACE("Journal::_WritePartialTransactionToLog(): Writing from memory: "
421			"%p, to disk: %" B_PRIdOFF "\n", finalData, logOffset);
422		size_t written = write_pos(fJournalVolume->Device(), logOffset,
423			finalData, fBlockSize);
424		if (written != fBlockSize) {
425			TRACE("Failed to write journal block.\n");
426			return B_IO_ERROR;
427		}
428
429		TRACE("Journal::_WritePartialTransactionToLog(): Wrote a journal block "
430			"at: %" B_PRIu32 "\n", logBlock);
431
432		blockCount++;
433		tag++;
434
435		status = cache_next_block_in_transaction(fFilesystemBlockCache,
436			fTransactionID, detached, &cookie, &blockNumber, NULL, NULL);
437	}
438
439	finished = status != B_OK;
440
441	// Write descriptor block
442	--tag;
443	tag->SetLastTagFlag();
444
445	fsblock_t physicalBlock;
446	status = MapBlock(descriptorBlockPos, physicalBlock);
447	if (status != B_OK)
448		return status;
449
450	off_t descriptorBlockOffset = physicalBlock * fBlockSize;
451
452	TRACE("Journal::_WritePartialTransactionToLog(): Writing to: %" B_PRIdOFF
453		"\n", descriptorBlockOffset);
454	size_t written = write_pos(fJournalVolume->Device(),
455		descriptorBlockOffset, descriptorBlock, fBlockSize);
456	if (written != fBlockSize) {
457		TRACE("Failed to write journal descriptor block.\n");
458		return B_IO_ERROR;
459	}
460
461	blockCount++;
462	logBlock = _WrapAroundLog(logBlock + 1);
463
464	return B_OK;
465}
466
467
468status_t
469Journal::_WriteTransactionToLog()
470{
471	TRACE("Journal::_WriteTransactionToLog()\n");
472	// Transaction enters the Flush state
473	bool detached = false;
474	TRACE("Journal::_WriteTransactionToLog(): Attempting to get transaction "
475		"size\n");
476	size_t size = _FullTransactionSize();
477	TRACE("Journal::_WriteTransactionToLog(): transaction size: %" B_PRIuSIZE
478		"\n", size);
479
480	if (size > fMaxTransactionSize) {
481		TRACE("Journal::_WriteTransactionToLog(): not enough free space "
482			"for the transaction. Attempting to free some space.\n");
483		size = _MainTransactionSize();
484		TRACE("Journal::_WriteTransactionToLog(): main transaction size: %"
485			B_PRIuSIZE "\n", size);
486
487		if (fHasSubTransaction && size < fMaxTransactionSize) {
488			TRACE("Journal::_WriteTransactionToLog(): transaction doesn't fit, "
489				"but it can be separated\n");
490			detached = true;
491		} else {
492			// Error: transaction can't fit in log
493			panic("transaction too large (size: %" B_PRIuSIZE ", max size: %"
494				B_PRIu32 ", log size: %" B_PRIu32 ")\n", size,
495				fMaxTransactionSize, fLogSize);
496			return B_BUFFER_OVERFLOW;
497		}
498	}
499
500	TRACE("Journal::_WriteTransactionToLog(): free log blocks: %" B_PRIu32
501		"\n", FreeLogBlocks());
502	if (size > FreeLogBlocks()) {
503		TRACE("Journal::_WriteTransactionToLog(): Syncing block cache\n");
504		cache_sync_transaction(fFilesystemBlockCache, fTransactionID);
505
506		if (size > FreeLogBlocks()) {
507			panic("Transaction fits, but sync didn't result in enough"
508				"free space.\n\tGot %" B_PRIu32 " when at least %" B_PRIuSIZE
509				" was expected.", FreeLogBlocks(), size);
510		}
511	}
512
513	TRACE("Journal::_WriteTransactionToLog(): finished managing space for "
514		"the transaction\n");
515
516	fHasSubTransaction = false;
517	if (!fIsStarted)
518		StartLog();
519
520	// Prepare Descriptor block
521	TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for "
522		"the descriptor block, block size %" B_PRIu32 "\n", fBlockSize);
523	JournalHeader* descriptorBlock =
524		(JournalHeader*)new(std::nothrow) uint8[fBlockSize];
525	if (descriptorBlock == NULL) {
526		TRACE("Journal::_WriteTransactionToLog(): Failed to allocate a buffer "
527			"for the descriptor block\n");
528		return B_NO_MEMORY;
529	}
530	ArrayDeleter<uint8> descriptorBlockDeleter((uint8*)descriptorBlock);
531
532	descriptorBlock->MakeDescriptor(fCurrentCommitID);
533
534	// Prepare Commit block
535	TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for "
536		"the commit block, block size %" B_PRIu32 "\n", fBlockSize);
537	JournalHeader* commitBlock =
538		(JournalHeader*)new(std::nothrow) uint8[fBlockSize];
539	if (commitBlock == NULL) {
540		TRACE("Journal::_WriteTransactionToLog(): Failed to allocate a buffer "
541			"for the commit block\n");
542		return B_NO_MEMORY;
543	}
544	ArrayDeleter<uint8> commitBlockDeleter((uint8*)commitBlock);
545
546	commitBlock->MakeCommit(fCurrentCommitID + 1);
547	memset(commitBlock->data, 0, fBlockSize - sizeof(JournalHeader));
548		// TODO: This probably isn't necessary
549
550	uint8* escapedData = NULL;
551	ArrayDeleter<uint8> escapedDataDeleter;
552
553	off_t blockNumber;
554	long cookie = 0;
555
556	status_t status = cache_next_block_in_transaction(fFilesystemBlockCache,
557		fTransactionID, detached, &cookie, &blockNumber, NULL, NULL);
558	if (status != B_OK) {
559		TRACE("Journal::_WriteTransactionToLog(): Transaction has no blocks to "
560			"write\n");
561		return B_OK;
562	}
563
564	uint32 blockCount = 0;
565
566	uint32 logBlock = _WrapAroundLog(fLogEnd);
567
568	bool finished = false;
569
570	status = _WritePartialTransactionToLog(descriptorBlock, detached,
571		&escapedData, logBlock, blockNumber, cookie, escapedDataDeleter,
572		blockCount, finished);
573	if (!finished && status != B_OK)
574		return status;
575
576	uint32 commitBlockPos = logBlock;
577
578	while (!finished) {
579		descriptorBlock->IncrementSequence();
580
581		status = _WritePartialTransactionToLog(descriptorBlock, detached,
582			&escapedData, logBlock, blockNumber, cookie, escapedDataDeleter,
583			blockCount, finished);
584		if (!finished && status != B_OK)
585			return status;
586
587		// It is okay to write the commit blocks of the partial transactions
588		// as long as the commit block of the first partial transaction isn't
589		// written. When it recovery reaches where the first commit should be
590		// and doesn't find it, it considers it found the end of the log.
591
592		fsblock_t physicalBlock;
593		status = MapBlock(logBlock, physicalBlock);
594		if (status != B_OK)
595			return status;
596
597		off_t logOffset = physicalBlock * fBlockSize;
598
599		TRACE("Journal::_WriteTransactionToLog(): Writting commit block to "
600			"%" B_PRIdOFF "\n", logOffset);
601		off_t written = write_pos(fJournalVolume->Device(), logOffset,
602			commitBlock, fBlockSize);
603		if (written != fBlockSize) {
604			TRACE("Failed to write journal commit block.\n");
605			return B_IO_ERROR;
606		}
607
608		commitBlock->IncrementSequence();
609		blockCount++;
610
611		logBlock = _WrapAroundLog(logBlock + 1);
612	}
613
614	// Transaction will enter the Commit state
615	fsblock_t physicalBlock;
616	status = MapBlock(commitBlockPos, physicalBlock);
617	if (status != B_OK)
618		return status;
619
620	off_t logOffset = physicalBlock * fBlockSize;
621
622	TRACE("Journal::_WriteTransactionToLog(): Writing to: %" B_PRIdOFF "\n",
623		logOffset);
624	off_t written = write_pos(fJournalVolume->Device(), logOffset, commitBlock,
625		fBlockSize);
626	if (written != fBlockSize) {
627		TRACE("Failed to write journal commit block.\n");
628		return B_IO_ERROR;
629	}
630
631	blockCount++;
632	fLogEnd = _WrapAroundLog(fLogEnd + blockCount);
633
634	status = _SaveSuperBlock();
635
636	// Transaction will enter Finished state
637	LogEntry *logEntry = new LogEntry(this, fLogEnd, fCurrentCommitID++);
638	TRACE("Journal::_WriteTransactionToLog(): Allocating log entry at %p\n",
639		logEntry);
640	if (logEntry == NULL) {
641		panic("no memory to allocate log entries!");
642		return B_NO_MEMORY;
643	}
644
645	mutex_lock(&fLogEntriesLock);
646	fLogEntries.Add(logEntry);
647	mutex_unlock(&fLogEntriesLock);
648
649	if (detached) {
650		fTransactionID = cache_detach_sub_transaction(fFilesystemBlockCache,
651			fTransactionID, _TransactionWritten, logEntry);
652		fUnwrittenTransactions = 1;
653
654		if (status == B_OK && _FullTransactionSize() > fLogSize) {
655			// If the transaction is too large after writing, there is no way to
656			// recover, so let this transaction fail.
657			ERROR("transaction too large (%" B_PRIuSIZE " blocks, log size %"
658				B_PRIu32 ")!\n", _FullTransactionSize(), fLogSize);
659			return B_BUFFER_OVERFLOW;
660		}
661	} else {
662		cache_end_transaction(fFilesystemBlockCache, fTransactionID,
663			_TransactionWritten, logEntry);
664		fUnwrittenTransactions = 0;
665	}
666
667	return B_OK;
668}
669
670
671status_t
672Journal::_SaveSuperBlock()
673{
674	TRACE("Journal::_SaveSuperBlock()\n");
675	fsblock_t physicalBlock;
676	status_t status = MapBlock(0, physicalBlock);
677	if (status != B_OK)
678		return status;
679
680	off_t superblockPos = physicalBlock * fBlockSize;
681
682	JournalSuperBlock superblock;
683	size_t bytesRead = read_pos(fJournalVolume->Device(), superblockPos,
684		&superblock, sizeof(superblock));
685
686	if (bytesRead != sizeof(superblock))
687		return B_IO_ERROR;
688
689	superblock.SetFirstCommitID(fFirstCommitID);
690	superblock.SetLogStart(fLogStart);
691
692	if (fChecksumEnabled)
693		superblock.SetChecksum(_Checksum(&superblock));
694
695	TRACE("Journal::SaveSuperBlock(): Write to %" B_PRIdOFF "\n",
696		superblockPos);
697	size_t bytesWritten = write_pos(fJournalVolume->Device(), superblockPos,
698		&superblock, sizeof(superblock));
699
700	if (bytesWritten != sizeof(superblock))
701		return B_IO_ERROR;
702
703	TRACE("Journal::_SaveSuperBlock(): Done\n");
704
705	return B_OK;
706}
707
708
709status_t
710Journal::_LoadSuperBlock()
711{
712	STATIC_ASSERT(sizeof(struct JournalHeader) == 12);
713	STATIC_ASSERT(sizeof(struct JournalSuperBlock) == 1024);
714
715	TRACE("Journal::_LoadSuperBlock()\n");
716	fsblock_t superblockPos;
717
718	status_t status = MapBlock(0, superblockPos);
719	if (status != B_OK)
720		return status;
721
722	TRACE("Journal::_LoadSuperBlock(): superblock physical block: %" B_PRIu64
723		"\n", superblockPos);
724
725	JournalSuperBlock superblock;
726	size_t bytesRead = read_pos(fJournalVolume->Device(), superblockPos
727		* fJournalVolume->BlockSize(), &superblock, sizeof(superblock));
728
729	if (bytesRead != sizeof(superblock)) {
730		ERROR("Journal::_LoadSuperBlock(): failed to read superblock\n");
731		return B_IO_ERROR;
732	}
733
734	if (!superblock.header.CheckMagic()) {
735		ERROR("Journal::_LoadSuperBlock(): Invalid superblock magic %" B_PRIx32
736			"\n", superblock.header.Magic());
737		return B_BAD_VALUE;
738	}
739
740	if (superblock.header.BlockType() == JOURNAL_SUPERBLOCK_V1) {
741		TRACE("Journal::_LoadSuperBlock(): Journal superblock version 1\n");
742		fVersion = 1;
743	} else if (superblock.header.BlockType() == JOURNAL_SUPERBLOCK_V2) {
744		TRACE("Journal::_LoadSuperBlock(): Journal superblock version 2\n");
745		fVersion = 2;
746	} else {
747		ERROR("Journal::_LoadSuperBlock(): Invalid superblock version\n");
748		return B_BAD_VALUE;
749	}
750
751	if (fVersion >= 2) {
752		TRACE("Journal::_LoadSuperBlock(): incompatible features %" B_PRIx32
753			", read-only features %" B_PRIx32 "\n",
754			superblock.IncompatibleFeatures(),
755			superblock.ReadOnlyCompatibleFeatures());
756
757		status = _CheckFeatures(&superblock);
758
759		if (status != B_OK)
760			return status;
761
762		if (fChecksumEnabled) {
763			if (superblock.Checksum() != _Checksum(&superblock)) {
764				ERROR("Journal::_LoadSuperBlock(): Invalid checksum\n");
765				return B_BAD_DATA;
766			}
767			fChecksumSeed = calculate_crc32c(0xffffffff, (uint8*)superblock.uuid,
768				sizeof(superblock.uuid));
769		}
770	}
771
772	fBlockSize = superblock.BlockSize();
773	fFirstCommitID = superblock.FirstCommitID();
774	fFirstLogBlock = superblock.FirstLogBlock();
775	fLogStart = superblock.LogStart();
776	fLogSize = superblock.NumBlocks();
777
778	uint32 descriptorTags = (fBlockSize - sizeof(JournalHeader))
779		/ sizeof(JournalBlockTag);
780		// Maximum tags per descriptor block
781	uint32 maxDescriptors = (fLogSize - 1) / (descriptorTags + 2);
782		// Maximum number of full journal transactions
783	fMaxTransactionSize = maxDescriptors * descriptorTags;
784	fMaxTransactionSize += (fLogSize - 1) - fMaxTransactionSize - 2;
785		// Maximum size of a "logical" transaction
786		// TODO: Why is "superblock.MaxTransactionBlocks();" zero?
787	//fFirstCacheCommitID = fFirstCommitID - fTransactionID /*+ 1*/;
788
789	TRACE("Journal::_LoadSuperBlock(): block size: %" B_PRIu32 ", first commit"
790		" id: %" B_PRIu32 ", first log block: %" B_PRIu32 ", log start: %"
791		B_PRIu32 ", log size: %" B_PRIu32 ", max transaction size: %" B_PRIu32
792		"\n", fBlockSize, fFirstCommitID, fFirstLogBlock, fLogStart,
793		fLogSize, fMaxTransactionSize);
794
795	return B_OK;
796}
797
798
799status_t
800Journal::_CheckFeatures(JournalSuperBlock* superblock)
801{
802	uint32 readonly = superblock->ReadOnlyCompatibleFeatures();
803	uint32 incompatible = superblock->IncompatibleFeatures();
804	bool hasReadonly = (readonly & ~JOURNAL_KNOWN_READ_ONLY_COMPATIBLE_FEATURES)
805		!= 0;
806	bool hasIncompatible = (incompatible
807		& ~JOURNAL_KNOWN_INCOMPATIBLE_FEATURES) != 0;
808	if (hasReadonly || hasIncompatible ) {
809		ERROR("Journal::_CheckFeatures(): Unsupported features: %" B_PRIx32
810			" %" B_PRIx32 "\n", readonly, incompatible);
811		return B_UNSUPPORTED;
812	}
813
814	bool hasCsumV2 =
815		(superblock->IncompatibleFeatures() & JOURNAL_FEATURE_INCOMPATIBLE_CSUM_V2) != 0;
816	bool hasCsumV3 =
817		(superblock->IncompatibleFeatures() & JOURNAL_FEATURE_INCOMPATIBLE_CSUM_V3) != 0;
818	if (hasCsumV2 && hasCsumV3) {
819		return B_BAD_VALUE;
820	}
821
822	fChecksumEnabled = hasCsumV2 && hasCsumV3;
823	fChecksumV3Enabled = hasCsumV3;
824	fFeature64bits =
825		(superblock->IncompatibleFeatures() & JOURNAL_FEATURE_INCOMPATIBLE_64BIT) != 0;
826	return B_OK;
827}
828
829
830uint32
831Journal::_Checksum(JournalSuperBlock* superblock)
832{
833	uint32 oldChecksum = superblock->checksum;
834	superblock->checksum = 0;
835	uint32 checksum = calculate_crc32c(0xffffffff, (uint8*)superblock,
836		sizeof(JournalSuperBlock));
837	superblock->checksum = oldChecksum;
838	return checksum;
839}
840
841
842bool
843Journal::_Checksum(uint8* block, bool set)
844{
845	JournalBlockTail *tail = (JournalBlockTail*)(block + fBlockSize
846		- sizeof(JournalBlockTail));
847	uint32 oldChecksum = tail->checksum;
848	tail->checksum = 0;
849	uint32 checksum = calculate_crc32c(0xffffffff, block, fBlockSize);
850	if (set) {
851		tail->checksum = checksum;
852	} else {
853		tail->checksum = oldChecksum;
854	}
855	return checksum == oldChecksum;
856}
857
858
859uint32
860Journal::_CountTags(JournalHeader* descriptorBlock)
861{
862	uint32 count = 0;
863	size_t tagSize = _TagSize();
864	size_t size = fBlockSize;
865
866	if (fChecksumEnabled)
867		size -= sizeof(JournalBlockTail);
868
869	JournalBlockTag* tags = (JournalBlockTag*)descriptorBlock->data;
870		// Skip the header
871	JournalBlockTag* lastTag = (JournalBlockTag*)
872		(descriptorBlock + size - tagSize);
873
874	while (tags < lastTag && (tags->Flags() & JOURNAL_FLAG_LAST_TAG) == 0) {
875		if ((tags->Flags() & JOURNAL_FLAG_SAME_UUID) == 0)
876			tags = (JournalBlockTag*)((uint8*)tags + 16); // Skip new UUID
877
878		TRACE("Journal::_CountTags(): Tag block: %" B_PRIu32 "\n",
879			tags->BlockNumber());
880
881		tags = (JournalBlockTag*)((uint8*)tags + tagSize); // Go to next tag
882		count++;
883	}
884
885	if ((tags->Flags() & JOURNAL_FLAG_LAST_TAG) != 0)
886		count++;
887
888	TRACE("Journal::_CountTags(): counted tags: %" B_PRIu32 "\n", count);
889
890	return count;
891}
892
893
894size_t
895Journal::_TagSize()
896{
897	if (fChecksumV3Enabled)
898		return sizeof(JournalBlockTagV3);
899
900	size_t size = sizeof(JournalBlockTag);
901	if (fChecksumEnabled)
902		size += sizeof(uint16);
903	if (!fFeature64bits)
904		size -= sizeof(uint32);
905	return size;
906}
907
908
909/*virtual*/ status_t
910Journal::Recover()
911{
912	TRACE("Journal::Recover()\n");
913	if (fLogStart == 0) // Journal was cleanly unmounted
914		return B_OK;
915
916	TRACE("Journal::Recover(): Journal needs recovery\n");
917
918	uint32 lastCommitID;
919
920	status_t status = _RecoverPassScan(lastCommitID);
921	if (status != B_OK)
922		return status;
923
924	status = _RecoverPassRevoke(lastCommitID);
925	if (status != B_OK)
926		return status;
927
928	return _RecoverPassReplay(lastCommitID);
929}
930
931
932// First pass: Find the end of the log
933status_t
934Journal::_RecoverPassScan(uint32& lastCommitID)
935{
936	TRACE("Journal Recover: 1st Pass: Scan\n");
937
938	CachedBlock cached(fJournalVolume);
939	JournalHeader* header;
940	uint32 nextCommitID = fFirstCommitID;
941	uint32 nextBlock = fLogStart;
942	fsblock_t nextBlockPos;
943
944	status_t status = MapBlock(nextBlock, nextBlockPos);
945	if (status != B_OK)
946		return status;
947
948	header = (JournalHeader*)cached.SetTo(nextBlockPos);
949
950	while (header->CheckMagic() && header->Sequence() == nextCommitID) {
951		uint32 blockType = header->BlockType();
952
953		if (blockType == JOURNAL_DESCRIPTOR_BLOCK) {
954			if (fChecksumEnabled && !_Checksum((uint8*)header, false)) {
955				ERROR("Journal::_RecoverPassScan(): Invalid checksum\n");
956				return B_BAD_DATA;
957			}
958			uint32 tags = _CountTags(header);
959			nextBlock += tags;
960			TRACE("Journal recover pass scan: Found a descriptor block with "
961				"%" B_PRIu32 " tags\n", tags);
962		} else if (blockType == JOURNAL_COMMIT_BLOCK) {
963			nextCommitID++;
964			TRACE("Journal recover pass scan: Found a commit block. Next "
965				"commit ID: %" B_PRIu32 "\n", nextCommitID);
966		} else if (blockType != JOURNAL_REVOKE_BLOCK) {
967			TRACE("Journal recover pass scan: Reached an unrecognized block, "
968				"assuming as log's end.\n");
969			break;
970		} else {
971			TRACE("Journal recover pass scan: Found a revoke block, "
972				"skipping it\n");
973		}
974
975		nextBlock = _WrapAroundLog(nextBlock + 1);
976
977		status = MapBlock(nextBlock, nextBlockPos);
978		if (status != B_OK)
979			return status;
980
981		header = (JournalHeader*)cached.SetTo(nextBlockPos);
982	}
983
984	TRACE("Journal Recovery pass scan: Last detected transaction ID: %"
985		B_PRIu32 "\n", nextCommitID);
986
987	lastCommitID = nextCommitID;
988	return B_OK;
989}
990
991
992// Second pass: Collect all revoked blocks
993status_t
994Journal::_RecoverPassRevoke(uint32 lastCommitID)
995{
996	TRACE("Journal Recover: 2nd Pass: Revoke\n");
997
998	CachedBlock cached(fJournalVolume);
999	JournalHeader* header;
1000	uint32 nextCommitID = fFirstCommitID;
1001	uint32 nextBlock = fLogStart;
1002	fsblock_t nextBlockPos;
1003
1004	status_t status = MapBlock(nextBlock, nextBlockPos);
1005	if (status != B_OK)
1006		return status;
1007
1008	header = (JournalHeader*)cached.SetTo(nextBlockPos);
1009
1010	while (nextCommitID < lastCommitID) {
1011		if (!header->CheckMagic() || header->Sequence() != nextCommitID) {
1012			// Somehow the log is different than the expexted
1013			return B_ERROR;
1014		}
1015
1016		uint32 blockType = header->BlockType();
1017
1018		if (blockType == JOURNAL_DESCRIPTOR_BLOCK)
1019			nextBlock += _CountTags(header);
1020		else if (blockType == JOURNAL_COMMIT_BLOCK)
1021			nextCommitID++;
1022		else if (blockType == JOURNAL_REVOKE_BLOCK) {
1023			TRACE("Journal::_RecoverPassRevoke(): Found a revoke block\n");
1024			status = fRevokeManager->ScanRevokeBlock(
1025				(JournalRevokeHeader*)header, nextCommitID);
1026
1027			if (status != B_OK)
1028				return status;
1029		} else {
1030			WARN("Journal::_RecoverPassRevoke(): Found an unrecognized block\n");
1031			break;
1032		}
1033
1034		nextBlock = _WrapAroundLog(nextBlock + 1);
1035
1036		status = MapBlock(nextBlock, nextBlockPos);
1037		if (status != B_OK)
1038			return status;
1039
1040		header = (JournalHeader*)cached.SetTo(nextBlockPos);
1041	}
1042
1043	if (nextCommitID != lastCommitID) {
1044		// Possibly because of some sort of IO error
1045		TRACE("Journal::_RecoverPassRevoke(): Incompatible commit IDs\n");
1046		return B_ERROR;
1047	}
1048
1049	TRACE("Journal recovery pass revoke: Revoked blocks: %" B_PRIu32 "\n",
1050		fRevokeManager->NumRevokes());
1051
1052	return B_OK;
1053}
1054
1055
1056// Third pass: Replay log
1057status_t
1058Journal::_RecoverPassReplay(uint32 lastCommitID)
1059{
1060	TRACE("Journal Recover: 3rd Pass: Replay\n");
1061
1062	uint32 nextCommitID = fFirstCommitID;
1063	uint32 nextBlock = fLogStart;
1064	fsblock_t nextBlockPos;
1065
1066	status_t status = MapBlock(nextBlock, nextBlockPos);
1067	if (status != B_OK)
1068		return status;
1069
1070	CachedBlock cached(fJournalVolume);
1071	JournalHeader* header = (JournalHeader*)cached.SetTo(nextBlockPos);
1072
1073	int count = 0;
1074
1075	uint8* data = new(std::nothrow) uint8[fBlockSize];
1076	if (data == NULL) {
1077		TRACE("Journal::_RecoverPassReplay(): Failed to allocate memory for "
1078			"data\n");
1079		return B_NO_MEMORY;
1080	}
1081
1082	ArrayDeleter<uint8> dataDeleter(data);
1083
1084	while (nextCommitID < lastCommitID) {
1085		if (!header->CheckMagic() || header->Sequence() != nextCommitID) {
1086			// Somehow the log is different than the expected
1087			ERROR("Journal::_RecoverPassReplay(): Weird problem with block\n");
1088			return B_ERROR;
1089		}
1090
1091		uint32 blockType = header->BlockType();
1092
1093		if (blockType == JOURNAL_DESCRIPTOR_BLOCK) {
1094			JournalBlockTag* last_tag = (JournalBlockTag*)((uint8*)header
1095				+ fBlockSize - sizeof(JournalBlockTag));
1096
1097			for (JournalBlockTag* tag = (JournalBlockTag*)header->data;
1098				tag <= last_tag; ++tag) {
1099				nextBlock = _WrapAroundLog(nextBlock + 1);
1100
1101				status = MapBlock(nextBlock, nextBlockPos);
1102				if (status != B_OK)
1103					return status;
1104
1105				if (!fRevokeManager->Lookup(tag->BlockNumber(),
1106						nextCommitID)) {
1107					// Block isn't revoked
1108					size_t read = read_pos(fJournalVolume->Device(),
1109						nextBlockPos * fBlockSize, data, fBlockSize);
1110					if (read != fBlockSize)
1111						return B_IO_ERROR;
1112
1113					if ((tag->Flags() & JOURNAL_FLAG_ESCAPED) != 0) {
1114						// Block is escaped
1115						((int32*)data)[0]
1116							= B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC);
1117					}
1118
1119					TRACE("Journal::_RevoverPassReplay(): Write to %" B_PRIu32
1120						"\n", tag->BlockNumber() * fBlockSize);
1121					size_t written = write_pos(fFilesystemVolume->Device(),
1122						tag->BlockNumber() * fBlockSize, data, fBlockSize);
1123
1124					if (written != fBlockSize)
1125						return B_IO_ERROR;
1126
1127					++count;
1128				}
1129
1130				if ((tag->Flags() & JOURNAL_FLAG_LAST_TAG) != 0)
1131					break;
1132				if ((tag->Flags() & JOURNAL_FLAG_SAME_UUID) == 0) {
1133					// TODO: Check new UUID with file system UUID
1134					tag += 2;
1135						// sizeof(JournalBlockTag) = 8
1136						// sizeof(UUID) = 16
1137				}
1138			}
1139		} else if (blockType == JOURNAL_COMMIT_BLOCK)
1140			nextCommitID++;
1141		else if (blockType != JOURNAL_REVOKE_BLOCK) {
1142			WARN("Journal::_RecoverPassReplay(): Found an unrecognized block\n");
1143			break;
1144		} // If blockType == JOURNAL_REVOKE_BLOCK we just skip it
1145
1146		nextBlock = _WrapAroundLog(nextBlock + 1);
1147
1148		status = MapBlock(nextBlock, nextBlockPos);
1149		if (status != B_OK)
1150			return status;
1151
1152		header = (JournalHeader*)cached.SetTo(nextBlockPos);
1153	}
1154
1155	if (nextCommitID != lastCommitID) {
1156		// Possibly because of some sort of IO error
1157		return B_ERROR;
1158	}
1159
1160	TRACE("Journal recovery pass replay: Replayed blocks: %u\n", count);
1161
1162	return B_OK;
1163}
1164
1165
1166status_t
1167Journal::_FlushLog(bool canWait, bool flushBlocks)
1168{
1169	TRACE("Journal::_FlushLog()\n");
1170	status_t status = canWait ? recursive_lock_lock(&fLock)
1171		: recursive_lock_trylock(&fLock);
1172
1173	TRACE("Journal::_FlushLog(): Acquired fLock, recursion: %" B_PRId32 "\n",
1174		recursive_lock_get_recursion(&fLock));
1175	if (status != B_OK)
1176		return status;
1177
1178	if (recursive_lock_get_recursion(&fLock) > 1) {
1179		// Called from inside a transaction
1180		recursive_lock_unlock(&fLock);
1181		TRACE("Journal::_FlushLog(): Called from a transaction. Leaving...\n");
1182		return B_OK;
1183	}
1184
1185	if (fUnwrittenTransactions != 0 && _FullTransactionSize() != 0) {
1186		status = _WriteTransactionToLog();
1187		if (status < B_OK)
1188			panic("Failed flushing transaction: %s\n", strerror(status));
1189	}
1190
1191	TRACE("Journal::_FlushLog(): Attempting to flush journal volume at %p\n",
1192		fJournalVolume);
1193
1194	// TODO: Not sure this is correct. Need to review...
1195	// NOTE: Not correct. Causes double lock of a block cache mutex
1196	// TODO: Need some other way to synchronize the journal...
1197	/*status = fJournalVolume->FlushDevice();
1198	if (status != B_OK)
1199		return status;*/
1200
1201	TRACE("Journal::_FlushLog(): Flushed journal volume\n");
1202
1203	if (flushBlocks) {
1204		TRACE("Journal::_FlushLog(): Attempting to flush file system volume "
1205			"at %p\n", fFilesystemVolume);
1206		status = fFilesystemVolume->FlushDevice();
1207		if (status == B_OK)
1208			TRACE("Journal::_FlushLog(): Flushed file system volume\n");
1209	}
1210
1211	TRACE("Journal::_FlushLog(): Finished. Releasing lock\n");
1212
1213	recursive_lock_unlock(&fLock);
1214
1215	TRACE("Journal::_FlushLog(): Done, final status: %s\n", strerror(status));
1216	return status;
1217}
1218
1219
1220inline uint32
1221Journal::_WrapAroundLog(uint32 block)
1222{
1223	TRACE("Journal::_WrapAroundLog()\n");
1224	if (block >= fLogSize)
1225		return block - fLogSize + fFirstLogBlock;
1226	else
1227		return block;
1228}
1229
1230
1231size_t
1232Journal::_CurrentTransactionSize() const
1233{
1234	TRACE("Journal::_CurrentTransactionSize(): transaction %" B_PRIu32 "\n",
1235		fTransactionID);
1236
1237	size_t count;
1238
1239	if (fHasSubTransaction) {
1240		count = cache_blocks_in_sub_transaction(fFilesystemBlockCache,
1241			fTransactionID);
1242
1243		TRACE("\tSub transaction size: %" B_PRIuSIZE "\n", count);
1244	} else {
1245		count =  cache_blocks_in_transaction(fFilesystemBlockCache,
1246			fTransactionID);
1247
1248		TRACE("\tTransaction size: %" B_PRIuSIZE "\n", count);
1249	}
1250
1251	return count;
1252}
1253
1254
1255size_t
1256Journal::_FullTransactionSize() const
1257{
1258	TRACE("Journal::_FullTransactionSize(): transaction %" B_PRIu32 "\n",
1259		fTransactionID);
1260	TRACE("\tFile sytem block cache: %p\n", fFilesystemBlockCache);
1261
1262	size_t count = cache_blocks_in_transaction(fFilesystemBlockCache,
1263		 fTransactionID);
1264
1265	TRACE("\tFull transaction size: %" B_PRIuSIZE "\n", count);
1266
1267	return count;
1268}
1269
1270
1271size_t
1272Journal::_MainTransactionSize() const
1273{
1274	TRACE("Journal::_MainTransactionSize(): transaction %" B_PRIu32 "\n",
1275		fTransactionID);
1276
1277	size_t count =  cache_blocks_in_main_transaction(fFilesystemBlockCache,
1278		fTransactionID);
1279
1280	TRACE("\tMain transaction size: %" B_PRIuSIZE "\n", count);
1281
1282	return count;
1283}
1284
1285
1286status_t
1287Journal::_TransactionDone(bool success)
1288{
1289	if (!success) {
1290		if (fHasSubTransaction) {
1291			TRACE("Journal::_TransactionDone(): transaction %" B_PRIu32
1292				" failed, aborting subtransaction\n", fTransactionID);
1293			cache_abort_sub_transaction(fFilesystemBlockCache, fTransactionID);
1294			// parent is unaffected
1295		} else {
1296			TRACE("Journal::_TransactionDone(): transaction %" B_PRIu32
1297				" failed, aborting\n", fTransactionID);
1298			cache_abort_transaction(fFilesystemBlockCache, fTransactionID);
1299			fUnwrittenTransactions = 0;
1300		}
1301
1302		TRACE("Journal::_TransactionDone(): returning B_OK\n");
1303		return B_OK;
1304	}
1305
1306	// If possible, delay flushing the transaction
1307	uint32 size = _FullTransactionSize();
1308	TRACE("Journal::_TransactionDone(): full transaction size: %" B_PRIu32
1309		", max transaction size: %" B_PRIu32 ", free log blocks: %" B_PRIu32
1310		"\n", size, fMaxTransactionSize, FreeLogBlocks());
1311	if (fMaxTransactionSize > 0 && size < fMaxTransactionSize) {
1312		TRACE("Journal::_TransactionDone(): delaying flush of transaction "
1313			"%" B_PRIu32 "\n", fTransactionID);
1314
1315		// Make sure the transaction fits in the log
1316		if (size < FreeLogBlocks())
1317			cache_sync_transaction(fFilesystemBlockCache, fTransactionID);
1318
1319		fUnwrittenTransactions++;
1320		TRACE("Journal::_TransactionDone(): returning B_OK\n");
1321		return B_OK;
1322	}
1323
1324	return _WriteTransactionToLog();
1325}
1326
1327
1328/*static*/ void
1329Journal::_TransactionWritten(int32 transactionID, int32 event, void* _logEntry)
1330{
1331	LogEntry* logEntry = (LogEntry*)_logEntry;
1332
1333	TRACE("Journal::_TransactionWritten(): Transaction %" B_PRIu32
1334		" checkpointed\n", transactionID);
1335
1336	Journal* journal = logEntry->GetJournal();
1337
1338	TRACE("Journal::_TransactionWritten(): log entry: %p, journal: %p\n",
1339		logEntry, journal);
1340	TRACE("Journal::_TransactionWritten(): log entries: %p\n",
1341		&journal->fLogEntries);
1342
1343	mutex_lock(&journal->fLogEntriesLock);
1344
1345	TRACE("Journal::_TransactionWritten(): first log entry: %p\n",
1346		journal->fLogEntries.First());
1347	if (logEntry == journal->fLogEntries.First()) {
1348		TRACE("Journal::_TransactionWritten(): Moving start of log to %"
1349			B_PRIu32 "\n", logEntry->Start());
1350		journal->fLogStart = logEntry->Start();
1351		journal->fFirstCommitID = logEntry->CommitID();
1352		TRACE("Journal::_TransactionWritten(): Setting commit ID to %" B_PRIu32
1353			"\n", logEntry->CommitID());
1354
1355		if (journal->_SaveSuperBlock() != B_OK)
1356			panic("ext2: Failed to write journal superblock\n");
1357	}
1358
1359	TRACE("Journal::_TransactionWritten(): Removing log entry\n");
1360	journal->fLogEntries.Remove(logEntry);
1361
1362	TRACE("Journal::_TransactionWritten(): Unlocking entries list\n");
1363	mutex_unlock(&journal->fLogEntriesLock);
1364
1365	TRACE("Journal::_TransactionWritten(): Deleting log entry at %p\n", logEntry);
1366	delete logEntry;
1367}
1368
1369
1370/*static*/ void
1371Journal::_TransactionIdle(int32 transactionID, int32 event, void* _journal)
1372{
1373	Journal* journal = (Journal*)_journal;
1374	journal->_FlushLog(false, false);
1375}
1376