001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: ScavengerFileReader.java,v 1.13.2.4 2008/01/07 15:14:13 cwl Exp $
007: */
008:
009: package com.sleepycat.je.log;
010:
011: import java.io.IOException;
012: import java.nio.ByteBuffer;
013: import java.util.HashSet;
014: import java.util.Set;
015:
016: import com.sleepycat.je.DatabaseException;
017: import com.sleepycat.je.dbi.EnvironmentImpl;
018: import com.sleepycat.je.log.entry.LogEntry;
019: import com.sleepycat.je.utilint.DbLsn;
020:
021: /**
022: * A ScavengerFileReader reads the log backwards. If it encounters a checksum
023: * error, it goes to the start of that log file and reads forward until it
024: * encounters a checksum error. It then continues the reading backwards in the
025: * log.
026: *
027: * The caller may set "dumpCorruptedBounds" to true if information about the
028: * start and finish of the corrupted portion should be displayed on stderr.
029: *
030: * The caller is expected to implement processEntryCallback. This method is
031: * called once for each entry that the ScavengerFileReader finds in the log.
032: */
033: abstract public class ScavengerFileReader extends FileReader {
034:
035: /* A Set of the entry type numbers that this FileReader should dump. */
036: private Set targetEntryTypes;
037:
038: private int readBufferSize;
039:
040: /* True if reader should write corrupted boundaries to System.err. */
041: private boolean dumpCorruptedBounds;
042:
043: /**
044: * Create this reader to start at a given LSN.
045: */
046: public ScavengerFileReader(EnvironmentImpl env, int readBufferSize,
047: long startLsn, long finishLsn, long endOfFileLsn)
048: throws IOException, DatabaseException {
049:
050: super (env, readBufferSize, false, startLsn, null, // single file number
051: endOfFileLsn, finishLsn);
052:
053: this .readBufferSize = readBufferSize;
054:
055: /*
056: * Indicate that a checksum error should not shutdown the whole
057: * environment.
058: */
059: anticipateChecksumErrors = true;
060: targetEntryTypes = new HashSet();
061: dumpCorruptedBounds = false;
062: }
063:
064: /**
065: * Set to true if corrupted boundaries should be dumped to stderr.
066: */
067: public void setDumpCorruptedBounds(boolean dumpCorruptedBounds) {
068: this .dumpCorruptedBounds = dumpCorruptedBounds;
069: }
070:
071: /**
072: * Tell the reader that we are interested in these kind of entries.
073: */
074: public void setTargetType(LogEntryType type) {
075: targetEntryTypes.add(new Byte(type.getTypeNum()));
076: }
077:
078: /*
079: * For each entry that is selected, just call processEntryCallback.
080: */
081: protected boolean processEntry(ByteBuffer entryBuffer)
082: throws DatabaseException {
083:
084: LogEntryType lastEntryType = LogEntryType.findType(
085: currentEntryHeader.getType(), currentEntryHeader
086: .getVersion());
087: LogEntry entry = lastEntryType.getSharedLogEntry();
088: readEntry(entry, entryBuffer, true); // readFullItem
089: processEntryCallback(entry, lastEntryType);
090: return true;
091: }
092:
093: /*
094: * Method overriden by the caller. Each entry of the types selected
095: * is passed to this method.
096: */
097: abstract protected void processEntryCallback(LogEntry entry,
098: LogEntryType entryType) throws DatabaseException;
099:
100: /*
101: * Read the next entry. If a checksum exception is encountered, attempt
102: * to find the other side of the corrupted area and try to re-read this
103: * file.
104: */
105: public boolean readNextEntry() throws DatabaseException,
106: IOException {
107:
108: long saveCurrentEntryOffset = currentEntryOffset;
109: try {
110: return super .readNextEntry();
111: } catch (DbChecksumException DCE) {
112: resyncReader(DbLsn.makeLsn(readBufferFileNum,
113: saveCurrentEntryOffset), dumpCorruptedBounds);
114: return super .readNextEntry();
115: }
116: }
117:
118: /*
119: * A checksum error has been encountered. Go to the start of this log file
120: * and read forward until the lower side of the corrupted area has been
121: * found.
122: */
123: protected boolean resyncReader(long nextGoodRecordPostCorruption,
124: boolean showCorruptedBounds) throws DatabaseException,
125: IOException {
126:
127: LastFileReader reader = null;
128: long tryReadBufferFileNum = DbLsn
129: .getFileNumber(nextGoodRecordPostCorruption);
130:
131: while (tryReadBufferFileNum >= 0) {
132: try {
133: reader = new LastFileReader(envImpl, readBufferSize,
134: new Long(tryReadBufferFileNum));
135: break;
136: } catch (DbChecksumException DCE) {
137:
138: /*
139: * We found a checksum error reading the header of this file
140: * so skip to a completely earlier file.
141: */
142: tryReadBufferFileNum--;
143: continue;
144: }
145: }
146:
147: boolean switchedFiles = tryReadBufferFileNum != DbLsn
148: .getFileNumber(nextGoodRecordPostCorruption);
149:
150: if (!switchedFiles) {
151:
152: /*
153: * This reader will not throw an exception if a checksum error is
154: * hit -- it will just exit.
155: */
156: while (reader.readNextEntry()) {
157: }
158: }
159:
160: long lastUsedLsn = reader.getLastValidLsn();
161: long nextAvailableLsn = reader.getEndOfLog();
162: if (showCorruptedBounds) {
163: System.err
164: .println("A checksum error was found in the log.");
165: System.err.println("Corruption begins at LSN:\n "
166: + DbLsn.toString(nextAvailableLsn));
167: System.err
168: .println("Last known good record before corruption is at LSN:\n "
169: + DbLsn.toString(lastUsedLsn));
170: System.err
171: .println("Next known good record after corruption is at LSN:\n "
172: + DbLsn
173: .toString(nextGoodRecordPostCorruption));
174: }
175:
176: startLsn = lastUsedLsn;
177: initStartingPosition(nextAvailableLsn, null);
178: if (switchedFiles) {
179: currentEntryPrevOffset = 0;
180: }
181: /* Indicate resync is permitted so don't throw exception. */
182: return true;
183: }
184:
185: /**
186: * @return true if this reader should process this entry, or just skip
187: * over it.
188: */
189: protected boolean isTargetEntry(byte logEntryTypeNumber,
190: byte logEntryTypeVersion) {
191: if (targetEntryTypes.size() == 0) {
192: /* We want to dump all entry types. */
193: return true;
194: } else {
195: return targetEntryTypes.contains(new Byte(
196: logEntryTypeNumber));
197: }
198: }
199: }
|