package com.sleepycat.je.log; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.utilint.DbLsn; import com.sleepycat.je.utilint.Tracer; import de.ovgu.cide.jakutil.*; /** * LastFileReader traverses the last log file, doing checksums and looking for * the end of the log. Different log types can be registered with it and it * will remember the last occurrence of targetted entry types. */ public class LastFileReader extends FileReader { private Set trackableEntries; private long nextUnprovenOffset; private long lastValidOffset; private LogEntryType entryType; private Map lastOffsetSeen; /** * This file reader is always positioned at the last file. */ public LastFileReader( EnvironmentImpl env, int readBufferSize) throws IOException, DatabaseException { super(env,readBufferSize,true,DbLsn.NULL_LSN,new Long(-1),DbLsn.NULL_LSN,DbLsn.NULL_LSN); trackableEntries=new HashSet(); lastOffsetSeen=new HashMap(); lastValidOffset=0; nextUnprovenOffset=0; anticipateChecksumErrors=true; } /** * Ctor which allows passing in the file number we want to read to the end * of. This is used by the ScavengerFileReader when it encounters a bad * log record in the middle of a file. */ public LastFileReader( EnvironmentImpl env, int readBufferSize, Long specificFileNumber) throws IOException, DatabaseException { super(env,readBufferSize,true,DbLsn.NULL_LSN,specificFileNumber,DbLsn.NULL_LSN,DbLsn.NULL_LSN); trackableEntries=new HashSet(); lastOffsetSeen=new HashMap(); lastValidOffset=0; nextUnprovenOffset=0; anticipateChecksumErrors=true; } /** * Override so that we always start at the last file. */ protected void initStartingPosition( long endOfFileLsn, Long singleFileNum) throws IOException, DatabaseException { eof=false; Long lastNum=((singleFileNum != null) && (singleFileNum.longValue() >= 0)) ? singleFileNum : fileManager.getLastFileNum(); FileHandle fileHandle=null; readBufferFileEnd=0; long fileLen=0; while ((fileHandle == null) && !eof) { if (lastNum == null) { eof=true; } else { try { readBufferFileNum=lastNum.longValue(); fileHandle=fileManager.getFileHandle(readBufferFileNum); fileLen=fileHandle.getFile().length(); if (fileLen <= FileManager.firstLogEntryOffset()) { lastNum=fileManager.getFollowingFileNum(lastNum.longValue(),false); this.hook477(fileHandle); fileHandle=null; } } catch ( DatabaseException e) { lastNum=attemptToMoveBadFile(e); fileHandle=null; } finally { this.hook478(fileHandle); } } } nextEntryOffset=0; } /** * Something is wrong with this file. If there is no data in this file (the * header is <= the file header size) then move this last file aside and * search the next "last" file. If the last file does have data in it, * throw an exception back to the application, since we're not sure what to * do now. */ private Long attemptToMoveBadFile( DatabaseException origException) throws DatabaseException, IOException { String fileName=fileManager.getFullFileNames(readBufferFileNum)[0]; File problemFile=new File(fileName); Long lastNum=null; if (problemFile.length() <= FileManager.firstLogEntryOffset()) { fileManager.clear(); lastNum=fileManager.getFollowingFileNum(readBufferFileNum,false); fileManager.renameFile(readBufferFileNum,FileManager.BAD_SUFFIX); } else { throw origException; } return lastNum; } public void setEndOfFile() throws IOException, DatabaseException { fileManager.truncateLog(readBufferFileNum,nextUnprovenOffset); } /** * @return The LSN to be used for the next log entry. */ public long getEndOfLog(){ return DbLsn.makeLsn(readBufferFileNum,nextUnprovenOffset); } public long getLastValidLsn(){ return DbLsn.makeLsn(readBufferFileNum,lastValidOffset); } public long getPrevOffset(){ return lastValidOffset; } public LogEntryType getEntryType(){ return entryType; } /** * Tell the reader that we are interested in these kind of entries. */ public void setTargetType( LogEntryType type){ trackableEntries.add(type); } /** * @return The last LSN seen in the log for this kind of entry, or null. */ public long getLastSeen( LogEntryType type){ Long typeNumber=(Long)lastOffsetSeen.get(type); if (typeNumber != null) { return DbLsn.makeLsn(readBufferFileNum,typeNumber.longValue()); } else { return DbLsn.NULL_LSN; } } /** * Validate the checksum on each entry, see if we should remember the LSN * of this entry. */ protected boolean processEntry( ByteBuffer entryBuffer){ entryBuffer.position(entryBuffer.position() + currentEntrySize); entryType=new LogEntryType(currentEntryTypeNum,currentEntryTypeVersion); if (trackableEntries.contains(entryType)) { lastOffsetSeen.put(entryType,new Long(currentEntryOffset)); } return true; } /** * readNextEntry will stop at a bad entry. * @return true if an element has been read. */ public boolean readNextEntry() throws DatabaseException, IOException { boolean foundEntry=false; nextUnprovenOffset=nextEntryOffset; try { foundEntry=super.readNextEntry(); lastValidOffset=currentEntryOffset; } catch ( DbChecksumException e) { Tracer.trace(Level.INFO,env,"Found checksum exception while searching " + " for end of log. Last valid entry is at " + DbLsn.toString(DbLsn.makeLsn(readBufferFileNum,lastValidOffset)) + " Bad entry is at "+ DbLsn.makeLsn(readBufferFileNum,currentEntryOffset)); } return foundEntry; } protected void hook477( FileHandle fileHandle) throws IOException, DatabaseException { } protected void hook478( FileHandle fileHandle) throws IOException, DatabaseException { } }