/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.io.fs; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.Map; import org.tmatesoft.svn.core.ISVNCanceller; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.SVNProperties; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSRecoverer { private FSFS myOwner; private ISVNCanceller myCanceller; public FSRecoverer(FSFS owner, ISVNCanceller canceller) { myOwner = owner; myCanceller = canceller == null ? ISVNCanceller.NULL : canceller; } public void runRecovery() throws SVNException { FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(myOwner); synchronized (writeLock) { try { writeLock.lock(); recover(); } finally { writeLock.unlock(); FSWriteLock.release(writeLock); } } } private void recover() throws SVNException { String nextNodeID = null; String nextCopyID = null; long maxRev = getLargestRevision(); long youngestRev = myOwner.getYoungestRevision(); if (youngestRev > maxRev) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Expected current rev to be <= {0} but found {1}", new Object[] { String.valueOf(maxRev), String.valueOf(youngestRev) }); SVNErrorManager.error(err, SVNLogType.FSFS); } if (myOwner.getDBFormat() < FSFS.MIN_NO_GLOBAL_IDS_FORMAT) { long[] rootOffset = { -1 }; String[] maxNodeID = { "0" }; String[] maxCopyID = { "0" }; for (long rev = 0; rev <= maxRev; rev++) { myCanceller.checkCancelled(); FSFile revFile = null; try { revFile = myOwner.getPackOrRevisionFSFile(rev); FSRepositoryUtil.loadRootChangesOffset(myOwner, rev, revFile, rootOffset, null); findMaxIDs(rev, revFile, rootOffset[0], maxNodeID, maxCopyID); } finally { if (revFile != null) { revFile.close(); } } } nextNodeID = FSRepositoryUtil.generateNextKey(maxNodeID[0]); nextCopyID = FSRepositoryUtil.generateNextKey(maxCopyID[0]); } File revpropFile = null; try { revpropFile = myOwner.getRevisionPropertiesFile(maxRev, false); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NO_SUCH_REVISION) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Revision {0} has a revs file but no revprops file", String.valueOf(maxRev)); SVNErrorManager.error(err, SVNLogType.FSFS); } throw svne; } if (!revpropFile.isFile()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Revision {0} has a non-file where its revprops file should be", String.valueOf(maxRev)); SVNErrorManager.error(err, SVNLogType.FSFS); } try { myOwner.writeCurrentFile(maxRev, nextNodeID, nextCopyID); } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, SVNLogType.FSFS); } } private void findMaxIDs(long rev, FSFile revFile, long offset, String[] maxNodeID, String[] maxCopyID) throws SVNException { revFile.seek(offset); Map headers = null; try { headers = revFile.readHeader(); } finally{ revFile.close(); } String revNodeIDStr = (String) headers.get(FSRevisionNode.HEADER_ID); FSID revNodeID = FSID.fromString(revNodeIDStr); SVNNodeKind nodeKind = SVNNodeKind.parseKind((String) headers.get(FSRevisionNode.HEADER_TYPE)); if (nodeKind != SVNNodeKind.DIR) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Recovery encountered a non-directory node"); SVNErrorManager.error(err, SVNLogType.FSFS); } String textRep = (String) headers.get(FSRevisionNode.HEADER_TEXT); if (textRep == null) { return; } FSRevisionNode revNode = new FSRevisionNode(); revNode.setId(revNodeID); revNode.setType(nodeKind); FSRevisionNode.parseRepresentationHeader(textRep, revNode, null, true, false); if (revNode.getTextRepresentation().getRevision() != rev) { return; } revFile.seek(revNode.getTextRepresentation().getOffset()); FSInputStream.FSRepresentationState repState = FSInputStream.readRepresentationLine(revFile); if (repState.myIsDelta) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Recovery encountered a deltified directory representation"); SVNErrorManager.error(err, SVNLogType.FSFS); } SVNProperties rawEntries = revFile.readProperties(false, false); for (Iterator entriesIter = rawEntries.nameSet().iterator(); entriesIter.hasNext();) { String name = (String) entriesIter.next(); String unparsedEntry = rawEntries.getStringValue(name); int spaceInd = unparsedEntry.indexOf(' '); if (spaceInd == -1 || spaceInd == unparsedEntry.length() - 1 || spaceInd == 0) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Directory entry corrupt"); SVNErrorManager.error(err, SVNLogType.FSFS); } String kindStr = unparsedEntry.substring(0, spaceInd); SVNNodeKind kind = SVNNodeKind.parseKind(kindStr); if (kind != SVNNodeKind.DIR && kind != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Directory entry corrupt"); SVNErrorManager.error(err, SVNLogType.FSFS); } String rawID = unparsedEntry.substring(spaceInd + 1); FSID id = FSID.fromString(rawID); if (id.getRevision() != rev) { continue; } String nodeID = id.getNodeID(); String copyID = id.getCopyID(); if (nodeID.compareTo(maxNodeID[0]) > 0) { maxNodeID[0] = nodeID; } if (copyID.compareTo(maxCopyID[0]) > 0) { maxCopyID[0] = copyID; } if (kind == SVNNodeKind.FILE) { continue; } findMaxIDs(rev, revFile, id.getOffset(), maxNodeID, maxCopyID); } } private long getLargestRevision() throws SVNException { long right = 1; while (true) { try { myOwner.getPackOrRevisionFSFile(right); } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NO_SUCH_REVISION) { break; } throw svne; } right <<= 1; } long left = right >> 1; while (left + 1 < right) { long probe = left + (right - left)/2; try { myOwner.getPackOrRevisionFSFile(probe); left = probe; } catch (SVNException svne) { if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NO_SUCH_REVISION) { right = probe; } else { throw svne; } } } return left; } }