/*
* ====================================================================
* 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.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.SVNRevisionProperty;
import org.tmatesoft.svn.core.internal.delta.SVNDeltaReader;
import org.tmatesoft.svn.core.internal.io.fs.revprop.SVNFSFSPackedRevProps;
import org.tmatesoft.svn.core.internal.io.fs.revprop.SVNFSFSPackedRevPropsManifest;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.internal.wc.SVNConfigFile;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileListUtil;
import org.tmatesoft.svn.core.internal.wc.SVNFileType;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.SVNWCProperties;
import org.tmatesoft.svn.core.io.ISVNDeltaConsumer;
import org.tmatesoft.svn.core.io.ISVNLockHandler;
import org.tmatesoft.svn.core.io.SVNLocationEntry;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.diff.SVNDeltaProcessor;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class FSFS {
public static final String DB_DIR = "db";
public static final String REVS_DIR = "revs";
public static final String REPOS_FORMAT_FILE = "format";
public static final String DB_FORMAT_FILE = "format";
public static final String DB_LOGS_LOCK_FILE = "db-logs.lock";
public static final String DB_LOCK_FILE = "db.lock";
public static final String CURRENT_FILE = "current";
public static final String UUID_FILE = "uuid";
public static final String FS_TYPE_FILE = "fs-type";
public static final String TXN_CURRENT_FILE = "txn-current";
public static final String MIN_UNPACKED_REV_FILE = "min-unpacked-rev";
public static final String TXN_CURRENT_LOCK_FILE = "txn-current-lock";
public static final String REVISION_PROPERTIES_DIR = "revprops";
public static final String WRITE_LOCK_FILE = "write-lock";
public static final String LOCKS_DIR = "locks";
public static final String DAV_DIR = "dav";
public static final String TRANSACTIONS_DIR = "transactions";
public static final String TRANSACTION_PROTOS_DIR = "txn-protorevs";
public static final String NODE_ORIGINS_DIR = "node-origins";
public static final String MANIFEST_FILE = "manifest";
public static final String REP_CACHE_DB = "rep-cache.db";
public static final String PACK_EXT = ".pack";
public static final String PACK_KIND_PACK = "pack";
public static final String PACK_KIND_MANIFEST = "manifest";
public static final String ENABLE_REP_SHARING_OPTION = "enable-rep-sharing";
public static final String REP_SHARING_SECTION = "rep-sharing";
public static final String PACKED_REVPROPS_SECTION = "packed-revprops";
public static final String COMPRESS_PACKED_REVPROPS_OPTION = "compress-packed-revprops";
public static final String REVPROP_PACK_SIZE_OPTION = "revprop-pack-size";
public static final String PATH_CONFIG = "fsfs.conf";
public static final String TXN_PATH_EXT = ".txn";
public static final String TXN_MERGEINFO_PATH = "mergeinfo";
public static final String TXN_PATH_EXT_CHILDREN = ".children";
public static final String PATH_PREFIX_NODE = "node.";
public static final String TXN_PATH_EXT_PROPS = ".props";
public static final String SVN_OPAQUE_LOCK_TOKEN = "opaquelocktoken:";
public static final String TXN_PATH_REV = "rev";
public static final String PATH_LOCK_KEY = "path";
public static final String CHILDREN_LOCK_KEY = "children";
public static final String TOKEN_LOCK_KEY = "token";
public static final String OWNER_LOCK_KEY = "owner";
public static final String IS_DAV_COMMENT_LOCK_KEY = "is_dav_comment";
public static final String CREATION_DATE_LOCK_KEY = "creation_date";
public static final String EXPIRATION_DATE_LOCK_KEY = "expiration_date";
public static final String COMMENT_LOCK_KEY = "comment";
public static final String PRE_12_COMPAT_UNNEEDED_FILE_CONTENTS =
"This file is not used by Subversion 1.3.x or later." +
"However, its existence is required for compatibility with" +
"Subversion 1.2.x or earlier.";
public static final int DIGEST_SUBDIR_LEN = 3;
public static final int REPOSITORY_FORMAT = 5;
public static final int REPOSITORY_FORMAT_LEGACY = 3;
public static final int DB_FORMAT_PRE_17 = 4;
public static final int DB_FORMAT = 6;
public static final int DB_FORMAT_LOW = 1;
public static final int LAYOUT_FORMAT_OPTION_MINIMAL_FORMAT = 3;
public static final int MIN_CURRENT_TXN_FORMAT = 3;
public static final int MIN_PROTOREVS_DIR_FORMAT = 3;
public static final int MIN_NO_GLOBAL_IDS_FORMAT = 3;
public static final int MIN_MERGE_INFO_FORMAT = 3;
public static final int MIN_REP_SHARING_FORMAT = 4;
public static final int MIN_PACKED_FORMAT = 4;
public static final int MIN_KIND_IN_CHANGED_FORMAT = 4;
public static final int MIN_PACKED_REVPROP_SQLITE_DEV_FORMAT = 5;
public static final int MIN_PACKED_REVPROP_FORMAT = 6;
//TODO: we should be able to change this via some option
private static long DEFAULT_MAX_FILES_PER_DIRECTORY = 1000;
private static final String DB_TYPE = "fsfs";
public static final String REVISION_PROPERTIES_DB = "revprops.db";
public static final String REVISION_PROPERTIES_TABLE = "revprop";
public static final String MIN_UNPACKED_REV = "min-unpacked-rev";
public static final boolean DB_FORMAT_PRE_17_USE_AS_DEFAULT = true;
//public static final boolean DB_FORMAT_PRE_17_USE_AS_DEFAULT = false;
private int myDBFormat;
private int myReposFormat;
private String myUUID;
private String myFSType;
private File myRepositoryRoot;
private File myRevisionsRoot;
private File myRevisionPropertiesRoot;
private File myTransactionsRoot;
private File myLocksRoot;
private File myDBRoot;
private File myWriteLockFile;
private File myCurrentFile;
private File myTransactionCurrentFile;
private File myTransactionCurrentLockFile;
private File myTransactionProtoRevsRoot;
private File myNodeOriginsDir;
private File myRepositoryFormatFile;
private File myDBFormatFile;
private File myUUIDFile;
private File myFSTypeFile;
private File myMinUnpackedRevFile;
private File myRepositoryCacheFile;
private long myMaxFilesPerDirectory;
private long myYoungestRevisionCache;
private long myMinUnpackedRevision;
private SVNConfigFile myConfig;
private IFSRepresentationCacheManager myReposCacheManager;
private long myMinUnpackedRevProp;
private boolean myIsHooksEnabled;
private boolean myCompressPackedRevprops;
private long myRevpropPackSize;
public FSFS(File repositoryRoot) {
myRepositoryRoot = repositoryRoot;
myMaxFilesPerDirectory = 0;
setHooksEnabled(true);
}
public void setHooksEnabled(boolean enabled) {
myIsHooksEnabled = enabled;
}
public boolean isHooksEnabled() {
return myIsHooksEnabled;
}
public int getDBFormat() {
return myDBFormat;
}
public long getMaxFilesPerDirectory() {
return myMaxFilesPerDirectory;
}
public int getReposFormat() {
return myReposFormat;
}
public void open() throws SVNException {
openRoot();
openDB();
}
public void close() throws SVNException {
if (myReposCacheManager != null) {
myReposCacheManager.close();
myReposCacheManager = null;
}
}
public void openForRecovery() throws SVNException {
openRoot();
//create new current file
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(this);
synchronized (writeLock) {
try {
writeLock.lock();
try {
SVNFileUtil.createFile(getCurrentFile(), "0 1 1\n", "US-ASCII");
} catch (SVNException svne) {
//ignore errors
}
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
openDB();
}
public void openRoot() throws SVNException {
// repo format /root/format
FSFile formatFile = new FSFile(getRepositoryFormatFile());
int format = -1;
try {
format = formatFile.readInt();
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_VERSION_FILE_FORMAT,
"First line of ''{0}'' contains non-digit", formatFile.getFile());
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
formatFile.close();
}
if (format != REPOSITORY_FORMAT && format != REPOSITORY_FORMAT_LEGACY) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_UNSUPPORTED_VERSION,
"Expected repository format ''{0}'' or " +
"''{1}''; found format ''{2}''",
new Object[] {new Integer(REPOSITORY_FORMAT_LEGACY),
new Integer(REPOSITORY_FORMAT),
new Integer(format)});
SVNErrorManager.error(err, SVNLogType.FSFS);
}
myReposFormat = format;
}
public void openDB() throws SVNException {
int format = readDBFormat();
FSRepositoryUtil.checkReposDBFormat(format);
myDBFormat = format;
// fs type /root/db/fs-type
getFSType();
if (myDBFormat >= MIN_PACKED_FORMAT) {
getMinUnpackedRev();
}
boolean isRepSharingAllowed = true;
SVNConfigFile config = loadConfig();
if (config != null) {
String optionValue = config.getPropertyValue(REP_SHARING_SECTION, ENABLE_REP_SHARING_OPTION);
isRepSharingAllowed = DefaultSVNOptions.getBooleanValue(optionValue, true);
}
if (myDBFormat >= MIN_REP_SHARING_FORMAT && isRepSharingAllowed) {
myReposCacheManager = FSRepresentationCacheUtil.open(this);
}
File dbCurrentFile = getCurrentFile();
if (!(dbCurrentFile.exists() && dbCurrentFile.canRead())) {
if (myReposCacheManager != null) {
myReposCacheManager.close();
myReposCacheManager = null;
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Can''t open file ''{0}''", dbCurrentFile);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
/* Open the revprops db. */
if (myDBFormat >= MIN_PACKED_REVPROP_FORMAT) {
updateMinUnpackedRevProp();
myCompressPackedRevprops = DefaultSVNOptions.getBooleanValue(config.getPropertyValue(PACKED_REVPROPS_SECTION, COMPRESS_PACKED_REVPROPS_OPTION), false);
String revpropPackSizeString = config.getPropertyValue(PACKED_REVPROPS_SECTION, REVPROP_PACK_SIZE_OPTION);
long defaultRevpropPackSize = myCompressPackedRevprops ? 0x100 : 0x40;
long revpropPackSize = defaultRevpropPackSize;
if (revpropPackSizeString != null) {
try {
revpropPackSize = Long.parseLong(revpropPackSizeString);
} catch (NumberFormatException e) {
//ignore
}
}
myRevpropPackSize = revpropPackSize;
} else {
myRevpropPackSize = 0x10000;
myCompressPackedRevprops = false;
}
}
public String getFSType() throws SVNException {
if (myFSType == null) {
// fs type /root/db/fs-type
FSFile fsTypeFile = new FSFile(getFSTypeFile());
try {
myFSType = fsTypeFile.readLine(128);
} finally {
fsTypeFile.close();
}
if (!DB_TYPE.equals(myFSType)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_UNKNOWN_FS_TYPE,
"Unsupported fs type ''{0}''", myFSType);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
return myFSType;
}
public int readDBFormat() throws SVNException {
int format = -1;
// fs format /root/db/format
FSFile formatFile = new FSFile(getDBFormatFile());
try {
format = formatFile.readInt();
readOptions(formatFile, format);
} catch (SVNException svne) {
if (svne.getCause() instanceof FileNotFoundException) {
format = DB_FORMAT_LOW;
} else if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.STREAM_UNEXPECTED_EOF) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_VERSION_FILE_FORMAT,
"Can''t read first line of format file ''{0}''", formatFile.getFile());
SVNErrorManager.error(err, SVNLogType.FSFS);
} else {
throw svne;
}
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_VERSION_FILE_FORMAT,
"Format file ''{0}'' contains an unexpected non-digit", formatFile.getFile());
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
formatFile.close();
}
return format;
}
public String getUUID() throws SVNException {
if(myUUID == null) {
// uuid
FSFile formatFile = new FSFile(getUUIDFile());
try {
myUUID = formatFile.readLine(38);
} finally {
formatFile.close();
}
}
return myUUID;
}
public File getDBRoot() {
if (myDBRoot == null) {
myDBRoot = new File(myRepositoryRoot, DB_DIR);
}
return myDBRoot;
}
public File getWriteLockFile() {
if (myWriteLockFile == null) {
myWriteLockFile = new File(getDBRoot(), WRITE_LOCK_FILE);
}
return myWriteLockFile;
}
public File getUUIDFile() {
if (myUUIDFile == null) {
myUUIDFile = new File(getDBRoot(), UUID_FILE);
}
return myUUIDFile;
}
public File getDBRevsDir() {
if (myRevisionsRoot == null) {
myRevisionsRoot = new File(getDBRoot(), REVS_DIR);
}
return myRevisionsRoot;
}
public File getDBLocksDir() {
if (myLocksRoot == null) {
myLocksRoot = new File(getDBRoot(), LOCKS_DIR);
}
return myLocksRoot;
}
public File getFSTypeFile() {
if (myFSTypeFile == null) {
myFSTypeFile = new File(getDBRoot(), FS_TYPE_FILE);
}
return myFSTypeFile;
}
public File getTransactionsParentDir(){
if (myTransactionsRoot == null) {
myTransactionsRoot = new File(getDBRoot(), TRANSACTIONS_DIR);
}
return myTransactionsRoot;
}
public File getRepositoryRoot(){
return myRepositoryRoot;
}
public File getRevisionPropertiesRoot() {
if (myRevisionPropertiesRoot == null) {
myRevisionPropertiesRoot = new File(getDBRoot(), REVISION_PROPERTIES_DIR);
}
return myRevisionPropertiesRoot;
}
public File getRepositoryFormatFile(){
if (myRepositoryFormatFile == null) {
myRepositoryFormatFile = new File(myRepositoryRoot, REPOS_FORMAT_FILE);
}
return myRepositoryFormatFile;
}
public File getDBFormatFile() {
if (myDBFormatFile == null) {
myDBFormatFile = new File(getDBRoot(), DB_FORMAT_FILE);
}
return myDBFormatFile;
}
public File getNodeOriginsDir() {
if (myNodeOriginsDir == null) {
myNodeOriginsDir = new File(getDBRoot(), NODE_ORIGINS_DIR);
}
return myNodeOriginsDir;
}
public File getCurrentFile() {
if(myCurrentFile == null){
myCurrentFile = new File(getDBRoot(), CURRENT_FILE);
}
return myCurrentFile;
}
public File getRepositoryCacheFile() {
if (myRepositoryCacheFile == null) {
myRepositoryCacheFile = new File(getDBRoot(), REP_CACHE_DB);
}
return myRepositoryCacheFile;
}
public File getDBLogsLockFile() throws SVNException {
File lockFile = new File(getDBRoot(), LOCKS_DIR + "/" + DB_LOGS_LOCK_FILE);
if (!lockFile.exists()) {
try {
SVNFileUtil.createFile(lockFile, PRE_12_COMPAT_UNNEEDED_FILE_CONTENTS, "US-ASCII");
} catch (SVNException svne) {
SVNErrorMessage err = svne.getErrorMessage().wrap("Creating db logs lock file");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
return lockFile;
}
public long getDatedRevision(Date date) throws SVNException {
long latest = getYoungestRevision();
long top = latest;
long bottom = 0;
long middle;
Date currentTime = null;
while (bottom <= top) {
middle = (top + bottom) / 2;
currentTime = getRevisionTime(middle);
if (currentTime.compareTo(date) > 0) {
if ((middle - 1) < 0) {
return 0;
}
Date prevTime = getRevisionTime(middle - 1);
if (prevTime.compareTo(date) < 0) {
return middle - 1;
}
top = middle - 1;
} else if (currentTime.compareTo(date) < 0) {
if ((middle + 1) > latest) {
return latest;
}
Date nextTime = getRevisionTime(middle + 1);
if (nextTime.compareTo(date) > 0) {
return middle;
}
bottom = middle + 1;
} else {
return middle;
}
}
return 0;
}
public long getYoungestRevision() throws SVNException {
FSFile file = new FSFile(getCurrentFile());
try {
String line = file.readLine(180);
int spaceIndex = line.indexOf(' ');
if (spaceIndex > 0) {
myYoungestRevisionCache = Long.parseLong(line.substring(0, spaceIndex));
} else {
myYoungestRevisionCache = Long.parseLong(line);
}
return myYoungestRevisionCache;
} catch (NumberFormatException nfe) {
myYoungestRevisionCache = 0;
} finally {
file.close();
}
return myYoungestRevisionCache;
}
public long getMinUnpackedRev() throws SVNException {
FSFile file = new FSFile(getMinUnpackedRevFile());
try {
myMinUnpackedRevision = file.readLong();
return myMinUnpackedRevision;
} catch (NumberFormatException nfe) {
myMinUnpackedRevision = 0;
} finally {
file.close();
}
return myMinUnpackedRevision;
}
public void upgrade() throws SVNException {
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(this);
synchronized (writeLock) {
try {
writeLock.lock();
if (myDBFormat == DB_FORMAT) {
return;
}
if (myDBFormat < MIN_CURRENT_TXN_FORMAT) {
File txnCurrentFile = getTransactionCurrentFile();
SVNFileUtil.createFile(txnCurrentFile, "0\n", "US-ASCII");
SVNFileUtil.createEmptyFile(getTransactionCurrentLockFile());
}
if (myDBFormat < MIN_PROTOREVS_DIR_FORMAT) {
File txnProtoRevsDir = getTransactionProtoRevsDir();
txnProtoRevsDir.mkdirs();
}
if (myDBFormat < MIN_PACKED_FORMAT) {
SVNFileUtil.createFile(getMinUnpackedRevFile(), "0\n", "US-ASCII");
}
if (myDBFormat < MIN_REP_SHARING_FORMAT ) {
SVNFileUtil.createFile(getMinUnpackedRevFile(), "0\n", "US-ASCII");
}
if (myDBFormat < MIN_PACKED_REVPROP_FORMAT) {
SVNFileUtil.createFile(getMinUnpackedRevPropPath(),"0\n", "US-ASCII");
}
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
// force reopen to create db.
close();
open();
}
}
public void writeDBFormat(int format, long maxFilesPerDir, boolean overwrite) throws SVNException {
File formatFile = getDBFormatFile();
SVNErrorManager.assertionFailure(format >= 1 && format <= DB_FORMAT, "unexpected format " + String.valueOf(format), SVNLogType.FSFS);
String contents = null;
if (format >= LAYOUT_FORMAT_OPTION_MINIMAL_FORMAT) {
if (maxFilesPerDir > 0) {
contents = format + "\nlayout sharded " + maxFilesPerDir + "\n";
} else {
contents = format + "\nlayout linear";
}
} else {
contents = format + "\n";
}
if (!overwrite) {
SVNFileUtil.createFile(formatFile, contents, "US-ASCII");
} else {
File tmpFile = SVNFileUtil.createUniqueFile(formatFile.getParentFile(), formatFile.getName(), ".tmp", false);
OutputStream os = null;
try {
os = SVNFileUtil.openFileForWriting(tmpFile);
os.write(contents.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage error = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getMessage());
SVNErrorManager.error(error, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(os);
}
if (SVNFileUtil.isWindows) {
SVNFileUtil.setReadonly(formatFile, false);
}
SVNFileUtil.rename(tmpFile, formatFile);
}
SVNFileUtil.setReadonly(formatFile, true);
}
public SVNProperties getRevisionProperties(long revision) throws SVNException {
try{
if (!SVNRevision.isValidRevisionNumber(revision)) {
revision = getYoungestRevision();
}
return readRevisionProperties(revision);
} catch(SVNException e ) {
if(e.getErrorMessage().getErrorCode()==SVNErrorCode.FS_NO_SUCH_REVISION && myDBFormat >= MIN_PACKED_REVPROP_FORMAT ) {
updateMinUnpackedRevProp();
return readRevisionProperties(revision);
}
throw e;
}
}
private SVNProperties readRevisionProperties(long revision) throws SVNException {
ensureRevisionsExists(revision);
SVNProperties properties = null;
if (!isPackedRevisionProperties(revision)) {
FSFile file = new FSFile(getRevisionPropertiesFile(revision, false));
try {
properties = file.readProperties(false, true);
} catch (SVNException e) {
// TODO file may not exist, we need to read from pack
properties = null;
throw e;
} finally {
file.close();
}
}
if (myDBFormat >= MIN_PACKED_REVPROP_FORMAT && properties == null) {
// read packed revision props
return readPackedRevisionProperties(revision);
// TODO wrap exception, do retry
}
return properties == null ? new SVNProperties() : properties;
}
private SVNProperties readPackedRevisionProperties(long revision) throws SVNException {
if (!isPackedRevisionProperties(revision)) {
updateMinUnpackedRevProp();
}
if (!isPackedRevisionProperties(revision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such packed revision {0}", (Long) revision);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
final File packFile = getPackedRevPropFile(revision);
final SVNFSFSPackedRevProps packedRevProps = SVNFSFSPackedRevProps.fromPackFile(packFile);
final SVNProperties properties = packedRevProps.parseProperties(revision);
return properties == null ? new SVNProperties() : properties;
}
private File getPackedRevPropFile(long revision) throws SVNException {
final File packShardDirectory = getPackedRevPropsShardPath(revision);
final File manifestFile = new File(packShardDirectory, MANIFEST_FILE);
final SVNFSFSPackedRevPropsManifest manifest = SVNFSFSPackedRevPropsManifest.fromFile(manifestFile, revision, myMaxFilesPerDirectory);
return new File(packShardDirectory, manifest.getPackName(revision));
}
private static long decodeUncompressedSize(InputStream inputStream, int lengthRecordSize, int[] outputBytesRead) throws SVNException {
int temp = 0;
int bytesRead = 0;
while (true) {
if (lengthRecordSize == bytesRead) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.SVNDIFF_INVALID_HEADER, "Decompression of svndiff data failed: size too large");
SVNErrorManager.error(errorMessage, SVNLogType.FSFS);
}
int c = 0;
try {
c = inputStream.read();
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR);
SVNErrorManager.error(errorMessage, e, SVNLogType.FSFS);
}
if (c < 0) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.SVNDIFF_UNEXPECTED_END, "Decompression of svndiff data failed: no size");
SVNErrorManager.error(errorMessage, SVNLogType.FSFS);
}
bytesRead++;
temp = (temp << 7) | (c & 0x7f);
if (c < 0x80) {
outputBytesRead[0] = bytesRead;
return temp;
}
}
}
private static SVNProperties readProperties(byte[] propsData) throws SVNException {
final FSFile fsFile = new FSFile(propsData);
try {
return fsFile.readProperties(false, true);
} finally {
fsFile.close();
}
}
private static long readNumber(BufferedReader reader) throws SVNException, IOException {
final String text = reader.readLine();
if (text == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Unexpected EOF");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
try {
return Long.parseLong(text);
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Number ''{0}'' invalid or too large", text);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return -1;
}
private static long readNumber(InputStream inputStream) throws SVNException {
char[] digits = new char[20];
int digitsCount = 0;
while (true) {
int c = 0;
try {
c = inputStream.read();
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR);
SVNErrorManager.error(errorMessage, e, SVNLogType.FSFS);
}
if (c < 0) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT);
SVNErrorManager.error(errorMessage, SVNLogType.FSFS);
}
if (c != '\n' && (c < '0'|| c > '9')) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT);
SVNErrorManager.error(errorMessage, SVNLogType.FSFS);
}
if (c == '\n') {
return Long.parseLong(new String(digits, 0, digitsCount));
}
digits[digitsCount] = (char) c;
digitsCount++;
}
}
private boolean isPackedRevisionProperties(long revision) {
return revision < myMinUnpackedRevProp && revision != 0 && myDBFormat >= MIN_PACKED_REVPROP_FORMAT;
}
public FSRevisionRoot createRevisionRoot(long revision) throws SVNException {
ensureRevisionsExists(revision);
return new FSRevisionRoot(this, revision);
}
public FSTransactionRoot createTransactionRoot(FSTransactionInfo txn) throws SVNException {
SVNProperties txnProps = getTransactionProperties(txn.getTxnId());
int flags = 0;
if (txnProps.getStringValue(SVNProperty.TXN_CHECK_OUT_OF_DATENESS) != null) {
flags |= FSTransactionRoot.SVN_FS_TXN_CHECK_OUT_OF_DATENESS;
}
if (txnProps.getStringValue(SVNProperty.TXN_CHECK_LOCKS) != null) {
flags |= FSTransactionRoot.SVN_FS_TXN_CHECK_LOCKS;
}
return new FSTransactionRoot(this, txn.getTxnId(), txn.getBaseRevision(), flags);
}
public FSTransactionInfo openTxn(String txnName) throws SVNException {
SVNFileType kind = SVNFileType.getType(getTransactionDir(txnName));
if (kind != SVNFileType.DIRECTORY) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_TRANSACTION, "No such transaction");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSTransactionRoot txnRoot = new FSTransactionRoot(this, txnName, -1, 0);
FSTransactionInfo localTxn = txnRoot.getTxn();
return new FSTransactionInfo(localTxn.getBaseRevision(), txnName);
}
public FSRevisionNode getRevisionNode(FSID id) throws SVNException {
FSFile revisionFile = null;
if (id.isTxn()) {
File file = new File(getTransactionDir(id.getTxnID()), PATH_PREFIX_NODE + id.getNodeID() + "." + id.getCopyID());
revisionFile = new FSFile(file);
} else {
revisionFile = openAndSeekRevision(id.getRevision(), id.getOffset());
}
Map headers = null;
try {
headers = revisionFile.readHeader();
} finally{
revisionFile.close();
}
FSRevisionNode node = FSRevisionNode.fromMap(headers);
if (node.isFreshTxnRoot()) {
node.setFreshRootPredecessorId(node.getPredecessorId());
}
return node;
}
public Map getDirContents(FSRevisionNode revNode) throws SVNException {
FSRepresentation txtRep = revNode.getTextRepresentation();
if (txtRep != null && txtRep.isTxn()) {
FSFile childrenFile = getTransactionRevisionNodeChildrenFile(revNode.getId());
Map entries = null;
try {
SVNProperties rawEntries = childrenFile.readProperties(false, false);
rawEntries.putAll(childrenFile.readProperties(true, false));
rawEntries.removeNullValues();
entries = parsePlainRepresentation(rawEntries, true);
} finally {
childrenFile.close();
}
return entries;
} else if (txtRep != null) {
return parsePlainRepresentation(parseProperties(txtRep), false);
}
return new SVNHashMap();// returns an empty map, must not be null!!
}
private byte[] parseRawDeltaProperties(FSRepresentation txtRep, StringBuilder outputChecksum) throws SVNException {
FSFile revisionFile = null;
revisionFile = openAndSeekRepresentation(txtRep);
String repHeader = revisionFile.readLine(160);
byte[] rawPropertiesBytes = null;
String checksum = null;
try {
if ("PLAIN".equals(repHeader)) {
revisionFile.resetDigest();
rawPropertiesBytes = new byte[(int) txtRep.getSize()];
revisionFile.read(rawPropertiesBytes, 0, rawPropertiesBytes.length);
checksum = revisionFile.digest();
if (checksum != null && outputChecksum != null) {
outputChecksum.append(checksum);
}
} else if ("DELTA".equals(repHeader)) {
InputStream baseStream = SVNFileUtil.DUMMY_IN;
rawPropertiesBytes = applyDeltaFromFSFile(revisionFile, (int) txtRep.getSize(), baseStream, outputChecksum);
} else if (repHeader.startsWith("DELTA ")) {
long baseRevision;
long baseItemIndex;
long baseLength;
final String[] repHeaderArray = repHeader.split(" ");
if (repHeaderArray.length < 4) {
SVNErrorMessage errr = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header ''{0}''", repHeader);
SVNErrorManager.error(errr, SVNLogType.FSFS);
}
try {
baseRevision = Long.parseLong(repHeaderArray[1]);
baseItemIndex = Long.parseLong(repHeaderArray[2]);
baseLength = Long.parseLong(repHeaderArray[3]);
final FSRepresentation baseRepresentation = new FSRepresentation();
baseRepresentation.setRevision(baseRevision);
baseRepresentation.setOffset(baseItemIndex);
baseRepresentation.setSize(baseLength);
final byte[] baseBuffer = parseRawDeltaProperties(baseRepresentation, null);
final ByteArrayInputStream inputStream = new ByteArrayInputStream(baseBuffer);
rawPropertiesBytes = applyDeltaFromFSFile(revisionFile, (int) txtRep.getSize(), inputStream, outputChecksum);
} catch (NumberFormatException e) {
SVNErrorMessage errr = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header ''{0}''", repHeader);
SVNErrorManager.error(errr, SVNLogType.FSFS);
}
}
return rawPropertiesBytes;
} catch (IOException e) {
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.FSFS);
} finally {
if (revisionFile != null) {
revisionFile.close();
}
}
return null;
}
private byte[] applyDeltaFromFSFile(FSFile revisionFile, int deltaSize, InputStream baseStream, StringBuilder outputChecksum) throws IOException, SVNException {
final byte[] rawPropertiesBytes;SVNDeltaReader deltaReader = new SVNDeltaReader();
byte[] buffer = new byte[2048];
int readCount = -1;
final SVNDeltaProcessor deltaProcessor = new SVNDeltaProcessor();
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
deltaProcessor.applyTextDelta(baseStream, byteArrayOutputStream, true);
while ((readCount = revisionFile.read(buffer, 0, deltaSize)) != -1) {
if (readCount == 0) {
continue;
}
deltaReader.nextWindow(buffer, 0, readCount, "", new ISVNDeltaConsumer() {
public void applyTextDelta(String path, String baseChecksum) throws SVNException {
}
public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
return deltaProcessor.textDeltaChunk(diffWindow);
}
public void textDeltaEnd(String path) throws SVNException {
}
});
deltaSize -= readCount;
if (deltaSize == 0) {
break;
}
}
String checksum = deltaProcessor.textDeltaEnd();
if (checksum != null && outputChecksum != null) {
outputChecksum.append(checksum);
}
rawPropertiesBytes = byteArrayOutputStream.toByteArray();
return rawPropertiesBytes;
}
private SVNProperties parseProperties(FSRepresentation txtRep) throws SVNException {
FSFile revisionFile = null;
try {
revisionFile = openAndSeekRepresentation(txtRep);
String repHeader = revisionFile.readLine(160);
SVNProperties rawEntries = null;
String checksum = null;
if ("PLAIN".equals(repHeader)) {
revisionFile.resetDigest();
rawEntries = revisionFile.readProperties(false, false);
checksum = revisionFile.digest();
} else if ("DELTA".equals(repHeader)) {
StringBuilder outputChecksum = new StringBuilder();
rawEntries = readProperties(parseRawDeltaProperties(txtRep, outputChecksum));
checksum = outputChecksum.toString();
} else if (repHeader.startsWith("DELTA ")) {
StringBuilder outputChecksum = new StringBuilder();
byte[] rawPropetiesBytes = parseRawDeltaProperties(txtRep, outputChecksum);
checksum = outputChecksum.toString();
rawEntries = readProperties(rawPropetiesBytes);
}
SVNErrorManager.assertionFailure(checksum != null, "Checksum should be computed", SVNLogType.FSFS);
if (!checksum.equals(txtRep.getMD5HexDigest())) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Checksum mismatch while reading representation:\n expected: {0}\n actual: {1}",
new Object[] { checksum, txtRep.getMD5HexDigest() });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return rawEntries;
} finally {
if(revisionFile != null){
revisionFile.close();
}
}
}
public SVNProperties getProperties(FSRevisionNode revNode) throws SVNException {
if (revNode.getPropsRepresentation() != null && revNode.getPropsRepresentation().isTxn()) {
FSFile propsFile = null;
try {
propsFile = getTransactionRevisionNodePropertiesFile(revNode.getId());
return propsFile.readProperties(false, true);
} finally {
if(propsFile != null){
propsFile.close();
}
}
} else if (revNode.getPropsRepresentation() != null) {
FSRepresentation propsRep = revNode.getPropsRepresentation();
SVNProperties properties = parseProperties(propsRep);
return properties == null ? new SVNProperties() : properties;
}
return new SVNProperties();// no properties? return an empty SVNProperties
}
public String[] getNextRevisionIDs() throws SVNException {
String[] ids = new String[2];
FSFile currentFile = new FSFile(getCurrentFile());
String idsLine = null;
try{
idsLine = currentFile.readLine(80);
}finally{
currentFile.close();
}
if (idsLine == null || idsLine.length() == 0) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupt current file");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
int spaceInd = idsLine.indexOf(' ');
if (spaceInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupt current file");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
idsLine = idsLine.substring(spaceInd + 1);
spaceInd = idsLine.indexOf(' ');
if (spaceInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Corrupt current file");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String nodeID = idsLine.substring(0, spaceInd);
String copyID = idsLine.substring(spaceInd + 1);
ids[0] = nodeID;
ids[1] = copyID;
return ids;
}
public String getAndIncrementTxnKey() throws SVNException {
FSWriteLock writeLock = FSWriteLock.getWriteLockForCurrentTxn("_" + TXN_CURRENT_FILE, this);
synchronized (writeLock) {
try {
writeLock.lock();
File txnCurrentFile = getTransactionCurrentFile();
FSFile reader = new FSFile(txnCurrentFile);
String txnId = null;
try {
txnId = reader.readLine(200);
} finally {
reader.close();
}
String nextTxnId = FSRepositoryUtil.generateNextKey(txnId);
OutputStream txnCurrentOS = null;
File tmpFile = null;
try {
tmpFile = SVNFileUtil.createUniqueFile(txnCurrentFile.getParentFile(),
TXN_CURRENT_FILE, ".tmp", false);
txnCurrentOS = SVNFileUtil.openFileForWriting(tmpFile);
nextTxnId = nextTxnId + "\n";
txnCurrentOS.write(nextTxnId.getBytes("UTF-8"));
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(txnCurrentOS);
}
SVNFileUtil.rename(tmpFile, txnCurrentFile);
return txnId;
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
}
public Map listTransactions() {
Map result = new SVNHashMap();
File txnsDir = getTransactionsParentDir();
File[] entries = SVNFileListUtil.listFiles(txnsDir);
for (int i = 0; i < entries.length; i++) {
File entry = entries[i];
if (entry.getName().length() <= TXN_PATH_EXT.length() || !entry.getName().endsWith(TXN_PATH_EXT)) {
continue;
}
String txnName = entry.getName().substring(0, entry.getName().lastIndexOf(TXN_PATH_EXT));
result.put(txnName, entry);
}
return result;
}
public File getNewRevisionFile(long newRevision) {
if (myMaxFilesPerDirectory > 0 && (newRevision % myMaxFilesPerDirectory == 0)) {
File shardDir = new File(getDBRevsDir(), String.valueOf(newRevision/myMaxFilesPerDirectory));
shardDir.mkdirs();
}
File revFile = null;
if (myMaxFilesPerDirectory > 0) {
File shardDir = new File(getDBRevsDir(), String.valueOf(newRevision/myMaxFilesPerDirectory));
revFile = new File(shardDir, String.valueOf(newRevision));
} else {
revFile = new File(getDBRevsDir(), String.valueOf(newRevision));
}
return revFile;
}
public File getNewRevisionPropertiesFile(long newRevision) {
if (myMaxFilesPerDirectory > 0 && (newRevision % myMaxFilesPerDirectory == 0)) {
File shardDir = new File(getRevisionPropertiesRoot(), String.valueOf(newRevision/myMaxFilesPerDirectory));
shardDir.mkdirs();
}
File revPropsFile = null;
if (myMaxFilesPerDirectory > 0) {
File shardDir = new File(getRevisionPropertiesRoot(), String.valueOf(newRevision/myMaxFilesPerDirectory));
revPropsFile = new File(shardDir, String.valueOf(newRevision));
} else {
revPropsFile = new File(getRevisionPropertiesRoot(), String.valueOf(newRevision));
}
return revPropsFile;
}
public File getTransactionDir(String txnID) {
return new File(getTransactionsParentDir(), txnID + TXN_PATH_EXT);
}
public void setYoungestRevisionCache(long revision) {
myYoungestRevisionCache = revision;
}
public void setUUID(String uuid) throws SVNException {
File uniqueFile = SVNFileUtil.createUniqueFile(getDBRoot(), UUID_FILE, ".tmp", false);
uuid += '\n';
OutputStream uuidOS = null;
try {
uuidOS = SVNFileUtil.openFileForWriting(uniqueFile);
uuidOS.write(uuid.getBytes("US-ASCII"));
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR,
"Error writing repository UUID to ''{0}''", getUUIDFile());
err.setChildErrorMessage(SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()));
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(uuidOS);
}
SVNFileUtil.rename(uniqueFile, getUUIDFile());
}
public File getRevisionPropertiesFile(long revision, boolean returnMissing) throws SVNException {
File revPropsFile = null;
if (myMaxFilesPerDirectory > 0) {
File shardDir = new File(getRevisionPropertiesRoot(), String.valueOf(revision/myMaxFilesPerDirectory));
revPropsFile = new File(shardDir, String.valueOf(revision));
} else {
revPropsFile = new File(getRevisionPropertiesRoot(), String.valueOf(revision));
}
if (!revPropsFile.exists() && !returnMissing) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such revision {0}", String.valueOf(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return revPropsFile;
}
public FSFile openAndSeekRepresentation(FSRepresentation rep) throws SVNException {
if (!rep.isTxn()) {
return openAndSeekRevision(rep.getRevision(), rep.getOffset());
}
return openAndSeekTransaction(rep);
}
public File getNextIDsFile(String txnID) {
return new File(getTransactionDir(txnID), "next-ids");
}
public void writeNextIDs(String txnID, String nodeID, String copyID) throws SVNException {
OutputStream nextIdsFile = null;
try {
nextIdsFile = SVNFileUtil.openFileForWriting(getNextIDsFile(txnID));
String ids = nodeID + " " + copyID + "\n";
nextIdsFile.write(ids.getBytes("UTF-8"));
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(nextIdsFile);
}
}
public void changeTransactionProperties(String txnId, SVNProperties txnProperties) throws SVNException {
for (Iterator iter = txnProperties.nameSet().iterator(); iter.hasNext();) {
String propName = (String) iter.next();
SVNPropertyValue propValue = txnProperties.getSVNPropertyValue(propName);
setTransactionProperty(txnId, propName, propValue);
}
}
public void setTransactionProperty(String txnID, String name, SVNPropertyValue propertyValue) throws SVNException {
FSRepositoryUtil.validateProperty(name, propertyValue);
SVNWCProperties revProps = new SVNWCProperties(getTransactionPropertiesFile(txnID), null);
revProps.setPropertyValue(name, propertyValue);
}
public void setRevisionProperty(long revision, String propertyName, SVNPropertyValue propertyValue) throws SVNException {
ensureRevisionsExists(revision);
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(this);
synchronized (writeLock) {
try {
writeLock.lock();
if (!isPackedRevisionProperties(revision)) {
SVNWCProperties revProps = new SVNWCProperties(getRevisionPropertiesFile(revision, false), null);
revProps.setPropertyValue(propertyName, propertyValue);
} else {
final File packShardDirectory = getPackedRevPropsShardPath(revision);
final File manifestFile = new File(packShardDirectory, MANIFEST_FILE);
final SVNFSFSPackedRevPropsManifest manifest = SVNFSFSPackedRevPropsManifest.fromFile(manifestFile, revision, myMaxFilesPerDirectory);
final File packedRevPropFile = new File(packShardDirectory, manifest.getPackName(revision));
final SVNFSFSPackedRevProps packedRevProps = SVNFSFSPackedRevProps.fromPackFile(packedRevPropFile);
SVNProperties properties = packedRevProps.parseProperties(revision);
properties.put(propertyName, propertyValue);
final List<SVNFSFSPackedRevProps> packs = packedRevProps.setProperties(revision, properties, getRevPropPackSize());
File tmpFile = SVNFileUtil.createUniqueFile(packShardDirectory, "svn", ".tmp", false);
if (packs.size() == 1) {
final SVNFSFSPackedRevProps pack = packs.get(0);
pack.writeToFile(tmpFile, isCompressPackedRevprops());
SVNFileUtil.rename(tmpFile, packedRevPropFile);
} else {
final Set<String> packNamesToDelete = new HashSet<String>(3);
for (SVNFSFSPackedRevProps pack : packs) {
final String oldPackName = manifest.getPackName(pack.getFirstRevision());
packNamesToDelete.add(oldPackName);
final String packName = manifest.updatePackName(pack.getFirstRevision(), (int) pack.getRevisionsCount());
File packFile = new File(packShardDirectory, packName);
pack.writeToFile(packFile, isCompressPackedRevprops());
}
SVNFileUtil.writeToFile(tmpFile, manifest.asString(), "UTF-8");
SVNFileUtil.rename(tmpFile, manifestFile);
for (String packNameToDelete : packNamesToDelete) {
final File packFile = new File(packShardDirectory, packNameToDelete);
SVNFileUtil.deleteFile(packFile);
}
}
}
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
}
protected long getRevPropPackSize() {
return myRevpropPackSize;
}
public SVNProperties getTransactionProperties(String txnID) throws SVNException {
FSFile txnPropsFile = new FSFile(getTransactionPropertiesFile(txnID));
try {
return txnPropsFile.readProperties(false, true);
} finally {
txnPropsFile.close();
}
}
public File getTransactionPropertiesFile(String txnID) {
return new File(getTransactionDir(txnID), "props");
}
public File getTransactionProtoRevsDir() {
if (myTransactionProtoRevsRoot == null) {
myTransactionProtoRevsRoot = new File(getDBRoot(), TRANSACTION_PROTOS_DIR);
}
return myTransactionProtoRevsRoot;
}
public File getTransactionProtoRevFile(String txnID) {
if (myDBFormat >= MIN_PROTOREVS_DIR_FORMAT) {
return new File(getTransactionProtoRevsDir(), txnID + ".rev");
}
return new File(getTransactionDir(txnID), "rev");
}
public File getTransactionProtoRevLockFile(String txnID) {
if (myDBFormat >= MIN_PROTOREVS_DIR_FORMAT) {
return new File(getTransactionProtoRevsDir(), txnID + ".rev-lock");
}
return new File(getTransactionDir(txnID), "rev-lock");
}
public void purgeTxn(String txnID) throws SVNException {
SVNFileUtil.deleteAll(getTransactionDir(txnID), true);
if (getDBFormat() >= FSFS.MIN_PROTOREVS_DIR_FORMAT) {
SVNFileUtil.deleteFile(getTransactionProtoRevFile(txnID));
SVNFileUtil.deleteFile(getTransactionProtoRevLockFile(txnID));
}
}
public void createNewTxnNodeRevisionFromRevision(String txnID, FSRevisionNode sourceNode) throws SVNException {
if (sourceNode.getId().isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Copying from transactions not allowed");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSRevisionNode revNode = FSRevisionNode.dumpRevisionNode(sourceNode);
revNode.setPredecessorId(sourceNode.getId());
revNode.setCount(revNode.getCount() + 1);
revNode.setCopyFromPath(null);
revNode.setIsFreshTxnRoot(true);
revNode.setCopyFromRevision(SVNRepository.INVALID_REVISION);
revNode.setId(FSID.createTxnId(sourceNode.getId().getNodeID(), sourceNode.getId().getCopyID(), txnID));
putTxnRevisionNode(revNode.getId(), revNode);
}
public void putTxnRevisionNode(FSID id, FSRevisionNode revNode) throws SVNException {
if (!id.isTxn()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Attempted to write to non-transaction");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
OutputStream revNodeFile = null;
try {
revNodeFile = SVNFileUtil.openFileForWriting(getTransactionRevNodeFile(id));
writeTxnNodeRevision(revNodeFile, revNode);
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(revNodeFile);
}
}
public File getTransactionRevNodeFile(FSID id) {
return new File(getTransactionDir(id.getTxnID()), PATH_PREFIX_NODE + id.getNodeID() + "." + id.getCopyID());
}
public void writeTxnNodeRevision(OutputStream revNodeFile, FSRevisionNode revNode) throws IOException {
String id = FSRevisionNode.HEADER_ID + ": " + revNode.getId() + "\n";
revNodeFile.write(id.getBytes("UTF-8"));
String type = FSRevisionNode.HEADER_TYPE + ": " + revNode.getType() + "\n";
revNodeFile.write(type.getBytes("UTF-8"));
if (revNode.getPredecessorId() != null) {
String predId = FSRevisionNode.HEADER_PRED + ": " + revNode.getPredecessorId() + "\n";
revNodeFile.write(predId.getBytes("UTF-8"));
}
String count = FSRevisionNode.HEADER_COUNT + ": " + revNode.getCount() + "\n";
revNodeFile.write(count.getBytes("UTF-8"));
if (revNode.getTextRepresentation() != null) {
FSRepresentation txtRep = revNode.getTextRepresentation();
String textRepresentation = FSRevisionNode.HEADER_TEXT + ": " + (txtRep.getTxnId() != null && revNode.getType() == SVNNodeKind.DIR ?
"-1" : txtRep.getStringRepresentation(myDBFormat)) + "\n";
revNodeFile.write(textRepresentation.getBytes("UTF-8"));
}
if (revNode.getPropsRepresentation() != null) {
FSRepresentation propRep = revNode.getPropsRepresentation();
String propsRepresentation = FSRevisionNode.HEADER_PROPS + ": " + (propRep.getTxnId() != null ?
"-1" : propRep.getStringRepresentation(myDBFormat)) + "\n";
revNodeFile.write(propsRepresentation.getBytes("UTF-8"));
}
String cpath = FSRevisionNode.HEADER_CPATH + ": " + revNode.getCreatedPath() + "\n";
revNodeFile.write(cpath.getBytes("UTF-8"));
if (revNode.getCopyFromPath() != null) {
String copyFromPath = FSRevisionNode.HEADER_COPYFROM + ": " + revNode.getCopyFromRevision() + " " + revNode.getCopyFromPath() + "\n";
revNodeFile.write(copyFromPath.getBytes("UTF-8"));
}
if (revNode.getCopyRootRevision() != revNode.getId().getRevision() ||
!revNode.getCopyRootPath().equals(revNode.getCreatedPath())) {
String copyroot = FSRevisionNode.HEADER_COPYROOT + ": " + revNode.getCopyRootRevision() + " " + revNode.getCopyRootPath() + "\n";
revNodeFile.write(copyroot.getBytes("UTF-8"));
}
if (revNode.isFreshTxnRoot()) {
String isFreshRootStr = FSRevisionNode.HEADER_IS_FRESH_TXN_ROOT + ": y\n";
revNodeFile.write(isFreshRootStr.getBytes("UTF-8"));
}
if (supportsMergeInfo()) {
if (revNode.getMergeInfoCount() > 0) {
String mergeInfoCntStr = FSRevisionNode.HEADER_MERGE_INFO_COUNT + ": " + revNode.getMergeInfoCount() + "\n";
revNodeFile.write(mergeInfoCntStr.getBytes("UTF-8"));
}
if (revNode.hasMergeInfo()) {
String hasMergeInfoStr = FSRevisionNode.HEADER_MERGE_INFO_HERE + ": y\n";
revNodeFile.write(hasMergeInfoStr.getBytes("UTF-8"));
}
}
revNodeFile.write("\n".getBytes("UTF-8"));
}
public SVNLock getLock(String repositoryPath, boolean haveWriteLock, boolean throwError) throws SVNException {
repositoryPath = SVNPathUtil.canonicalizeAbsolutePath(repositoryPath);
SVNLock lock = fetchLockFromDigestFile(null, repositoryPath, null);
if (lock == null) {
if (!throwError) {
return null;
}
SVNErrorManager.error(FSErrors.errorNoSuchLock(repositoryPath, this), SVNLogType.FSFS);
}
Date current = new Date(System.currentTimeMillis());
if (lock.getExpirationDate() != null && current.compareTo(lock.getExpirationDate()) > 0) {
if (haveWriteLock) {
deleteLock(lock);
}
if (!throwError) {
return null;
}
SVNErrorManager.error(FSErrors.errorLockExpired(lock.getID(), this), SVNLogType.FSFS);
}
return lock;
}
public void deleteLock(SVNLock lock) throws SVNException {
String reposPath = lock.getPath();
String childToKill = null;
Collection<String> children = new ArrayList<String>();
while (true) {
fetchLockFromDigestFile(null, reposPath, children);
if (childToKill != null) {
children.remove(childToKill);
}
if (children.size() == 0) {
childToKill = getDigestFromRepositoryPath(reposPath);
File digestFile = getDigestFileFromRepositoryPath(reposPath);
SVNFileUtil.deleteFile(digestFile);
} else {
writeDigestLockFile(null, children, reposPath, false);
childToKill = null;
}
if ("/".equals(reposPath)) {
break;
}
reposPath = SVNPathUtil.removeTail(reposPath);
if ("".equals(reposPath)) {
reposPath = "/";
}
children.clear();
}
}
public void walkDigestFiles(File digestFile, ISVNLockHandler getLocksHandler, boolean haveWriteLock) throws SVNException {
Collection children = new LinkedList();
SVNLock lock = fetchLockFromDigestFile(digestFile, null, children);
if (lock != null) {
Date current = new Date(System.currentTimeMillis());
if (lock.getExpirationDate() == null || current.compareTo(lock.getExpirationDate()) < 0) {
getLocksHandler.handleLock(lock.getPath(), lock, null);
} else if (haveWriteLock) {
deleteLock(lock);
}
}
if (children.isEmpty()) {
return;
}
for (Iterator entries = children.iterator(); entries.hasNext();) {
String digestName = (String) entries.next();
File parent = new File(getDBLocksDir(), digestName.substring(0, FSFS.DIGEST_SUBDIR_LEN));
File childDigestFile = new File(parent, digestName);
walkDigestFiles(childDigestFile, getLocksHandler, haveWriteLock);
}
}
public SVNLock getLockHelper(String repositoryPath, boolean haveWriteLock) throws SVNException {
SVNLock lock = null;
try {
lock = getLock(repositoryPath, haveWriteLock, false);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NO_SUCH_LOCK || svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_LOCK_EXPIRED) {
return null;
}
throw svne;
}
return lock;
}
public SVNLock fetchLockFromDigestFile(File digestFile, String repositoryPath, Collection children) throws SVNException {
File digestLockFile = digestFile == null ? getDigestFileFromRepositoryPath(repositoryPath) : digestFile;
SVNProperties lockProps = null;
if (digestLockFile.exists()) {
FSFile reader = new FSFile(digestLockFile);
try {
lockProps = reader.readProperties(false, true);
} catch (SVNException svne) {
SVNErrorMessage err = svne.getErrorMessage().wrap("Can't parse lock/entries hashfile ''{0}''", digestLockFile);
SVNErrorManager.error(err, SVNLogType.FSFS);
} finally {
reader.close();
}
} else {
lockProps = new SVNProperties();
}
SVNLock lock = null;
String lockPath = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.PATH_LOCK_KEY));
if (lockPath != null) {
String lockToken = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.TOKEN_LOCK_KEY));
if (lockToken == null) {
SVNErrorManager.error(FSErrors.errorCorruptLockFile(lockPath, this), SVNLogType.FSFS);
}
String lockOwner = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.OWNER_LOCK_KEY));
if (lockOwner == null) {
SVNErrorManager.error(FSErrors.errorCorruptLockFile(lockPath, this), SVNLogType.FSFS);
}
String davComment = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.IS_DAV_COMMENT_LOCK_KEY));
if (davComment == null) {
SVNErrorManager.error(FSErrors.errorCorruptLockFile(lockPath, this), SVNLogType.FSFS);
}
String creationTime = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.CREATION_DATE_LOCK_KEY));
if (creationTime == null) {
SVNErrorManager.error(FSErrors.errorCorruptLockFile(lockPath, this), SVNLogType.FSFS);
}
Date creationDate = SVNDate.parseDateString(creationTime);
String expirationTime = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.EXPIRATION_DATE_LOCK_KEY));
Date expirationDate = null;
if (expirationTime != null) {
expirationDate = SVNDate.parseDateString(expirationTime);
}
String comment = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.COMMENT_LOCK_KEY));
lock = new FSLock(lockPath, lockToken, lockOwner, comment, creationDate, expirationDate, "1".equals(davComment));
}
String childEntries = SVNPropertyValue.getPropertyAsString(lockProps.getSVNPropertyValue(FSFS.CHILDREN_LOCK_KEY));
if (children != null && childEntries != null) {
String[] digests = childEntries.split("\n");
for (int i = 0; i < digests.length; i++) {
children.add(digests[i]);
}
}
return lock;
}
public File getDigestFileFromRepositoryPath(String repositoryPath) throws SVNException {
String digest = getDigestFromRepositoryPath(repositoryPath);
File parent = new File(getDBLocksDir(), digest.substring(0, FSFS.DIGEST_SUBDIR_LEN));
return new File(parent, digest);
}
public String getDigestFromRepositoryPath(String repositoryPath) throws SVNException {
repositoryPath = SVNPathUtil.canonicalizeAbsolutePath(repositoryPath);
MessageDigest digestFromPath = null;
try {
digestFromPath = MessageDigest.getInstance("MD5");
digestFromPath.update(repositoryPath.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException nsae) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "MD5 implementation not found: {0}", nsae.getLocalizedMessage());
SVNErrorManager.error(err, nsae, SVNLogType.FSFS);
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
}
return SVNFileUtil.toHexDigest(digestFromPath);
}
public void unlockPath(String path, String token, String username, boolean breakLock, boolean enableHooks) throws SVNException {
path = SVNPathUtil.canonicalizeAbsolutePath(path);
String[] paths = {path};
if (!breakLock && username == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_USER, "Cannot unlock path ''{0}'', no authenticated username available", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (enableHooks && isHooksEnabled()) {
FSHooks.runPreUnlockHook(myRepositoryRoot, path, username);
}
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(this);
synchronized (writeLock) {
try {
writeLock.lock();
unlock(path, token, username, breakLock);
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
if (enableHooks && isHooksEnabled()) {
try {
FSHooks.runPostUnlockHook(myRepositoryRoot, paths, username);
} catch (SVNException svne) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_POST_UNLOCK_HOOK_FAILED, "Unlock succeeded, but post-unlock hook failed");
err.setChildErrorMessage(svne.getErrorMessage());
SVNErrorManager.error(err, svne, SVNLogType.FSFS);
}
}
}
public SVNLock lockPath(String path, String token, String username, String comment, Date expirationDate, long currentRevision,
boolean stealLock, boolean isDAVComment) throws SVNException {
path = SVNPathUtil.canonicalizeAbsolutePath(path);
String[] paths = { path };
if (username == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_USER,
"Cannot lock path ''{0}'', no authenticated username available.", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String customToken = null;
if (isHooksEnabled()) {
customToken = FSHooks.runPreLockHook(myRepositoryRoot, path, username, comment, stealLock);
if (customToken != null) {
token = customToken;
}
}
SVNLock lock = null;
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(this);
synchronized (writeLock) {
try {
writeLock.lock();
lock = lock(path, token, username, comment, expirationDate, currentRevision, stealLock, isDAVComment);
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
if (isHooksEnabled()) {
try {
FSHooks.runPostLockHook(myRepositoryRoot, paths, username);
} catch (SVNException svne) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.REPOS_POST_LOCK_HOOK_FAILED, "Lock succeeded, but post-lock hook failed");
err.setChildErrorMessage(svne.getErrorMessage());
SVNErrorManager.error(err, svne, SVNLogType.FSFS);
}
}
return lock;
}
public SVNProperties compoundMetaProperties(long revision) throws SVNException {
SVNProperties metaProperties = new SVNProperties();
SVNProperties revProps = getRevisionProperties(revision);
String uuid = getUUID();
String rev = String.valueOf(revision);
metaProperties.put(SVNProperty.LAST_AUTHOR, revProps.getStringValue(SVNRevisionProperty.AUTHOR));
metaProperties.put(SVNProperty.COMMITTED_DATE, revProps.getStringValue(SVNRevisionProperty.DATE));
metaProperties.put(SVNProperty.COMMITTED_REVISION, rev);
metaProperties.put(SVNProperty.UUID, uuid);
return metaProperties;
}
public long getDeletedRevision(String path, long startRev, long endRev) throws SVNException {
if (FSRepository.isInvalidRevision(startRev)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Invalid start revision {0}", new Long(startRev));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (FSRepository.isInvalidRevision(endRev)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Invalid end revision {0}", new Long(endRev));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (startRev > endRev) {
long tmpRev = endRev;
endRev = startRev;
startRev = tmpRev;
}
FSRevisionRoot startRoot = createRevisionRoot(startRev);
FSRevisionNode startNode = null;
try {
startNode = startRoot.getRevisionNode(path);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) {
return SVNRepository.INVALID_REVISION;
}
throw svne;
}
FSID startNodeId = startNode.getId();
FSRevisionRoot endRoot = createRevisionRoot(endRev);
FSRevisionNode endNode = null;
try {
endNode = endRoot.getRevisionNode(path);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() != SVNErrorCode.FS_NOT_FOUND) {
throw svne;
}
}
if (endNode != null) {
FSID endNodeId = endNode.getId();
if (startNodeId.compareTo(endNodeId) != -1) {
FSClosestCopy closestCopy = endRoot.getClosestCopy(path);
if (closestCopy == null || closestCopy.getRevisionRoot() == null ||
closestCopy.getRevisionRoot().getRevision() <= startRev) {
return SVNRepository.INVALID_REVISION;
}
}
}
long midRev = (startRev + endRev)/2;
while (true) {
FSRevisionRoot root = createRevisionRoot(midRev);
FSRevisionNode node = null;
try {
node = root.getRevisionNode(path);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.FS_NOT_FOUND) {
endRev = midRev;
midRev = (startRev + endRev)/2;
} else {
throw svne;
}
}
if (node != null) {
FSID currentNodeId = node.getId();
int nodeRelationship = startNodeId.compareTo(currentNodeId);
FSClosestCopy closestCopy = root.getClosestCopy(path);
if (nodeRelationship == -1 || (closestCopy != null &&
closestCopy.getRevisionRoot() != null &&
closestCopy.getRevisionRoot().getRevision() > startRev)) {
endRev = midRev;
midRev = (startRev + endRev)/2;
} else if (endRev - midRev == 1) {
return endRev;
} else {
startRev = midRev;
midRev = (startRev + endRev)/2;
}
}
}
}
public SVNLocationEntry getPreviousLocation(String path, long revision, long[] appearedRevision) throws SVNException {
if (appearedRevision != null && appearedRevision.length > 0) {
appearedRevision[0] = SVNRepository.INVALID_REVISION;
}
FSRevisionRoot root = createRevisionRoot(revision);
FSClosestCopy closestCopy = root.getClosestCopy(path);
if (closestCopy == null) {
return null;
}
FSRevisionRoot copyTargetRoot = closestCopy.getRevisionRoot();
if (copyTargetRoot == null) {
return null;
}
String copyTargetPath = closestCopy.getPath();
FSRevisionNode copyFromNode = copyTargetRoot.getRevisionNode(copyTargetPath);
String copyFromPath = copyFromNode.getCopyFromPath();
long copyFromRevision = copyFromNode.getCopyFromRevision();
String remainder = "";
if (!path.equals(copyTargetPath)) {
remainder = path.substring(copyTargetPath.length());
if (remainder.startsWith("/")) {
remainder = remainder.substring(1);
}
}
String previousPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(copyFromPath, remainder));
if (appearedRevision != null && appearedRevision.length > 0) {
appearedRevision[0] = copyTargetRoot.getRevision();
}
return new SVNLocationEntry(copyFromRevision, previousPath);
}
public String getNodeOrigin(String nodeID) throws SVNException {
SVNProperties nodeOrigins = getNodeOriginsFromFile(nodeID);
if (nodeOrigins != null) {
return nodeOrigins.getStringValue(nodeID);
}
return null;
}
public void setNodeOrigin(String nodeID, FSID nodeRevisionID) throws SVNException {
File nodeOriginsDir = getNodeOriginsDir();
ensureDirExists(nodeOriginsDir, true);
SVNProperties nodeOrigins = getNodeOriginsFromFile(nodeID);
if (nodeOrigins == null) {
nodeOrigins = new SVNProperties();
}
String oldNodeRevID = nodeOrigins.getStringValue(nodeID);
String nodeRevIDToStore = nodeRevisionID.toString();
if (oldNodeRevID != null && !nodeRevIDToStore.equals(oldNodeRevID)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT,
"Node origin for ''{0}'' exists with a different value ({1}) than what we were about " +
"to store ({2})", new Object[] { nodeID, oldNodeRevID, nodeRevIDToStore });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
nodeOrigins.put(nodeID, nodeRevIDToStore);
File nodeOriginFile = getNodeOriginFile(nodeID);
File tmpFile = SVNFileUtil.createUniqueFile(nodeOriginFile.getParentFile(), nodeOriginFile.getName(),
".tmp", false);
SVNWCProperties.setProperties(nodeOrigins, nodeOriginFile, tmpFile,
SVNWCProperties.SVN_HASH_TERMINATOR);
}
public boolean supportsMergeInfo() {
return myDBFormat >= MIN_MERGE_INFO_FORMAT;
}
public void readOptions(FSFile formatFile, int formatNumber) throws SVNException {
while (true) {
String line = null;
try {
line = formatFile.readLine(80);
} catch (SVNException svne) {
if (svne.getErrorMessage().getErrorCode() == SVNErrorCode.STREAM_UNEXPECTED_EOF) {
break;
}
}
if (formatNumber >= LAYOUT_FORMAT_OPTION_MINIMAL_FORMAT && line.startsWith("layout ")) {
String optionValue = line.substring(7);
if (optionValue.equals("linear")) {
myMaxFilesPerDirectory = 0;
continue;
} else if (optionValue.startsWith("sharded ")) {
optionValue = optionValue.substring(8);
try {
myMaxFilesPerDirectory = Long.parseLong(optionValue);
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_VERSION_FILE_FORMAT,
"Format file ''{0}'' contains an unexpected non-digit", formatFile.getFile());
SVNErrorManager.error(err, SVNLogType.FSFS);
}
continue;
}
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_VERSION_FILE_FORMAT,
"''{0}'' contains invalid filesystem format option ''{1}''",
new Object[] {formatFile.getFile(), line});
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
public IFSRepresentationCacheManager getRepositoryCacheManager() {
return myReposCacheManager;
}
public static File findRepositoryRoot(File path) {
if (path == null) {
path = new File("");
}
File rootPath = path;
while (!isRepositoryRoot(rootPath)) {
rootPath = rootPath.getParentFile();
if (rootPath == null) {
return null;
}
}
return rootPath;
}
public static String findRepositoryRoot(String host, String path) {
if (path == null) {
path = "";
}
String testPath = host != null ? SVNPathUtil.append("\\\\" + host, path) : path;
testPath = testPath.replaceFirst("\\|", "\\:");
File rootPath = new File(testPath).getAbsoluteFile();
while (!isRepositoryRoot(rootPath)) {
if (rootPath.getParentFile() == null) {
return null;
}
path = SVNPathUtil.removeTail(path);
rootPath = rootPath.getParentFile();
}
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
while (path.endsWith("\\")) {
path = path.substring(0, path.length() - 1);
}
return path;
}
public static long getDefaultMaxFilesPerDirectory() {
return DEFAULT_MAX_FILES_PER_DIRECTORY;
}
public static void setDefaultMaxFilesPerDirectory(long maxFilesPerDirectory) {
DEFAULT_MAX_FILES_PER_DIRECTORY = maxFilesPerDirectory;
}
protected boolean isPackedRevision(long revision) {
return revision < myMinUnpackedRevision;
}
protected File getNodeOriginFile(String nodeID) {
String nodeIDMinusLastChar = nodeID.length() == 1 ? "0" : nodeID.substring(0, nodeID.length() - 1);
return new File(getNodeOriginsDir(), nodeIDMinusLastChar);
}
protected FSFile getTransactionRevisionPrototypeFile(String txnID) {
File revFile = getTransactionProtoRevFile(txnID);
return new FSFile(revFile);
}
protected FSFile getTransactionChangesFile(String txnID) {
File file = new File(getTransactionDir(txnID), "changes");
return new FSFile(file);
}
protected FSFile getTransactionRevisionNodeChildrenFile(FSID txnID) {
File childrenFile = new File(getTransactionDir(txnID.getTxnID()), PATH_PREFIX_NODE + txnID.getNodeID() +
"." + txnID.getCopyID() + TXN_PATH_EXT_CHILDREN);
return new FSFile(childrenFile);
}
protected FSFile getRevisionFSFile(long revision) throws SVNException {
File revisionFile = getRevisionFile(revision);
if (!revisionFile.exists()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such revision {0}", String.valueOf(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return new FSFile(revisionFile);
}
protected FSFile getPackOrRevisionFSFile(long revision) throws SVNException {
File file = getAbsoluteRevisionPath(revision);
if (!file.exists()) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "No such revision {0}", String.valueOf(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return new FSFile(file);
}
protected File getAbsoluteRevisionPath(long revision) throws SVNException {
if (!isPackedRevision(revision)) {
File revFile = getRevisionFile(revision);
if (revFile.exists()) {
return revFile;
}
getMinUnpackedRev();
if (!isPackedRevision(revision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION, "Revision file ''{0}'' does not exist, and r{1} is not packed",
new Object[] { revFile, String.valueOf(revision) });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
return getPackedRevPath(revision, PACK_KIND_PACK);
}
protected FSFile getTransactionRevisionNodePropertiesFile(FSID id) {
File revNodePropsFile = new File(getTransactionDir(id.getTxnID()), PATH_PREFIX_NODE + id.getNodeID() + "." + id.getCopyID() + TXN_PATH_EXT_PROPS);
return new FSFile(revNodePropsFile);
}
protected File getPackedRevPath(long revision, String kind) throws SVNException {
SVNErrorManager.assertionFailure(myMaxFilesPerDirectory > 0, "max files per directory is 0 or negative: " + String.valueOf(myMaxFilesPerDirectory), SVNLogType.FSFS);
SVNErrorManager.assertionFailure(isPackedRevision(revision), "revision " + String.valueOf(revision) + " is not packed", SVNLogType.FSFS);
File file = new File(getDBRevsDir(), (revision/myMaxFilesPerDirectory) + PACK_EXT);
file = new File(file, kind);
return file;
}
protected File getPackedRevPropsShardPath(long revision) throws SVNException {
return new File(getRevisionPropertiesRoot(), (revision/myMaxFilesPerDirectory) + PACK_EXT);
}
protected File getPackDir(long revision) {
return new File(getDBRevsDir(), revision + PACK_EXT);
}
protected File getPackFile(long revision) {
return new File(getPackDir(revision), PACK_KIND_PACK);
}
protected File getManifestFile(long revision) {
return new File(getPackDir(revision), PACK_KIND_MANIFEST);
}
protected File getRevisionFile(long revision) throws SVNException {
SVNErrorManager.assertionFailure(!isPackedRevision(revision), "revision " + String.valueOf(revision) + " is not expected to be packed", SVNLogType.FSFS);
File revisionFile = null;
if (myMaxFilesPerDirectory > 0) {
File shardDir = new File(getDBRevsDir(), String.valueOf(revision/myMaxFilesPerDirectory));
revisionFile = new File(shardDir, String.valueOf(revision));
} else {
revisionFile = new File(getDBRevsDir(), String.valueOf(revision));
}
return revisionFile;
}
protected File getMinUnpackedRevFile() {
if (myMinUnpackedRevFile == null) {
myMinUnpackedRevFile = new File(getDBRoot(), MIN_UNPACKED_REV_FILE);
}
return myMinUnpackedRevFile;
}
protected File getTransactionCurrentFile(){
if(myTransactionCurrentFile == null){
myTransactionCurrentFile = new File(getDBRoot(), TXN_CURRENT_FILE);
}
return myTransactionCurrentFile;
}
protected File getTransactionCurrentLockFile(){
if(myTransactionCurrentLockFile == null){
myTransactionCurrentLockFile = new File(getDBRoot(), TXN_CURRENT_LOCK_FILE);
}
return myTransactionCurrentLockFile;
}
public File getConfigFile() {
return new File(getDBRoot(), PATH_CONFIG);
}
protected void writeCurrentFile(long revision, String nextNodeID, String nextCopyID) throws SVNException, IOException {
String line = null;
if (getDBFormat() >= FSFS.MIN_NO_GLOBAL_IDS_FORMAT) {
line = revision + "\n";
} else {
line = revision + " " + nextNodeID + " " + nextCopyID + "\n";
}
File currentFile = getCurrentFile();
File tmpCurrentFile = SVNFileUtil.createUniqueFile(currentFile.getParentFile(), currentFile.getName(),
".tmp", false);
OutputStream currentOS = null;
try {
currentOS = SVNFileUtil.openFileForWriting(tmpCurrentFile);
currentOS.write(line.getBytes("US-ASCII"));
} finally {
SVNFileUtil.closeFile(currentOS);
}
SVNFileUtil.rename(tmpCurrentFile, currentFile);
}
protected long getPackedOffset(long revision) throws SVNException {
//TODO: later on introduce invoking memcache here to fetch\store the requested data
//long shard = revision / myMaxFilesPerDirectory;
File manifestFile = getPackedRevPath(revision, PACK_KIND_MANIFEST);
BufferedReader reader = null;
LinkedList manifest = new LinkedList();
try {
reader = new BufferedReader(new InputStreamReader(SVNFileUtil.openFileForReading(manifestFile)));
String line = null;
while ((line = reader.readLine()) != null) {
Long offset = null;
try {
offset = Long.valueOf(line);
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
manifest.add(offset);
}
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(reader);
}
Long revOffsetLong = (Long) manifest.get((int) (revision % myMaxFilesPerDirectory));
SVNErrorManager.assertionFailure(revOffsetLong != null, "offset for revision " + String.valueOf(revision) + " is null", SVNLogType.FSFS);
return revOffsetLong.longValue();
}
private SVNConfigFile loadConfig() {
File confFile = getConfigFile();
if (myDBFormat < MIN_REP_SHARING_FORMAT || !confFile.exists()) {
return null;
}
myConfig = new SVNConfigFile(confFile);
return myConfig;
}
private void ensureRevisionsExists(long revision) throws SVNException {
if (FSRepository.isInvalidRevision(revision)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION,
"Invalid revision number ''{0}''", new Long(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (revision <= myYoungestRevisionCache) {
return;
}
getYoungestRevision();
if (revision <= myYoungestRevisionCache) {
return;
}
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NO_SUCH_REVISION,
"No such revision {0}", String.valueOf(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
private SVNProperties getNodeOriginsFromFile(String nodeID) throws SVNException {
File nodeOriginFile = getNodeOriginFile(nodeID);
if (!nodeOriginFile.exists()) {
return null;
}
FSFile reader = new FSFile(nodeOriginFile);
return reader.readProperties(false, true);
}
private void unlock(String path, String token, String username, boolean breakLock) throws SVNException {
SVNLock lock = getLock(path, true, true);
if (!breakLock) {
if (token == null || !token.equals(lock.getID())) {
SVNErrorManager.error(FSErrors.errorNoSuchLock(lock.getPath(), this), SVNLogType.FSFS);
}
if (username == null || "".equals(username)) {
SVNErrorManager.error(FSErrors.errorNoUser(this), SVNLogType.FSFS);
}
if (!username.equals(lock.getOwner())) {
SVNErrorManager.error(FSErrors.errorLockOwnerMismatch(username, lock.getOwner(), this), SVNLogType.FSFS);
}
}
deleteLock(lock);
}
private SVNLock lock(String path, String token, String username, String comment, Date expirationDate, long currentRevision,
boolean stealLock, boolean isDAVComment) throws SVNException {
long youngestRev = getYoungestRevision();
FSRevisionRoot root = createRevisionRoot(youngestRev);
SVNNodeKind kind = root.checkNodeKind(path);
if (token != null) {
if (!token.startsWith("opaquelocktoken:")) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_BAD_LOCK_TOKEN,
"Lock token URI ''{0}'' has bad scheme; expected ''opaquelocktoken''", token);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
for (int i = 0; i < token.length(); i++) {
if (token.charAt(i) > 255) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_BAD_LOCK_TOKEN,
"Lock token ''{0}'' is not ASCII at byte ''{1}''", new Object[] {token, new Integer(i)});
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
if (!SVNEncodingUtil.isXMLSafe(token)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_BAD_LOCK_TOKEN,
"Lock token URI ''{0}'' is not XML-safe", token);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
if (kind == SVNNodeKind.DIR) {
SVNErrorManager.error(FSErrors.errorNotFile(path, this), SVNLogType.FSFS);
} else if (kind == SVNNodeKind.NONE) {
if (currentRevision >= 0) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_OUT_OF_DATE, "Path ''{0}'' doesn''t exist in HEAD revision", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
} else {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FOUND, "Path ''{0}'' doesn''t exist in HEAD revision", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
if (username == null || "".equals(username)) {
SVNErrorManager.error(FSErrors.errorNoUser(this), SVNLogType.FSFS);
}
if (FSRepository.isValidRevision(currentRevision)) {
FSRevisionNode node = root.getRevisionNode(path);
long createdRev = node.getCreatedRevision();
if (FSRepository.isInvalidRevision(createdRev)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_OUT_OF_DATE, "Path ''{0}'' doesn''t exist in HEAD revision", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
if (currentRevision < createdRev) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_OUT_OF_DATE, "Lock failed: newer version of ''{0}'' exists", path);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
SVNLock existingLock = getLockHelper(path, true);
if (existingLock != null) {
if (!stealLock) {
SVNErrorManager.error(FSErrors.errorPathAlreadyLocked(existingLock.getPath(), existingLock.getOwner(), this), SVNLogType.FSFS);
} else {
deleteLock(existingLock);
}
}
SVNLock lock = null;
if (token == null) {
token = FSRepositoryUtil.generateLockToken();
lock = new FSLock(path, token, username, comment, new Date(System.currentTimeMillis()), expirationDate, isDAVComment);
} else {
lock = new FSLock(path, token, username, comment, new Date(System.currentTimeMillis()), expirationDate, isDAVComment);
}
setLock(lock, isDAVComment);
return lock;
}
private void setLock(SVNLock lock, boolean isDAVComment) throws SVNException {
if (lock == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "FATAL error: attempted to set a null lock");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String lastChild = "";
String path = lock.getPath();
Collection children = new ArrayList();
while (true) {
String digestFileName = getDigestFromRepositoryPath(path);
SVNLock fetchedLock = fetchLockFromDigestFile(null, path, children);
if (lock != null) {
fetchedLock = lock;
lock = null;
lastChild = digestFileName;
} else {
if (!children.isEmpty() && children.contains(lastChild)) {
break;
}
children.add(lastChild);
}
writeDigestLockFile(fetchedLock, children, path, isDAVComment);
if ("/".equals(path)) {
break;
}
path = SVNPathUtil.removeTail(path);
if ("".equals(path)) {
path = "/";
}
children.clear();
}
}
private boolean ensureDirExists(File dir, boolean create) {
if (!dir.exists() && create) {
return dir.mkdirs();
} else if (!dir.exists()) {
return false;
}
return true;
}
private void writeDigestLockFile(SVNLock lock, Collection children, String repositoryPath, boolean isDAVComment) throws SVNException {
if (!ensureDirExists(getDBLocksDir(), true)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
"Can''t create a directory at ''{0}''", getDBLocksDir());
SVNErrorManager.error(err, SVNLogType.FSFS);
}
File digestLockFile = getDigestFileFromRepositoryPath(repositoryPath);
String digest = getDigestFromRepositoryPath(repositoryPath);
File lockDigestSubdir = new File(getDBLocksDir(), digest.substring(0, FSFS.DIGEST_SUBDIR_LEN));
if (!ensureDirExists(lockDigestSubdir, true)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Can't create a directory at ''{0}''", lockDigestSubdir);
SVNErrorManager.error(err, SVNLogType.FSFS);
}
SVNProperties props = new SVNProperties();
if (lock != null) {
props.put(FSFS.PATH_LOCK_KEY, lock.getPath());
props.put(FSFS.OWNER_LOCK_KEY, lock.getOwner());
props.put(FSFS.TOKEN_LOCK_KEY, lock.getID());
String isDAVCommentValue = isDAVComment ? "1" : "0";
props.put(FSFS.IS_DAV_COMMENT_LOCK_KEY, isDAVCommentValue);
if (lock.getComment() != null) {
props.put(FSFS.COMMENT_LOCK_KEY, lock.getComment());
}
if (lock.getCreationDate() != null) {
props.put(FSFS.CREATION_DATE_LOCK_KEY, SVNDate.formatDate(lock.getCreationDate()));
}
if (lock.getExpirationDate() != null) {
props.put(FSFS.EXPIRATION_DATE_LOCK_KEY, SVNDate.formatDate(lock.getExpirationDate()));
}
}
if (children != null && children.size() > 0) {
Object[] digests = children.toArray();
StringBuffer value = new StringBuffer();
for (int i = 0; i < digests.length; i++) {
value.append(digests[i]);
value.append('\n');
}
props.put(FSFS.CHILDREN_LOCK_KEY, value.toString());
}
try {
SVNWCProperties.setProperties(props, digestLockFile, SVNFileUtil.createUniqueFile(digestLockFile.getParentFile(), digestLockFile.getName(), ".tmp", false), SVNWCProperties.SVN_HASH_TERMINATOR);
} catch (SVNException svne) {
SVNErrorMessage err = svne.getErrorMessage().wrap("Cannot write lock/entries hashfile ''{0}''", digestLockFile);
SVNErrorManager.error(err, svne, SVNLogType.FSFS);
}
}
private FSFile openAndSeekTransaction(FSRepresentation rep) {
FSFile file = getTransactionRevisionPrototypeFile(rep.getTxnId());
file.seek(rep.getOffset());
return file;
}
private FSFile openAndSeekRevision(long revision, long offset) throws SVNException {
ensureRevisionsExists(revision);
FSFile file = getPackOrRevisionFSFile(revision);
if (isPackedRevision(revision)) {
long revOffset = getPackedOffset(revision);
offset += revOffset;
}
file.seek(offset);
return file;
}
private Map parsePlainRepresentation(SVNProperties entries, boolean mayContainNulls) throws SVNException {
Map representationMap = new SVNHashMap();
for (Iterator iterator = entries.nameSet().iterator(); iterator.hasNext();) {
String name = (String) iterator.next();
String unparsedEntry = entries.getStringValue(name);
if (unparsedEntry == null && mayContainNulls) {
continue;
}
FSEntry nextRepEntry = parseRepEntryValue(name, unparsedEntry);
if (nextRepEntry == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Directory entry corrupt");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
representationMap.put(name, nextRepEntry);
}
return representationMap;
}
private FSEntry parseRepEntryValue(String name, String value) {
if (value == null) {
return null;
}
int spaceInd = value.indexOf(' ');
if (spaceInd == -1) {
return null;
}
String kind = value.substring(0, spaceInd);
String rawID = value.substring(spaceInd + 1);
SVNNodeKind type = SVNNodeKind.parseKind(kind);
FSID id = FSID.fromString(rawID);
if ((type != SVNNodeKind.DIR && type != SVNNodeKind.FILE) || id == null) {
return null;
}
return new FSEntry(id, type, name);
}
private Date getRevisionTime(long revision) throws SVNException {
SVNProperties revisionProperties = getRevisionProperties(revision);
String timeString = revisionProperties.getStringValue(SVNRevisionProperty.DATE);
if (timeString == null) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, "Failed to find time on revision {0}", new Long(revision));
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return SVNDate.parseDateString(timeString);
}
private static boolean isRepositoryRoot(File candidatePath) {
File formatFile = new File(candidatePath, REPOS_FORMAT_FILE);
SVNFileType fileType = SVNFileType.getType(formatFile);
if (fileType != SVNFileType.FILE) {
return false;
}
File dbFile = new File(candidatePath, DB_DIR);
fileType = SVNFileType.getType(dbFile);
if (fileType != SVNFileType.DIRECTORY && fileType != SVNFileType.SYMLINK) {
return false;
}
return true;
}
public File getRevisionPropertiesDbPath() {
return SVNFileUtil.createFilePath(getRevisionPropertiesRoot(), REVISION_PROPERTIES_DB);
}
public File getMinUnpackedRevPropPath() {
return SVNFileUtil.createFilePath(getDBRoot(), MIN_UNPACKED_REV);
}
public void updateMinUnpackedRevProp() throws SVNException {
myMinUnpackedRevProp = getMinUnpackedRevProp();
}
public long getMinUnpackedRevProp() throws SVNException {
FSFile file = new FSFile(getMinUnpackedRevPropPath());
try {
return file.readLong();
} catch (NumberFormatException nfe) {
return 0;
} finally {
file.close();
}
}
public boolean isCompressPackedRevprops() {
return myCompressPackedRevprops;
}
}