--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.store.raw.log.Scan\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.store.raw.log;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.io.ArrayInputStream;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.store.raw.log.LogInstant;\r
+\r
+import org.apache.derby.iapi.store.raw.xact.TransactionId;\r
+\r
+import org.apache.derby.impl.store.raw.log.LogCounter;\r
+import org.apache.derby.impl.store.raw.log.LogRecord;\r
+import org.apache.derby.impl.store.raw.log.StreamLogScan;\r
+\r
+import org.apache.derby.io.StorageRandomAccessFile;\r
+\r
+import java.io.IOException;\r
+import org.apache.derby.iapi.store.raw.Loggable;\r
+\r
+/**\r
+\r
+ Scan the the log which is implemented by a series of log files.n\r
+ This log scan knows how to move across log file if it is positioned at\r
+ the boundary of a log file and needs to getNextRecord.\r
+\r
+ <PRE>\r
+ 4 bytes - length of user data, i.e. N\r
+ 8 bytes - long representing log instant\r
+ N bytes of supplied data\r
+ 4 bytes - length of user data, i.e. N\r
+ </PRE>\r
+\r
+*/\r
+\r
+public class Scan implements StreamLogScan {\r
+\r
+ // value for scanDirection\r
+ public static final byte FORWARD = 1;\r
+ public static final byte BACKWARD = 2;\r
+ public static final byte BACKWARD_FROM_LOG_END = 4;\r
+\r
+ private StorageRandomAccessFile scan; // an output stream to the log file\r
+ private LogToFile logFactory; // log factory knows how to to skip\r
+ // from log file to log file\r
+\r
+ private long currentLogFileNumber; // the log file the scan is currently on\r
+\r
+ private long currentLogFileLength; // the size of the current log file\r
+ // used only for FORWARD scan to determine when\r
+ // to switch the next log file\r
+\r
+ private long knownGoodLogEnd; // For FORWARD scan only\r
+ // during recovery, we need to determine the end\r
+ // of the log. Everytime a complete log record\r
+ // is read in, knownGoodLogEnd is set to the\r
+ // log instant of the next log record if it is\r
+ // on the same log file.\r
+ // \r
+ // only valid afer a successfull getNextRecord\r
+ // on a FOWARD scan. \r
+\r
+\r
+ private long currentInstant; // the log instant the scan is\r
+ // currently on - only valid after a\r
+ // successful getNextRecord\r
+\r
+ private long stopAt; // scan until we find a log record whose \r
+ // log instance < stopAt if we scan BACKWARD\r
+ // log instance > stopAt if we scan FORWARD\r
+ // log instance >= stopAt if we scan FORWARD_FLUSHED\r
+\r
+\r
+ private byte scanDirection; // BACKWARD or FORWARD\r
+\r
+ private boolean fuzzyLogEnd = false; //get sets to true during forward scan\r
+ //for recovery, if there were\r
+ //partial writes at the end of the log before crash;\r
+ //during forward scan for recovery.\r
+\r
+\r
+ /**\r
+ For backward scan, we expect a scan positioned at the end of the next log record.\r
+ For forward scan, we expect a scan positioned at the beginning of the next log record.\r
+\r
+ For forward flushed scan, we expect stopAt to be the instant for the\r
+ first not-flushed log record. Like any forward scan, we expect a scan\r
+ positioned at the beginning of the next log record.\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ @exception IOException cannot access the log at the new position.\r
+ */\r
+ public Scan(LogToFile logFactory, long startAt, LogInstant stopAt, byte direction)\r
+ throws IOException, StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(startAt != LogCounter.INVALID_LOG_INSTANT, \r
+ "cannot start scan on an invalid log instant");\r
+\r
+ this.logFactory = logFactory;\r
+ currentLogFileNumber = LogCounter.getLogFileNumber(startAt);\r
+ currentLogFileLength = -1;\r
+ knownGoodLogEnd = LogCounter.INVALID_LOG_INSTANT;// set at getNextRecord for FORWARD scan\r
+ currentInstant = LogCounter.INVALID_LOG_INSTANT; // set at getNextRecord\r
+ if (stopAt != null)\r
+ this.stopAt = ((LogCounter) stopAt).getValueAsLong();\r
+ else\r
+ this.stopAt = LogCounter.INVALID_LOG_INSTANT;\r
+\r
+ switch(direction)\r
+ {\r
+ case FORWARD:\r
+ scan = logFactory.getLogFileAtPosition(startAt);\r
+ scanDirection = FORWARD;\r
+\r
+ if (SanityManager.DEBUG)\r
+ if (scan == null)\r
+ SanityManager.THROWASSERT(\r
+ "scan null at " + LogCounter.toDebugString(startAt));\r
+\r
+ // NOTE: just get the length of the file without syncing.\r
+ // this only works because the only place forward scan is used\r
+ // right now is on recovery redo and nothing is being added to \r
+ // the current log file. When the forward scan is used for some\r
+ // other purpose, need to sync access to the end of the log\r
+ currentLogFileLength = scan.length();\r
+ break;\r
+\r
+ case BACKWARD:\r
+ // startAt is at the front of the log record, for backward\r
+ // scan we need to be positioned at the end of the log record\r
+ scan = logFactory.getLogFileAtPosition(startAt);\r
+ int logsize = scan.readInt();\r
+\r
+ // skip forward over the log record and all the overhead, but remember\r
+ // we just read an int off the overhead\r
+ scan.seek(scan.getFilePointer() + logsize + LogToFile.LOG_RECORD_OVERHEAD - 4);\r
+ scanDirection = BACKWARD;\r
+ break;\r
+\r
+ case BACKWARD_FROM_LOG_END:\r
+ // startAt is at the end of the log, no need to skip the log record\r
+ scan = logFactory.getLogFileAtPosition(startAt);\r
+ scanDirection = BACKWARD;\r
+ break;\r
+\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Methods of StreamLogScan\r
+ */\r
+\r
+ /**\r
+ Read the next log record.\r
+ Switching log to a previous log file if necessary, \r
+ Resize the input stream byte array if necessary. \r
+ @see StreamLogScan#getNextRecord\r
+\r
+ @return the next LogRecord, or null if the end of the\r
+ scan has been reached.\r
+\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ public LogRecord getNextRecord(ArrayInputStream input, \r
+ TransactionId tranId, \r
+ int groupmask)\r
+ throws StandardException\r
+ {\r
+ if (scan == null)\r
+ return null;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(scanDirection != 0, "scan has been secretly closed!");\r
+\r
+ LogRecord lr = null;\r
+ try\r
+ {\r
+ if (scanDirection == BACKWARD)\r
+ lr = getNextRecordBackward(input, tranId, groupmask);\r
+ else if (scanDirection == FORWARD)\r
+ lr = getNextRecordForward(input, tranId, groupmask);\r
+\r
+ return lr;\r
+\r
+ }\r
+ catch (IOException ioe)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ ioe.printStackTrace();\r
+\r
+ throw logFactory.markCorrupt(\r
+ StandardException.newException(SQLState.LOG_CORRUPTED, ioe));\r
+ }\r
+ catch (ClassNotFoundException cnfe)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ cnfe.printStackTrace();\r
+\r
+ throw logFactory.markCorrupt(\r
+ StandardException.newException(SQLState.LOG_CORRUPTED, cnfe));\r
+ }\r
+ finally\r
+ {\r
+ if (lr == null)\r
+ close(); // no more log record, close the scan\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ Read the previous log record.\r
+ Switching log to a previous log file if necessary, \r
+ Resize the input stream byte array if necessary. \r
+ @see StreamLogScan#getNextRecord\r
+\r
+ Side effects include: \r
+ on a successful read, setting currentInstant.\r
+ on a log file switch, setting currentLogFileNumber.\r
+\r
+ @return the previous LogRecord, or null if the end of the\r
+ scan has been reached.\r
+ */\r
+ private LogRecord getNextRecordBackward(ArrayInputStream input, \r
+ TransactionId tranId, \r
+ int groupmask) \r
+ throws StandardException, IOException, ClassNotFoundException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(scanDirection == BACKWARD, "can only called by backward scan");\r
+\r
+ // scan is positioned just past the last byte of the record, or\r
+ // right at the beginning of the file (end of the file header)\r
+ // may need to switch log file\r
+\r
+ boolean candidate;\r
+ // if we have filtering, peek at the group and/or the transaction id,\r
+ // do them in one read rather than 2 reads.\r
+ int peekAmount = LogRecord.formatOverhead() + LogRecord.maxGroupStoredSize();\r
+ if (tranId != null)\r
+ peekAmount += LogRecord.maxTransactionIdStoredSize(tranId);\r
+\r
+ int readAmount; // the number of bytes actually read\r
+\r
+ LogRecord lr;\r
+ long curpos = scan.getFilePointer();\r
+\r
+ do\r
+ {\r
+ // this log record is a candidate unless proven otherwise\r
+ candidate = true; \r
+ lr = null;\r
+ readAmount = -1;\r
+\r
+ if (curpos == LogToFile.LOG_FILE_HEADER_SIZE)\r
+ {\r
+ // don't go thru the trouble of switching log file if we\r
+ // will have gone past stopAt\r
+ if (stopAt != LogCounter.INVALID_LOG_INSTANT &&\r
+ LogCounter.getLogFileNumber(stopAt) == currentLogFileNumber)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "stopping at " + currentLogFileNumber);\r
+ }\r
+ }\r
+\r
+ return null; // no more log record\r
+ }\r
+ \r
+ // figure out where the last log record is in the previous\r
+ // log file\r
+ scan.seek(LogToFile.LOG_FILE_HEADER_PREVIOUS_LOG_INSTANT_OFFSET);\r
+ long previousLogInstant = scan.readLong();\r
+ scan.close();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(previousLogInstant != LogCounter.INVALID_LOG_INSTANT,\r
+ "scanning backward beyond the first log file");\r
+ if (currentLogFileNumber != \r
+ LogCounter.getLogFileNumber(previousLogInstant) + 1)\r
+ SanityManager.THROWASSERT(\r
+ "scanning backward but get incorrect log file number " + \r
+ "expected " + (currentLogFileNumber -1) + \r
+ "get " +\r
+ LogCounter.getLogFileNumber(previousLogInstant));\r
+\r
+ SanityManager.ASSERT(LogCounter.getLogFilePosition(previousLogInstant) > \r
+ LogToFile.LOG_FILE_HEADER_SIZE,\r
+ "scanning backward encounter completely empty log file");\r
+\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "scanning backwards from log file " +\r
+ currentLogFileNumber + ", switch to (" + \r
+ LogCounter.getLogFileNumber(previousLogInstant) + "," +\r
+ LogCounter.getLogFilePosition(previousLogInstant) + ")"\r
+ );\r
+ }\r
+\r
+ // log file switch, set this.currentLogFileNumber\r
+ currentLogFileNumber = LogCounter.getLogFileNumber(previousLogInstant);\r
+\r
+ scan = logFactory.getLogFileAtPosition(previousLogInstant);\r
+\r
+ // scan is located right past the last byte of the last log\r
+ // record in the previous log file. currentLogFileNumber is\r
+ // set. We asserted that the scan is not located right at the\r
+ // end of the file header, in other words, there is at least\r
+ // one log record in this log file.\r
+ curpos = scan.getFilePointer();\r
+\r
+ // if the log file happens to be empty skip and proceed. \r
+ // ideally this case should never occur because log switch is\r
+ // not suppose to happen on an empty log file. \r
+ // But it is safer to put following check incase if it ever\r
+ // happens to avoid any recovery issues. \r
+ if (curpos == LogToFile.LOG_FILE_HEADER_SIZE)\r
+ continue;\r
+ }\r
+\r
+ scan.seek(curpos - 4);\r
+ int recordLength = scan.readInt(); // get the length after the log record\r
+\r
+ // calculate where this log record started.\r
+ // include the eight bytes for the long log instant at the front\r
+ // the four bytes of length in the front and the four bytes we just read\r
+ long recordStartPosition = curpos - recordLength -\r
+ LogToFile.LOG_RECORD_OVERHEAD; \r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (recordStartPosition < LogToFile.LOG_FILE_HEADER_SIZE)\r
+ SanityManager.THROWASSERT(\r
+ "next position " + recordStartPosition +\r
+ " recordLength " + recordLength + \r
+ " current file position " + scan.getFilePointer());\r
+\r
+ scan.seek(recordStartPosition);\r
+\r
+ // read the length before the log record and check it against the\r
+ // length after the log record\r
+ int checkLength = scan.readInt();\r
+\r
+ if (checkLength != recordLength)\r
+ {\r
+ long inst = LogCounter.makeLogInstantAsLong(currentLogFileNumber, recordStartPosition);\r
+\r
+ throw logFactory.markCorrupt(\r
+ StandardException.newException(\r
+ SQLState.LOG_RECORD_CORRUPTED, \r
+ new Long(checkLength),\r
+ new Long(recordLength),\r
+ new Long(inst),\r
+ new Long(currentLogFileNumber)));\r
+ }\r
+ }\r
+ else\r
+ {\r
+ // skip over the length in insane\r
+ scan.seek(recordStartPosition+4);\r
+ }\r
+\r
+ // scan is positioned just before the log instant\r
+ // read the current log instant - this is the currentInstant if we have not\r
+ // exceeded the scan limit\r
+ currentInstant = scan.readLong();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ // sanity check the current instant against the scan position\r
+ if (LogCounter.getLogFileNumber(currentInstant) !=\r
+ currentLogFileNumber ||\r
+ LogCounter.getLogFilePosition(currentInstant) !=\r
+ recordStartPosition)\r
+ SanityManager.THROWASSERT(\r
+ "Wrong LogInstant on log record " +\r
+ LogCounter.toDebugString(currentInstant) + \r
+ " version real position (" +\r
+ currentLogFileNumber + "," +\r
+ recordStartPosition + ")");\r
+ }\r
+\r
+\r
+ // if stopAt == INVALID_LOG_INSTANT, no stop instant, read till\r
+ // nothing more can be read. Else check scan limit\r
+ if (currentInstant < stopAt && stopAt != LogCounter.INVALID_LOG_INSTANT)\r
+ {\r
+ currentInstant = LogCounter.INVALID_LOG_INSTANT;\r
+ return null; // we went past the stopAt\r
+ }\r
+\r
+\r
+ byte[] data = input.getData();\r
+\r
+ if (data.length < recordLength)\r
+ {\r
+ // make a new array of sufficient size and reset the arrary\r
+ // in the input stream\r
+ data = new byte[recordLength];\r
+ input.setData(data);\r
+ }\r
+\r
+ // If the log is encrypted, we must do the filtering after reading\r
+ // and decrypting the record.\r
+ if (logFactory.databaseEncrypted())\r
+ {\r
+ scan.readFully(data, 0, recordLength);\r
+ int len = logFactory.decrypt(data, 0, recordLength, data, 0);\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(len == recordLength);\r
+ input.setLimit(0, recordLength);\r
+ }\r
+ else // no need to decrypt, only get the group and tid if we filter \r
+ {\r
+ if (groupmask == 0 && tranId == null)\r
+ {\r
+ // no filter, get the whole thing\r
+ scan.readFully(data, 0, recordLength);\r
+ input.setLimit(0, recordLength);\r
+ }\r
+ else\r
+ {\r
+ // Read only enough so that group and the tran id is in\r
+ // the data buffer. Group is stored as compressed int\r
+ // and tran id is stored as who knows what. read min\r
+ // of peekAmount or recordLength\r
+ readAmount = (recordLength > peekAmount) ?\r
+ peekAmount : recordLength; \r
+\r
+ // in the data buffer, we now have enough to peek\r
+ scan.readFully(data, 0, readAmount);\r
+ input.setLimit(0, readAmount);\r
+ }\r
+ }\r
+\r
+ lr = (LogRecord) input.readObject();\r
+\r
+ // skip the checksum log records, there is no need to look at them \r
+ // during backward scans. They are used only in forwardscan during recovery. \r
+ if(lr.isChecksum())\r
+ {\r
+ candidate = false; \r
+ }else if (groupmask != 0 || tranId != null)\r
+ {\r
+\r
+ // skip the checksum log records \r
+ if(lr.isChecksum())\r
+ candidate = false; \r
+\r
+ if (candidate && groupmask != 0 && (groupmask & lr.group()) == 0)\r
+ candidate = false; // no match, throw this log record out \r
+\r
+ if (candidate && tranId != null)\r
+ {\r
+ TransactionId tid = lr.getTransactionId();\r
+ if (!tid.equals(tranId)) // nomatch\r
+ candidate = false; // throw this log record out\r
+ }\r
+\r
+ // if this log record is not filtered out, we need to read\r
+ // in the rest of the log record to the input buffer.\r
+ // Except if it is an encrypted database, in which case the\r
+ // entire log record have already be read in for\r
+ // decryption.\r
+ if (candidate && !logFactory.databaseEncrypted())\r
+ {\r
+ // read the rest of the log into the buffer\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(readAmount > 0);\r
+\r
+ if (readAmount < recordLength)\r
+ {\r
+ // Need to remember where we are because the log\r
+ // record may have read part of it off the input\r
+ // stream already and that position is lost when we\r
+ // set limit again.\r
+ int inputPosition = input.getPosition();\r
+\r
+ scan.readFully(data, readAmount,\r
+ recordLength-readAmount); \r
+\r
+ input.setLimit(0, recordLength);\r
+ input.setPosition(inputPosition);\r
+ }\r
+ }\r
+ }\r
+\r
+ // go back to the start of the log record so that the next time\r
+ // this method is called, it is positioned right past the last byte\r
+ // of the record.\r
+ curpos = recordStartPosition;\r
+ scan.seek(curpos);\r
+\r
+ } while (candidate == false);\r
+\r
+ return lr;\r
+\r
+ }\r
+\r
+ /**\r
+ Read the next log record.\r
+ Switching log to a previous log file if necessary, \r
+ Resize the input stream byte array if necessary. \r
+ @see StreamLogScan#getNextRecord\r
+\r
+ Side effects include: \r
+ on a successful read, setting currentInstant, knownGoodLogEnd\r
+ on a log file switch, setting currentLogFileNumber, currentLogFileLength.\r
+ on detecting a fuzzy log end that needs clearing, it will call\r
+ logFactory to clear the fuzzy log end.\r
+\r
+ @return the next LogRecord, or null if the end of the\r
+ scan has been reached.\r
+ */\r
+ private LogRecord getNextRecordForward(ArrayInputStream input, \r
+ TransactionId tranId, \r
+ int groupmask)\r
+ throws StandardException, IOException, ClassNotFoundException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(scanDirection == FORWARD, "can only called by forward scan");\r
+\r
+ // NOTE:\r
+ //\r
+ // if forward scan, scan is positioned at the first byte of the\r
+ // next record, or the end of file - note the the 'end of file'\r
+ // is defined at the time the scan is initialized. If we are\r
+ // on the current log file, it may well have grown by now...\r
+ //\r
+ // This is not a problem in reality because the only forward\r
+ // scan on the log now is recovery redo and the log does not\r
+ // grow. If in the future, a foward scan of the log is used\r
+ // for some other reasons, need to keep this in mind.\r
+ //\r
+\r
+ // first we need to make sure the entire log record is on the\r
+ // log, or else this is a fuzzy log end.\r
+\r
+ // RESOLVE: can get this from knownGoodLogEnd if this is not the first\r
+ // time getNext is called. Probably just as fast to call\r
+ // scan.getFilePointer though...\r
+ long recordStartPosition = scan.getFilePointer();\r
+\r
+ boolean candidate;\r
+\r
+ // if we have filtering, peek at the group and/or the transaction id,\r
+ // do them in one read rather than 2 reads.\r
+ int peekAmount = LogRecord.formatOverhead() + LogRecord.maxGroupStoredSize();\r
+ if (tranId != null)\r
+ peekAmount += LogRecord.maxTransactionIdStoredSize(tranId);\r
+\r
+ int readAmount; // the number of bytes actually read\r
+\r
+ LogRecord lr;\r
+\r
+ do\r
+ {\r
+ // this log record is a candidate unless proven otherwise\r
+ candidate = true;\r
+ lr = null;\r
+ readAmount = -1;\r
+\r
+ // if we are not right at the end but this position + 4 is at\r
+ // or exceeds the end, we know we don't have a complete log\r
+ // record. This is the log file and chalk it up as the fuzzy\r
+ // end.\r
+ if (recordStartPosition + 4 > currentLogFileLength)\r
+ {\r
+ // since there is no end of log file marker, we are at the\r
+ // end of the log.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "detected fuzzy log end on log file " + \r
+ currentLogFileNumber + \r
+ " record start position " + recordStartPosition + \r
+ " file length " + currentLogFileLength);\r
+ }\r
+ }\r
+ \r
+ //if recordStartPosition == currentLogFileLength\r
+ //there is NO fuzz, it just a properly ended log \r
+ //without the end marker. \r
+ if(recordStartPosition != currentLogFileLength)\r
+ fuzzyLogEnd = true ;\r
+\r
+ // don't bother to write the end of log file marker because\r
+ // if it is not overwritten by the next log record then\r
+ // the next time the database is recovered it will come\r
+ // back right here\r
+ return null;\r
+ }\r
+\r
+ // read in the length before the log record\r
+ int recordLength = scan.readInt();\r
+\r
+ while (recordLength == 0 || recordStartPosition + recordLength +\r
+ LogToFile.LOG_RECORD_OVERHEAD > currentLogFileLength) \r
+ {\r
+ // if recordLength is zero or the log record goes beyond the\r
+ // current file, then we have detected the end of a log file.\r
+ //\r
+ // If recordLength == 0 then we know that this log file has either\r
+ // been properly switched or it had a 1/2 written log record which \r
+ // was subsequently cleared by clearFuzzyEnd.\r
+ //\r
+ // If recordLength != 0 but log record goes beyond the current log\r
+ // file, we have detected a fuzzy end. This is the last log file\r
+ // since we will clear it by clearFuzzyEnd.\r
+\r
+ if (recordLength != 0) // this is a fuzzy log end\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(\r
+ LogToFile.DBG_FLAG, \r
+ "detected fuzzy log end on log file " + \r
+ currentLogFileNumber + \r
+ " record start position " + \r
+ recordStartPosition + \r
+ " file length " + currentLogFileLength + \r
+ " recordLength=" + recordLength );\r
+ }\r
+ }\r
+\r
+ fuzzyLogEnd = true;\r
+ scan.close();\r
+ scan = null;\r
+\r
+ return null;\r
+ }\r
+\r
+ // recordLength == 0\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ if (recordStartPosition + 4 == currentLogFileLength)\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "detected proper log end on log file " + \r
+ currentLogFileNumber);\r
+ }\r
+ else\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "detected zapped log end on log file " + \r
+ currentLogFileNumber +\r
+ " end marker at " + \r
+ recordStartPosition + \r
+ " real end at " + currentLogFileLength);\r
+ }\r
+ }\r
+ }\r
+ \r
+ // don't go thru the trouble of switching log file if we\r
+ // have will have gone past stopAt if we want to stop here\r
+ if (stopAt != LogCounter.INVALID_LOG_INSTANT &&\r
+ LogCounter.getLogFileNumber(stopAt) == currentLogFileNumber)\r
+ {\r
+ return null;\r
+ }\r
+\r
+ //\r
+ // we have a log end marker and we don't want to stop yet, switch\r
+ // log file\r
+ //\r
+ scan.close();\r
+\r
+ // set this.currentLogFileNumber\r
+ scan = logFactory.getLogFileAtBeginning(++currentLogFileNumber);\r
+ if (scan == null) // we have seen the last log file\r
+ {\r
+ return null;\r
+ }\r
+\r
+ // scan is position just past the log header\r
+ recordStartPosition = scan.getFilePointer();\r
+\r
+ // Verify that the header of the new log file refers\r
+ // to the end of the log record of the previous file\r
+ // (Rest of header has been verified by getLogFileAtBeginning)\r
+ scan.seek(LogToFile\r
+ .LOG_FILE_HEADER_PREVIOUS_LOG_INSTANT_OFFSET);\r
+ long previousLogInstant = scan.readLong();\r
+ if (previousLogInstant != knownGoodLogEnd) {\r
+ // If there is a mismatch, something is wrong and\r
+ // we return null to stop the scan. The same\r
+ // behavior occurs when getLogFileAtBeginning\r
+ // detects an error in the other fields of the header.\r
+ if (SanityManager.DEBUG) {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG)) {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "log file " \r
+ + currentLogFileNumber \r
+ + ": previous log record: "\r
+ + previousLogInstant\r
+ + " known previous log record: "\r
+ + knownGoodLogEnd);\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+\r
+\r
+ scan.seek(recordStartPosition);\r
+\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "switched to next log file " + \r
+ currentLogFileNumber);\r
+ }\r
+ }\r
+\r
+ // Advance knownGoodLogEnd to make sure that if this\r
+ // log file is the last log file and empty, logging\r
+ // continues in this file, not the old file.\r
+ knownGoodLogEnd = LogCounter.makeLogInstantAsLong\r
+ (currentLogFileNumber, recordStartPosition);\r
+\r
+ // set this.currentLogFileLength\r
+ currentLogFileLength = scan.length();\r
+\r
+ if (recordStartPosition+4 >= currentLogFileLength) // empty log file\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "log file " + currentLogFileNumber + \r
+ " is empty");\r
+ }\r
+ }\r
+\r
+ return null;\r
+ }\r
+\r
+ // we have successfully switched to the next log file.\r
+ // scan is positioned just before the next log record\r
+ // see if this one is written in entirety\r
+ recordLength = scan.readInt();\r
+ }\r
+\r
+ // we know the entire log record is on this log file\r
+\r
+ // read the current log instant\r
+ currentInstant = scan.readLong();\r
+\r
+ /*check if the current instant happens is less than the last one. \r
+ *This can happen if system crashed before writing the log instant\r
+ *completely. If the instant is partially written it will be less\r
+ *than the last one and should be the last record that was suppose to\r
+ *get written. Currentlt preallocated files are filled with zeros,\r
+ *this should hold good.\r
+ *Note: In case of Non-preallocated files earlier check with log\r
+ * file lengths should have found the end. But in prellocated files, log file\r
+ *length is not sufficiant to find the log end. This check \r
+ *is must to find the end in preallocated log files. \r
+ */\r
+ if(currentInstant < knownGoodLogEnd)\r
+ {\r
+ fuzzyLogEnd = true ;\r
+ return null;\r
+ }\r
+\r
+ // sanity check it \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (LogCounter.getLogFileNumber(currentInstant) !=\r
+ currentLogFileNumber ||\r
+ LogCounter.getLogFilePosition(currentInstant) !=\r
+ recordStartPosition)\r
+ SanityManager.THROWASSERT(\r
+ "Wrong LogInstant on log record " +\r
+ LogCounter.toDebugString(currentInstant) + \r
+ " version real position (" +\r
+ currentLogFileNumber + "," +\r
+ recordStartPosition + ")");\r
+ }\r
+\r
+\r
+ // if stopAt == INVALID_LOG_INSTANT, no stop instant, read till\r
+ // nothing more can be read. Else check scan limit\r
+ if (stopAt != LogCounter.INVALID_LOG_INSTANT && currentInstant > stopAt)\r
+ {\r
+ currentInstant = LogCounter.INVALID_LOG_INSTANT;\r
+ return null; // we went past the stopAt\r
+ }\r
+\r
+ // read in the log record\r
+ byte[] data = input.getData();\r
+\r
+ if (data.length < recordLength)\r
+ {\r
+ // make a new array of sufficient size and reset the arrary\r
+ // in the input stream\r
+ data = new byte[recordLength];\r
+ input.setData(data);\r
+ }\r
+\r
+ // If the log is encrypted, we must do the filtering after\r
+ // reading and decryptiong the record.\r
+\r
+ if (logFactory.databaseEncrypted())\r
+ {\r
+ scan.readFully(data, 0, recordLength);\r
+ int len = logFactory.decrypt(data, 0, recordLength, data, 0);\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(len == recordLength);\r
+\r
+ input.setLimit(0, len);\r
+ }\r
+ else // no need to decrypt, only get the group and tid if we filter \r
+ {\r
+ if (groupmask == 0 && tranId == null)\r
+ {\r
+ // no filter, get the whole thing\r
+ scan.readFully(data, 0, recordLength);\r
+ input.setLimit(0, recordLength);\r
+ }\r
+ else\r
+ {\r
+ // Read only enough so that group and the tran id is in\r
+ // the data buffer. Group is stored as compressed int\r
+ // and tran id is stored as who knows what. read min\r
+ // of peekAmount or recordLength\r
+ readAmount = (recordLength > peekAmount) ?\r
+ peekAmount : recordLength; \r
+\r
+ // in the data buffer, we now have enough to peek\r
+ scan.readFully(data, 0, readAmount);\r
+ input.setLimit(0, readAmount);\r
+ }\r
+ }\r
+\r
+ lr = (LogRecord) input.readObject();\r
+ if (groupmask != 0 || tranId != null)\r
+ {\r
+ if (groupmask != 0 && (groupmask & lr.group()) == 0)\r
+ candidate = false; // no match, throw this log record out \r
+\r
+ if (candidate && tranId != null)\r
+ {\r
+ TransactionId tid = lr.getTransactionId();\r
+ if (!tid.equals(tranId)) // nomatch\r
+ candidate = false; // throw this log record out\r
+ }\r
+\r
+ // if this log record is not filtered out, we need to read\r
+ // in the rest of the log record to the input buffer.\r
+ // Except if it is an encrypted database, in which case the\r
+ // entire log record have already be read in for\r
+ // decryption.\r
+ if (candidate && !logFactory.databaseEncrypted())\r
+ {\r
+ // read the rest of the log into the buffer\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(readAmount > 0);\r
+\r
+ if (readAmount < recordLength)\r
+ {\r
+ // Need to remember where we are because the log\r
+ // record may have read part of it off the input\r
+ // stream already and that position is lost when we\r
+ // set limit again.\r
+ int inputPosition = input.getPosition();\r
+\r
+ scan.readFully(data, readAmount,\r
+ recordLength-readAmount); \r
+\r
+ input.setLimit(0, recordLength);\r
+ input.setPosition(inputPosition);\r
+ }\r
+ }\r
+ }\r
+\r
+ /*check if the logrecord length written before and after the \r
+ *log record are equal, if not the end of of the log is reached.\r
+ *This can happen if system crashed before writing the length field \r
+ *in the end of the records completely. If the length is partially\r
+ *written or not written at all it will not match with length written \r
+ *in the beginning of the log record. Currentlt preallocated files\r
+ *are filled with zeros, log record length can never be zero; \r
+ *if the lengths are not matching, end of the properly written log\r
+ *is reached.\r
+ *Note: In case of Non-preallocated files earlier fuzzy case check with log\r
+ * file lengths should have found the end. But in prellocated files, log file\r
+ *length is not sufficiant to find the log end. This check \r
+ *is must to find the end in preallocated log files. \r
+ */\r
+ // read the length after the log record and check it against the\r
+ // length before the log record, make sure we go to the correct\r
+ // place for skipped log record.\r
+ if (!candidate)\r
+ scan.seek(recordStartPosition - 4);\r
+ int checkLength = scan.readInt();\r
+ if (checkLength != recordLength && checkLength < recordLength)\r
+ {\r
+\r
+\r
+ //lengh written in the end of the log record should be always\r
+ //less then the length written in the beginning if the log\r
+ //record was half written before the crash.\r
+ if(checkLength < recordLength)\r
+ {\r
+ fuzzyLogEnd = true ;\r
+ return null;\r
+ }else\r
+ {\r
+ \r
+ //If checklength > recordLength then it can be not be a partial write\r
+ //probablly it is corrupted for some reason , this should never\r
+ //happen throw error in debug mode. In non debug case , let's\r
+ //hope it's only is wrong and system can proceed. \r
+ \r
+ if (SanityManager.DEBUG)\r
+ { \r
+ throw logFactory.markCorrupt\r
+ (StandardException.newException(\r
+ SQLState.LOG_RECORD_CORRUPTED, \r
+ new Long(checkLength),\r
+ new Long(recordLength),\r
+ new Long(currentInstant),\r
+ new Long(currentLogFileNumber)));\r
+\r
+ }\r
+ \r
+ //In non debug case, do nothing , let's hope it's only\r
+ //length part that is incorrect and system can proceed. \r
+ }\r
+\r
+ }\r
+\r
+ // next record start position is right after this record\r
+ recordStartPosition += recordLength + LogToFile.LOG_RECORD_OVERHEAD;\r
+ knownGoodLogEnd = LogCounter.makeLogInstantAsLong\r
+ (currentLogFileNumber, recordStartPosition);\r
+\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (recordStartPosition != scan.getFilePointer())\r
+ SanityManager.THROWASSERT(\r
+ "calculated end " + recordStartPosition + \r
+ " != real end " + scan.getFilePointer());\r
+ }\r
+ else\r
+ {\r
+ // seek to the start of the next log record\r
+ scan.seek(recordStartPosition);\r
+ }\r
+\r
+ // the scan is now positioned just past this log record and right\r
+ // at the beginning of the next log record\r
+\r
+\r
+ /** if the current log record is a checksum log record then\r
+ * using the information available in this record validate\r
+ * that data in the log file by matching the checksum in \r
+ * checksum log record and by recalculating the checksum for the \r
+ * specified length of the data in the log file. cheksum values\r
+ * should match unless the right was incomplete before the crash.\r
+ */\r
+ if(lr.isChecksum())\r
+ {\r
+ // checksum log record should not be returned to the logger recovery redo\r
+ // routines, it is just used to identify the incomplete log writes.\r
+\r
+ candidate = false;\r
+ Loggable op = lr.getLoggable(); \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DUMP_LOG_ONLY) ||\r
+ SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "scanned " + "Null" + " : " + op + \r
+ " instant = " + \r
+ LogCounter.toDebugString(currentInstant) + \r
+ " logEnd = " + LogCounter.toDebugString(knownGoodLogEnd));\r
+ }\r
+\r
+ ChecksumOperation clop = (ChecksumOperation) op;\r
+ int ckDataLength = clop.getDataLength(); \r
+ // resize the buffer to be size of checksum data length if required.\r
+ if (data.length < ckDataLength)\r
+ {\r
+ // make a new array of sufficient size and reset the arrary\r
+ // in the input stream\r
+ data = new byte[ckDataLength];\r
+ input.setData(data);\r
+ input.setLimit(0, ckDataLength);\r
+ }\r
+ \r
+ boolean validChecksum = false;\r
+ // check if the expected number of bytes by the checksum log\r
+ // record actually exist in the file and then verify if checksum\r
+ // is valid to identify any incomplete out of order writes.\r
+ if((recordStartPosition + ckDataLength) <= currentLogFileLength)\r
+ {\r
+ // read the data into the buffer\r
+ scan.readFully(data, 0, ckDataLength);\r
+ // verify the checksum \r
+ if(clop.isChecksumValid(data, 0 , ckDataLength))\r
+ validChecksum = true;\r
+ }\r
+\r
+\r
+ if(!validChecksum)\r
+ {\r
+ // declare that the end of the transaction log is fuzzy, checksum is invalid\r
+ // only when the writes are incomplete; this can happen\r
+ // only when writes at the end of the log were partially\r
+ // written before the crash. \r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(\r
+ LogToFile.DBG_FLAG, \r
+ "detected fuzzy log end on log file while doing checksum checks " + \r
+ currentLogFileNumber + \r
+ " checksum record start position " + recordStartPosition + \r
+ " file length " + currentLogFileLength + \r
+ " checksumDataLength=" + ckDataLength);\r
+ }\r
+ \r
+ }\r
+ \r
+ fuzzyLogEnd = true;\r
+ scan.close();\r
+ scan = null;\r
+ return null;\r
+ }\r
+\r
+ // reset the scan to the start of the next log record\r
+ scan.seek(recordStartPosition);\r
+ }\r
+\r
+\r
+ } while (candidate == false) ;\r
+\r
+ return lr;\r
+ }\r
+\r
+\r
+ /**\r
+ Reset the scan to the given LogInstant.\r
+\r
+ @param instant the position to reset to\r
+ @exception IOException scan cannot access the log at the new position.\r
+ @exception StandardException standard Derby error policy\r
+ */\r
+\r
+ public void resetPosition(LogInstant instant) \r
+ throws IOException, StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(instant != null);\r
+\r
+ long instant_long = ((LogCounter)instant).getValueAsLong();\r
+\r
+ if ((instant_long == LogCounter.INVALID_LOG_INSTANT) ||\r
+ (stopAt != LogCounter.INVALID_LOG_INSTANT &&\r
+ (scanDirection == FORWARD && instant_long > stopAt) ||\r
+ (scanDirection == FORWARD && instant_long < stopAt)))\r
+ {\r
+ close();\r
+\r
+ throw StandardException.newException(\r
+ SQLState.LOG_RESET_BEYOND_SCAN_LIMIT, \r
+ instant, new LogCounter(stopAt));\r
+ }\r
+ else\r
+ {\r
+ long fnum = ((LogCounter)instant).getLogFileNumber();\r
+\r
+ if (fnum != currentLogFileNumber)\r
+ {\r
+ if (SanityManager.DEBUG) \r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "Scan " + scanDirection +\r
+ " resetting to " + instant + \r
+ " need to switch log from " + \r
+ currentLogFileNumber + " to " + fnum);\r
+ }\r
+ }\r
+\r
+ scan.close();\r
+ scan = logFactory.getLogFileAtPosition(instant_long);\r
+\r
+ currentLogFileNumber= fnum;\r
+\r
+ if (scanDirection == FORWARD)\r
+ {\r
+ // NOTE: \r
+ //\r
+ // just get the length of the file without syncing.\r
+ // this only works because the only place forward scan is used\r
+ // right now is on recovery redo and nothing is being added to \r
+ // the current log file. When the forward scan is used for some\r
+ // other purpose, need to sync access to the end of the log\r
+ //\r
+ currentLogFileLength = scan.length();\r
+ }\r
+ }\r
+ else\r
+\r
+ {\r
+ long fpos = ((LogCounter)instant).getLogFilePosition();\r
+ scan.seek(fpos);\r
+\r
+ //\r
+ //RESOLVE: Can this be optimized? Does it belong here.\r
+ currentLogFileLength = scan.length();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "Scan reset to " + instant);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ currentInstant = instant_long;\r
+\r
+ //scan is being reset, it is possibly that, scan is doing a random \r
+ //access of the log file. set the knownGoodLogEnd to the instant\r
+ //scan is being reset to.\r
+ //Note: reset gets called with undo forward scan for CLR processing during \r
+ //recovery, if this value is not reset checks to find the end of log \r
+ //getNextRecordForward() will fail because undoscan scans log file\r
+ //back & forth to redo CLR's.\r
+ knownGoodLogEnd = currentInstant;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (SanityManager.DEBUG_ON(LogToFile.DBG_FLAG))\r
+ {\r
+ SanityManager.DEBUG(LogToFile.DBG_FLAG, \r
+ "Scan.getInstant reset to " + currentInstant + \r
+ LogCounter.toDebugString(currentInstant));\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ Return the log instant (as an integer) the scan is currently on - this is the log\r
+ instant of the log record that was returned by getNextRecord.\r
+ */\r
+ public long getInstant()\r
+ {\r
+ return currentInstant;\r
+ }\r
+\r
+ /**\r
+ Return the log instant at the end of the log record on the current\r
+ LogFile in the form of a log instant.\r
+ After the scan has been closed, the end of the last log record will be\r
+ returned except when the scan ended in an empty log file. In that\r
+ case, the start of this empty log file will be returned. (This is\r
+ done to make sure new log records are inserted into the newest log\r
+ file.)\r
+ */\r
+ public long getLogRecordEnd()\r
+ {\r
+ return knownGoodLogEnd;\r
+ }\r
+\r
+ /**\r
+ returns true if there is partially writen log records before the crash \r
+ in the last log file. Partiall wrires are identified during forward \r
+ redo scans for log recovery.\r
+ */\r
+ public boolean isLogEndFuzzy()\r
+ {\r
+ return fuzzyLogEnd;\r
+ }\r
+\r
+ /**\r
+ Return the log instant the scan is currently on - this is the log\r
+ instant of the log record that was returned by getNextRecord.\r
+ */\r
+ public LogInstant getLogInstant()\r
+ {\r
+ if (currentInstant == LogCounter.INVALID_LOG_INSTANT)\r
+ return null;\r
+ else\r
+ return new LogCounter(currentInstant);\r
+ }\r
+\r
+ /**\r
+ Close the scan.\r
+ */\r
+ public void close()\r
+ {\r
+ if (scan != null)\r
+ {\r
+ try \r
+ {\r
+ scan.close();\r
+ }\r
+ catch (IOException ioe)\r
+ {}\r
+\r
+ scan = null;\r
+ }\r
+\r
+ logFactory = null;\r
+ currentLogFileNumber = -1;\r
+ currentLogFileLength = -1;\r
+ // Do not reset knownGoodLogEnd, it needs to be available after the\r
+ // scan has closed.\r
+ currentInstant = LogCounter.INVALID_LOG_INSTANT;\r
+ stopAt = LogCounter.INVALID_LOG_INSTANT;\r
+ scanDirection = 0;\r
+ }\r
+\r
+}\r