提交 b6ad6d6a authored 作者: Thomas Mueller's avatar Thomas Mueller

Improved compatibility with the Java 7 FileSystem abstraction.

上级 fa398730
...@@ -1483,7 +1483,7 @@ As an example, to use the the <code>nio</code> file system, use the following da ...@@ -1483,7 +1483,7 @@ As an example, to use the the <code>nio</code> file system, use the following da
<code>jdbc:h2:nio:~/test</code>. <code>jdbc:h2:nio:~/test</code>.
</p> </p>
<p> <p>
To register a new file system, extend the classes <code>org.h2.store.fs.FilePath, FileObject</code>, To register a new file system, extend the classes <code>org.h2.store.fs.FilePath, FileBase</code>,
and call the method <code>FilePath.register</code> before using it. and call the method <code>FilePath.register</code> before using it.
</p> </p>
<p> <p>
......
...@@ -101,8 +101,8 @@ public class BackupCommand extends Prepared { ...@@ -101,8 +101,8 @@ public class BackupCommand extends Prepared {
} }
private static void backupFile(ZipOutputStream out, String base, String fn) throws IOException { private static void backupFile(ZipOutputStream out, String base, String fn) throws IOException {
String f = FileUtils.getCanonicalPath(fn); String f = FileUtils.toRealPath(fn);
base = FileUtils.getCanonicalPath(base); base = FileUtils.toRealPath(base);
if (!f.startsWith(base)) { if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base); DbException.throwInternalError(f + " does not start with " + base);
} }
......
...@@ -159,7 +159,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -159,7 +159,7 @@ public class ConnectionInfo implements Cloneable {
*/ */
public void setBaseDir(String dir) { public void setBaseDir(String dir) {
if (persistent) { if (persistent) {
String absDir = FileUtils.unwrap(FileUtils.getCanonicalPath(dir)); String absDir = FileUtils.unwrap(FileUtils.toRealPath(dir));
boolean absolute = FileUtils.isAbsolute(name); boolean absolute = FileUtils.isAbsolute(name);
String n; String n;
String prefix = null; String prefix = null;
...@@ -170,7 +170,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -170,7 +170,7 @@ public class ConnectionInfo implements Cloneable {
prefix = name.substring(0, name.length() - n.length()); prefix = name.substring(0, name.length() - n.length());
n = dir + SysProperties.FILE_SEPARATOR + n; n = dir + SysProperties.FILE_SEPARATOR + n;
} }
String normalizedName = FileUtils.unwrap(FileUtils.getCanonicalPath(n)); String normalizedName = FileUtils.unwrap(FileUtils.toRealPath(n));
if (normalizedName.equals(absDir) || !normalizedName.startsWith(absDir)) { if (normalizedName.equals(absDir) || !normalizedName.startsWith(absDir)) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " + throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " +
absDir); absDir);
...@@ -364,7 +364,7 @@ public class ConnectionInfo implements Cloneable { ...@@ -364,7 +364,7 @@ public class ConnectionInfo implements Cloneable {
if (persistent) { if (persistent) {
if (nameNormalized == null) { if (nameNormalized == null) {
String suffix = Constants.SUFFIX_PAGE_FILE; String suffix = Constants.SUFFIX_PAGE_FILE;
String n = FileUtils.getCanonicalPath(name + suffix); String n = FileUtils.toRealPath(name + suffix);
String fileName = FileUtils.getName(n); String fileName = FileUtils.getName(n);
if (fileName.length() < suffix.length() + 1) { if (fileName.length() < suffix.length() + 1) {
throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name); throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name);
......
...@@ -1348,7 +1348,7 @@ public class Database implements DataHandler { ...@@ -1348,7 +1348,7 @@ public class Database implements DataHandler {
public String getDatabasePath() { public String getDatabasePath() {
if (persistent) { if (persistent) {
return FileUtils.getCanonicalPath(databaseName); return FileUtils.toRealPath(databaseName);
} }
return null; return null;
} }
...@@ -1477,8 +1477,7 @@ public class Database implements DataHandler { ...@@ -1477,8 +1477,7 @@ public class Database implements DataHandler {
private void deleteOldTempFiles() { private void deleteOldTempFiles() {
String path = FileUtils.getParent(databaseName); String path = FileUtils.getParent(databaseName);
String[] list = FileUtils.listFiles(path); for (String name : FileUtils.newDirectoryStream(path)) {
for (String name : list) {
if (name.endsWith(Constants.SUFFIX_TEMP_FILE) && name.startsWith(databaseName)) { if (name.endsWith(Constants.SUFFIX_TEMP_FILE) && name.startsWith(databaseName)) {
// can't always delete the files, they may still be open // can't always delete the files, they may still be open
FileUtils.tryDelete(name); FileUtils.tryDelete(name);
......
...@@ -244,7 +244,7 @@ public class CipherFactory { ...@@ -244,7 +244,7 @@ public class CipherFactory {
throw DbException.convertToIOException(e); throw DbException.convertToIOException(e);
} }
} }
String absolutePath = FileUtils.getCanonicalPath(fileName); String absolutePath = FileUtils.toRealPath(fileName);
System.setProperty(KEYSTORE_KEY, absolutePath); System.setProperty(KEYSTORE_KEY, absolutePath);
} }
if (p.getProperty(KEYSTORE_PASSWORD_KEY) == null) { if (p.getProperty(KEYSTORE_PASSWORD_KEY) == null) {
......
...@@ -90,7 +90,7 @@ public class Data { ...@@ -90,7 +90,7 @@ public class Data {
/** /**
* The data itself. * The data itself.
*/ */
protected byte[] data; private byte[] data;
/** /**
* The current write or read position. * The current write or read position.
...@@ -102,7 +102,7 @@ public class Data { ...@@ -102,7 +102,7 @@ public class Data {
*/ */
private final DataHandler handler; private final DataHandler handler;
protected Data(DataHandler handler, byte[] data) { private Data(DataHandler handler, byte[] data) {
this.handler = handler; this.handler = handler;
this.data = data; this.data = data;
} }
...@@ -1131,7 +1131,7 @@ public class Data { ...@@ -1131,7 +1131,7 @@ public class Data {
* @param x the value * @param x the value
* @return the len * @return the len
*/ */
public static int getVarIntLen(int x) { private static int getVarIntLen(int x) {
if ((x & (-1 << 7)) == 0) { if ((x & (-1 << 7)) == 0) {
return 1; return 1;
} else if ((x & (-1 << 14)) == 0) { } else if ((x & (-1 << 14)) == 0) {
......
...@@ -58,7 +58,7 @@ public class FileLister { ...@@ -58,7 +58,7 @@ public class FileLister {
if (dir == null || dir.equals("")) { if (dir == null || dir.equals("")) {
return "."; return ".";
} }
return FileUtils.getCanonicalPath(dir); return FileUtils.toRealPath(dir);
} }
/** /**
...@@ -74,10 +74,8 @@ public class FileLister { ...@@ -74,10 +74,8 @@ public class FileLister {
public static ArrayList<String> getDatabaseFiles(String dir, String db, boolean all) { public static ArrayList<String> getDatabaseFiles(String dir, String db, boolean all) {
ArrayList<String> files = New.arrayList(); ArrayList<String> files = New.arrayList();
// for Windows, File.getCanonicalPath("...b.") returns just "...b" // for Windows, File.getCanonicalPath("...b.") returns just "...b"
String start = db == null ? null : (FileUtils.getCanonicalPath(dir + "/" + db) + "."); String start = db == null ? null : (FileUtils.toRealPath(dir + "/" + db) + ".");
String[] list = FileUtils.listFiles(dir); for (String f : FileUtils.newDirectoryStream(dir)) {
for (int i = 0; list != null && i < list.length; i++) {
String f = list[i];
boolean ok = false; boolean ok = false;
if (f.endsWith(Constants.SUFFIX_LOBS_DIRECTORY)) { if (f.endsWith(Constants.SUFFIX_LOBS_DIRECTORY)) {
if (start == null || f.startsWith(start)) { if (start == null || f.startsWith(start)) {
......
...@@ -8,12 +8,13 @@ package org.h2.store; ...@@ -8,12 +8,13 @@ package org.h2.store;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.security.SecureFileStore; import org.h2.security.SecureFileStore;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.util.TempFileDeleter; import org.h2.util.TempFileDeleter;
import org.h2.util.Utils; import org.h2.util.Utils;
...@@ -46,7 +47,7 @@ public class FileStore { ...@@ -46,7 +47,7 @@ public class FileStore {
*/ */
protected DataHandler handler; protected DataHandler handler;
private FileObject file; private FileChannel file;
private long filePos; private long filePos;
private long fileLength; private long fileLength;
private Reference<?> autoDeleteReference; private Reference<?> autoDeleteReference;
...@@ -78,7 +79,7 @@ public class FileStore { ...@@ -78,7 +79,7 @@ public class FileStore {
} else { } else {
FileUtils.createDirectories(FileUtils.getParent(name)); FileUtils.createDirectories(FileUtils.getParent(name));
} }
file = FileUtils.openFileObject(name, mode); file = FileUtils.open(name, mode);
if (exists) { if (exists) {
fileLength = file.size(); fileLength = file.size();
} }
...@@ -272,7 +273,7 @@ public class FileStore { ...@@ -272,7 +273,7 @@ public class FileStore {
} }
checkPowerOff(); checkPowerOff();
try { try {
file.readFully(b, off, len); FileUtils.readFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e) { } catch (IOException e) {
throw DbException.convertIOException(e, name); throw DbException.convertIOException(e, name);
} }
...@@ -323,11 +324,11 @@ public class FileStore { ...@@ -323,11 +324,11 @@ public class FileStore {
checkWritingAllowed(); checkWritingAllowed();
checkPowerOff(); checkPowerOff();
try { try {
file.write(b, off, len); FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e) { } catch (IOException e) {
if (freeUpDiskSpace()) { if (freeUpDiskSpace()) {
try { try {
file.write(b, off, len); FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e2) { } catch (IOException e2) {
throw DbException.convertIOException(e2, name); throw DbException.convertIOException(e2, name);
} }
...@@ -362,7 +363,7 @@ public class FileStore { ...@@ -362,7 +363,7 @@ public class FileStore {
if (newLength > fileLength) { if (newLength > fileLength) {
long pos = filePos; long pos = filePos;
file.position(newLength - 1); file.position(newLength - 1);
file.write(new byte[1], 0, 1); FileUtils.writeFully(file, ByteBuffer.wrap(new byte[1]));
file.position(pos); file.position(pos);
} else { } else {
file.truncate(newLength); file.truncate(newLength);
...@@ -468,7 +469,7 @@ public class FileStore { ...@@ -468,7 +469,7 @@ public class FileStore {
*/ */
public void openFile() throws IOException { public void openFile() throws IOException {
if (file == null) { if (file == null) {
file = FileUtils.openFileObject(name, mode); file = FileUtils.open(name, mode);
file.position(filePos); file.position(filePos);
} }
} }
......
package org.h2.store.fs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
/**
* The base class for file implementations.
*/
public abstract class FileBase extends FileChannel {
public void force(boolean metaData) throws IOException {
// ignore
}
public FileLock lock(long position, long size, boolean shared) throws IOException {
throw new UnsupportedOperationException();
}
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
throw new UnsupportedOperationException();
}
public abstract long position() throws IOException;
public abstract FileChannel position(long newPosition) throws IOException;
public abstract int read(ByteBuffer dst) throws IOException;
public int read(ByteBuffer dst, long position) throws IOException {
throw new UnsupportedOperationException();
}
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
throw new UnsupportedOperationException();
}
public abstract long size() throws IOException;
public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException {
throw new UnsupportedOperationException();
}
public long transferTo(long position, long count, WritableByteChannel target)
throws IOException {
throw new UnsupportedOperationException();
}
public abstract FileChannel truncate(long size) throws IOException;
public FileLock tryLock(long position, long size, boolean shared) throws IOException {
throw new UnsupportedOperationException();
}
public abstract int write(ByteBuffer src) throws IOException;
public int write(ByteBuffer src, long position) throws IOException {
throw new UnsupportedOperationException(); }
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
throw new UnsupportedOperationException(); }
protected void implCloseChannel() throws IOException {
// ignore
}
}
...@@ -8,29 +8,31 @@ package org.h2.store.fs; ...@@ -8,29 +8,31 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/** /**
* Allows to read from a file object like an input stream. * Allows to read from a file channel like an input stream.
*/ */
public class FileObjectInputStream extends InputStream { public class FileChannelInputStream extends InputStream {
private FileObject file; private FileChannel channel;
private byte[] buffer = { 0 }; private byte[] buffer = { 0 };
/** /**
* Create a new file object input stream from the file object. * Create a new file object input stream from the file channel.
* *
* @param file the file object * @param channel the file channel
*/ */
public FileObjectInputStream(FileObject file) { public FileChannelInputStream(FileChannel channel) {
this.file = file; this.channel = channel;
} }
public int read() throws IOException { public int read() throws IOException {
if (file.position() >= file.size()) { if (channel.position() >= channel.size()) {
return -1; return -1;
} }
file.readFully(buffer, 0, 1); FileUtils.readFully(channel, ByteBuffer.wrap(buffer));
return buffer[0] & 0xff; return buffer[0] & 0xff;
} }
...@@ -39,15 +41,15 @@ public class FileObjectInputStream extends InputStream { ...@@ -39,15 +41,15 @@ public class FileObjectInputStream extends InputStream {
} }
public int read(byte[] b, int off, int len) throws IOException { public int read(byte[] b, int off, int len) throws IOException {
if (file.position() + len < file.size()) { if (channel.position() + len < channel.size()) {
file.readFully(b, off, len); FileUtils.readFully(channel, ByteBuffer.wrap(b, off, len));
return len; return len;
} }
return super.read(b, off, len); return super.read(b, off, len);
} }
public void close() throws IOException { public void close() throws IOException {
file.close(); channel.close();
} }
} }
...@@ -8,46 +8,48 @@ package org.h2.store.fs; ...@@ -8,46 +8,48 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/** /**
* Allows to write to a file object like an output stream. * Allows to write to a file channel like an output stream.
*/ */
public class FileObjectOutputStream extends OutputStream { public class FileChannelOutputStream extends OutputStream {
private FileObject file; private FileChannel channel;
private byte[] buffer = { 0 }; private byte[] buffer = { 0 };
/** /**
* Create a new file object output stream from the file object. * Create a new file object output stream from the file channel.
* *
* @param file the file object * @param channel the file channel
* @param append true for append mode, false for truncate and overwrite * @param append true for append mode, false for truncate and overwrite
*/ */
public FileObjectOutputStream(FileObject file, boolean append) throws IOException { public FileChannelOutputStream(FileChannel channel, boolean append) throws IOException {
this.file = file; this.channel = channel;
if (append) { if (append) {
file.position(file.size()); channel.position(channel.size());
} else { } else {
file.position(0); channel.position(0);
file.truncate(0); channel.truncate(0);
} }
} }
public void write(int b) throws IOException { public void write(int b) throws IOException {
buffer[0] = (byte) b; buffer[0] = (byte) b;
file.write(buffer, 0, 1); FileUtils.writeFully(channel, ByteBuffer.wrap(buffer));
} }
public void write(byte[] b) throws IOException { public void write(byte[] b) throws IOException {
file.write(b, 0, b.length); FileUtils.writeFully(channel, ByteBuffer.wrap(b));
} }
public void write(byte[] b, int off, int len) throws IOException { public void write(byte[] b, int off, int len) throws IOException {
file.write(b, off, len); FileUtils.writeFully(channel, ByteBuffer.wrap(b, off, len));
} }
public void close() throws IOException { public void close() throws IOException {
file.close(); channel.close();
} }
} }
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.IOException;
import java.nio.channels.FileLock;
/**
* This interface represents a random access file.
*/
public interface FileObject {
/**
* Get the length of the file.
*
* @return the length
*/
long size() throws IOException;
/**
* Close the file.
*/
void close() throws IOException;
/**
* Read from the file.
* @param b the byte array
* @param off the offset
* @param len the number of bytes
*/
void readFully(byte[] b, int off, int len) throws IOException;
/**
* Go to the specified position in the file.
*
* @param pos the new position
*/
void position(long pos) throws IOException;
/**
* Write to the file.
*
* @param b the byte array
* @param off the offset
* @param len the number of bytes
*/
void write(byte[] b, int off, int len) throws IOException;
/**
* Get the file pointer.
*
* @return the current file pointer
*/
long position() throws IOException;
/**
* Force changes to the physical location (sync).
*
* @param metaData whether the file metadata should be written as well
*/
void force(boolean metaData) throws IOException;
/**
* Change the length of the file.
*
* @param newLength the new length
*/
void truncate(long newLength) throws IOException;
/**
* Try to lock the file exclusively.
*
* @return a lock object if successful, or null if not
*/
FileLock tryLock() throws IOException;
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import org.h2.constant.SysProperties;
/**
* This class extends a java.io.RandomAccessFile.
*/
public class FileObjectDisk implements FileObject {
private final RandomAccessFile file;
private final String name;
FileObjectDisk(String fileName, String mode) throws FileNotFoundException {
this.file = new RandomAccessFile(fileName, mode);
this.name = fileName;
}
public void force(boolean metaData) throws IOException {
String m = SysProperties.SYNC_METHOD;
if ("".equals(m)) {
// do nothing
} else if ("sync".equals(m)) {
file.getFD().sync();
} else if ("force".equals(m)) {
file.getChannel().force(true);
} else if ("forceFalse".equals(m)) {
file.getChannel().force(false);
} else {
file.getFD().sync();
}
}
public void truncate(long newLength) throws IOException {
if (newLength < file.length()) {
// some implementations actually only support truncate
file.setLength(newLength);
}
}
public synchronized FileLock tryLock() throws IOException {
return file.getChannel().tryLock();
}
public void close() throws IOException {
file.close();
}
public long position() throws IOException {
return file.getFilePointer();
}
public long size() throws IOException {
return file.length();
}
public void readFully(byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
}
public void position(long pos) throws IOException {
file.seek(pos);
}
public void write(byte[] b, int off, int len) throws IOException {
file.write(b, off, len);
}
public String toString() {
return name;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.IOException;
import java.nio.channels.FileLock;
/**
* This class represents an in-memory file.
*/
public class FileObjectMem implements FileObject {
private final FileObjectMemData data;
private final boolean readOnly;
private long pos;
FileObjectMem(FileObjectMemData data, boolean readOnly) {
this.data = data;
this.readOnly = readOnly;
}
public long size() {
return data.length();
}
public void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
data.touch(readOnly);
pos = Math.min(pos, newLength);
data.truncate(newLength);
}
public void position(long newPos) {
this.pos = (int) newPos;
}
public void write(byte[] b, int off, int len) throws IOException {
data.touch(readOnly);
pos = data.readWrite(pos, b, off, len, true);
}
public void readFully(byte[] b, int off, int len) throws IOException {
pos = data.readWrite(pos, b, off, len, false);
}
public long position() {
return pos;
}
public void close() {
pos = 0;
}
public void force(boolean metaData) throws IOException {
// do nothing
}
public FileLock tryLock() {
return null;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.h2.compress.CompressLZF;
import org.h2.util.MathUtils;
/**
* This class contains the data of an in-memory random access file.
* Data compression using the LZF algorithm is supported as well.
*/
class FileObjectMemData {
private static final int CACHE_SIZE = 8;
private static final int BLOCK_SIZE_SHIFT = 10;
private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT;
private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;
private static final CompressLZF LZF = new CompressLZF();
private static final byte[] BUFFER = new byte[BLOCK_SIZE * 2];
private static final byte[] COMPRESSED_EMPTY_BLOCK;
private static final Cache<CompressItem, CompressItem> COMPRESS_LATER =
new Cache<CompressItem, CompressItem>(CACHE_SIZE);
private String name;
private final boolean compress;
private long length;
private byte[][] data;
private long lastModified;
private boolean isReadOnly;
private volatile boolean locked;
static {
byte[] n = new byte[BLOCK_SIZE];
int len = LZF.compress(n, BLOCK_SIZE, BUFFER, 0);
COMPRESSED_EMPTY_BLOCK = new byte[len];
System.arraycopy(BUFFER, 0, COMPRESSED_EMPTY_BLOCK, 0, len);
}
/**
* This small cache compresses the data if an element leaves the cache.
*/
static class Cache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private int size;
Cache(int size) {
super(size, (float) 0.75, true);
this.size = size;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
if (size() < size) {
return false;
}
CompressItem c = (CompressItem) eldest.getKey();
compress(c.data, c.page);
return true;
}
}
/**
* Represents a compressed item.
*/
static class CompressItem {
/**
* The file data.
*/
byte[][] data;
/**
* The page to compress.
*/
int page;
public int hashCode() {
return page;
}
public boolean equals(Object o) {
if (o instanceof CompressItem) {
CompressItem c = (CompressItem) o;
return c.data == data && c.page == page;
}
return false;
}
}
FileObjectMemData(String name, boolean compress) {
this.name = name;
this.compress = compress;
data = new byte[0][];
lastModified = System.currentTimeMillis();
}
private static void compressLater(byte[][] data, int page) {
CompressItem c = new CompressItem();
c.data = data;
c.page = page;
synchronized (LZF) {
COMPRESS_LATER.put(c, c);
}
}
private static void expand(byte[][] data, int page) {
byte[] d = data[page];
if (d.length == BLOCK_SIZE) {
return;
}
byte[] out = new byte[BLOCK_SIZE];
if (d != COMPRESSED_EMPTY_BLOCK) {
synchronized (LZF) {
LZF.expand(d, 0, d.length, out, 0, BLOCK_SIZE);
}
}
data[page] = out;
}
/**
* Compress the data in a byte array.
*
* @param data the page array
* @param page which page to compress
*/
static void compress(byte[][] data, int page) {
byte[] d = data[page];
synchronized (LZF) {
int len = LZF.compress(d, BLOCK_SIZE, BUFFER, 0);
if (len <= BLOCK_SIZE) {
d = new byte[len];
System.arraycopy(BUFFER, 0, d, 0, len);
data[page] = d;
}
}
}
/**
* Update the last modified time.
*
* @param openReadOnly if the file was opened in read-only mode
*/
void touch(boolean openReadOnly) throws IOException {
if (isReadOnly || openReadOnly) {
throw new IOException("Read only");
}
lastModified = System.currentTimeMillis();
}
/**
* Get the file length.
*
* @return the length
*/
long length() {
return length;
}
/**
* Truncate the file.
*
* @param newLength the new length
*/
void truncate(long newLength) {
changeLength(newLength);
long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE);
if (end != newLength) {
int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT);
expand(data, lastPage);
byte[] d = data[lastPage];
for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
d[i] = 0;
}
if (compress) {
compressLater(data, lastPage);
}
}
}
private void changeLength(long len) {
length = len;
len = MathUtils.roundUpLong(len, BLOCK_SIZE);
int blocks = (int) (len >>> BLOCK_SIZE_SHIFT);
if (blocks != data.length) {
byte[][] n = new byte[blocks][];
System.arraycopy(data, 0, n, 0, Math.min(data.length, n.length));
for (int i = data.length; i < blocks; i++) {
n[i] = COMPRESSED_EMPTY_BLOCK;
}
data = n;
}
}
/**
* Read or write.
*
* @param pos the position
* @param b the byte array
* @param off the offset within the byte array
* @param len the number of bytes
* @param write true for writing
* @return the new position
*/
long readWrite(long pos, byte[] b, int off, int len, boolean write) throws IOException {
long end = pos + len;
if (end > length) {
if (write) {
changeLength(end);
} else {
if (len == 0) {
return pos;
}
throw new EOFException("File: " + name);
}
}
while (len > 0) {
int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK));
int page = (int) (pos >>> BLOCK_SIZE_SHIFT);
expand(data, page);
byte[] block = data[page];
int blockOffset = (int) (pos & BLOCK_SIZE_MASK);
if (write) {
System.arraycopy(b, off, block, blockOffset, l);
} else {
System.arraycopy(block, blockOffset, b, off, l);
}
if (compress) {
compressLater(data, page);
}
off += l;
pos += l;
len -= l;
}
return pos;
}
/**
* Set the file name.
*
* @param name the name
*/
void setName(String name) {
this.name = name;
}
/**
* Get the file name
*
* @return the name
*/
String getName() {
return name;
}
/**
* Get the last modified time.
*
* @return the time
*/
long getLastModified() {
return lastModified;
}
/**
* Check whether writing is allowed.
*
* @return true if it is
*/
boolean canWrite() {
return !isReadOnly;
}
/**
* Set the read-only flag.
*
* @return true
*/
boolean setReadOnly() {
isReadOnly = true;
return true;
}
/**
* Lock the file.
*
* @return if successful
*/
synchronized boolean tryLock() {
if (locked) {
return false;
}
locked = true;
return true;
}
/**
* Unlock the file.
*/
public synchronized void releaseLock() {
locked = false;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: Jan Kotek
*/
package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.NonWritableChannelException;
/**
* File which uses NIO FileChannel.
*/
public class FileObjectNio implements FileObject {
private final String name;
private final RandomAccessFile file;
private final FileChannel channel;
private long length;
FileObjectNio(String fileName, String mode) throws IOException {
this.name = fileName;
file = new RandomAccessFile(fileName, mode);
channel = file.getChannel();
length = file.length();
}
public void close() throws IOException {
channel.close();
file.close();
}
public long position() throws IOException {
return channel.position();
}
public long size() throws IOException {
return channel.size();
}
public void readFully(byte[] b, int off, int len) throws IOException {
if (len == 0) {
return;
}
if (channel.position() + len > length) {
throw new EOFException();
}
ByteBuffer buf = ByteBuffer.wrap(b);
buf.position(off);
buf.limit(off + len);
channel.read(buf);
}
public void position(long pos) throws IOException {
channel.position(pos);
}
public void truncate(long newLength) throws IOException {
if (newLength >= channel.size()) {
return;
}
long pos = channel.position();
try {
channel.truncate(newLength);
} catch (NonWritableChannelException e) {
throw new IOException("read only");
}
if (pos > newLength) {
pos = newLength;
}
channel.position(pos);
length = newLength;
}
public void force(boolean metaData) throws IOException {
channel.force(metaData);
}
public void write(byte[] b, int off, int len) throws IOException {
ByteBuffer buf = ByteBuffer.wrap(b);
buf.position(off);
buf.limit(off + len);
try {
channel.write(buf);
} catch (NonWritableChannelException e) {
throw new IOException("read only");
}
}
public synchronized FileLock tryLock() throws IOException {
return channel.tryLock();
}
public String toString() {
return name;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: Jan Kotek
*/
package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileLock;
import java.nio.channels.FileChannel.MapMode;
import org.h2.constant.SysProperties;
/**
* FileObject which is using NIO MappedByteBuffer mapped to memory from file.
* The file size is limited to 2 GB.
*/
public class FileObjectNioMapped implements FileObject {
private static final long GC_TIMEOUT_MS = 10000;
private final String name;
private final MapMode mode;
private RandomAccessFile file;
private MappedByteBuffer mapped;
/**
* The position within the file. Can't use the position of the mapped buffer
* because it doesn't support seeking past the end of the file.
*/
private int pos;
FileObjectNioMapped(String fileName, String mode) throws IOException {
if ("r".equals(mode)) {
this.mode = MapMode.READ_ONLY;
} else {
this.mode = MapMode.READ_WRITE;
}
this.name = fileName;
file = new RandomAccessFile(fileName, mode);
reMap();
}
private void unMap() throws IOException {
if (mapped == null) {
return;
}
// first write all data
mapped.force();
// need to dispose old direct buffer, see bug
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038
boolean useSystemGc = true;
if (SysProperties.NIO_CLEANER_HACK) {
try {
Method cleanerMethod = mapped.getClass().getMethod("cleaner");
cleanerMethod.setAccessible(true);
Object cleaner = cleanerMethod.invoke(mapped);
if (cleaner != null) {
Method clearMethod = cleaner.getClass().getMethod("clean");
clearMethod.invoke(cleaner);
}
useSystemGc = false;
} catch (Throwable e) {
// useSystemGc is already true
} finally {
mapped = null;
}
}
if (useSystemGc) {
WeakReference<MappedByteBuffer> bufferWeakRef = new WeakReference<MappedByteBuffer>(mapped);
mapped = null;
long start = System.currentTimeMillis();
while (bufferWeakRef.get() != null) {
if (System.currentTimeMillis() - start > GC_TIMEOUT_MS) {
throw new IOException("Timeout (" + GC_TIMEOUT_MS
+ " ms) reached while trying to GC mapped buffer");
}
System.gc();
Thread.yield();
}
}
}
/**
* Re-map byte buffer into memory, called when file size has changed or file
* was created.
*/
private void reMap() throws IOException {
int oldPos = 0;
if (mapped != null) {
oldPos = pos;
unMap();
}
long length = file.length();
checkFileSizeLimit(length);
// maps new MappedByteBuffer; the old one is disposed during GC
mapped = file.getChannel().map(mode, 0, length);
int limit = mapped.limit();
int capacity = mapped.capacity();
if (limit < length || capacity < length) {
throw new IOException("Unable to map: length=" + limit + " capacity=" + capacity + " length=" + length);
}
if (SysProperties.NIO_LOAD_MAPPED) {
mapped.load();
}
this.pos = Math.min(oldPos, (int) length);
}
private static void checkFileSizeLimit(long length) throws IOException {
if (length > Integer.MAX_VALUE) {
throw new IOException("File over 2GB is not supported yet when using this file system");
}
}
public synchronized void close() throws IOException {
if (file != null) {
unMap();
file.close();
file = null;
}
}
public long position() {
return pos;
}
public String toString() {
return "nioMapped:" + name;
}
public synchronized long size() throws IOException {
return file.length();
}
public synchronized void readFully(byte[] b, int off, int len) throws EOFException {
try {
mapped.position(pos);
mapped.get(b, off, len);
pos += len;
} catch (IllegalArgumentException e) {
EOFException e2 = new EOFException("EOF");
e2.initCause(e);
throw e2;
} catch (BufferUnderflowException e) {
EOFException e2 = new EOFException("EOF");
e2.initCause(e);
throw e2;
}
}
public void position(long pos) throws IOException {
checkFileSizeLimit(pos);
this.pos = (int) pos;
}
public synchronized void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
setFileLength(newLength);
}
public synchronized void setFileLength(long newLength) throws IOException {
checkFileSizeLimit(newLength);
int oldPos = pos;
unMap();
for (int i = 0;; i++) {
try {
file.setLength(newLength);
break;
} catch (IOException e) {
if (i > 16 || e.toString().indexOf("user-mapped section open") < 0) {
throw e;
}
}
System.gc();
}
reMap();
pos = (int) Math.min(newLength, oldPos);
}
public void force(boolean metaData) throws IOException {
mapped.force();
file.getFD().sync();
}
public synchronized void write(byte[] b, int off, int len) throws IOException {
// check if need to expand file
if (mapped.capacity() < pos + len) {
setFileLength(pos + len);
}
mapped.position(pos);
mapped.put(b, off, len);
pos += len;
}
public synchronized FileLock tryLock() throws IOException {
return file.getChannel().tryLock();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.IOException;
import java.nio.channels.FileLock;
/**
* A file object that records all write operations and can re-play them.
*/
public class FileObjectRec implements FileObject {
private final FilePathRec fs;
private final FileObject file;
private final String name;
FileObjectRec(FilePathRec fs, FileObject file, String fileName) {
this.fs = fs;
this.file = file;
this.name = fileName;
}
public void close() throws IOException {
file.close();
}
public long position() throws IOException {
return file.position();
}
public long size() throws IOException {
return file.size();
}
public void readFully(byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
}
public void position(long pos) throws IOException {
file.position(pos);
}
public void truncate(long newLength) throws IOException {
fs.log(Recorder.TRUNCATE, name, null, newLength);
file.truncate(newLength);
}
public void force(boolean metaData) throws IOException {
file.force(metaData);
}
public void write(byte[] b, int off, int len) throws IOException {
byte[] buff = b;
if (off != 0 || len != b.length) {
buff = new byte[len];
System.arraycopy(b, off, buff, 0, len);
}
file.write(b, off, len);
fs.log(Recorder.WRITE, name, buff, file.position());
}
public FileLock tryLock() throws IOException {
return file.tryLock();
}
}
\ No newline at end of file
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.FileLock;
import org.h2.message.DbException;
/**
* A file that may be split into multiple smaller files.
*/
public class FileObjectSplit implements FileObject {
private final FilePathSplit file;
private final String mode;
private final long maxLength;
private FileObject[] list;
private long filePointer;
private long length;
FileObjectSplit(FilePathSplit file, String mode, FileObject[] list, long length, long maxLength) {
this.file = file;
this.mode = mode;
this.list = list;
this.length = length;
this.maxLength = maxLength;
}
public void close() throws IOException {
for (FileObject f : list) {
f.close();
}
}
public long position() {
return filePointer;
}
public long size() {
return length;
}
private int read(byte[] b, int off, int len) throws IOException {
long offset = filePointer % maxLength;
int l = (int) Math.min(len, maxLength - offset);
FileObject fo = getFileObject();
fo.position(offset);
fo.readFully(b, off, l);
filePointer += l;
return l;
}
public void readFully(byte[] b, int off, int len) throws IOException {
if (filePointer + len > length) {
throw new EOFException();
}
while (true) {
int l = read(b, off, len);
len -= l;
if (len <= 0) {
return;
}
off += l;
}
}
public void position(long pos) {
filePointer = pos;
}
private FileObject getFileObject() throws IOException {
int id = (int) (filePointer / maxLength);
while (id >= list.length) {
int i = list.length;
FileObject[] newList = new FileObject[i + 1];
System.arraycopy(list, 0, newList, 0, i);
FilePath f = file.getBase(i);
newList[i] = f.openFileObject(mode);
list = newList;
}
return list[id];
}
public void truncate(long newLength) throws IOException {
if (newLength >= length) {
return;
}
filePointer = Math.min(filePointer, newLength);
int newFileCount = 1 + (int) (newLength / maxLength);
if (newFileCount < list.length) {
// delete some of the files
FileObject[] newList = new FileObject[newFileCount];
// delete backwards, so that truncating is somewhat transactional
for (int i = list.length - 1; i >= newFileCount; i--) {
// verify the file is writable
list[i].truncate(0);
list[i].close();
try {
file.getBase(i).delete();
} catch (DbException e) {
throw DbException.convertToIOException(e);
}
}
System.arraycopy(list, 0, newList, 0, newList.length);
list = newList;
}
long size = newLength - maxLength * (newFileCount - 1);
list[list.length - 1].truncate(size);
this.length = newLength;
}
public void force(boolean metaData) throws IOException {
for (FileObject f : list) {
f.force(metaData);
}
}
public void write(byte[] b, int off, int len) throws IOException {
if (filePointer >= length && filePointer > maxLength) {
// may need to extend and create files
long oldFilePointer = filePointer;
long x = length - (length % maxLength) + maxLength;
for (; x < filePointer; x += maxLength) {
if (x > length) {
position(x - 1);
writePart(new byte[1], 0, 1);
}
filePointer = oldFilePointer;
}
}
while (true) {
int l = writePart(b, off, len);
len -= l;
if (len <= 0) {
return;
}
off += l;
}
}
private int writePart(byte[] b, int off, int len) throws IOException {
long offset = filePointer % maxLength;
int l = (int) Math.min(len, maxLength - offset);
FileObject fo = getFileObject();
fo.position(offset);
fo.write(b, off, l);
filePointer += l;
length = Math.max(length, filePointer);
return l;
}
public FileLock tryLock() throws IOException {
return list[0].tryLock();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileLock;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.h2.util.IOUtils;
/**
* The file is read from a stream. When reading from start to end, the same
* input stream is re-used, however when reading from end to start, a new input
* stream is opened for each request.
*/
public class FileObjectZip implements FileObject {
private static final byte[] SKIP_BUFFER = new byte[1024];
private ZipFile file;
private ZipEntry entry;
private long pos;
private InputStream in;
private long inPos;
private long length;
private boolean skipUsingRead;
FileObjectZip(ZipFile file, ZipEntry entry) {
this.file = file;
this.entry = entry;
length = entry.getSize();
}
public void close() {
// nothing to do
}
public long position() {
return pos;
}
public long size() {
return length;
}
public void readFully(byte[] b, int off, int len) throws IOException {
if (inPos > pos) {
if (in != null) {
in.close();
}
in = null;
}
if (in == null) {
in = file.getInputStream(entry);
inPos = 0;
}
if (inPos < pos) {
long skip = pos - inPos;
if (!skipUsingRead) {
try {
IOUtils.skipFully(in, skip);
} catch (NullPointerException e) {
// workaround for Android
skipUsingRead = true;
}
}
if (skipUsingRead) {
while (skip > 0) {
int s = (int) Math.min(SKIP_BUFFER.length, skip);
s = in.read(SKIP_BUFFER, 0, s);
skip -= s;
}
}
inPos = pos;
}
int l = IOUtils.readFully(in, b, off, len);
if (l != len) {
throw new EOFException();
}
pos += len;
inPos += len;
}
public void position(long newPos) {
this.pos = newPos;
}
public void truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
public void force(boolean metaData) throws IOException {
// nothing to do
}
public void write(byte[] b, int off, int len) throws IOException {
throw new IOException("File is read-only");
}
public FileLock tryLock() {
return null;
}
}
...@@ -9,6 +9,7 @@ package org.h2.store.fs; ...@@ -9,6 +9,7 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -142,18 +143,18 @@ public abstract class FilePath { ...@@ -142,18 +143,18 @@ public abstract class FilePath {
public abstract void delete(); public abstract void delete();
/** /**
* List the files in the given directory. * List the files and directories in the given directory.
* *
* @return the list of fully qualified file names * @return the list of fully qualified file names
*/ */
public abstract List<FilePath> listFiles(); public abstract List<FilePath> newDirectoryStream();
/** /**
* Normalize a file name. * Normalize a file name.
* *
* @return the normalized file name * @return the normalized file name
*/ */
public abstract FilePath getCanonicalPath(); public abstract FilePath toRealPath();
/** /**
* Get the parent directory of a file or directory. * Get the parent directory of a file or directory.
...@@ -220,7 +221,7 @@ public abstract class FilePath { ...@@ -220,7 +221,7 @@ public abstract class FilePath {
* @param mode the access mode. Supported are r, rw, rws, rwd * @param mode the access mode. Supported are r, rw, rws, rwd
* @return the file object * @return the file object
*/ */
public abstract FileObject openFileObject(String mode) throws IOException; public abstract FileChannel open(String mode) throws IOException;
/** /**
* Create an input stream to read from the file. * Create an input stream to read from the file.
...@@ -253,7 +254,7 @@ public abstract class FilePath { ...@@ -253,7 +254,7 @@ public abstract class FilePath {
getNextTempFileNamePart(true); getNextTempFileNamePart(true);
continue; continue;
} }
p.openFileObject("rw").close(); p.open("rw").close();
return p; return p;
} }
} }
......
...@@ -15,6 +15,9 @@ import java.io.InputStream; ...@@ -15,6 +15,9 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
...@@ -146,7 +149,7 @@ public class FilePathDisk extends FilePath { ...@@ -146,7 +149,7 @@ public class FilePathDisk extends FilePath {
throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, name); throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, name);
} }
public List<FilePath> listFiles() { public List<FilePath> newDirectoryStream() {
ArrayList<FilePath> list = New.arrayList(); ArrayList<FilePath> list = New.arrayList();
File f = new File(name); File f = new File(name);
try { try {
...@@ -175,7 +178,7 @@ public class FilePathDisk extends FilePath { ...@@ -175,7 +178,7 @@ public class FilePathDisk extends FilePath {
return f.setReadOnly(); return f.setReadOnly();
} }
public FilePathDisk getCanonicalPath() { public FilePathDisk toRealPath() {
try { try {
String fileName = new File(name).getCanonicalPath(); String fileName = new File(name).getCanonicalPath();
return getPath(fileName); return getPath(fileName);
...@@ -311,15 +314,15 @@ public class FilePathDisk extends FilePath { ...@@ -311,15 +314,15 @@ public class FilePathDisk extends FilePath {
} }
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
FileObjectDisk f; FileDisk f;
try { try {
f = new FileObjectDisk(name, mode); f = new FileDisk(name, mode);
IOUtils.trace("openFileObject", name, f); IOUtils.trace("open", name, f);
} catch (IOException e) { } catch (IOException e) {
freeMemoryAndFinalize(); freeMemoryAndFinalize();
try { try {
f = new FileObjectDisk(name, mode); f = new FileDisk(name, mode);
} catch (IOException e2) { } catch (IOException e2) {
throw e; throw e;
} }
...@@ -363,3 +366,81 @@ public class FilePathDisk extends FilePath { ...@@ -363,3 +366,81 @@ public class FilePathDisk extends FilePath {
} }
} }
/**
* Uses java.io.RandomAccessFile to access a file.
*/
class FileDisk extends FileBase {
private final RandomAccessFile file;
private final String name;
FileDisk(String fileName, String mode) throws FileNotFoundException {
this.file = new RandomAccessFile(fileName, mode);
this.name = fileName;
}
public void force(boolean metaData) throws IOException {
String m = SysProperties.SYNC_METHOD;
if ("".equals(m)) {
// do nothing
} else if ("sync".equals(m)) {
file.getFD().sync();
} else if ("force".equals(m)) {
file.getChannel().force(true);
} else if ("forceFalse".equals(m)) {
file.getChannel().force(false);
} else {
file.getFD().sync();
}
}
public FileChannel truncate(long newLength) throws IOException {
if (newLength < file.length()) {
// some implementations actually only support truncate
file.setLength(newLength);
}
return this;
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.getChannel().tryLock();
}
public void implCloseChannel() throws IOException {
file.close();
}
public long position() throws IOException {
return file.getFilePointer();
}
public long size() throws IOException {
return file.length();
}
public int read(ByteBuffer dst) throws IOException {
int len = file.read(dst.array(), dst.position(), dst.remaining());
if (len > 0) {
dst.position(dst.position() + len);
}
return len;
}
public FileChannel position(long pos) throws IOException {
file.seek(pos);
return this;
}
public int write(ByteBuffer src) throws IOException {
int len = src.remaining();
file.write(src.array(), src.position(), len);
src.position(src.position() + len);
return len;
}
public String toString() {
return name;
}
}
...@@ -9,10 +9,17 @@ package org.h2.store.fs; ...@@ -9,10 +9,17 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import org.h2.compress.CompressLZF;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
/** /**
...@@ -21,7 +28,7 @@ import org.h2.util.New; ...@@ -21,7 +28,7 @@ import org.h2.util.New;
*/ */
public class FilePathMem extends FilePath { public class FilePathMem extends FilePath {
private static final TreeMap<String, FileObjectMemData> MEMORY_FILES = new TreeMap<String, FileObjectMemData>(); private static final TreeMap<String, FileMemData> MEMORY_FILES = new TreeMap<String, FileMemData>();
public FilePathMem getPath(String path) { public FilePathMem getPath(String path) {
FilePathMem p = new FilePathMem(); FilePathMem p = new FilePathMem();
...@@ -35,7 +42,7 @@ public class FilePathMem extends FilePath { ...@@ -35,7 +42,7 @@ public class FilePathMem extends FilePath {
public void moveTo(FilePath newName) { public void moveTo(FilePath newName) {
synchronized (MEMORY_FILES) { synchronized (MEMORY_FILES) {
FileObjectMemData f = getMemoryFile(); FileMemData f = getMemoryFile();
f.setName(newName.name); f.setName(newName.name);
MEMORY_FILES.remove(name); MEMORY_FILES.remove(name);
MEMORY_FILES.put(newName.name, f); MEMORY_FILES.put(newName.name, f);
...@@ -70,7 +77,7 @@ public class FilePathMem extends FilePath { ...@@ -70,7 +77,7 @@ public class FilePathMem extends FilePath {
} }
} }
public List<FilePath> listFiles() { public List<FilePath> newDirectoryStream() {
ArrayList<FilePath> list = New.arrayList(); ArrayList<FilePath> list = New.arrayList();
synchronized (MEMORY_FILES) { synchronized (MEMORY_FILES) {
for (String n : MEMORY_FILES.tailMap(name).keySet()) { for (String n : MEMORY_FILES.tailMap(name).keySet()) {
...@@ -108,7 +115,7 @@ public class FilePathMem extends FilePath { ...@@ -108,7 +115,7 @@ public class FilePathMem extends FilePath {
return true; return true;
} }
public FilePathMem getCanonicalPath() { public FilePathMem toRealPath() {
return this; return this;
} }
...@@ -122,30 +129,30 @@ public class FilePathMem extends FilePath { ...@@ -122,30 +129,30 @@ public class FilePathMem extends FilePath {
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
try { try {
FileObjectMemData obj = getMemoryFile(); FileMemData obj = getMemoryFile();
FileObjectMem m = new FileObjectMem(obj, false); FileMem m = new FileMem(obj, false);
return new FileObjectOutputStream(m, append); return new FileChannelOutputStream(m, append);
} catch (IOException e) { } catch (IOException e) {
throw DbException.convertIOException(e, name); throw DbException.convertIOException(e, name);
} }
} }
public InputStream newInputStream() { public InputStream newInputStream() {
FileObjectMemData obj = getMemoryFile(); FileMemData obj = getMemoryFile();
FileObjectMem m = new FileObjectMem(obj, true); FileMem m = new FileMem(obj, true);
return new FileObjectInputStream(m); return new FileChannelInputStream(m);
} }
public FileObject openFileObject(String mode) { public FileChannel open(String mode) {
FileObjectMemData obj = getMemoryFile(); FileMemData obj = getMemoryFile();
return new FileObjectMem(obj, "r".equals(mode)); return new FileMem(obj, "r".equals(mode));
} }
private FileObjectMemData getMemoryFile() { private FileMemData getMemoryFile() {
synchronized (MEMORY_FILES) { synchronized (MEMORY_FILES) {
FileObjectMemData m = MEMORY_FILES.get(name); FileMemData m = MEMORY_FILES.get(name);
if (m == null) { if (m == null) {
m = new FileObjectMemData(name, compressed()); m = new FileMemData(name, compressed());
MEMORY_FILES.put(name, m); MEMORY_FILES.put(name, m);
} }
return m; return m;
...@@ -195,3 +202,375 @@ class FilePathMemLZF extends FilePathMem { ...@@ -195,3 +202,375 @@ class FilePathMemLZF extends FilePathMem {
} }
/**
* This class represents an in-memory file.
*/
class FileMem extends FileBase {
private final FileMemData data;
private final boolean readOnly;
private long pos;
FileMem(FileMemData data, boolean readOnly) {
this.data = data;
this.readOnly = readOnly;
}
public long size() {
return data.length();
}
public FileChannel truncate(long newLength) throws IOException {
if (newLength < size()) {
data.touch(readOnly);
pos = Math.min(pos, newLength);
data.truncate(newLength);
}
return this;
}
public FileChannel position(long newPos) {
this.pos = (int) newPos;
return this;
}
public int write(ByteBuffer src) throws IOException {
int len = src.remaining();
if (len == 0) {
return 0;
}
data.touch(readOnly);
pos = data.readWrite(pos, src.array(), src.position(), len, true);
src.position(src.position() + len);
return len;
}
public int read(ByteBuffer dst) throws IOException {
int len = dst.remaining();
if (len == 0) {
return 0;
}
long newPos = data.readWrite(pos, dst.array(), dst.position(), len, false);
len = (int) (newPos - pos);
if (len <= 0) {
return -1;
}
dst.position(dst.position() + len);
pos = newPos;
return len;
}
public long position() {
return pos;
}
public void implCloseChannel() throws IOException {
pos = 0;
}
public void force(boolean metaData) throws IOException {
// do nothing
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return null;
}
}
/**
* This class contains the data of an in-memory random access file.
* Data compression using the LZF algorithm is supported as well.
*/
class FileMemData {
private static final int CACHE_SIZE = 8;
private static final int BLOCK_SIZE_SHIFT = 10;
private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT;
private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1;
private static final CompressLZF LZF = new CompressLZF();
private static final byte[] BUFFER = new byte[BLOCK_SIZE * 2];
private static final byte[] COMPRESSED_EMPTY_BLOCK;
private static final Cache<CompressItem, CompressItem> COMPRESS_LATER =
new Cache<CompressItem, CompressItem>(CACHE_SIZE);
private String name;
private final boolean compress;
private long length;
private byte[][] data;
private long lastModified;
private boolean isReadOnly;
private volatile boolean locked;
static {
byte[] n = new byte[BLOCK_SIZE];
int len = LZF.compress(n, BLOCK_SIZE, BUFFER, 0);
COMPRESSED_EMPTY_BLOCK = new byte[len];
System.arraycopy(BUFFER, 0, COMPRESSED_EMPTY_BLOCK, 0, len);
}
/**
* This small cache compresses the data if an element leaves the cache.
*/
static class Cache<K, V> extends LinkedHashMap<K, V> {
private static final long serialVersionUID = 1L;
private int size;
Cache(int size) {
super(size, (float) 0.75, true);
this.size = size;
}
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
if (size() < size) {
return false;
}
CompressItem c = (CompressItem) eldest.getKey();
compress(c.data, c.page);
return true;
}
}
/**
* Represents a compressed item.
*/
static class CompressItem {
/**
* The file data.
*/
byte[][] data;
/**
* The page to compress.
*/
int page;
public int hashCode() {
return page;
}
public boolean equals(Object o) {
if (o instanceof CompressItem) {
CompressItem c = (CompressItem) o;
return c.data == data && c.page == page;
}
return false;
}
}
FileMemData(String name, boolean compress) {
this.name = name;
this.compress = compress;
data = new byte[0][];
lastModified = System.currentTimeMillis();
}
private static void compressLater(byte[][] data, int page) {
CompressItem c = new CompressItem();
c.data = data;
c.page = page;
synchronized (LZF) {
COMPRESS_LATER.put(c, c);
}
}
private static void expand(byte[][] data, int page) {
byte[] d = data[page];
if (d.length == BLOCK_SIZE) {
return;
}
byte[] out = new byte[BLOCK_SIZE];
if (d != COMPRESSED_EMPTY_BLOCK) {
synchronized (LZF) {
LZF.expand(d, 0, d.length, out, 0, BLOCK_SIZE);
}
}
data[page] = out;
}
/**
* Compress the data in a byte array.
*
* @param data the page array
* @param page which page to compress
*/
static void compress(byte[][] data, int page) {
byte[] d = data[page];
synchronized (LZF) {
int len = LZF.compress(d, BLOCK_SIZE, BUFFER, 0);
if (len <= BLOCK_SIZE) {
d = new byte[len];
System.arraycopy(BUFFER, 0, d, 0, len);
data[page] = d;
}
}
}
/**
* Update the last modified time.
*
* @param openReadOnly if the file was opened in read-only mode
*/
void touch(boolean openReadOnly) throws IOException {
if (isReadOnly || openReadOnly) {
throw new IOException("Read only");
}
lastModified = System.currentTimeMillis();
}
/**
* Get the file length.
*
* @return the length
*/
long length() {
return length;
}
/**
* Truncate the file.
*
* @param newLength the new length
*/
void truncate(long newLength) {
changeLength(newLength);
long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE);
if (end != newLength) {
int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT);
expand(data, lastPage);
byte[] d = data[lastPage];
for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) {
d[i] = 0;
}
if (compress) {
compressLater(data, lastPage);
}
}
}
private void changeLength(long len) {
length = len;
len = MathUtils.roundUpLong(len, BLOCK_SIZE);
int blocks = (int) (len >>> BLOCK_SIZE_SHIFT);
if (blocks != data.length) {
byte[][] n = new byte[blocks][];
System.arraycopy(data, 0, n, 0, Math.min(data.length, n.length));
for (int i = data.length; i < blocks; i++) {
n[i] = COMPRESSED_EMPTY_BLOCK;
}
data = n;
}
}
/**
* Read or write.
*
* @param pos the position
* @param b the byte array
* @param off the offset within the byte array
* @param len the number of bytes
* @param write true for writing
* @return the new position
*/
long readWrite(long pos, byte[] b, int off, int len, boolean write) {
long end = pos + len;
if (end > length) {
if (write) {
changeLength(end);
} else {
return pos;
}
}
while (len > 0) {
int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK));
int page = (int) (pos >>> BLOCK_SIZE_SHIFT);
expand(data, page);
byte[] block = data[page];
int blockOffset = (int) (pos & BLOCK_SIZE_MASK);
if (write) {
System.arraycopy(b, off, block, blockOffset, l);
} else {
System.arraycopy(block, blockOffset, b, off, l);
}
if (compress) {
compressLater(data, page);
}
off += l;
pos += l;
len -= l;
}
return pos;
}
/**
* Set the file name.
*
* @param name the name
*/
void setName(String name) {
this.name = name;
}
/**
* Get the file name
*
* @return the name
*/
String getName() {
return name;
}
/**
* Get the last modified time.
*
* @return the time
*/
long getLastModified() {
return lastModified;
}
/**
* Check whether writing is allowed.
*
* @return true if it is
*/
boolean canWrite() {
return !isReadOnly;
}
/**
* Set the read-only flag.
*
* @return true
*/
boolean setReadOnly() {
isReadOnly = true;
return true;
}
/**
* Lock the file.
*
* @return if successful
*/
synchronized boolean tryLock() {
if (locked) {
return false;
}
locked = true;
return true;
}
/**
* Unlock the file.
*/
public synchronized void releaseLock() {
locked = false;
}
}
...@@ -7,6 +7,11 @@ ...@@ -7,6 +7,11 @@
package org.h2.store.fs; package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.NonWritableChannelException;
/** /**
* This file system stores files on disk and uses java.nio to access the files. * This file system stores files on disk and uses java.nio to access the files.
...@@ -14,8 +19,8 @@ import java.io.IOException; ...@@ -14,8 +19,8 @@ import java.io.IOException;
*/ */
public class FilePathNio extends FilePathWrapper { public class FilePathNio extends FilePathWrapper {
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
return new FileObjectNio(name.substring(getScheme().length() + 1), mode); return new FileNio(name.substring(getScheme().length() + 1), mode);
} }
public String getScheme() { public String getScheme() {
...@@ -23,3 +28,73 @@ public class FilePathNio extends FilePathWrapper { ...@@ -23,3 +28,73 @@ public class FilePathNio extends FilePathWrapper {
} }
} }
/**
* File which uses NIO FileChannel.
*/
class FileNio extends FileBase {
private final String name;
private final FileChannel channel;
FileNio(String fileName, String mode) throws IOException {
this.name = fileName;
channel = new RandomAccessFile(fileName, mode).getChannel();
}
public void implCloseChannel() throws IOException {
channel.close();
}
public long position() throws IOException {
return channel.position();
}
public long size() throws IOException {
return channel.size();
}
public int read(ByteBuffer dst) throws IOException {
return channel.read(dst);
}
public FileChannel position(long pos) throws IOException {
channel.position(pos);
return this;
}
public FileChannel truncate(long newLength) throws IOException {
try {
channel.truncate(newLength);
if (channel.position() > newLength) {
// looks like a bug in this FileChannel implementation, as the
// documentation says the position needs to be changed
channel.position(newLength);
}
return this;
} catch (NonWritableChannelException e) {
throw new IOException("read only");
}
}
public void force(boolean metaData) throws IOException {
channel.force(metaData);
}
public int write(ByteBuffer src) throws IOException {
try {
return channel.write(src);
} catch (NonWritableChannelException e) {
throw new IOException("read only");
}
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return channel.tryLock();
}
public String toString() {
return name;
}
}
...@@ -6,7 +6,17 @@ ...@@ -6,7 +6,17 @@
*/ */
package org.h2.store.fs; package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import org.h2.constant.SysProperties;
/** /**
* This file system stores files on disk and uses java.nio to access the files. * This file system stores files on disk and uses java.nio to access the files.
...@@ -14,8 +24,8 @@ import java.io.IOException; ...@@ -14,8 +24,8 @@ import java.io.IOException;
*/ */
public class FilePathNioMapped extends FilePathNio { public class FilePathNioMapped extends FilePathNio {
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
return new FileObjectNioMapped(name.substring(getScheme().length() + 1), mode); return new FileNioMapped(name.substring(getScheme().length() + 1), mode);
} }
public String getScheme() { public String getScheme() {
...@@ -23,3 +33,207 @@ public class FilePathNioMapped extends FilePathNio { ...@@ -23,3 +33,207 @@ public class FilePathNioMapped extends FilePathNio {
} }
} }
/**
* Uses memory mapped files.
* The file size is limited to 2 GB.
*/
class FileNioMapped extends FileBase {
private static final long GC_TIMEOUT_MS = 10000;
private final String name;
private final MapMode mode;
private RandomAccessFile file;
private MappedByteBuffer mapped;
private long fileLength;
/**
* The position within the file. Can't use the position of the mapped buffer
* because it doesn't support seeking past the end of the file.
*/
private int pos;
FileNioMapped(String fileName, String mode) throws IOException {
if ("r".equals(mode)) {
this.mode = MapMode.READ_ONLY;
} else {
this.mode = MapMode.READ_WRITE;
}
this.name = fileName;
file = new RandomAccessFile(fileName, mode);
reMap();
}
private void unMap() throws IOException {
if (mapped == null) {
return;
}
// first write all data
mapped.force();
// need to dispose old direct buffer, see bug
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038
boolean useSystemGc = true;
if (SysProperties.NIO_CLEANER_HACK) {
try {
Method cleanerMethod = mapped.getClass().getMethod("cleaner");
cleanerMethod.setAccessible(true);
Object cleaner = cleanerMethod.invoke(mapped);
if (cleaner != null) {
Method clearMethod = cleaner.getClass().getMethod("clean");
clearMethod.invoke(cleaner);
}
useSystemGc = false;
} catch (Throwable e) {
// useSystemGc is already true
} finally {
mapped = null;
}
}
if (useSystemGc) {
WeakReference<MappedByteBuffer> bufferWeakRef = new WeakReference<MappedByteBuffer>(mapped);
mapped = null;
long start = System.currentTimeMillis();
while (bufferWeakRef.get() != null) {
if (System.currentTimeMillis() - start > GC_TIMEOUT_MS) {
throw new IOException("Timeout (" + GC_TIMEOUT_MS
+ " ms) reached while trying to GC mapped buffer");
}
System.gc();
Thread.yield();
}
}
}
/**
* Re-map byte buffer into memory, called when file size has changed or file
* was created.
*/
private void reMap() throws IOException {
int oldPos = 0;
if (mapped != null) {
oldPos = pos;
unMap();
}
fileLength = file.length();
checkFileSizeLimit(fileLength);
// maps new MappedByteBuffer; the old one is disposed during GC
mapped = file.getChannel().map(mode, 0, fileLength);
int limit = mapped.limit();
int capacity = mapped.capacity();
if (limit < fileLength || capacity < fileLength) {
throw new IOException("Unable to map: length=" + limit + " capacity=" + capacity + " length=" + fileLength);
}
if (SysProperties.NIO_LOAD_MAPPED) {
mapped.load();
}
this.pos = Math.min(oldPos, (int) fileLength);
}
private static void checkFileSizeLimit(long length) throws IOException {
if (length > Integer.MAX_VALUE) {
throw new IOException("File over 2GB is not supported yet when using this file system");
}
}
public void implCloseChannel() throws IOException {
if (file != null) {
unMap();
file.close();
file = null;
}
}
public long position() {
return pos;
}
public String toString() {
return "nioMapped:" + name;
}
public synchronized long size() throws IOException {
return fileLength;
}
public synchronized int read(ByteBuffer dst) throws IOException {
try {
int len = dst.remaining();
if (len == 0) {
return 0;
}
len = (int) Math.min(len, fileLength - pos);
if (len <= 0) {
return -1;
}
mapped.position(pos);
mapped.get(dst.array(), dst.position(), len);
dst.position(dst.position() + len);
pos += len;
return len;
} catch (IllegalArgumentException e) {
EOFException e2 = new EOFException("EOF");
e2.initCause(e);
throw e2;
} catch (BufferUnderflowException e) {
EOFException e2 = new EOFException("EOF");
e2.initCause(e);
throw e2;
}
}
public FileChannel position(long pos) throws IOException {
checkFileSizeLimit(pos);
this.pos = (int) pos;
return this;
}
public synchronized FileChannel truncate(long newLength) throws IOException {
if (newLength < size()) {
setFileLength(newLength);
}
return this;
}
public synchronized void setFileLength(long newLength) throws IOException {
checkFileSizeLimit(newLength);
int oldPos = pos;
unMap();
for (int i = 0;; i++) {
try {
file.setLength(newLength);
break;
} catch (IOException e) {
if (i > 16 || e.toString().indexOf("user-mapped section open") < 0) {
throw e;
}
}
System.gc();
}
reMap();
pos = (int) Math.min(newLength, oldPos);
}
public void force(boolean metaData) throws IOException {
mapped.force();
file.getFD().sync();
}
public synchronized int write(ByteBuffer src) throws IOException {
int len = src.remaining();
// check if need to expand file
if (mapped.capacity() < pos + len) {
setFileLength(pos + len);
}
mapped.position(pos);
mapped.put(src);
pos += len;
return len;
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.getChannel().tryLock();
}
}
...@@ -8,6 +8,9 @@ package org.h2.store.fs; ...@@ -8,6 +8,9 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
/** /**
* A file system that records all write operations and can re-play them. * A file system that records all write operations and can re-play them.
...@@ -52,8 +55,8 @@ public class FilePathRec extends FilePathWrapper { ...@@ -52,8 +55,8 @@ public class FilePathRec extends FilePathWrapper {
super.delete(); super.delete();
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
return new FileObjectRec(this, super.openFileObject(mode), name); return new FileRec(this, super.open(mode), name);
} }
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
...@@ -108,3 +111,68 @@ public class FilePathRec extends FilePathWrapper { ...@@ -108,3 +111,68 @@ public class FilePathRec extends FilePathWrapper {
} }
} }
/**
* A file object that records all write operations and can re-play them.
*/
class FileRec extends FileBase {
private final FilePathRec rec;
private final FileChannel channel;
private final String name;
FileRec(FilePathRec rec, FileChannel file, String fileName) {
this.rec = rec;
this.channel = file;
this.name = fileName;
}
public void implCloseChannel() throws IOException {
channel.close();
}
public long position() throws IOException {
return channel.position();
}
public long size() throws IOException {
return channel.size();
}
public int read(ByteBuffer dst) throws IOException {
return channel.read(dst);
}
public FileChannel position(long pos) throws IOException {
channel.position(pos);
return this;
}
public FileChannel truncate(long newLength) throws IOException {
rec.log(Recorder.TRUNCATE, name, null, newLength);
channel.truncate(newLength);
return this;
}
public void force(boolean metaData) throws IOException {
channel.force(metaData);
}
public int write(ByteBuffer src) throws IOException {
byte[] buff = src.array();
int len = src.remaining();
if (src.position() != 0 || len != buff.length) {
byte[] b = new byte[len];
System.arraycopy(buff, src.position(), b, 0, len);
buff = b;
}
int result = channel.write(src);
rec.log(Recorder.WRITE, name, buff, channel.position());
return result;
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return channel.tryLock();
}
}
...@@ -10,6 +10,9 @@ import java.io.IOException; ...@@ -10,6 +10,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.SequenceInputStream; import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
...@@ -83,8 +86,8 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -83,8 +86,8 @@ public class FilePathSplit extends FilePathWrapper {
return length; return length;
} }
public ArrayList<FilePath> listFiles() { public ArrayList<FilePath> newDirectoryStream() {
List<FilePath> list = getBase().listFiles(); List<FilePath> list = getBase().newDirectoryStream();
ArrayList<FilePath> newList = New.arrayList(); ArrayList<FilePath> newList = New.arrayList();
for (int i = 0, size = list.size(); i < size; i++) { for (int i = 0, size = list.size(); i < size; i++) {
FilePath f = list.get(i); FilePath f = list.get(i);
...@@ -109,20 +112,18 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -109,20 +112,18 @@ public class FilePathSplit extends FilePathWrapper {
return input; return input;
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
ArrayList<FileObject> list = New.arrayList(); ArrayList<FileChannel> list = New.arrayList();
FileObject o = getBase().openFileObject(mode); list.add(getBase().open(mode));
list.add(o);
for (int i = 1;; i++) { for (int i = 1;; i++) {
FilePath f = getBase(i); FilePath f = getBase(i);
if (f.exists()) { if (f.exists()) {
o = f.openFileObject(mode); list.add(f.open(mode));
list.add(o);
} else { } else {
break; break;
} }
} }
FileObject[] array = new FileObject[list.size()]; FileChannel[] array = new FileChannel[list.size()];
list.toArray(array); list.toArray(array);
long maxLength = array[0].size(); long maxLength = array[0].size();
long length = maxLength; long length = maxLength;
...@@ -136,21 +137,21 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -136,21 +137,21 @@ public class FilePathSplit extends FilePathWrapper {
closeAndThrow(0, array, array[0], maxLength); closeAndThrow(0, array, array[0], maxLength);
} }
for (int i = 1; i < array.length - 1; i++) { for (int i = 1; i < array.length - 1; i++) {
o = array[i]; FileChannel c = array[i];
long l = o.size(); long l = c.size();
length += l; length += l;
if (l != maxLength) { if (l != maxLength) {
closeAndThrow(i, array, o, maxLength); closeAndThrow(i, array, c, maxLength);
} }
} }
o = array[array.length - 1]; FileChannel c = array[array.length - 1];
long l = o.size(); long l = c.size();
length += l; length += l;
if (l > maxLength) { if (l > maxLength) {
closeAndThrow(array.length - 1, array, o, maxLength); closeAndThrow(array.length - 1, array, c, maxLength);
} }
} }
FileObjectSplit fo = new FileObjectSplit(this, mode, array, length, maxLength); FileSplit fo = new FileSplit(this, mode, array, length, maxLength);
return fo; return fo;
} }
...@@ -158,9 +159,9 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -158,9 +159,9 @@ public class FilePathSplit extends FilePathWrapper {
return 1L << Integer.decode(parse(name)[0]).intValue(); return 1L << Integer.decode(parse(name)[0]).intValue();
} }
private void closeAndThrow(int id, FileObject[] array, FileObject o, long maxLength) throws IOException { private void closeAndThrow(int id, FileChannel[] array, FileChannel o, long maxLength) throws IOException {
String message = "Expected file length: " + maxLength + " got: " + o.size() + " for " + getName(id); String message = "Expected file length: " + maxLength + " got: " + o.size() + " for " + getName(id);
for (FileObject f : array) { for (FileChannel f : array) {
f.close(); f.close();
} }
throw new IOException(message); throw new IOException(message);
...@@ -168,7 +169,7 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -168,7 +169,7 @@ public class FilePathSplit extends FilePathWrapper {
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
try { try {
return new FileObjectOutputStream(openFileObject("rw"), append); return new FileChannelOutputStream(open("rw"), append);
} catch (IOException e) { } catch (IOException e) {
throw DbException.convertIOException(e, name); throw DbException.convertIOException(e, name);
} }
...@@ -230,4 +231,147 @@ public class FilePathSplit extends FilePathWrapper { ...@@ -230,4 +231,147 @@ public class FilePathSplit extends FilePathWrapper {
return "split"; return "split";
} }
} }
\ No newline at end of file
/**
* A file that may be split into multiple smaller files.
*/
class FileSplit extends FileBase {
private final FilePathSplit file;
private final String mode;
private final long maxLength;
private FileChannel[] list;
private long filePointer;
private long length;
FileSplit(FilePathSplit file, String mode, FileChannel[] list, long length, long maxLength) {
this.file = file;
this.mode = mode;
this.list = list;
this.length = length;
this.maxLength = maxLength;
}
public void implCloseChannel() throws IOException {
for (FileChannel c : list) {
c.close();
}
}
public long position() {
return filePointer;
}
public long size() {
return length;
}
public int read(ByteBuffer dst) throws IOException {
int len = dst.remaining();
if (len == 0) {
return 0;
}
len = (int) Math.min(len, length - filePointer);
if (len <= 0) {
return -1;
}
long offset = filePointer % maxLength;
len = (int) Math.min(len, maxLength - offset);
FileChannel channel = getFileChannel();
channel.position(offset);
len = channel.read(dst);
filePointer += len;
return len;
}
public FileChannel position(long pos) {
filePointer = pos;
return this;
}
private FileChannel getFileChannel() throws IOException {
int id = (int) (filePointer / maxLength);
while (id >= list.length) {
int i = list.length;
FileChannel[] newList = new FileChannel[i + 1];
System.arraycopy(list, 0, newList, 0, i);
FilePath f = file.getBase(i);
newList[i] = f.open(mode);
list = newList;
}
return list[id];
}
public FileChannel truncate(long newLength) throws IOException {
if (newLength >= length) {
return this;
}
filePointer = Math.min(filePointer, newLength);
int newFileCount = 1 + (int) (newLength / maxLength);
if (newFileCount < list.length) {
// delete some of the files
FileChannel[] newList = new FileChannel[newFileCount];
// delete backwards, so that truncating is somewhat transactional
for (int i = list.length - 1; i >= newFileCount; i--) {
// verify the file is writable
list[i].truncate(0);
list[i].close();
try {
file.getBase(i).delete();
} catch (DbException e) {
throw DbException.convertToIOException(e);
}
}
System.arraycopy(list, 0, newList, 0, newList.length);
list = newList;
}
long size = newLength - maxLength * (newFileCount - 1);
list[list.length - 1].truncate(size);
this.length = newLength;
return this;
}
public void force(boolean metaData) throws IOException {
for (FileChannel c : list) {
c.force(metaData);
}
}
public int write(ByteBuffer src) throws IOException {
if (filePointer >= length && filePointer > maxLength) {
// may need to extend and create files
long oldFilePointer = filePointer;
long x = length - (length % maxLength) + maxLength;
for (; x < filePointer; x += maxLength) {
if (x > length) {
// expand the file size
position(x - 1);
write(ByteBuffer.wrap(new byte[1]));
}
filePointer = oldFilePointer;
}
}
long offset = filePointer % maxLength;
int len = src.remaining();
FileChannel channel = getFileChannel();
channel.position(offset);
int l = (int) Math.min(len, maxLength - offset);
if (l == len) {
l = channel.write(src);
} else {
int oldLimit = src.limit();
src.limit(src.position() + l);
l = channel.write(src);
src.limit(oldLimit);
}
filePointer += l;
length = Math.max(length, filePointer);
return l;
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return list[0].tryLock();
}
}
...@@ -9,6 +9,7 @@ package org.h2.store.fs; ...@@ -9,6 +9,7 @@ package org.h2.store.fs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.List; import java.util.List;
import org.h2.message.DbException; import org.h2.message.DbException;
...@@ -103,12 +104,12 @@ public abstract class FilePathWrapper extends FilePath { ...@@ -103,12 +104,12 @@ public abstract class FilePathWrapper extends FilePath {
return base.lastModified(); return base.lastModified();
} }
public FilePath getCanonicalPath() { public FilePath toRealPath() {
return wrap(base.getCanonicalPath()); return wrap(base.toRealPath());
} }
public List<FilePath> listFiles() { public List<FilePath> newDirectoryStream() {
List<FilePath> list = base.listFiles(); List<FilePath> list = base.newDirectoryStream();
for (int i = 0, len = list.size(); i < len; i++) { for (int i = 0, len = list.size(); i < len; i++) {
list.set(i, wrap(list.get(i))); list.set(i, wrap(list.get(i)));
} }
...@@ -127,8 +128,8 @@ public abstract class FilePathWrapper extends FilePath { ...@@ -127,8 +128,8 @@ public abstract class FilePathWrapper extends FilePath {
return base.newOutputStream(append); return base.newOutputStream(append);
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
return base.openFileObject(mode); return base.open(mode);
} }
public boolean setReadOnly() { public boolean setReadOnly() {
......
...@@ -10,11 +10,15 @@ import java.io.FileNotFoundException; ...@@ -10,11 +10,15 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.New; import org.h2.util.New;
/** /**
...@@ -112,7 +116,7 @@ public class FilePathZip extends FilePath { ...@@ -112,7 +116,7 @@ public class FilePathZip extends FilePath {
} }
} }
public ArrayList<FilePath> listFiles() { public ArrayList<FilePath> newDirectoryStream() {
String path = name; String path = name;
ArrayList<FilePath> list = New.arrayList(); ArrayList<FilePath> list = New.arrayList();
try { try {
...@@ -147,16 +151,16 @@ public class FilePathZip extends FilePath { ...@@ -147,16 +151,16 @@ public class FilePathZip extends FilePath {
} }
public InputStream newInputStream() throws IOException { public InputStream newInputStream() throws IOException {
return new FileObjectInputStream(openFileObject("r")); return new FileChannelInputStream(open("r"));
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
ZipFile file = openZipFile(); ZipFile file = openZipFile();
ZipEntry entry = file.getEntry(getEntryName()); ZipEntry entry = file.getEntry(getEntryName());
if (entry == null) { if (entry == null) {
throw new FileNotFoundException(name); throw new FileNotFoundException(name);
} }
return new FileObjectZip(file, entry); return new FileZip(file, entry);
} }
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
...@@ -178,7 +182,7 @@ public class FilePathZip extends FilePath { ...@@ -178,7 +182,7 @@ public class FilePathZip extends FilePath {
return FilePathDisk.expandUserHomeDirectory(fileName); return FilePathDisk.expandUserHomeDirectory(fileName);
} }
public FilePath getCanonicalPath() { public FilePath toRealPath() {
return this; return this;
} }
...@@ -214,3 +218,100 @@ public class FilePathZip extends FilePath { ...@@ -214,3 +218,100 @@ public class FilePathZip extends FilePath {
} }
} }
/**
* The file is read from a stream. When reading from start to end, the same
* input stream is re-used, however when reading from end to start, a new input
* stream is opened for each request.
*/
class FileZip extends FileBase {
private static final byte[] SKIP_BUFFER = new byte[1024];
private ZipFile file;
private ZipEntry entry;
private long pos;
private InputStream in;
private long inPos;
private long length;
private boolean skipUsingRead;
FileZip(ZipFile file, ZipEntry entry) {
this.file = file;
this.entry = entry;
length = entry.getSize();
}
public long position() {
return pos;
}
public long size() {
return length;
}
public int read(ByteBuffer dst) throws IOException {
seek();
int len = in.read(dst.array(), dst.position(), dst.remaining());
if (len > 0) {
dst.position(dst.position() + len);
pos += len;
inPos += len;
}
return len;
}
private void seek() throws IOException {
if (inPos > pos) {
if (in != null) {
in.close();
}
in = null;
}
if (in == null) {
in = file.getInputStream(entry);
inPos = 0;
}
if (inPos < pos) {
long skip = pos - inPos;
if (!skipUsingRead) {
try {
IOUtils.skipFully(in, skip);
} catch (NullPointerException e) {
// workaround for Android
skipUsingRead = true;
}
}
if (skipUsingRead) {
while (skip > 0) {
int s = (int) Math.min(SKIP_BUFFER.length, skip);
s = in.read(SKIP_BUFFER, 0, s);
skip -= s;
}
}
inPos = pos;
}
}
public FileChannel position(long newPos) {
this.pos = newPos;
return this;
}
public FileChannel truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
public void force(boolean metaData) throws IOException {
// nothing to do
}
public int write(ByteBuffer src) throws IOException {
throw new IOException("File is read-only");
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return null;
}
}
package org.h2.store.fs; package org.h2.store.fs;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List; import java.util.List;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.New;
/** /**
* This utility class contains utility functions that use the file system * This utility class contains utility functions that use the file system
...@@ -59,14 +63,14 @@ public class FileUtils { ...@@ -59,14 +63,14 @@ public class FileUtils {
} }
/** /**
* Normalize a file name. * Get the canonical file or directory name.
* This method is similar to Java 7 <code>java.nio.file.Path.toRealPath</code>. * This method is similar to Java 7 <code>java.nio.file.Path.toRealPath</code>.
* *
* @param fileName the file name * @param fileName the file name
* @return the normalized file name * @return the normalized file name
*/ */
public static String getCanonicalPath(String fileName) { public static String toRealPath(String fileName) {
return FilePath.get(fileName).getCanonicalPath().toString(); return FilePath.get(fileName).toRealPath().toString();
} }
/** /**
...@@ -116,19 +120,20 @@ public class FileUtils { ...@@ -116,19 +120,20 @@ public class FileUtils {
} }
/** /**
* List the files in the given directory. * List the files and directories in the given directory.
* This method is similar to Java 7 <code>java.nio.file.Path.newDirectoryStream</code>. * This method is similar to Java 7 <code>java.nio.file.Path.newDirectoryStream</code>.
* *
* @param path the directory * @param path the directory
* @return the list of fully qualified file names * @return the list of fully qualified file names
*/ */
public static String[] listFiles(String path) { public static List<String> newDirectoryStream(String path) {
List<FilePath> list = FilePath.get(path).listFiles(); List<FilePath> list = FilePath.get(path).newDirectoryStream();
String[] array = new String[list.size()]; int len = list.size();
for (int i = 0, len = list.size(); i < len; i++) { List<String> result = New.arrayList(len);
array[i] = list.get(i).toString(); for (int i = 0; i < len; i++) {
result.add(list.get(i).toString());
} }
return array; return result;
} }
/** /**
...@@ -174,8 +179,8 @@ public class FileUtils { ...@@ -174,8 +179,8 @@ public class FileUtils {
* @param mode the access mode. Supported are r, rw, rws, rwd * @param mode the access mode. Supported are r, rw, rws, rwd
* @return the file object * @return the file object
*/ */
public static FileObject openFileObject(String fileName, String mode) throws IOException { public static FileChannel open(String fileName, String mode) throws IOException {
return FilePath.get(fileName).openFileObject(mode); return FilePath.get(fileName).open(mode);
} }
/** /**
...@@ -248,7 +253,7 @@ public class FileUtils { ...@@ -248,7 +253,7 @@ public class FileUtils {
public static void deleteRecursive(String path, boolean tryOnly) { public static void deleteRecursive(String path, boolean tryOnly) {
if (exists(path)) { if (exists(path)) {
if (isDirectory(path)) { if (isDirectory(path)) {
for (String s : listFiles(path)) { for (String s : newDirectoryStream(path)) {
deleteRecursive(s, tryOnly); deleteRecursive(s, tryOnly);
} }
} }
...@@ -324,4 +329,33 @@ public class FileUtils { ...@@ -324,4 +329,33 @@ public class FileUtils {
return FilePath.get(prefix).createTempFile(suffix, deleteOnExit, inTempDir).toString(); return FilePath.get(prefix).createTempFile(suffix, deleteOnExit, inTempDir).toString();
} }
/**
* Fully read from the file. This will read all remaining bytes,
* or throw an EOFException if not successful.
*
* @param channel the file channel
* @param dst the byte buffer
*/
public static void readFully(FileChannel channel, ByteBuffer dst) throws IOException {
do {
int r = channel.read(dst);
if (r < 0) {
throw new EOFException();
}
} while (dst.remaining() > 0);
}
/**
* Fully write to the file. This will write all remaining bytes.
*
* @param channel the file channel
* @param src the byte buffer
*/
public static void writeFully(FileChannel channel, ByteBuffer src) throws IOException {
do {
channel.write(src);
} while (src.remaining() > 0);
}
} }
...@@ -11,7 +11,6 @@ import java.io.IOException; ...@@ -11,7 +11,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
...@@ -109,7 +108,7 @@ public class Backup extends Tool { ...@@ -109,7 +108,7 @@ public class Backup extends Tool {
List<String> list; List<String> list;
boolean allFiles = db != null && db.length() == 0; boolean allFiles = db != null && db.length() == 0;
if (allFiles) { if (allFiles) {
list = Arrays.asList(FileUtils.listFiles(directory)); list = FileUtils.newDirectoryStream(directory);
} else { } else {
list = FileLister.getDatabaseFiles(directory, db, true); list = FileLister.getDatabaseFiles(directory, db, true);
} }
...@@ -122,7 +121,7 @@ public class Backup extends Tool { ...@@ -122,7 +121,7 @@ public class Backup extends Tool {
if (!quiet) { if (!quiet) {
FileLister.tryUnlockDatabase(list, "backup"); FileLister.tryUnlockDatabase(list, "backup");
} }
zipFileName = FileUtils.getCanonicalPath(zipFileName); zipFileName = FileUtils.toRealPath(zipFileName);
FileUtils.delete(zipFileName); FileUtils.delete(zipFileName);
OutputStream fileOut = null; OutputStream fileOut = null;
try { try {
...@@ -136,7 +135,7 @@ public class Backup extends Tool { ...@@ -136,7 +135,7 @@ public class Backup extends Tool {
} }
} }
for (String fileName : list) { for (String fileName : list) {
String f = FileUtils.getCanonicalPath(fileName); String f = FileUtils.toRealPath(fileName);
if (!f.startsWith(base)) { if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base); DbException.throwInternalError(f + " does not start with " + base);
} }
......
...@@ -237,7 +237,7 @@ public class ValueLob extends Value { ...@@ -237,7 +237,7 @@ public class ValueLob extends Value {
name = SysProperties.FILE_SEPARATOR + f + Constants.SUFFIX_LOBS_DIRECTORY + name; name = SysProperties.FILE_SEPARATOR + f + Constants.SUFFIX_LOBS_DIRECTORY + name;
objectId /= SysProperties.LOB_FILES_PER_DIRECTORY; objectId /= SysProperties.LOB_FILES_PER_DIRECTORY;
} }
name = FileUtils.getCanonicalPath(path + Constants.SUFFIX_LOBS_DIRECTORY + name); name = FileUtils.toRealPath(path + Constants.SUFFIX_LOBS_DIRECTORY + name);
return name; return name;
} }
...@@ -322,12 +322,12 @@ public class ValueLob extends Value { ...@@ -322,12 +322,12 @@ public class ValueLob extends Value {
SmallLRUCache<String, String[]> cache = h.getLobFileListCache(); SmallLRUCache<String, String[]> cache = h.getLobFileListCache();
String[] list; String[] list;
if (cache == null) { if (cache == null) {
list = FileUtils.listFiles(dir); list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
} else { } else {
synchronized (cache) { synchronized (cache) {
list = cache.get(dir); list = cache.get(dir);
if (list == null) { if (list == null) {
list = FileUtils.listFiles(dir); list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
cache.put(dir, list); cache.put(dir, list);
} }
} }
...@@ -719,7 +719,7 @@ public class ValueLob extends Value { ...@@ -719,7 +719,7 @@ public class ValueLob extends Value {
} }
private static void removeAllForTable(DataHandler handler, String dir, int tableId) { private static void removeAllForTable(DataHandler handler, String dir, int tableId) {
for (String name : FileUtils.listFiles(dir)) { for (String name : FileUtils.newDirectoryStream(dir)) {
if (FileUtils.isDirectory(name)) { if (FileUtils.isDirectory(name)) {
removeAllForTable(handler, name, tableId); removeAllForTable(handler, name, tableId);
} else { } else {
......
...@@ -17,6 +17,7 @@ import java.sql.SQLException; ...@@ -17,6 +17,7 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Time; import java.sql.Time;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.List;
import java.util.Random; import java.util.Random;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
...@@ -1218,10 +1219,8 @@ public class TestCases extends TestBase { ...@@ -1218,10 +1219,8 @@ public class TestCases extends TestBase {
conn.close(); conn.close();
String[] list = FileUtils.listFiles(getBaseDir() + "/cases.lobs.db"); List<String> list = FileUtils.newDirectoryStream(getBaseDir() + "/cases.lobs.db");
if (list != null && list.length > 0) { assertEquals("Lob file was not deleted: " + list, 0, list.size());
fail("Lob file was not deleted");
}
} }
private void testDeleteTop() throws SQLException { private void testDeleteTop() throws SQLException {
......
...@@ -632,8 +632,7 @@ public class TestLinkedTable extends TestBase { ...@@ -632,8 +632,7 @@ public class TestLinkedTable extends TestBase {
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)");
conn.close(); conn.close();
String[] files = FileUtils.listFiles(getBaseDir()); for (String file : FileUtils.newDirectoryStream(getBaseDir())) {
for (String file : files) {
String name = FileUtils.getName(file); String name = FileUtils.getName(file);
if ((name.startsWith("testLinkedTableInReadOnlyDb")) && (!name.endsWith(".trace.db"))) { if ((name.startsWith("testLinkedTableInReadOnlyDb")) && (!name.endsWith(".trace.db"))) {
FileUtils.setReadOnly(file); FileUtils.setReadOnly(file);
......
...@@ -23,6 +23,7 @@ import java.sql.SQLException; ...@@ -23,6 +23,7 @@ import java.sql.SQLException;
import java.sql.Savepoint; import java.sql.Savepoint;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import java.util.Random;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
...@@ -385,21 +386,21 @@ public class TestLob extends TestBase { ...@@ -385,21 +386,21 @@ public class TestLob extends TestBase {
stat.execute("create table test(id int primary key, name clob)"); stat.execute("create table test(id int primary key, name clob)");
stat.execute("insert into test values(1, space(10000))"); stat.execute("insert into test values(1, space(10000))");
assertEquals(1, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(1, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("drop table test"); stat.execute("drop table test");
assertEquals(0, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(0, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("create table test(id int primary key, name clob)"); stat.execute("create table test(id int primary key, name clob)");
stat.execute("insert into test values(1, space(10000))"); stat.execute("insert into test values(1, space(10000))");
assertEquals(1, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(1, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("drop all objects"); stat.execute("drop all objects");
assertEquals(0, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(0, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("create table test(id int primary key, name clob)"); stat.execute("create table test(id int primary key, name clob)");
stat.execute("insert into test values(1, space(10000))"); stat.execute("insert into test values(1, space(10000))");
assertEquals(1, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(1, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("truncate table test"); stat.execute("truncate table test");
assertEquals(0, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(0, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
conn.close(); conn.close();
} }
...@@ -438,13 +439,10 @@ public class TestLob extends TestBase { ...@@ -438,13 +439,10 @@ public class TestLob extends TestBase {
} }
private void testTempFilesDeleted() throws Exception { private void testTempFilesDeleted() throws Exception {
String[] list;
FileUtils.deleteRecursive(TEMP_DIR, true); FileUtils.deleteRecursive(TEMP_DIR, true);
FileUtils.createDirectories(TEMP_DIR); FileUtils.createDirectories(TEMP_DIR);
list = FileUtils.listFiles(TEMP_DIR); List<String> list = FileUtils.newDirectoryStream(TEMP_DIR);
if (list.length > 0) { assertEquals("Unexpected temp file: " + list, 0, list.size());
fail("Unexpected temp file: " + list[0]);
}
deleteDb("lob"); deleteDb("lob");
Connection conn = getConnection("lob"); Connection conn = getConnection("lob");
Statement stat; Statement stat;
...@@ -457,10 +455,8 @@ public class TestLob extends TestBase { ...@@ -457,10 +455,8 @@ public class TestLob extends TestBase {
rs.getCharacterStream("name").close(); rs.getCharacterStream("name").close();
rs.close(); rs.close();
conn.close(); conn.close();
list = FileUtils.listFiles(TEMP_DIR); list = FileUtils.newDirectoryStream(TEMP_DIR);
if (list.length > 0) { assertEquals("Unexpected temp file: " + list, 0, list.size());
fail("Unexpected temp file: " + list[0]);
}
} }
private static void testAddLobRestart() throws SQLException { private static void testAddLobRestart() throws SQLException {
...@@ -503,10 +499,10 @@ public class TestLob extends TestBase { ...@@ -503,10 +499,10 @@ public class TestLob extends TestBase {
Connection conn = getConnection("lob"); Connection conn = getConnection("lob");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("create table test(data clob) as select space(100000) from dual"); stat.execute("create table test(data clob) as select space(100000) from dual");
assertEquals(1, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(1, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
stat.execute("delete from test"); stat.execute("delete from test");
conn.close(); conn.close();
assertEquals(0, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length); assertEquals(0, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
} }
private void testLobServerMemory() throws SQLException { private void testLobServerMemory() throws SQLException {
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
*/ */
package org.h2.test.db; package org.h2.test.db;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
...@@ -14,7 +16,6 @@ import java.sql.SQLException; ...@@ -14,7 +16,6 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.api.DatabaseEventListener; import org.h2.api.DatabaseEventListener;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.Restore; import org.h2.tools.Restore;
...@@ -67,10 +68,10 @@ public class TestOpenClose extends TestBase implements DatabaseEventListener { ...@@ -67,10 +68,10 @@ public class TestOpenClose extends TestBase implements DatabaseEventListener {
conn = DriverManager.getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2"); conn = DriverManager.getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2");
conn.createStatement().execute("create table test(id int, name varchar) as select 1, space(1000000)"); conn.createStatement().execute("create table test(id int, name varchar) as select 1, space(1000000)");
conn.close(); conn.close();
FileObject f = FileUtils.openFileObject(getBaseDir() + "/openClose2.h2.db.1.part", "rw"); FileChannel c = FileUtils.open(getBaseDir() + "/openClose2.h2.db.1.part", "rw");
f.position(f.size() * 2 - 1); c.position(c.size() * 2 - 1);
f.write(new byte[1], 0, 1); c.write(ByteBuffer.wrap(new byte[1]));
f.close(); c.close();
assertThrows(ErrorCode.IO_EXCEPTION_2, this). assertThrows(ErrorCode.IO_EXCEPTION_2, this).
getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2"); getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2");
FileUtils.delete("split:" + getBaseDir() + "/openClose2.h2.db"); FileUtils.delete("split:" + getBaseDir() + "/openClose2.h2.db");
......
...@@ -68,7 +68,7 @@ public class TestClearReferences extends TestBase { ...@@ -68,7 +68,7 @@ public class TestClearReferences extends TestBase {
// initialize the known classes // initialize the known classes
MathUtils.secureRandomLong(); MathUtils.secureRandomLong();
ValueInt.get(1); ValueInt.get(1);
Class.forName("org.h2.store.fs.FileObjectMemData"); Class.forName("org.h2.store.fs.FileMemData");
clear(); clear();
......
...@@ -13,7 +13,6 @@ import java.sql.PreparedStatement; ...@@ -13,7 +13,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.jdbc.JdbcConnection; import org.h2.jdbc.JdbcConnection;
...@@ -604,7 +603,7 @@ public class TestFileLockSerialized extends TestBase { ...@@ -604,7 +603,7 @@ public class TestFileLockSerialized extends TestBase {
stat.execute("insert into test values(0)"); stat.execute("insert into test values(0)");
conn.close(); conn.close();
List<String> filesWithoutSerialized = Arrays.asList(FileUtils.listFiles(getBaseDir())); List<String> filesWithoutSerialized = FileUtils.newDirectoryStream(getBaseDir());
deleteDb("fileLockSerialized"); deleteDb("fileLockSerialized");
// with serialized // with serialized
...@@ -616,7 +615,7 @@ public class TestFileLockSerialized extends TestBase { ...@@ -616,7 +615,7 @@ public class TestFileLockSerialized extends TestBase {
stat.execute("insert into test values(0)"); stat.execute("insert into test values(0)");
conn.close(); conn.close();
List<String> filesWithSerialized = Arrays.asList(FileUtils.listFiles(getBaseDir())); List<String> filesWithSerialized = FileUtils.newDirectoryStream(getBaseDir());
if (filesWithoutSerialized.size() != filesWithSerialized.size()) { if (filesWithoutSerialized.size() != filesWithSerialized.size()) {
for (int i = 0; i < filesWithoutSerialized.size(); i++) { for (int i = 0; i < filesWithoutSerialized.size(); i++) {
if (!filesWithSerialized.contains(filesWithoutSerialized.get(i))) { if (!filesWithSerialized.contains(filesWithoutSerialized.get(i))) {
......
...@@ -12,6 +12,8 @@ import java.io.IOException; ...@@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
...@@ -21,10 +23,10 @@ import java.sql.Statement; ...@@ -21,10 +23,10 @@ import java.sql.Statement;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import org.h2.dev.fs.FilePathCrypt; import org.h2.dev.fs.FilePathCrypt;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.test.utils.AssertThrows;
import org.h2.test.utils.FilePathDebug; import org.h2.test.utils.FilePathDebug;
import org.h2.tools.Backup; import org.h2.tools.Backup;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
...@@ -58,7 +60,7 @@ public class TestFileSystem extends TestBase { ...@@ -58,7 +60,7 @@ public class TestFileSystem extends TestBase {
testDatabaseInJar(); testDatabaseInJar();
// set default part size to 1 << 10 // set default part size to 1 << 10
String f = "split:10:" + getBaseDir() + "/fs"; String f = "split:10:" + getBaseDir() + "/fs";
FileUtils.getCanonicalPath(f); FileUtils.toRealPath(f);
testFileSystem(getBaseDir() + "/fs"); testFileSystem(getBaseDir() + "/fs");
testFileSystem("memFS:"); testFileSystem("memFS:");
testFileSystem("memLZF:"); testFileSystem("memLZF:");
...@@ -85,8 +87,7 @@ public class TestFileSystem extends TestBase { ...@@ -85,8 +87,7 @@ public class TestFileSystem extends TestBase {
private void testMemFsDir() throws IOException { private void testMemFsDir() throws IOException {
FileUtils.newOutputStream("memFS:data/test/a.txt", false).close(); FileUtils.newOutputStream("memFS:data/test/a.txt", false).close();
String[] list = FileUtils.listFiles("memFS:data/test"); assertEquals(1, FileUtils.newDirectoryStream("memFS:data/test").size());
assertEquals(1, list.length);
FileUtils.deleteRecursive("memFS:", false); FileUtils.deleteRecursive("memFS:", false);
} }
...@@ -110,15 +111,15 @@ public class TestFileSystem extends TestBase { ...@@ -110,15 +111,15 @@ public class TestFileSystem extends TestBase {
private void testSimpleExpandTruncateSize() throws Exception { private void testSimpleExpandTruncateSize() throws Exception {
String f = "memFS:" + getBaseDir() + "/fs/test.data"; String f = "memFS:" + getBaseDir() + "/fs/test.data";
FileUtils.createDirectories("memFS:" + getBaseDir() + "/fs"); FileUtils.createDirectories("memFS:" + getBaseDir() + "/fs");
FileObject o = FileUtils.openFileObject(f, "rw"); FileChannel c = FileUtils.open(f, "rw");
o.position(4000); c.position(4000);
o.write(new byte[1], 0, 1); c.write(ByteBuffer.wrap(new byte[1]));
FileLock lock = o.tryLock(); FileLock lock = c.tryLock();
o.truncate(0); c.truncate(0);
if (lock != null) { if (lock != null) {
lock.release(); lock.release();
} }
o.close(); c.close();
} }
private void testSplitDatabaseInZip() throws SQLException { private void testSplitDatabaseInZip() throws SQLException {
...@@ -184,7 +185,7 @@ public class TestFileSystem extends TestBase { ...@@ -184,7 +185,7 @@ public class TestFileSystem extends TestBase {
conn.close(); conn.close();
deleteDb("fsJar"); deleteDb("fsJar");
for (String f : FileUtils.listFiles("zip:" + getBaseDir() + "/fsJar.zip")) { for (String f : FileUtils.newDirectoryStream("zip:" + getBaseDir() + "/fsJar.zip")) {
assertTrue(FileUtils.isAbsolute(f)); assertTrue(FileUtils.isAbsolute(f));
assertTrue(!FileUtils.isDirectory(f)); assertTrue(!FileUtils.isDirectory(f));
assertTrue(FileUtils.size(f) > 0); assertTrue(FileUtils.size(f) > 0);
...@@ -209,7 +210,7 @@ public class TestFileSystem extends TestBase { ...@@ -209,7 +210,7 @@ public class TestFileSystem extends TestBase {
} }
private void testUserHome() { private void testUserHome() {
String fileName = FileUtils.getCanonicalPath("~/test"); String fileName = FileUtils.toRealPath("~/test");
String userDir = System.getProperty("user.home").replace('\\', '/'); String userDir = System.getProperty("user.home").replace('\\', '/');
assertTrue(fileName.startsWith(userDir)); assertTrue(fileName.startsWith(userDir));
} }
...@@ -222,55 +223,64 @@ public class TestFileSystem extends TestBase { ...@@ -222,55 +223,64 @@ public class TestFileSystem extends TestBase {
private void testSimple(final String fsBase) throws Exception { private void testSimple(final String fsBase) throws Exception {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
for (String s : FileUtils.listFiles(fsBase)) { for (String s : FileUtils.newDirectoryStream(fsBase)) {
FileUtils.delete(s); FileUtils.delete(s);
} }
FileUtils.createDirectories(fsBase + "/test"); FileUtils.createDirectories(fsBase + "/test");
FileUtils.delete(fsBase + "/test"); FileUtils.delete(fsBase + "/test");
FileUtils.delete(fsBase + "/test2"); FileUtils.delete(fsBase + "/test2");
assertTrue(FileUtils.createFile(fsBase + "/test")); assertTrue(FileUtils.createFile(fsBase + "/test"));
List<FilePath> p = FilePath.get(fsBase).listFiles(); List<FilePath> p = FilePath.get(fsBase).newDirectoryStream();
assertEquals(1, p.size()); assertEquals(1, p.size());
String can = FilePath.get(fsBase + "/test").getCanonicalPath().toString(); String can = FilePath.get(fsBase + "/test").toRealPath().toString();
assertEquals(can, p.get(0).toString()); assertEquals(can, p.get(0).toString());
assertTrue(FileUtils.canWrite(fsBase + "/test")); assertTrue(FileUtils.canWrite(fsBase + "/test"));
FileObject fo = FileUtils.openFileObject(fsBase + "/test", "rw"); FileChannel channel = FileUtils.open(fsBase + "/test", "rw");
byte[] buffer = new byte[10000]; byte[] buffer = new byte[10000];
Random random = new Random(1); Random random = new Random(1);
random.nextBytes(buffer); random.nextBytes(buffer);
fo.write(buffer, 0, 10000); channel.write(ByteBuffer.wrap(buffer));
assertEquals(10000, fo.size()); assertEquals(10000, channel.size());
fo.position(20000); channel.position(20000);
assertEquals(20000, fo.position()); assertEquals(20000, channel.position());
assertThrows(EOFException.class, fo).readFully(buffer, 0, 1); assertEquals(-1, channel.read(ByteBuffer.wrap(buffer, 0, 1)));
String path = fsBase + "/test"; String path = fsBase + "/test";
assertEquals("test", FileUtils.getName(path)); assertEquals("test", FileUtils.getName(path));
can = FilePath.get(fsBase).getCanonicalPath().toString(); can = FilePath.get(fsBase).toRealPath().toString();
String can2 = FileUtils.getCanonicalPath(FileUtils.getParent(path)); String can2 = FileUtils.toRealPath(FileUtils.getParent(path));
assertEquals(can, can2); assertEquals(can, can2);
FileLock lock = fo.tryLock(); FileLock lock = channel.tryLock();
if (lock != null) { if (lock != null) {
lock.release(); lock.release();
} }
assertEquals(10000, fo.size()); assertEquals(10000, channel.size());
fo.close(); channel.close();
assertEquals(10000, FileUtils.size(fsBase + "/test")); assertEquals(10000, FileUtils.size(fsBase + "/test"));
fo = FileUtils.openFileObject(fsBase + "/test", "r"); channel = FileUtils.open(fsBase + "/test", "r");
byte[] test = new byte[10000]; final byte[] test = new byte[10000];
fo.readFully(test, 0, 10000); FileUtils.readFully(channel, ByteBuffer.wrap(test, 0, 10000));
assertEquals(buffer, test); assertEquals(buffer, test);
assertThrows(IOException.class, fo).write(test, 0, 10); final FileChannel fc = channel;
assertThrows(IOException.class, fo).truncate(10); new AssertThrows(IOException.class) {
fo.close(); public void test() throws Exception {
fc.write(ByteBuffer.wrap(test, 0, 10));
}
};
new AssertThrows(IOException.class) {
public void test() throws Exception {
fc.truncate(10);
}
};
channel.close();
long lastMod = FileUtils.lastModified(fsBase + "/test"); long lastMod = FileUtils.lastModified(fsBase + "/test");
if (lastMod < time - 1999) { if (lastMod < time - 1999) {
// at most 2 seconds difference // at most 2 seconds difference
assertEquals(time, lastMod); assertEquals(time, lastMod);
} }
assertEquals(10000, FileUtils.size(fsBase + "/test")); assertEquals(10000, FileUtils.size(fsBase + "/test"));
String[] list = FileUtils.listFiles(fsBase); List<String> list = FileUtils.newDirectoryStream(fsBase);
assertEquals(1, list.length); assertEquals(1, list.size());
assertTrue(list[0].endsWith("test")); assertTrue(list.get(0).endsWith("test"));
FileUtils.copy(fsBase + "/test", fsBase + "/test3"); FileUtils.copy(fsBase + "/test", fsBase + "/test3");
FileUtils.moveTo(fsBase + "/test3", fsBase + "/test2"); FileUtils.moveTo(fsBase + "/test3", fsBase + "/test2");
assertTrue(!FileUtils.exists(fsBase + "/test3")); assertTrue(!FileUtils.exists(fsBase + "/test3"));
...@@ -314,8 +324,8 @@ public class TestFileSystem extends TestBase { ...@@ -314,8 +324,8 @@ public class TestFileSystem extends TestBase {
file.delete(); file.delete();
RandomAccessFile ra = new RandomAccessFile(file, "rw"); RandomAccessFile ra = new RandomAccessFile(file, "rw");
FileUtils.delete(s); FileUtils.delete(s);
FileObject f = FileUtils.openFileObject(s, "rw"); FileChannel f = FileUtils.open(s, "rw");
assertThrows(EOFException.class, f).readFully(new byte[1], 0, 1); assertEquals(-1, f.read(ByteBuffer.wrap(new byte[1])));
f.force(true); f.force(true);
Random random = new Random(seed); Random random = new Random(seed);
int size = getSize(100, 500); int size = getSize(100, 500);
...@@ -337,7 +347,7 @@ public class TestFileSystem extends TestBase { ...@@ -337,7 +347,7 @@ public class TestFileSystem extends TestBase {
random.nextBytes(buffer); random.nextBytes(buffer);
trace("write " + buffer.length); trace("write " + buffer.length);
buff.append("write " + buffer.length + "\n"); buff.append("write " + buffer.length + "\n");
f.write(buffer, 0, buffer.length); f.write(ByteBuffer.wrap(buffer));
ra.write(buffer, 0, buffer.length); ra.write(buffer, 0, buffer.length);
break; break;
} }
...@@ -360,7 +370,11 @@ public class TestFileSystem extends TestBase { ...@@ -360,7 +370,11 @@ public class TestFileSystem extends TestBase {
byte[] b2 = new byte[len]; byte[] b2 = new byte[len];
trace("readFully " + len); trace("readFully " + len);
ra.readFully(b1, 0, len); ra.readFully(b1, 0, len);
f.readFully(b2, 0, len); try {
FileUtils.readFully(f, ByteBuffer.wrap(b2, 0, len));
} catch (EOFException e) {
e.printStackTrace();
}
buff.append("readFully " + len + "\n"); buff.append("readFully " + len + "\n");
assertEquals(b1, b2); assertEquals(b1, b2);
break; break;
...@@ -383,7 +397,7 @@ public class TestFileSystem extends TestBase { ...@@ -383,7 +397,7 @@ public class TestFileSystem extends TestBase {
f.close(); f.close();
ra.close(); ra.close();
ra = new RandomAccessFile(file, "rw"); ra = new RandomAccessFile(file, "rw");
f = FileUtils.openFileObject(s, "rw"); f = FileUtils.open(s, "rw");
assertEquals(ra.length(), f.size()); assertEquals(ra.length(), f.size());
break; break;
} }
......
...@@ -6,13 +6,13 @@ ...@@ -6,13 +6,13 @@
*/ */
package org.h2.test.unit; package org.h2.test.unit;
import java.nio.channels.FileChannel;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.Restore; import org.h2.tools.Restore;
...@@ -229,11 +229,11 @@ public class TestPageStoreCoverage extends TestBase { ...@@ -229,11 +229,11 @@ public class TestPageStoreCoverage extends TestBase {
stat.execute("drop table if exists INFORMATION_SCHEMA.LOB_DATA"); stat.execute("drop table if exists INFORMATION_SCHEMA.LOB_DATA");
stat.execute("drop table if exists INFORMATION_SCHEMA.LOB_MAP"); stat.execute("drop table if exists INFORMATION_SCHEMA.LOB_MAP");
conn.close(); conn.close();
FileObject f = FileUtils.openFileObject(fileName, "rw"); FileChannel f = FileUtils.open(fileName, "rw");
// create a new database // create a new database
conn = getConnection("pageStore"); conn = getConnection("pageStore");
conn.close(); conn.close();
f = FileUtils.openFileObject(fileName, "rw"); f = FileUtils.open(fileName, "rw");
f.truncate(16); f.truncate(16);
// create a new database // create a new database
conn = getConnection("pageStore"); conn = getConnection("pageStore");
......
...@@ -9,12 +9,13 @@ package org.h2.test.unit; ...@@ -9,12 +9,13 @@ package org.h2.test.unit;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
...@@ -134,14 +135,14 @@ public class TestRecovery extends TestBase { ...@@ -134,14 +135,14 @@ public class TestRecovery extends TestBase {
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar) as select 1, 'Hello World1'"); stat.execute("create table test(id int, name varchar) as select 1, 'Hello World1'");
conn.close(); conn.close();
FileObject f = FileUtils.openFileObject(getBaseDir() + "/recovery.h2.db", "rw"); FileChannel f = FileUtils.open(getBaseDir() + "/recovery.h2.db", "rw");
byte[] buff = new byte[Constants.DEFAULT_PAGE_SIZE]; byte[] buff = new byte[Constants.DEFAULT_PAGE_SIZE];
while (f.position() < f.size()) { while (f.position() < f.size()) {
f.readFully(buff, 0, buff.length); FileUtils.readFully(f, ByteBuffer.wrap(buff));
if (new String(buff).indexOf("Hello World1") >= 0) { if (new String(buff).indexOf("Hello World1") >= 0) {
buff[buff.length - 1]++; buff[buff.length - 1]++;
f.position(f.position() - buff.length); f.position(f.position() - buff.length);
f.write(buff, 0, buff.length); f.write(ByteBuffer.wrap(buff));
} }
} }
f.close(); f.close();
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.utils;
import java.io.IOException;
import java.nio.channels.FileLock;
import org.h2.store.fs.FileObject;
/**
* A debugging file that logs all operations.
*/
public class FileDebug implements FileObject {
private final FilePathDebug fs;
private final FileObject file;
private final String name;
FileDebug(FilePathDebug fs, FileObject file, String name) {
this.fs = fs;
this.file = file;
this.name = fs.getScheme() + ":" + name;
}
public void close() throws IOException {
debug("close");
file.close();
}
public long position() throws IOException {
debug("getFilePointer");
return file.position();
}
public long size() throws IOException {
debug("length");
return file.size();
}
public void readFully(byte[] b, int off, int len) throws IOException {
debug("readFully", file.position(), off, len);
file.readFully(b, off, len);
}
public void position(long pos) throws IOException {
debug("seek", pos);
file.position(pos);
}
public void truncate(long newLength) throws IOException {
checkPowerOff();
debug("truncate", newLength);
file.truncate(newLength);
}
public void force(boolean metaData) throws IOException {
debug("force");
file.force(metaData);
}
public void write(byte[] b, int off, int len) throws IOException {
checkPowerOff();
debug("write", file.position(), off, len);
file.write(b, off, len);
}
private void debug(String method, Object... params) {
fs.trace(name, method, params);
}
private void checkPowerOff() throws IOException {
try {
fs.checkPowerOff();
} catch (IOException e) {
try {
file.close();
} catch (IOException e2) {
// ignore
}
throw e;
}
}
public FileLock tryLock() throws IOException {
debug("tryLock");
return file.tryLock();
}
}
\ No newline at end of file
...@@ -10,8 +10,11 @@ import java.io.FilterInputStream; ...@@ -10,8 +10,11 @@ import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.List; import java.util.List;
import org.h2.store.fs.FileObject; import org.h2.store.fs.FileBase;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathWrapper; import org.h2.store.fs.FilePathWrapper;
...@@ -116,14 +119,14 @@ public class FilePathDebug extends FilePathWrapper { ...@@ -116,14 +119,14 @@ public class FilePathDebug extends FilePathWrapper {
return super.size(); return super.size();
} }
public List<FilePath> listFiles() { public List<FilePath> newDirectoryStream() {
trace(name, "listFiles"); trace(name, "newDirectoryStream");
return super.listFiles(); return super.newDirectoryStream();
} }
public FilePath getCanonicalPath() { public FilePath toRealPath() {
trace(name, "getCanonicalPath"); trace(name, "toRealPath");
return super.getCanonicalPath(); return super.toRealPath();
} }
public InputStream newInputStream() throws IOException { public InputStream newInputStream() throws IOException {
...@@ -151,9 +154,9 @@ public class FilePathDebug extends FilePathWrapper { ...@@ -151,9 +154,9 @@ public class FilePathDebug extends FilePathWrapper {
}; };
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
trace(name, "openFileObject", mode); trace(name, "open", mode);
return new FileDebug(this, super.openFileObject(mode), name); return new FileDebug(this, super.open(mode), name);
} }
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
...@@ -211,3 +214,86 @@ public class FilePathDebug extends FilePathWrapper { ...@@ -211,3 +214,86 @@ public class FilePathDebug extends FilePathWrapper {
} }
} }
/**
* A debugging file that logs all operations.
*/
class FileDebug extends FileBase {
private final FilePathDebug debug;
private final FileChannel channel;
private final String name;
FileDebug(FilePathDebug debug, FileChannel channel, String name) {
this.debug = debug;
this.channel = channel;
this.name = debug.getScheme() + ":" + name;
}
public void implCloseChannel() throws IOException {
debug("close");
channel.close();
}
public long position() throws IOException {
debug("getFilePointer");
return channel.position();
}
public long size() throws IOException {
debug("length");
return channel.size();
}
public int read(ByteBuffer dst) throws IOException {
debug("read", channel.position(), dst.position(), dst.remaining());
return channel.read(dst);
}
public FileChannel position(long pos) throws IOException {
debug("seek", pos);
channel.position(pos);
return this;
}
public FileChannel truncate(long newLength) throws IOException {
checkPowerOff();
debug("truncate", newLength);
channel.truncate(newLength);
return this;
}
public void force(boolean metaData) throws IOException {
debug("force");
channel.force(metaData);
}
public int write(ByteBuffer src) throws IOException {
checkPowerOff();
debug("write", channel.position(), src.position(), src.remaining());
return channel.write(src);
}
private void debug(String method, Object... params) {
debug.trace(name, method, params);
}
private void checkPowerOff() throws IOException {
try {
debug.checkPowerOff();
} catch (IOException e) {
try {
channel.close();
} catch (IOException e2) {
// ignore
}
throw e;
}
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
debug("tryLock");
return channel.tryLock();
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.fs;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.FileLock;
import org.h2.engine.Constants;
import org.h2.security.BlockCipher;
import org.h2.security.CipherFactory;
import org.h2.security.SHA256;
import org.h2.store.fs.FileObject;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/**
* An encrypted file.
*/
public class FileObjectCrypt implements FileObject {
static final int HEADER_LENGTH = 4096;
static final int BLOCK_SIZE = Constants.FILE_BLOCK_SIZE;
// TODO improve the header
// TODO store the number of empty blocks in the last block
private static final byte[] HEADER = "-- H2 crypt --\n\0".getBytes();
private static final int SALT_POS = HEADER.length;
private static final int SALT_LENGTH = 16;
private static final int HASH_ITERATIONS = Constants.ENCRYPTION_KEY_HASH_ITERATIONS;
private final String name;
private final FileObject file;
private final BlockCipher cipher, cipherForInitVector;
private byte[] bufferForInitVector;
public FileObjectCrypt(String name, String algorithm, String password, FileObject file) throws IOException {
this.name = name;
this.file = file;
boolean newFile = file.size() < HEADER_LENGTH + BLOCK_SIZE;
byte[] filePasswordHash;
if (algorithm.endsWith("-hash")) {
filePasswordHash = StringUtils.convertHexToBytes(password);
algorithm = algorithm.substring(0, algorithm.length() - "-hash".length());
} else {
filePasswordHash = SHA256.getKeyPasswordHash("file", password.toCharArray());
}
cipher = CipherFactory.getBlockCipher(algorithm);
cipherForInitVector = CipherFactory.getBlockCipher(algorithm);
int keyIterations = HASH_ITERATIONS;
byte[] salt;
if (newFile) {
salt = MathUtils.secureRandomBytes(SALT_LENGTH);
file.write(HEADER, 0, HEADER.length);
file.position(SALT_POS);
file.write(salt, 0, salt.length);
} else {
salt = new byte[SALT_LENGTH];
file.position(SALT_POS);
file.readFully(salt, 0, SALT_LENGTH);
}
byte[] key = SHA256.getHashWithSalt(filePasswordHash, salt);
for (int i = 0; i < keyIterations; i++) {
key = SHA256.getHash(key, true);
}
cipher.setKey(key);
bufferForInitVector = new byte[BLOCK_SIZE];
position(0);
}
public long position() throws IOException {
return Math.max(0, file.position() - HEADER_LENGTH);
}
public long size() throws IOException {
return Math.max(0, file.size() - HEADER_LENGTH - BLOCK_SIZE);
}
public void position(long pos) throws IOException {
file.position(pos + HEADER_LENGTH);
}
public void force(boolean metaData) throws IOException {
file.force(metaData);
}
public FileLock tryLock() throws IOException {
return file.tryLock();
}
public void close() throws IOException {
file.close();
}
public void truncate(long newLength) throws IOException {
if (newLength >= size()) {
return;
}
int mod = (int) (newLength % BLOCK_SIZE);
if (mod == 0) {
file.truncate(HEADER_LENGTH + newLength);
} else {
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE - mod);
byte[] buff = new byte[BLOCK_SIZE - mod];
long pos = position();
position(newLength);
write(buff, 0, buff.length);
position(pos);
}
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE);
if (newLength < position()) {
position(newLength);
}
}
public void readFully(byte[] b, int off, int len) throws IOException {
long pos = position();
long length = size();
if (pos + len > length) {
throw new EOFException("pos: " + pos + " len: " + len + " length: " + length);
}
int posMod = (int) (pos % BLOCK_SIZE);
if (posMod == 0 && len % BLOCK_SIZE == 0) {
readAligned(pos, b, off, len);
} else {
long p = pos - posMod;
int l = len;
if (posMod != 0) {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
position(p);
byte[] temp = new byte[l];
try {
readAligned(p, temp, 0, l);
System.arraycopy(temp, posMod, b, off, len);
} finally {
position(pos + len);
}
}
}
public void write(byte[] b, int off, int len) throws IOException {
long pos = position();
int posMod = (int) (pos % BLOCK_SIZE);
if (posMod == 0 && len % BLOCK_SIZE == 0) {
byte[] temp = new byte[len];
System.arraycopy(b, off, temp, 0, len);
writeAligned(pos, temp, 0, len);
} else {
long p = pos - posMod;
int l = len;
if (posMod != 0) {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
position(p);
byte[] temp = new byte[l];
if (file.size() < HEADER_LENGTH + p + l) {
file.position(HEADER_LENGTH + p + l - 1);
file.write(new byte[1], 0, 1);
position(p);
}
readAligned(p, temp, 0, l);
System.arraycopy(b, off, temp, posMod, len);
position(p);
try {
writeAligned(p, temp, 0, l);
} finally {
position(pos + len);
}
}
pos = file.position();
if (file.size() < pos + BLOCK_SIZE) {
file.position(pos + BLOCK_SIZE - 1);
file.write(new byte[1], 0, 1);
file.position(pos);
}
}
private void readAligned(long pos, byte[] b, int off, int len) throws IOException {
file.readFully(b, off, len);
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// empty blocks are not decrypted
if (b[p + off + i] != 0) {
cipher.decrypt(b, p + off, BLOCK_SIZE);
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
break;
}
}
}
}
private void writeAligned(long pos, byte[] b, int off, int len) throws IOException {
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// empty blocks are not decrypted
if (b[p + off + i] != 0) {
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
cipher.encrypt(b, p + off, BLOCK_SIZE);
break;
}
}
}
file.write(b, off, len);
}
private void xorInitVector(byte[] b, int off, int len, long p) {
byte[] iv = bufferForInitVector;
while (len > 0) {
for (int i = 0; i < BLOCK_SIZE; i += 8) {
long block = (p + i) >>> 3;
iv[i] = (byte) (block >> 56);
iv[i + 1] = (byte) (block >> 48);
iv[i + 2] = (byte) (block >> 40);
iv[i + 3] = (byte) (block >> 32);
iv[i + 4] = (byte) (block >> 24);
iv[i + 5] = (byte) (block >> 16);
iv[i + 6] = (byte) (block >> 8);
iv[i + 7] = (byte) block;
}
cipherForInitVector.encrypt(iv, 0, BLOCK_SIZE);
for (int i = 0; i < BLOCK_SIZE; i++) {
b[off + i] ^= iv[i];
}
p += BLOCK_SIZE;
off += BLOCK_SIZE;
len -= BLOCK_SIZE;
}
}
public String toString() {
return name;
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.dev.fs;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileLock;
import java.util.zip.ZipInputStream;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;
/**
* The file is read from a stream. When reading from start to end, the same
* input stream is re-used, however when reading from end to start, a new input
* stream is opened for each request.
*/
public class FileObjectZip2 implements FileObject {
private static final byte[] SKIP_BUFFER = new byte[1024];
private final String fullName;
private final String name;
private final long length;
private long pos;
private InputStream in;
private long inPos;
private boolean skipUsingRead;
FileObjectZip2(String fullName, String name, ZipInputStream in, long length) {
this.fullName = fullName;
this.name = name;
this.length = length;
this.in = in;
}
public void close() {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
public long position() {
return pos;
}
public long size() {
return length;
}
public void readFully(byte[] b, int off, int len) throws IOException {
if (inPos > pos) {
if (in != null) {
in.close();
}
in = null;
}
if (in == null) {
in = FileUtils.newInputStream(fullName);
inPos = 0;
}
if (inPos < pos) {
long skip = pos - inPos;
if (!skipUsingRead) {
try {
IOUtils.skipFully(in, skip);
} catch (NullPointerException e) {
// workaround for Android
skipUsingRead = true;
}
}
if (skipUsingRead) {
while (skip > 0) {
int s = (int) Math.min(SKIP_BUFFER.length, skip);
s = in.read(SKIP_BUFFER, 0, s);
skip -= s;
}
}
inPos = pos;
}
int l = IOUtils.readFully(in, b, off, len);
if (l != len) {
throw new EOFException();
}
pos += len;
inPos += len;
}
public void position(long newPos) {
this.pos = newPos;
}
public void truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
public void force(boolean metaData) throws IOException {
// nothing to do
}
public void write(byte[] b, int off, int len) throws IOException {
throw new IOException("File is read-only");
}
public FileLock tryLock() {
return null;
}
public String toString() {
return name;
}
}
...@@ -9,13 +9,22 @@ package org.h2.dev.fs; ...@@ -9,13 +9,22 @@ package org.h2.dev.fs;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import org.h2.engine.Constants;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.store.fs.FileObject; import org.h2.security.BlockCipher;
import org.h2.store.fs.FileObjectInputStream; import org.h2.security.CipherFactory;
import org.h2.store.fs.FileObjectOutputStream; import org.h2.security.SHA256;
import org.h2.store.fs.FileBase;
import org.h2.store.fs.FileChannelInputStream;
import org.h2.store.fs.FileChannelOutputStream;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathWrapper; import org.h2.store.fs.FilePathWrapper;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.util.MathUtils;
import org.h2.util.StringUtils;
/** /**
* A file system that encrypts the contents of the files. * A file system that encrypts the contents of the files.
...@@ -40,19 +49,18 @@ public class FilePathCrypt extends FilePathWrapper { ...@@ -40,19 +49,18 @@ public class FilePathCrypt extends FilePathWrapper {
public long size() { public long size() {
long len = getBase().size(); long len = getBase().size();
return Math.max(0, len - FileObjectCrypt.HEADER_LENGTH - FileObjectCrypt.BLOCK_SIZE); return Math.max(0, len - FileCrypt.HEADER_LENGTH - FileCrypt.BLOCK_SIZE);
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
String[] parsed = parse(name); String[] parsed = parse(name);
FileObject file = FileUtils.openFileObject(parsed[2], mode); FileChannel file = FileUtils.open(parsed[2], mode);
return new FileObjectCrypt(name, parsed[0], parsed[1], file); return new FileCrypt(name, parsed[0], parsed[1], file);
} }
public OutputStream newOutputStream(boolean append) { public OutputStream newOutputStream(boolean append) {
try { try {
FileObject file = openFileObject("rw"); return new FileChannelOutputStream(open("rw"), append);
return new FileObjectOutputStream(file, append);
} catch (IOException e) { } catch (IOException e) {
throw DbException.convertIOException(e, name); throw DbException.convertIOException(e, name);
} }
...@@ -60,8 +68,7 @@ public class FilePathCrypt extends FilePathWrapper { ...@@ -60,8 +68,7 @@ public class FilePathCrypt extends FilePathWrapper {
public InputStream newInputStream() { public InputStream newInputStream() {
try { try {
FileObject file = openFileObject("r"); return new FileChannelInputStream(open("r"));
return new FileObjectInputStream(file);
} catch (IOException e) { } catch (IOException e) {
throw DbException.convertIOException(e, name); throw DbException.convertIOException(e, name);
} }
...@@ -99,3 +106,245 @@ public class FilePathCrypt extends FilePathWrapper { ...@@ -99,3 +106,245 @@ public class FilePathCrypt extends FilePathWrapper {
} }
} }
/**
* An encrypted file.
*/
class FileCrypt extends FileBase {
static final int HEADER_LENGTH = 4096;
static final int BLOCK_SIZE = Constants.FILE_BLOCK_SIZE;
// TODO improve the header
// TODO store the number of empty blocks in the last block
private static final byte[] HEADER = "-- H2 crypt --\n\0".getBytes();
private static final int SALT_POS = HEADER.length;
private static final int SALT_LENGTH = 16;
private static final int HASH_ITERATIONS = Constants.ENCRYPTION_KEY_HASH_ITERATIONS;
private final String name;
private final FileChannel file;
private final BlockCipher cipher, cipherForInitVector;
private byte[] bufferForInitVector;
public FileCrypt(String name, String algorithm, String password, FileChannel file) throws IOException {
this.name = name;
this.file = file;
boolean newFile = file.size() < HEADER_LENGTH + BLOCK_SIZE;
byte[] filePasswordHash;
if (algorithm.endsWith("-hash")) {
filePasswordHash = StringUtils.convertHexToBytes(password);
algorithm = algorithm.substring(0, algorithm.length() - "-hash".length());
} else {
filePasswordHash = SHA256.getKeyPasswordHash("file", password.toCharArray());
}
cipher = CipherFactory.getBlockCipher(algorithm);
cipherForInitVector = CipherFactory.getBlockCipher(algorithm);
int keyIterations = HASH_ITERATIONS;
byte[] salt;
if (newFile) {
salt = MathUtils.secureRandomBytes(SALT_LENGTH);
FileUtils.writeFully(file, ByteBuffer.wrap(HEADER));
file.position(SALT_POS);
FileUtils.writeFully(file, ByteBuffer.wrap(salt));
} else {
salt = new byte[SALT_LENGTH];
file.position(SALT_POS);
FileUtils.readFully(file, ByteBuffer.wrap(salt));
}
byte[] key = SHA256.getHashWithSalt(filePasswordHash, salt);
for (int i = 0; i < keyIterations; i++) {
key = SHA256.getHash(key, true);
}
cipher.setKey(key);
bufferForInitVector = new byte[BLOCK_SIZE];
position(0);
}
public long position() throws IOException {
return Math.max(0, file.position() - HEADER_LENGTH);
}
public long size() throws IOException {
return Math.max(0, file.size() - HEADER_LENGTH - BLOCK_SIZE);
}
public FileChannel position(long pos) throws IOException {
file.position(pos + HEADER_LENGTH);
return this;
}
public void force(boolean metaData) throws IOException {
file.force(metaData);
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.tryLock();
}
public void implCloseChannel() throws IOException {
file.close();
}
public FileChannel truncate(long newLength) throws IOException {
if (newLength >= size()) {
return this;
}
int mod = (int) (newLength % BLOCK_SIZE);
if (mod == 0) {
file.truncate(HEADER_LENGTH + newLength);
} else {
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE - mod);
byte[] buff = new byte[BLOCK_SIZE - mod];
long pos = position();
position(newLength);
write(buff, 0, buff.length);
position(pos);
}
file.truncate(HEADER_LENGTH + newLength + BLOCK_SIZE);
if (newLength < position()) {
position(newLength);
}
return this;
}
public int read(ByteBuffer dst) throws IOException {
int len = dst.remaining();
if (len == 0) {
return 0;
}
long pos = position();
len = (int) Math.min(len, size() - pos);
if (len <= 0) {
return -1;
}
int posMod = (int) (pos % BLOCK_SIZE);
if (posMod == 0 && len % BLOCK_SIZE == 0) {
readAligned(pos, dst.array(), dst.position(), len);
} else {
long p = pos - posMod;
int l = len;
if (posMod != 0) {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
position(p);
byte[] temp = new byte[l];
try {
readAligned(p, temp, 0, l);
System.arraycopy(temp, posMod, dst.array(), dst.position(), len);
} finally {
position(pos + len);
}
}
dst.position(dst.position() + len);
return len;
}
public int write(ByteBuffer src) throws IOException {
int len = src.remaining();
if (len == 0) {
return 0;
}
write(src.array(), src.position(), len);
src.position(src.position() + len);
return len;
}
private void write(byte[] b, int off, int len) throws IOException {
long pos = position();
int posMod = (int) (pos % BLOCK_SIZE);
if (posMod == 0 && len % BLOCK_SIZE == 0) {
byte[] temp = new byte[len];
System.arraycopy(b, off, temp, 0, len);
writeAligned(pos, temp, 0, len);
} else {
long p = pos - posMod;
int l = len;
if (posMod != 0) {
l += posMod;
}
l = MathUtils.roundUpInt(l, BLOCK_SIZE);
position(p);
byte[] temp = new byte[l];
if (file.size() < HEADER_LENGTH + p + l) {
file.position(HEADER_LENGTH + p + l - 1);
FileUtils.writeFully(file, ByteBuffer.wrap(new byte[1]));
position(p);
}
readAligned(p, temp, 0, l);
System.arraycopy(b, off, temp, posMod, len);
position(p);
try {
writeAligned(p, temp, 0, l);
} finally {
position(pos + len);
}
}
pos = file.position();
if (file.size() < pos + BLOCK_SIZE) {
file.position(pos + BLOCK_SIZE - 1);
FileUtils.writeFully(file, ByteBuffer.wrap(new byte[1]));
file.position(pos);
}
}
private void readAligned(long pos, byte[] b, int off, int len) throws IOException {
FileUtils.readFully(file, ByteBuffer.wrap(b, off, len));
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// empty blocks are not decrypted
if (b[p + off + i] != 0) {
cipher.decrypt(b, p + off, BLOCK_SIZE);
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
break;
}
}
}
}
private void writeAligned(long pos, byte[] b, int off, int len) throws IOException {
for (int p = 0; p < len; p += BLOCK_SIZE) {
for (int i = 0; i < BLOCK_SIZE; i++) {
// empty blocks are not decrypted
if (b[p + off + i] != 0) {
xorInitVector(b, p + off, BLOCK_SIZE, p + pos);
cipher.encrypt(b, p + off, BLOCK_SIZE);
break;
}
}
}
FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len));
}
private void xorInitVector(byte[] b, int off, int len, long p) {
byte[] iv = bufferForInitVector;
while (len > 0) {
for (int i = 0; i < BLOCK_SIZE; i += 8) {
long block = (p + i) >>> 3;
iv[i] = (byte) (block >> 56);
iv[i + 1] = (byte) (block >> 48);
iv[i + 2] = (byte) (block >> 40);
iv[i + 3] = (byte) (block >> 32);
iv[i + 4] = (byte) (block >> 24);
iv[i + 5] = (byte) (block >> 16);
iv[i + 6] = (byte) (block >> 8);
iv[i + 7] = (byte) block;
}
cipherForInitVector.encrypt(iv, 0, BLOCK_SIZE);
for (int i = 0; i < BLOCK_SIZE; i++) {
b[off + i] ^= iv[i];
}
p += BLOCK_SIZE;
off += BLOCK_SIZE;
len -= BLOCK_SIZE;
}
}
public String toString() {
return name;
}
}
...@@ -10,16 +10,20 @@ import java.io.FileNotFoundException; ...@@ -10,16 +10,20 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.store.fs.FileObject; import org.h2.store.fs.FileBase;
import org.h2.store.fs.FileObjectInputStream; import org.h2.store.fs.FileChannelInputStream;
import org.h2.store.fs.FilePath; import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathDisk; import org.h2.store.fs.FilePathDisk;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.New; import org.h2.util.New;
/** /**
...@@ -184,7 +188,7 @@ public class FilePathZip2 extends FilePath { ...@@ -184,7 +188,7 @@ public class FilePathZip2 extends FilePath {
} }
} }
public ArrayList<FilePath> listFiles() { public ArrayList<FilePath> newDirectoryStream() {
String path = name; String path = name;
try { try {
if (path.indexOf('!') < 0) { if (path.indexOf('!') < 0) {
...@@ -218,16 +222,15 @@ public class FilePathZip2 extends FilePath { ...@@ -218,16 +222,15 @@ public class FilePathZip2 extends FilePath {
} }
} }
public FilePath getCanonicalPath() { public FilePath toRealPath() {
return this; return this;
} }
public InputStream newInputStream() throws IOException { public InputStream newInputStream() throws IOException {
FileObject file = openFileObject("r"); return new FileChannelInputStream(open("r"));
return new FileObjectInputStream(file);
} }
public FileObject openFileObject(String mode) throws IOException { public FileChannel open(String mode) throws IOException {
String entryName = getEntryName(); String entryName = getEntryName();
if (entryName.length() == 0) { if (entryName.length() == 0) {
throw new FileNotFoundException(); throw new FileNotFoundException();
...@@ -239,7 +242,7 @@ public class FilePathZip2 extends FilePath { ...@@ -239,7 +242,7 @@ public class FilePathZip2 extends FilePath {
break; break;
} }
if (entry.getName().equals(entryName)) { if (entry.getName().equals(entryName)) {
return new FileObjectZip2(name, entryName, in, size()); return new FileZip2(name, entryName, in, size());
} }
in.closeEntry(); in.closeEntry();
} }
...@@ -301,4 +304,114 @@ public class FilePathZip2 extends FilePath { ...@@ -301,4 +304,114 @@ public class FilePathZip2 extends FilePath {
return "zip2"; return "zip2";
} }
}
/**
* The file is read from a stream. When reading from start to end, the same
* input stream is re-used, however when reading from end to start, a new input
* stream is opened for each request.
*/
class FileZip2 extends FileBase {
private static final byte[] SKIP_BUFFER = new byte[1024];
private final String fullName;
private final String name;
private final long length;
private long pos;
private InputStream in;
private long inPos;
private boolean skipUsingRead;
FileZip2(String fullName, String name, ZipInputStream in, long length) {
this.fullName = fullName;
this.name = name;
this.length = length;
this.in = in;
}
public void implCloseChannel() throws IOException {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
public long position() {
return pos;
}
public long size() {
return length;
}
public int read(ByteBuffer dst) throws IOException {
seek();
int len = in.read(dst.array(), dst.position(), dst.remaining());
if (len > 0) {
dst.position(dst.position() + len);
pos += len;
inPos += len;
}
return len;
}
private void seek() throws IOException {
if (inPos > pos) {
if (in != null) {
in.close();
}
in = null;
}
if (in == null) {
in = FileUtils.newInputStream(fullName);
inPos = 0;
}
if (inPos < pos) {
long skip = pos - inPos;
if (!skipUsingRead) {
try {
IOUtils.skipFully(in, skip);
} catch (NullPointerException e) {
// workaround for Android
skipUsingRead = true;
}
}
if (skipUsingRead) {
while (skip > 0) {
int s = (int) Math.min(SKIP_BUFFER.length, skip);
s = in.read(SKIP_BUFFER, 0, s);
skip -= s;
}
}
inPos = pos;
}
}
public FileChannel position(long newPos) {
this.pos = newPos;
return this;
}
public FileChannel truncate(long newLength) throws IOException {
throw new IOException("File is read-only");
}
public void force(boolean metaData) throws IOException {
// nothing to do
}
public int write(ByteBuffer src) throws IOException {
throw new IOException("File is read-only");
}
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return null;
}
public String toString() {
return name;
}
} }
\ No newline at end of file
...@@ -14,6 +14,7 @@ import java.io.InputStream; ...@@ -14,6 +14,7 @@ import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -24,7 +25,6 @@ import org.h2.command.dml.BackupCommand; ...@@ -24,7 +25,6 @@ import org.h2.command.dml.BackupCommand;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils; import org.h2.util.IOUtils;
import org.h2.util.New; import org.h2.util.New;
...@@ -127,7 +127,7 @@ public class FileShell extends Tool { ...@@ -127,7 +127,7 @@ public class FileShell extends Tool {
if (reader == null) { if (reader == null) {
reader = new BufferedReader(new InputStreamReader(in)); reader = new BufferedReader(new InputStreamReader(in));
} }
println(FileUtils.getCanonicalPath(currentWorkingDirectory)); println(FileUtils.toRealPath(currentWorkingDirectory));
while (true) { while (true) {
try { try {
print("> "); print("> ");
...@@ -212,7 +212,7 @@ public class FileShell extends Tool { ...@@ -212,7 +212,7 @@ public class FileShell extends Tool {
} }
end(list, i); end(list, i);
println(dir); println(dir);
for (String file : FileUtils.listFiles(dir)) { for (String file : FileUtils.newDirectoryStream(dir)) {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
buff.append(FileUtils.isDirectory(file) ? "d" : "-"); buff.append(FileUtils.isDirectory(file) ? "d" : "-");
buff.append(FileUtils.canWrite(file) ? "rw" : "r-"); buff.append(FileUtils.canWrite(file) ? "rw" : "r-");
...@@ -236,7 +236,7 @@ public class FileShell extends Tool { ...@@ -236,7 +236,7 @@ public class FileShell extends Tool {
FileUtils.moveTo(source, target); FileUtils.moveTo(source, target);
} else if ("pwd".equals(c)) { } else if ("pwd".equals(c)) {
end(list, i); end(list, i);
println(FileUtils.getCanonicalPath(currentWorkingDirectory)); println(FileUtils.toRealPath(currentWorkingDirectory));
} else if ("rm".equals(c)) { } else if ("rm".equals(c)) {
if ("-r".equals(list[i])) { if ("-r".equals(list[i])) {
i++; i++;
...@@ -311,9 +311,9 @@ public class FileShell extends Tool { ...@@ -311,9 +311,9 @@ public class FileShell extends Tool {
} }
private void truncate(String fileName, long length) { private void truncate(String fileName, long length) {
FileObject f = null; FileChannel f = null;
try { try {
f = FileUtils.openFileObject(fileName, "rw"); f = FileUtils.open(fileName, "rw");
f.truncate(length); f.truncate(length);
} catch (IOException e) { } catch (IOException e) {
error(e); error(e);
...@@ -340,7 +340,7 @@ public class FileShell extends Tool { ...@@ -340,7 +340,7 @@ public class FileShell extends Tool {
fileOut = FileUtils.newOutputStream(zipFileName, false); fileOut = FileUtils.newOutputStream(zipFileName, false);
ZipOutputStream zipOut = new ZipOutputStream(fileOut); ZipOutputStream zipOut = new ZipOutputStream(fileOut);
for (String fileName : source) { for (String fileName : source) {
String f = FileUtils.getCanonicalPath(fileName); String f = FileUtils.toRealPath(fileName);
if (!f.startsWith(base)) { if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base); DbException.throwInternalError(f + " does not start with " + base);
} }
...@@ -432,7 +432,7 @@ public class FileShell extends Tool { ...@@ -432,7 +432,7 @@ public class FileShell extends Tool {
private void addFilesRecursive(String f, ArrayList<String> target) { private void addFilesRecursive(String f, ArrayList<String> target) {
if (FileUtils.isDirectory(f)) { if (FileUtils.isDirectory(f)) {
for (String c : FileUtils.listFiles(f)) { for (String c : FileUtils.newDirectoryStream(f)) {
addFilesRecursive(c, target); addFilesRecursive(c, target);
} }
} else { } else {
...@@ -447,7 +447,7 @@ public class FileShell extends Tool { ...@@ -447,7 +447,7 @@ public class FileShell extends Tool {
String unwrapped = FileUtils.unwrap(f); String unwrapped = FileUtils.unwrap(f);
String prefix = f.substring(0, f.length() - unwrapped.length()); String prefix = f.substring(0, f.length() - unwrapped.length());
f = prefix + currentWorkingDirectory + SysProperties.FILE_SEPARATOR + unwrapped; f = prefix + currentWorkingDirectory + SysProperties.FILE_SEPARATOR + unwrapped;
return FileUtils.getCanonicalPath(f); return FileUtils.toRealPath(f);
} }
private void showHelp() { private void showHelp() {
......
...@@ -292,7 +292,7 @@ public class FtpServer extends Tool implements Service { ...@@ -292,7 +292,7 @@ public class FtpServer extends Tool implements Service {
*/ */
String getDirectoryListing(String directory, boolean listDirectories) { String getDirectoryListing(String directory, boolean listDirectories) {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
for (String fileName : FileUtils.listFiles(directory)) { for (String fileName : FileUtils.newDirectoryStream(directory)) {
if (!FileUtils.isDirectory(fileName) || (FileUtils.isDirectory(fileName) && listDirectories)) { if (!FileUtils.isDirectory(fileName) || (FileUtils.isDirectory(fileName) && listDirectories)) {
appendFile(buff, fileName); appendFile(buff, fileName);
} }
...@@ -327,7 +327,7 @@ public class FtpServer extends Tool implements Service { ...@@ -327,7 +327,7 @@ public class FtpServer extends Tool implements Service {
if ("-ftpPort".equals(a)) { if ("-ftpPort".equals(a)) {
port = Integer.decode(args[++i]); port = Integer.decode(args[++i]);
} else if ("-ftpDir".equals(a)) { } else if ("-ftpDir".equals(a)) {
root = FileUtils.getCanonicalPath(args[++i]); root = FileUtils.toRealPath(args[++i]);
} else if ("-ftpRead".equals(a)) { } else if ("-ftpRead".equals(a)) {
readUserName = args[++i]; readUserName = args[++i];
} else if ("-ftpWrite".equals(a)) { } else if ("-ftpWrite".equals(a)) {
...@@ -351,7 +351,7 @@ public class FtpServer extends Tool implements Service { ...@@ -351,7 +351,7 @@ public class FtpServer extends Tool implements Service {
} }
public void start() { public void start() {
root = FileUtils.getCanonicalPath(root); root = FileUtils.toRealPath(root);
FileUtils.createDirectories(root); FileUtils.createDirectories(root);
serverSocket = NetUtils.createServerSocket(port, false); serverSocket = NetUtils.createServerSocket(port, false);
port = serverSocket.getLocalPort(); port = serverSocket.getLocalPort();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论