if (tranId != null)
peekAmount += LogRecord.maxTransactionIdStoredSize(tranId);
int readAmount; // the number of bytes actually read
LogRecord lr;
do
{
// this log record is a candidate unless proven otherwise
candidate = true;
lr = null;
readAmount = -1;
// if we are not right at the end but this position + 4 is at
// or exceeds the end, we know we don't have a complete log
// record. This is the log file and chalk it up as the fuzzy
// end.
if (recordStartPosition + 4 > currentLogFileLength)
{
// since there is no end of log file marker, we are at the
// end of the log.
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"detected fuzzy log end on log file " +
currentLogFileNumber +
" record start position " + recordStartPosition +
" file length " + currentLogFileLength);
}
}
//if recordStartPosition == currentLogFileLength
//there is NO fuzz, it just a properly ended log
//without the end marker.
if(recordStartPosition != currentLogFileLength)
fuzzyLogEnd = true ;
// don't bother to write the end of log file marker because
// if it is not overwritten by the next log record then
// the next time the database is recovered it will come
// back right here
return null;
}
// read in the length before the log record
int recordLength = scan.readInt();
while (recordLength == 0 || recordStartPosition + recordLength +
LogToFile.LOG_RECORD_OVERHEAD > currentLogFileLength)
{
// if recordLength is zero or the log record goes beyond the
// current file, then we have detected the end of a log file.
//
// If recordLength == 0 then we know that this log file has either
// been properly switched or it had a 1/2 written log record which
// was subsequently cleared by clearFuzzyEnd.
//
// If recordLength != 0 but log record goes beyond the current log
// file, we have detected a fuzzy end. This is the last log file
// since we will clear it by clearFuzzyEnd.
if (recordLength != 0) // this is a fuzzy log end
{
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(
LogToFile.DBG_FLAG,
"detected fuzzy log end on log file " +
currentLogFileNumber +
" record start position " +
recordStartPosition +
" file length " + currentLogFileLength +
" recordLength=" + recordLength );
}
}
fuzzyLogEnd = true;
scan.close();
scan = null;
return null;
}
// recordLength == 0
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
if (recordStartPosition + 4 == currentLogFileLength)
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"detected proper log end on log file " +
currentLogFileNumber);
}
else
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"detected zapped log end on log file " +
currentLogFileNumber +
" end marker at " +
recordStartPosition +
" real end at " + currentLogFileLength);
}
}
}
// don't go thru the trouble of switching log file if we
// have will have gone past stopAt if we want to stop here
if (stopAt != LogCounter.INVALID_LOG_INSTANT &&
LogCounter.getLogFileNumber(stopAt) == currentLogFileNumber)
{
return null;
}
//
// we have a log end marker and we don't want to stop yet, switch
// log file
//
scan.close();
// set this.currentLogFileNumber
scan = logFactory.getLogFileAtBeginning(++currentLogFileNumber);
if (scan == null) // we have seen the last log file
{
return null;
}
// scan is position just past the log header
recordStartPosition = scan.getFilePointer();
// Verify that the header of the new log file refers
// to the end of the log record of the previous file
// (Rest of header has been verified by getLogFileAtBeginning)
scan.seek(LogToFile
.LOG_FILE_HEADER_PREVIOUS_LOG_INSTANT_OFFSET);
long previousLogInstant = scan.readLong();
if (previousLogInstant != knownGoodLogEnd) {
// If there is a mismatch, something is wrong and
// we return null to stop the scan. The same
// behavior occurs when getLogFileAtBeginning
// detects an error in the other fields of the header.
if (SanityManager.DEBUG) {
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) {
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"log file "
+ currentLogFileNumber
+ ": previous log record: "
+ previousLogInstant
+ " known previous log record: "
+ knownGoodLogEnd);
}
}
return null;
}
scan.seek(recordStartPosition);
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"switched to next log file " +
currentLogFileNumber);
}
}
// Advance knownGoodLogEnd to make sure that if this
// log file is the last log file and empty, logging
// continues in this file, not the old file.
knownGoodLogEnd = LogCounter.makeLogInstantAsLong
(currentLogFileNumber, recordStartPosition);
// set this.currentLogFileLength
currentLogFileLength = scan.length();
if (recordStartPosition+4 >= currentLogFileLength) // empty log file
{
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))
{
SanityManager.DEBUG(LogToFile.DBG_FLAG,
"log file " + currentLogFileNumber +
" is empty");
}
}
return null;
}
// we have successfully switched to the next log file.
// scan is positioned just before the next log record
// see if this one is written in entirety
recordLength = scan.readInt();
}
// we know the entire log record is on this log file
// read the current log instant
currentInstant = scan.readLong();
/*check if the current instant happens is less than the last one.
*This can happen if system crashed before writing the log instant
*completely. If the instant is partially written it will be less
*than the last one and should be the last record that was suppose to
*get written. Currentlt preallocated files are filled with zeros,
*this should hold good.
*Note: In case of Non-preallocated files earlier check with log
* file lengths should have found the end. But in prellocated files, log file
*length is not sufficiant to find the log end. This check
*is must to find the end in preallocated log files.
*/
if(currentInstant < knownGoodLogEnd)
{
fuzzyLogEnd = true ;
return null;
}
// sanity check it
if (SanityManager.DEBUG)
{
if (LogCounter.getLogFileNumber(currentInstant) !=
currentLogFileNumber ||
LogCounter.getLogFilePosition(currentInstant) !=
recordStartPosition)
SanityManager.THROWASSERT(
"Wrong LogInstant on log record " +
LogCounter.toDebugString(currentInstant) +
" version real position (" +
currentLogFileNumber + "," +
recordStartPosition + ")");
}
// if stopAt == INVALID_LOG_INSTANT, no stop instant, read till
// nothing more can be read. Else check scan limit
if (stopAt != LogCounter.INVALID_LOG_INSTANT && currentInstant > stopAt)
{
currentInstant = LogCounter.INVALID_LOG_INSTANT;
return null; // we went past the stopAt
}
// read in the log record
byte[] data = input.getData();
if (data.length < recordLength)
{
// make a new array of sufficient size and reset the arrary
// in the input stream
data = new byte[recordLength];
input.setData(data);
}
// If the log is encrypted, we must do the filtering after
// reading and decryptiong the record.
if (logFactory.databaseEncrypted())
{
scan.readFully(data, 0, recordLength);
int len = logFactory.decrypt(data, 0, recordLength, data, 0);
if (SanityManager.DEBUG)
SanityManager.ASSERT(len == recordLength);
input.setLimit(0, len);
}
else // no need to decrypt, only get the group and tid if we filter
{
if (groupmask == 0 && tranId == null)
{
// no filter, get the whole thing
scan.readFully(data, 0, recordLength);
input.setLimit(0, recordLength);
}
else
{
// Read only enough so that group and the tran id is in
// the data buffer. Group is stored as compressed int
// and tran id is stored as who knows what. read min
// of peekAmount or recordLength
readAmount = (recordLength > peekAmount) ?
peekAmount : recordLength;
// in the data buffer, we now have enough to peek
scan.readFully(data, 0, readAmount);
input.setLimit(0, readAmount);
}
}
lr = (LogRecord) input.readObject();
if (groupmask != 0 || tranId != null)
{
if (groupmask != 0 && (groupmask & lr.group()) == 0)
candidate = false; // no match, throw this log record out
if (candidate && tranId != null)
{
TransactionId tid = lr.getTransactionId();
if (!tid.equals(tranId)) // nomatch
candidate = false; // throw this log record out
}
// if this log record is not filtered out, we need to read
// in the rest of the log record to the input buffer.
// Except if it is an encrypted database, in which case the
// entire log record have already be read in for
// decryption.
if (candidate && !logFactory.databaseEncrypted())
{
// read the rest of the log into the buffer
if (SanityManager.DEBUG)
SanityManager.ASSERT(readAmount > 0);
if (readAmount < recordLength)
{
// Need to remember where we are because the log
// record may have read part of it off the input
// stream already and that position is lost when we
// set limit again.
int inputPosition = input.getPosition();
scan.readFully(data, readAmount,
recordLength-readAmount);
input.setLimit(0, recordLength);
input.setPosition(inputPosition);
}
}
}
/*check if the logrecord length written before and after the
*log record are equal, if not the end of of the log is reached.
*This can happen if system crashed before writing the length field
*in the end of the records completely. If the length is partially
*written or not written at all it will not match with length written
*in the beginning of the log record. Currentlt preallocated files
*are filled with zeros, log record length can never be zero;
*if the lengths are not matching, end of the properly written log
*is reached.
*Note: In case of Non-preallocated files earlier fuzzy case check with log
* file lengths should have found the end. But in prellocated files, log file
*length is not sufficiant to find the log end. This check
*is must to find the end in preallocated log files.
*/
// read the length after the log record and check it against the
// length before the log record, make sure we go to the correct
// place for skipped log record.
if (!candidate)
scan.seek(recordStartPosition - 4);
int checkLength = scan.readInt();
if (checkLength != recordLength && checkLength < recordLength)
{
//lengh written in the end of the log record should be always
//less then the length written in the beginning if the log
//record was half written before the crash.
if(checkLength < recordLength)
{
fuzzyLogEnd = true ;
return null;
}else
{
//If checklength > recordLength then it can be not be a partial write
//probablly it is corrupted for some reason , this should never
//happen throw error in debug mode. In non debug case , let's
//hope it's only is wrong and system can proceed.
if (SanityManager.DEBUG)
{
throw logFactory.markCorrupt
(StandardException.newException(
SQLState.LOG_RECORD_CORRUPTED,
new Long(checkLength),
new Long(recordLength),
new Long(currentInstant),
new Long(currentLogFileNumber)));
}
//In non debug case, do nothing , let's hope it's only
//length part that is incorrect and system can proceed.
}
}
// next record start position is right after this record
recordStartPosition += recordLength + LogToFile.LOG_RECORD_OVERHEAD;
knownGoodLogEnd = LogCounter.makeLogInstantAsLong
(currentLogFileNumber, recordStartPosition);
if (SanityManager.DEBUG)
{
if (recordStartPosition != scan.getFilePointer())
SanityManager.THROWASSERT(
"calculated end " + recordStartPosition +
" != real end " + scan.getFilePointer());
}
else
{
// seek to the start of the next log record
scan.seek(recordStartPosition);
}
// the scan is now positioned just past this log record and right
// at the beginning of the next log record
/** if the current log record is a checksum log record then
* using the information available in this record validate
* that data in the log file by matching the checksum in
* checksum log record and by recalculating the checksum for the
* specified length of the data in the log file. cheksum values
* should match unless the right was incomplete before the crash.
*/
if(lr.isChecksum())
{
// checksum log record should not be returned to the logger recovery redo
// routines, it is just used to identify the incomplete log writes.
candidate = false;
Loggable op = lr.getLoggable();
if (SanityManager.DEBUG)
{
if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY) ||
SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))