提交 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
<code>jdbc:h2:nio:~/test</code>.
</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.
</p>
<p>
......
......@@ -101,8 +101,8 @@ public class BackupCommand extends Prepared {
}
private static void backupFile(ZipOutputStream out, String base, String fn) throws IOException {
String f = FileUtils.getCanonicalPath(fn);
base = FileUtils.getCanonicalPath(base);
String f = FileUtils.toRealPath(fn);
base = FileUtils.toRealPath(base);
if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base);
}
......
......@@ -159,7 +159,7 @@ public class ConnectionInfo implements Cloneable {
*/
public void setBaseDir(String dir) {
if (persistent) {
String absDir = FileUtils.unwrap(FileUtils.getCanonicalPath(dir));
String absDir = FileUtils.unwrap(FileUtils.toRealPath(dir));
boolean absolute = FileUtils.isAbsolute(name);
String n;
String prefix = null;
......@@ -170,7 +170,7 @@ public class ConnectionInfo implements Cloneable {
prefix = name.substring(0, name.length() - n.length());
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)) {
throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " +
absDir);
......@@ -364,7 +364,7 @@ public class ConnectionInfo implements Cloneable {
if (persistent) {
if (nameNormalized == null) {
String suffix = Constants.SUFFIX_PAGE_FILE;
String n = FileUtils.getCanonicalPath(name + suffix);
String n = FileUtils.toRealPath(name + suffix);
String fileName = FileUtils.getName(n);
if (fileName.length() < suffix.length() + 1) {
throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name);
......
......@@ -1348,7 +1348,7 @@ public class Database implements DataHandler {
public String getDatabasePath() {
if (persistent) {
return FileUtils.getCanonicalPath(databaseName);
return FileUtils.toRealPath(databaseName);
}
return null;
}
......@@ -1477,8 +1477,7 @@ public class Database implements DataHandler {
private void deleteOldTempFiles() {
String path = FileUtils.getParent(databaseName);
String[] list = FileUtils.listFiles(path);
for (String name : list) {
for (String name : FileUtils.newDirectoryStream(path)) {
if (name.endsWith(Constants.SUFFIX_TEMP_FILE) && name.startsWith(databaseName)) {
// can't always delete the files, they may still be open
FileUtils.tryDelete(name);
......
......@@ -244,7 +244,7 @@ public class CipherFactory {
throw DbException.convertToIOException(e);
}
}
String absolutePath = FileUtils.getCanonicalPath(fileName);
String absolutePath = FileUtils.toRealPath(fileName);
System.setProperty(KEYSTORE_KEY, absolutePath);
}
if (p.getProperty(KEYSTORE_PASSWORD_KEY) == null) {
......
......@@ -90,7 +90,7 @@ public class Data {
/**
* The data itself.
*/
protected byte[] data;
private byte[] data;
/**
* The current write or read position.
......@@ -102,7 +102,7 @@ public class Data {
*/
private final DataHandler handler;
protected Data(DataHandler handler, byte[] data) {
private Data(DataHandler handler, byte[] data) {
this.handler = handler;
this.data = data;
}
......@@ -1131,7 +1131,7 @@ public class Data {
* @param x the value
* @return the len
*/
public static int getVarIntLen(int x) {
private static int getVarIntLen(int x) {
if ((x & (-1 << 7)) == 0) {
return 1;
} else if ((x & (-1 << 14)) == 0) {
......
......@@ -58,7 +58,7 @@ public class FileLister {
if (dir == null || dir.equals("")) {
return ".";
}
return FileUtils.getCanonicalPath(dir);
return FileUtils.toRealPath(dir);
}
/**
......@@ -74,10 +74,8 @@ public class FileLister {
public static ArrayList<String> getDatabaseFiles(String dir, String db, boolean all) {
ArrayList<String> files = New.arrayList();
// for Windows, File.getCanonicalPath("...b.") returns just "...b"
String start = db == null ? null : (FileUtils.getCanonicalPath(dir + "/" + db) + ".");
String[] list = FileUtils.listFiles(dir);
for (int i = 0; list != null && i < list.length; i++) {
String f = list[i];
String start = db == null ? null : (FileUtils.toRealPath(dir + "/" + db) + ".");
for (String f : FileUtils.newDirectoryStream(dir)) {
boolean ok = false;
if (f.endsWith(Constants.SUFFIX_LOBS_DIRECTORY)) {
if (start == null || f.startsWith(start)) {
......
......@@ -8,12 +8,13 @@ package org.h2.store;
import java.io.IOException;
import java.lang.ref.Reference;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.security.SecureFileStore;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
......@@ -46,7 +47,7 @@ public class FileStore {
*/
protected DataHandler handler;
private FileObject file;
private FileChannel file;
private long filePos;
private long fileLength;
private Reference<?> autoDeleteReference;
......@@ -78,7 +79,7 @@ public class FileStore {
} else {
FileUtils.createDirectories(FileUtils.getParent(name));
}
file = FileUtils.openFileObject(name, mode);
file = FileUtils.open(name, mode);
if (exists) {
fileLength = file.size();
}
......@@ -272,7 +273,7 @@ public class FileStore {
}
checkPowerOff();
try {
file.readFully(b, off, len);
FileUtils.readFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e) {
throw DbException.convertIOException(e, name);
}
......@@ -323,11 +324,11 @@ public class FileStore {
checkWritingAllowed();
checkPowerOff();
try {
file.write(b, off, len);
FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e) {
if (freeUpDiskSpace()) {
try {
file.write(b, off, len);
FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len));
} catch (IOException e2) {
throw DbException.convertIOException(e2, name);
}
......@@ -362,7 +363,7 @@ public class FileStore {
if (newLength > fileLength) {
long pos = filePos;
file.position(newLength - 1);
file.write(new byte[1], 0, 1);
FileUtils.writeFully(file, ByteBuffer.wrap(new byte[1]));
file.position(pos);
} else {
file.truncate(newLength);
......@@ -468,7 +469,7 @@ public class FileStore {
*/
public void openFile() throws IOException {
if (file == null) {
file = FileUtils.openFileObject(name, mode);
file = FileUtils.open(name, mode);
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;
import java.io.IOException;
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 };
/**
* 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) {
this.file = file;
public FileChannelInputStream(FileChannel channel) {
this.channel = channel;
}
public int read() throws IOException {
if (file.position() >= file.size()) {
if (channel.position() >= channel.size()) {
return -1;
}
file.readFully(buffer, 0, 1);
FileUtils.readFully(channel, ByteBuffer.wrap(buffer));
return buffer[0] & 0xff;
}
......@@ -39,15 +41,15 @@ public class FileObjectInputStream extends InputStream {
}
public int read(byte[] b, int off, int len) throws IOException {
if (file.position() + len < file.size()) {
file.readFully(b, off, len);
if (channel.position() + len < channel.size()) {
FileUtils.readFully(channel, ByteBuffer.wrap(b, off, len));
return len;
}
return super.read(b, off, len);
}
public void close() throws IOException {
file.close();
channel.close();
}
}
......@@ -8,46 +8,48 @@ package org.h2.store.fs;
import java.io.IOException;
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 };
/**
* 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
*/
public FileObjectOutputStream(FileObject file, boolean append) throws IOException {
this.file = file;
public FileChannelOutputStream(FileChannel channel, boolean append) throws IOException {
this.channel = channel;
if (append) {
file.position(file.size());
channel.position(channel.size());
} else {
file.position(0);
file.truncate(0);
channel.position(0);
channel.truncate(0);
}
}
public void write(int b) throws IOException {
buffer[0] = (byte) b;
file.write(buffer, 0, 1);
FileUtils.writeFully(channel, ByteBuffer.wrap(buffer));
}
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 {
file.write(b, off, len);
FileUtils.writeFully(channel, ByteBuffer.wrap(b, off, len));
}
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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -142,18 +143,18 @@ public abstract class FilePath {
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
*/
public abstract List<FilePath> listFiles();
public abstract List<FilePath> newDirectoryStream();
/**
* Normalize a file name.
*
* @return the normalized file name
*/
public abstract FilePath getCanonicalPath();
public abstract FilePath toRealPath();
/**
* Get the parent directory of a file or directory.
......@@ -220,7 +221,7 @@ public abstract class FilePath {
* @param mode the access mode. Supported are r, rw, rws, rwd
* @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.
......@@ -253,7 +254,7 @@ public abstract class FilePath {
getNextTempFileNamePart(true);
continue;
}
p.openFileObject("rw").close();
p.open("rw").close();
return p;
}
}
......
......@@ -15,6 +15,9 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
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.List;
import org.h2.constant.ErrorCode;
......@@ -146,7 +149,7 @@ public class FilePathDisk extends FilePath {
throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, name);
}
public List<FilePath> listFiles() {
public List<FilePath> newDirectoryStream() {
ArrayList<FilePath> list = New.arrayList();
File f = new File(name);
try {
......@@ -175,7 +178,7 @@ public class FilePathDisk extends FilePath {
return f.setReadOnly();
}
public FilePathDisk getCanonicalPath() {
public FilePathDisk toRealPath() {
try {
String fileName = new File(name).getCanonicalPath();
return getPath(fileName);
......@@ -311,15 +314,15 @@ public class FilePathDisk extends FilePath {
}
}
public FileObject openFileObject(String mode) throws IOException {
FileObjectDisk f;
public FileChannel open(String mode) throws IOException {
FileDisk f;
try {
f = new FileObjectDisk(name, mode);
IOUtils.trace("openFileObject", name, f);
f = new FileDisk(name, mode);
IOUtils.trace("open", name, f);
} catch (IOException e) {
freeMemoryAndFinalize();
try {
f = new FileObjectDisk(name, mode);
f = new FileDisk(name, mode);
} catch (IOException e2) {
throw e;
}
......@@ -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;
}
}
......@@ -7,6 +7,11 @@
package org.h2.store.fs;
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.
......@@ -14,8 +19,8 @@ import java.io.IOException;
*/
public class FilePathNio extends FilePathWrapper {
public FileObject openFileObject(String mode) throws IOException {
return new FileObjectNio(name.substring(getScheme().length() + 1), mode);
public FileChannel open(String mode) throws IOException {
return new FileNio(name.substring(getScheme().length() + 1), mode);
}
public String getScheme() {
......@@ -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 @@
*/
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.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.
......@@ -14,8 +24,8 @@ import java.io.IOException;
*/
public class FilePathNioMapped extends FilePathNio {
public FileObject openFileObject(String mode) throws IOException {
return new FileObjectNioMapped(name.substring(getScheme().length() + 1), mode);
public FileChannel open(String mode) throws IOException {
return new FileNioMapped(name.substring(getScheme().length() + 1), mode);
}
public String getScheme() {
......@@ -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;
import java.io.IOException;
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.
......@@ -52,8 +55,8 @@ public class FilePathRec extends FilePathWrapper {
super.delete();
}
public FileObject openFileObject(String mode) throws IOException {
return new FileObjectRec(this, super.openFileObject(mode), name);
public FileChannel open(String mode) throws IOException {
return new FileRec(this, super.open(mode), name);
}
public OutputStream newOutputStream(boolean append) {
......@@ -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;
import java.io.InputStream;
import java.io.OutputStream;
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.List;
import org.h2.constant.SysProperties;
......@@ -83,8 +86,8 @@ public class FilePathSplit extends FilePathWrapper {
return length;
}
public ArrayList<FilePath> listFiles() {
List<FilePath> list = getBase().listFiles();
public ArrayList<FilePath> newDirectoryStream() {
List<FilePath> list = getBase().newDirectoryStream();
ArrayList<FilePath> newList = New.arrayList();
for (int i = 0, size = list.size(); i < size; i++) {
FilePath f = list.get(i);
......@@ -109,20 +112,18 @@ public class FilePathSplit extends FilePathWrapper {
return input;
}
public FileObject openFileObject(String mode) throws IOException {
ArrayList<FileObject> list = New.arrayList();
FileObject o = getBase().openFileObject(mode);
list.add(o);
public FileChannel open(String mode) throws IOException {
ArrayList<FileChannel> list = New.arrayList();
list.add(getBase().open(mode));
for (int i = 1;; i++) {
FilePath f = getBase(i);
if (f.exists()) {
o = f.openFileObject(mode);
list.add(o);
list.add(f.open(mode));
} else {
break;
}
}
FileObject[] array = new FileObject[list.size()];
FileChannel[] array = new FileChannel[list.size()];
list.toArray(array);
long maxLength = array[0].size();
long length = maxLength;
......@@ -136,21 +137,21 @@ public class FilePathSplit extends FilePathWrapper {
closeAndThrow(0, array, array[0], maxLength);
}
for (int i = 1; i < array.length - 1; i++) {
o = array[i];
long l = o.size();
FileChannel c = array[i];
long l = c.size();
length += l;
if (l != maxLength) {
closeAndThrow(i, array, o, maxLength);
closeAndThrow(i, array, c, maxLength);
}
}
o = array[array.length - 1];
long l = o.size();
FileChannel c = array[array.length - 1];
long l = c.size();
length += l;
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;
}
......@@ -158,9 +159,9 @@ public class FilePathSplit extends FilePathWrapper {
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);
for (FileObject f : array) {
for (FileChannel f : array) {
f.close();
}
throw new IOException(message);
......@@ -168,7 +169,7 @@ public class FilePathSplit extends FilePathWrapper {
public OutputStream newOutputStream(boolean append) {
try {
return new FileObjectOutputStream(openFileObject("rw"), append);
return new FileChannelOutputStream(open("rw"), append);
} catch (IOException e) {
throw DbException.convertIOException(e, name);
}
......@@ -230,4 +231,147 @@ public class FilePathSplit extends FilePathWrapper {
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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.List;
import org.h2.message.DbException;
......@@ -103,12 +104,12 @@ public abstract class FilePathWrapper extends FilePath {
return base.lastModified();
}
public FilePath getCanonicalPath() {
return wrap(base.getCanonicalPath());
public FilePath toRealPath() {
return wrap(base.toRealPath());
}
public List<FilePath> listFiles() {
List<FilePath> list = base.listFiles();
public List<FilePath> newDirectoryStream() {
List<FilePath> list = base.newDirectoryStream();
for (int i = 0, len = list.size(); i < len; i++) {
list.set(i, wrap(list.get(i)));
}
......@@ -127,8 +128,8 @@ public abstract class FilePathWrapper extends FilePath {
return base.newOutputStream(append);
}
public FileObject openFileObject(String mode) throws IOException {
return base.openFileObject(mode);
public FileChannel open(String mode) throws IOException {
return base.open(mode);
}
public boolean setReadOnly() {
......
......@@ -10,11 +10,15 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
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.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.New;
/**
......@@ -112,7 +116,7 @@ public class FilePathZip extends FilePath {
}
}
public ArrayList<FilePath> listFiles() {
public ArrayList<FilePath> newDirectoryStream() {
String path = name;
ArrayList<FilePath> list = New.arrayList();
try {
......@@ -147,16 +151,16 @@ public class FilePathZip extends FilePath {
}
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();
ZipEntry entry = file.getEntry(getEntryName());
if (entry == null) {
throw new FileNotFoundException(name);
}
return new FileObjectZip(file, entry);
return new FileZip(file, entry);
}
public OutputStream newOutputStream(boolean append) {
......@@ -178,7 +182,7 @@ public class FilePathZip extends FilePath {
return FilePathDisk.expandUserHomeDirectory(fileName);
}
public FilePath getCanonicalPath() {
public FilePath toRealPath() {
return this;
}
......@@ -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;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
import org.h2.constant.ErrorCode;
import org.h2.message.DbException;
import org.h2.util.IOUtils;
import org.h2.util.New;
/**
* This utility class contains utility functions that use the file system
......@@ -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>.
*
* @param fileName the file name
* @return the normalized file name
*/
public static String getCanonicalPath(String fileName) {
return FilePath.get(fileName).getCanonicalPath().toString();
public static String toRealPath(String fileName) {
return FilePath.get(fileName).toRealPath().toString();
}
/**
......@@ -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>.
*
* @param path the directory
* @return the list of fully qualified file names
*/
public static String[] listFiles(String path) {
List<FilePath> list = FilePath.get(path).listFiles();
String[] array = new String[list.size()];
for (int i = 0, len = list.size(); i < len; i++) {
array[i] = list.get(i).toString();
public static List<String> newDirectoryStream(String path) {
List<FilePath> list = FilePath.get(path).newDirectoryStream();
int len = list.size();
List<String> result = New.arrayList(len);
for (int i = 0; i < len; i++) {
result.add(list.get(i).toString());
}
return array;
return result;
}
/**
......@@ -174,8 +179,8 @@ public class FileUtils {
* @param mode the access mode. Supported are r, rw, rws, rwd
* @return the file object
*/
public static FileObject openFileObject(String fileName, String mode) throws IOException {
return FilePath.get(fileName).openFileObject(mode);
public static FileChannel open(String fileName, String mode) throws IOException {
return FilePath.get(fileName).open(mode);
}
/**
......@@ -248,7 +253,7 @@ public class FileUtils {
public static void deleteRecursive(String path, boolean tryOnly) {
if (exists(path)) {
if (isDirectory(path)) {
for (String s : listFiles(path)) {
for (String s : newDirectoryStream(path)) {
deleteRecursive(s, tryOnly);
}
}
......@@ -324,4 +329,33 @@ public class FileUtils {
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;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
......@@ -109,7 +108,7 @@ public class Backup extends Tool {
List<String> list;
boolean allFiles = db != null && db.length() == 0;
if (allFiles) {
list = Arrays.asList(FileUtils.listFiles(directory));
list = FileUtils.newDirectoryStream(directory);
} else {
list = FileLister.getDatabaseFiles(directory, db, true);
}
......@@ -122,7 +121,7 @@ public class Backup extends Tool {
if (!quiet) {
FileLister.tryUnlockDatabase(list, "backup");
}
zipFileName = FileUtils.getCanonicalPath(zipFileName);
zipFileName = FileUtils.toRealPath(zipFileName);
FileUtils.delete(zipFileName);
OutputStream fileOut = null;
try {
......@@ -136,7 +135,7 @@ public class Backup extends Tool {
}
}
for (String fileName : list) {
String f = FileUtils.getCanonicalPath(fileName);
String f = FileUtils.toRealPath(fileName);
if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base);
}
......
......@@ -237,7 +237,7 @@ public class ValueLob extends Value {
name = SysProperties.FILE_SEPARATOR + f + Constants.SUFFIX_LOBS_DIRECTORY + name;
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;
}
......@@ -322,12 +322,12 @@ public class ValueLob extends Value {
SmallLRUCache<String, String[]> cache = h.getLobFileListCache();
String[] list;
if (cache == null) {
list = FileUtils.listFiles(dir);
list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
} else {
synchronized (cache) {
list = cache.get(dir);
if (list == null) {
list = FileUtils.listFiles(dir);
list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
cache.put(dir, list);
}
}
......@@ -719,7 +719,7 @@ public class ValueLob extends Value {
}
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)) {
removeAllForTable(handler, name, tableId);
} else {
......
......@@ -17,6 +17,7 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.List;
import java.util.Random;
import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileUtils;
......@@ -1218,10 +1219,8 @@ public class TestCases extends TestBase {
conn.close();
String[] list = FileUtils.listFiles(getBaseDir() + "/cases.lobs.db");
if (list != null && list.length > 0) {
fail("Lob file was not deleted");
}
List<String> list = FileUtils.newDirectoryStream(getBaseDir() + "/cases.lobs.db");
assertEquals("Lob file was not deleted: " + list, 0, list.size());
}
private void testDeleteTop() throws SQLException {
......
......@@ -632,8 +632,7 @@ public class TestLinkedTable extends TestBase {
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)");
conn.close();
String[] files = FileUtils.listFiles(getBaseDir());
for (String file : files) {
for (String file : FileUtils.newDirectoryStream(getBaseDir())) {
String name = FileUtils.getName(file);
if ((name.startsWith("testLinkedTableInReadOnlyDb")) && (!name.endsWith(".trace.db"))) {
FileUtils.setReadOnly(file);
......
......@@ -23,6 +23,7 @@ import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -385,21 +386,21 @@ public class TestLob extends TestBase {
stat.execute("create table test(id int primary key, name clob)");
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");
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("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");
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("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");
assertEquals(0, FileUtils.listFiles(getBaseDir() + "/lob.lobs.db").length);
assertEquals(0, FileUtils.newDirectoryStream(getBaseDir() + "/lob.lobs.db").size());
conn.close();
}
......@@ -438,13 +439,10 @@ public class TestLob extends TestBase {
}
private void testTempFilesDeleted() throws Exception {
String[] list;
FileUtils.deleteRecursive(TEMP_DIR, true);
FileUtils.createDirectories(TEMP_DIR);
list = FileUtils.listFiles(TEMP_DIR);
if (list.length > 0) {
fail("Unexpected temp file: " + list[0]);
}
List<String> list = FileUtils.newDirectoryStream(TEMP_DIR);
assertEquals("Unexpected temp file: " + list, 0, list.size());
deleteDb("lob");
Connection conn = getConnection("lob");
Statement stat;
......@@ -457,10 +455,8 @@ public class TestLob extends TestBase {
rs.getCharacterStream("name").close();
rs.close();
conn.close();
list = FileUtils.listFiles(TEMP_DIR);
if (list.length > 0) {
fail("Unexpected temp file: " + list[0]);
}
list = FileUtils.newDirectoryStream(TEMP_DIR);
assertEquals("Unexpected temp file: " + list, 0, list.size());
}
private static void testAddLobRestart() throws SQLException {
......@@ -503,10 +499,10 @@ public class TestLob extends TestBase {
Connection conn = getConnection("lob");
Statement stat = conn.createStatement();
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");
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 {
......
......@@ -6,6 +6,8 @@
*/
package org.h2.test.db;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
......@@ -14,7 +16,6 @@ import java.sql.SQLException;
import java.sql.Statement;
import org.h2.api.DatabaseEventListener;
import org.h2.constant.ErrorCode;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.tools.Restore;
......@@ -67,10 +68,10 @@ public class TestOpenClose extends TestBase implements DatabaseEventListener {
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.close();
FileObject f = FileUtils.openFileObject(getBaseDir() + "/openClose2.h2.db.1.part", "rw");
f.position(f.size() * 2 - 1);
f.write(new byte[1], 0, 1);
f.close();
FileChannel c = FileUtils.open(getBaseDir() + "/openClose2.h2.db.1.part", "rw");
c.position(c.size() * 2 - 1);
c.write(ByteBuffer.wrap(new byte[1]));
c.close();
assertThrows(ErrorCode.IO_EXCEPTION_2, this).
getConnection("jdbc:h2:split:18:" + getBaseDir() + "/openClose2");
FileUtils.delete("split:" + getBaseDir() + "/openClose2.h2.db");
......
......@@ -68,7 +68,7 @@ public class TestClearReferences extends TestBase {
// initialize the known classes
MathUtils.secureRandomLong();
ValueInt.get(1);
Class.forName("org.h2.store.fs.FileObjectMemData");
Class.forName("org.h2.store.fs.FileMemData");
clear();
......
......@@ -13,7 +13,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.List;
import org.h2.constant.ErrorCode;
import org.h2.jdbc.JdbcConnection;
......@@ -604,7 +603,7 @@ public class TestFileLockSerialized extends TestBase {
stat.execute("insert into test values(0)");
conn.close();
List<String> filesWithoutSerialized = Arrays.asList(FileUtils.listFiles(getBaseDir()));
List<String> filesWithoutSerialized = FileUtils.newDirectoryStream(getBaseDir());
deleteDb("fileLockSerialized");
// with serialized
......@@ -616,7 +615,7 @@ public class TestFileLockSerialized extends TestBase {
stat.execute("insert into test values(0)");
conn.close();
List<String> filesWithSerialized = Arrays.asList(FileUtils.listFiles(getBaseDir()));
List<String> filesWithSerialized = FileUtils.newDirectoryStream(getBaseDir());
if (filesWithoutSerialized.size() != filesWithSerialized.size()) {
for (int i = 0; i < filesWithoutSerialized.size(); i++) {
if (!filesWithSerialized.contains(filesWithoutSerialized.get(i))) {
......
......@@ -12,6 +12,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.sql.Connection;
import java.sql.DriverManager;
......@@ -21,10 +23,10 @@ import java.sql.Statement;
import java.util.List;
import java.util.Random;
import org.h2.dev.fs.FilePathCrypt;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.test.utils.AssertThrows;
import org.h2.test.utils.FilePathDebug;
import org.h2.tools.Backup;
import org.h2.tools.DeleteDbFiles;
......@@ -58,7 +60,7 @@ public class TestFileSystem extends TestBase {
testDatabaseInJar();
// set default part size to 1 << 10
String f = "split:10:" + getBaseDir() + "/fs";
FileUtils.getCanonicalPath(f);
FileUtils.toRealPath(f);
testFileSystem(getBaseDir() + "/fs");
testFileSystem("memFS:");
testFileSystem("memLZF:");
......@@ -85,8 +87,7 @@ public class TestFileSystem extends TestBase {
private void testMemFsDir() throws IOException {
FileUtils.newOutputStream("memFS:data/test/a.txt", false).close();
String[] list = FileUtils.listFiles("memFS:data/test");
assertEquals(1, list.length);
assertEquals(1, FileUtils.newDirectoryStream("memFS:data/test").size());
FileUtils.deleteRecursive("memFS:", false);
}
......@@ -110,15 +111,15 @@ public class TestFileSystem extends TestBase {
private void testSimpleExpandTruncateSize() throws Exception {
String f = "memFS:" + getBaseDir() + "/fs/test.data";
FileUtils.createDirectories("memFS:" + getBaseDir() + "/fs");
FileObject o = FileUtils.openFileObject(f, "rw");
o.position(4000);
o.write(new byte[1], 0, 1);
FileLock lock = o.tryLock();
o.truncate(0);
FileChannel c = FileUtils.open(f, "rw");
c.position(4000);
c.write(ByteBuffer.wrap(new byte[1]));
FileLock lock = c.tryLock();
c.truncate(0);
if (lock != null) {
lock.release();
}
o.close();
c.close();
}
private void testSplitDatabaseInZip() throws SQLException {
......@@ -184,7 +185,7 @@ public class TestFileSystem extends TestBase {
conn.close();
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.isDirectory(f));
assertTrue(FileUtils.size(f) > 0);
......@@ -209,7 +210,7 @@ public class TestFileSystem extends TestBase {
}
private void testUserHome() {
String fileName = FileUtils.getCanonicalPath("~/test");
String fileName = FileUtils.toRealPath("~/test");
String userDir = System.getProperty("user.home").replace('\\', '/');
assertTrue(fileName.startsWith(userDir));
}
......@@ -222,55 +223,64 @@ public class TestFileSystem extends TestBase {
private void testSimple(final String fsBase) throws Exception {
long time = System.currentTimeMillis();
for (String s : FileUtils.listFiles(fsBase)) {
for (String s : FileUtils.newDirectoryStream(fsBase)) {
FileUtils.delete(s);
}
FileUtils.createDirectories(fsBase + "/test");
FileUtils.delete(fsBase + "/test");
FileUtils.delete(fsBase + "/test2");
assertTrue(FileUtils.createFile(fsBase + "/test"));
List<FilePath> p = FilePath.get(fsBase).listFiles();
List<FilePath> p = FilePath.get(fsBase).newDirectoryStream();
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());
assertTrue(FileUtils.canWrite(fsBase + "/test"));
FileObject fo = FileUtils.openFileObject(fsBase + "/test", "rw");
FileChannel channel = FileUtils.open(fsBase + "/test", "rw");
byte[] buffer = new byte[10000];
Random random = new Random(1);
random.nextBytes(buffer);
fo.write(buffer, 0, 10000);
assertEquals(10000, fo.size());
fo.position(20000);
assertEquals(20000, fo.position());
assertThrows(EOFException.class, fo).readFully(buffer, 0, 1);
channel.write(ByteBuffer.wrap(buffer));
assertEquals(10000, channel.size());
channel.position(20000);
assertEquals(20000, channel.position());
assertEquals(-1, channel.read(ByteBuffer.wrap(buffer, 0, 1)));
String path = fsBase + "/test";
assertEquals("test", FileUtils.getName(path));
can = FilePath.get(fsBase).getCanonicalPath().toString();
String can2 = FileUtils.getCanonicalPath(FileUtils.getParent(path));
can = FilePath.get(fsBase).toRealPath().toString();
String can2 = FileUtils.toRealPath(FileUtils.getParent(path));
assertEquals(can, can2);
FileLock lock = fo.tryLock();
FileLock lock = channel.tryLock();
if (lock != null) {
lock.release();
}
assertEquals(10000, fo.size());
fo.close();
assertEquals(10000, channel.size());
channel.close();
assertEquals(10000, FileUtils.size(fsBase + "/test"));
fo = FileUtils.openFileObject(fsBase + "/test", "r");
byte[] test = new byte[10000];
fo.readFully(test, 0, 10000);
channel = FileUtils.open(fsBase + "/test", "r");
final byte[] test = new byte[10000];
FileUtils.readFully(channel, ByteBuffer.wrap(test, 0, 10000));
assertEquals(buffer, test);
assertThrows(IOException.class, fo).write(test, 0, 10);
assertThrows(IOException.class, fo).truncate(10);
fo.close();
final FileChannel fc = channel;
new AssertThrows(IOException.class) {
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");
if (lastMod < time - 1999) {
// at most 2 seconds difference
assertEquals(time, lastMod);
}
assertEquals(10000, FileUtils.size(fsBase + "/test"));
String[] list = FileUtils.listFiles(fsBase);
assertEquals(1, list.length);
assertTrue(list[0].endsWith("test"));
List<String> list = FileUtils.newDirectoryStream(fsBase);
assertEquals(1, list.size());
assertTrue(list.get(0).endsWith("test"));
FileUtils.copy(fsBase + "/test", fsBase + "/test3");
FileUtils.moveTo(fsBase + "/test3", fsBase + "/test2");
assertTrue(!FileUtils.exists(fsBase + "/test3"));
......@@ -314,8 +324,8 @@ public class TestFileSystem extends TestBase {
file.delete();
RandomAccessFile ra = new RandomAccessFile(file, "rw");
FileUtils.delete(s);
FileObject f = FileUtils.openFileObject(s, "rw");
assertThrows(EOFException.class, f).readFully(new byte[1], 0, 1);
FileChannel f = FileUtils.open(s, "rw");
assertEquals(-1, f.read(ByteBuffer.wrap(new byte[1])));
f.force(true);
Random random = new Random(seed);
int size = getSize(100, 500);
......@@ -337,7 +347,7 @@ public class TestFileSystem extends TestBase {
random.nextBytes(buffer);
trace("write " + buffer.length);
buff.append("write " + buffer.length + "\n");
f.write(buffer, 0, buffer.length);
f.write(ByteBuffer.wrap(buffer));
ra.write(buffer, 0, buffer.length);
break;
}
......@@ -360,7 +370,11 @@ public class TestFileSystem extends TestBase {
byte[] b2 = new byte[len];
trace("readFully " + 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");
assertEquals(b1, b2);
break;
......@@ -383,7 +397,7 @@ public class TestFileSystem extends TestBase {
f.close();
ra.close();
ra = new RandomAccessFile(file, "rw");
f = FileUtils.openFileObject(s, "rw");
f = FileUtils.open(s, "rw");
assertEquals(ra.length(), f.size());
break;
}
......
......@@ -6,13 +6,13 @@
*/
package org.h2.test.unit;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.tools.Restore;
......@@ -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_MAP");
conn.close();
FileObject f = FileUtils.openFileObject(fileName, "rw");
FileChannel f = FileUtils.open(fileName, "rw");
// create a new database
conn = getConnection("pageStore");
conn.close();
f = FileUtils.openFileObject(fileName, "rw");
f = FileUtils.open(fileName, "rw");
f.truncate(16);
// create a new database
conn = getConnection("pageStore");
......
......@@ -9,12 +9,13 @@ package org.h2.test.unit;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.engine.Constants;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.tools.DeleteDbFiles;
......@@ -134,14 +135,14 @@ public class TestRecovery extends TestBase {
Statement stat = conn.createStatement();
stat.execute("create table test(id int, name varchar) as select 1, 'Hello World1'");
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];
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) {
buff[buff.length - 1]++;
f.position(f.position() - buff.length);
f.write(buff, 0, buff.length);
f.write(ByteBuffer.wrap(buff));
}
}
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;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
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.FilePathWrapper;
......@@ -116,14 +119,14 @@ public class FilePathDebug extends FilePathWrapper {
return super.size();
}
public List<FilePath> listFiles() {
trace(name, "listFiles");
return super.listFiles();
public List<FilePath> newDirectoryStream() {
trace(name, "newDirectoryStream");
return super.newDirectoryStream();
}
public FilePath getCanonicalPath() {
trace(name, "getCanonicalPath");
return super.getCanonicalPath();
public FilePath toRealPath() {
trace(name, "toRealPath");
return super.toRealPath();
}
public InputStream newInputStream() throws IOException {
......@@ -151,9 +154,9 @@ public class FilePathDebug extends FilePathWrapper {
};
}
public FileObject openFileObject(String mode) throws IOException {
trace(name, "openFileObject", mode);
return new FileDebug(this, super.openFileObject(mode), name);
public FileChannel open(String mode) throws IOException {
trace(name, "open", mode);
return new FileDebug(this, super.open(mode), name);
}
public OutputStream newOutputStream(boolean append) {
......@@ -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;
}
}
......@@ -10,16 +10,20 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
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.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileObjectInputStream;
import org.h2.store.fs.FileBase;
import org.h2.store.fs.FileChannelInputStream;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathDisk;
import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.New;
/**
......@@ -184,7 +188,7 @@ public class FilePathZip2 extends FilePath {
}
}
public ArrayList<FilePath> listFiles() {
public ArrayList<FilePath> newDirectoryStream() {
String path = name;
try {
if (path.indexOf('!') < 0) {
......@@ -218,16 +222,15 @@ public class FilePathZip2 extends FilePath {
}
}
public FilePath getCanonicalPath() {
public FilePath toRealPath() {
return this;
}
public InputStream newInputStream() throws IOException {
FileObject file = openFileObject("r");
return new FileObjectInputStream(file);
return new FileChannelInputStream(open("r"));
}
public FileObject openFileObject(String mode) throws IOException {
public FileChannel open(String mode) throws IOException {
String entryName = getEntryName();
if (entryName.length() == 0) {
throw new FileNotFoundException();
......@@ -239,7 +242,7 @@ public class FilePathZip2 extends FilePath {
break;
}
if (entry.getName().equals(entryName)) {
return new FileObjectZip2(name, entryName, in, size());
return new FileZip2(name, entryName, in, size());
}
in.closeEntry();
}
......@@ -301,4 +304,114 @@ public class FilePathZip2 extends FilePath {
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;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.channels.FileChannel;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
......@@ -24,7 +25,6 @@ import org.h2.command.dml.BackupCommand;
import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.store.fs.FileObject;
import org.h2.store.fs.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.New;
......@@ -127,7 +127,7 @@ public class FileShell extends Tool {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(in));
}
println(FileUtils.getCanonicalPath(currentWorkingDirectory));
println(FileUtils.toRealPath(currentWorkingDirectory));
while (true) {
try {
print("> ");
......@@ -212,7 +212,7 @@ public class FileShell extends Tool {
}
end(list, i);
println(dir);
for (String file : FileUtils.listFiles(dir)) {
for (String file : FileUtils.newDirectoryStream(dir)) {
StringBuilder buff = new StringBuilder();
buff.append(FileUtils.isDirectory(file) ? "d" : "-");
buff.append(FileUtils.canWrite(file) ? "rw" : "r-");
......@@ -236,7 +236,7 @@ public class FileShell extends Tool {
FileUtils.moveTo(source, target);
} else if ("pwd".equals(c)) {
end(list, i);
println(FileUtils.getCanonicalPath(currentWorkingDirectory));
println(FileUtils.toRealPath(currentWorkingDirectory));
} else if ("rm".equals(c)) {
if ("-r".equals(list[i])) {
i++;
......@@ -311,9 +311,9 @@ public class FileShell extends Tool {
}
private void truncate(String fileName, long length) {
FileObject f = null;
FileChannel f = null;
try {
f = FileUtils.openFileObject(fileName, "rw");
f = FileUtils.open(fileName, "rw");
f.truncate(length);
} catch (IOException e) {
error(e);
......@@ -340,7 +340,7 @@ public class FileShell extends Tool {
fileOut = FileUtils.newOutputStream(zipFileName, false);
ZipOutputStream zipOut = new ZipOutputStream(fileOut);
for (String fileName : source) {
String f = FileUtils.getCanonicalPath(fileName);
String f = FileUtils.toRealPath(fileName);
if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base);
}
......@@ -432,7 +432,7 @@ public class FileShell extends Tool {
private void addFilesRecursive(String f, ArrayList<String> target) {
if (FileUtils.isDirectory(f)) {
for (String c : FileUtils.listFiles(f)) {
for (String c : FileUtils.newDirectoryStream(f)) {
addFilesRecursive(c, target);
}
} else {
......@@ -447,7 +447,7 @@ public class FileShell extends Tool {
String unwrapped = FileUtils.unwrap(f);
String prefix = f.substring(0, f.length() - unwrapped.length());
f = prefix + currentWorkingDirectory + SysProperties.FILE_SEPARATOR + unwrapped;
return FileUtils.getCanonicalPath(f);
return FileUtils.toRealPath(f);
}
private void showHelp() {
......
......@@ -292,7 +292,7 @@ public class FtpServer extends Tool implements Service {
*/
String getDirectoryListing(String directory, boolean listDirectories) {
StringBuilder buff = new StringBuilder();
for (String fileName : FileUtils.listFiles(directory)) {
for (String fileName : FileUtils.newDirectoryStream(directory)) {
if (!FileUtils.isDirectory(fileName) || (FileUtils.isDirectory(fileName) && listDirectories)) {
appendFile(buff, fileName);
}
......@@ -327,7 +327,7 @@ public class FtpServer extends Tool implements Service {
if ("-ftpPort".equals(a)) {
port = Integer.decode(args[++i]);
} else if ("-ftpDir".equals(a)) {
root = FileUtils.getCanonicalPath(args[++i]);
root = FileUtils.toRealPath(args[++i]);
} else if ("-ftpRead".equals(a)) {
readUserName = args[++i];
} else if ("-ftpWrite".equals(a)) {
......@@ -351,7 +351,7 @@ public class FtpServer extends Tool implements Service {
}
public void start() {
root = FileUtils.getCanonicalPath(root);
root = FileUtils.toRealPath(root);
FileUtils.createDirectories(root);
serverSocket = NetUtils.createServerSocket(port, false);
port = serverSocket.getLocalPort();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论