8034802: (zipfs) newFileSystem throws UOE when the zip file is located in a custom file system

Reviewed-by: xiaofeya, clanger
This commit is contained in:
Xueming Shen 2018-09-18 10:43:01 -07:00
parent 9ed646a020
commit 196c20c0d1
4 changed files with 752 additions and 344 deletions

View File

@ -0,0 +1,252 @@
/*
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nio.zipfs;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.util.Arrays;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ByteArrayChannel implements SeekableByteChannel {
private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
private byte buf[];
/*
* The current position of this channel.
*/
private int pos;
/*
* The index that is one greater than the last valid byte in the channel.
*/
private int last;
private boolean closed;
private boolean readonly;
/*
* Creates a {@code ByteArrayChannel} with size {@code sz}.
*/
ByteArrayChannel(int sz, boolean readonly) {
this.buf = new byte[sz];
this.pos = this.last = 0;
this.readonly = readonly;
}
/*
* Creates a ByteArrayChannel with its 'pos' at 0 and its 'last' at buf's end.
* Note: no defensive copy of the 'buf', used directly.
*/
ByteArrayChannel(byte[] buf, boolean readonly) {
this.buf = buf;
this.pos = 0;
this.last = buf.length;
this.readonly = readonly;
}
@Override
public boolean isOpen() {
return !closed;
}
@Override
public long position() throws IOException {
beginRead();
try {
ensureOpen();
return pos;
} finally {
endRead();
}
}
@Override
public SeekableByteChannel position(long pos) throws IOException {
beginWrite();
try {
ensureOpen();
if (pos < 0 || pos >= Integer.MAX_VALUE)
throw new IllegalArgumentException("Illegal position " + pos);
this.pos = Math.min((int)pos, last);
return this;
} finally {
endWrite();
}
}
@Override
public int read(ByteBuffer dst) throws IOException {
beginWrite();
try {
ensureOpen();
if (pos == last)
return -1;
int n = Math.min(dst.remaining(), last - pos);
dst.put(buf, pos, n);
pos += n;
return n;
} finally {
endWrite();
}
}
@Override
public SeekableByteChannel truncate(long size) throws IOException {
if (readonly)
throw new NonWritableChannelException();
ensureOpen();
throw new UnsupportedOperationException();
}
@Override
public int write(ByteBuffer src) throws IOException {
if (readonly)
throw new NonWritableChannelException();
beginWrite();
try {
ensureOpen();
int n = src.remaining();
ensureCapacity(pos + n);
src.get(buf, pos, n);
pos += n;
if (pos > last) {
last = pos;
}
return n;
} finally {
endWrite();
}
}
@Override
public long size() throws IOException {
beginRead();
try {
ensureOpen();
return last;
} finally {
endRead();
}
}
@Override
public void close() throws IOException {
if (closed)
return;
beginWrite();
try {
closed = true;
buf = null;
pos = 0;
last = 0;
} finally {
endWrite();
}
}
/**
* Creates a newly allocated byte array. Its size is the current
* size of this channel and the valid contents of the buffer
* have been copied into it.
*
* @return the current contents of this channel, as a byte array.
*/
public byte[] toByteArray() {
beginRead();
try {
// avoid copy if last == bytes.length?
return Arrays.copyOf(buf, last);
} finally {
endRead();
}
}
private void ensureOpen() throws IOException {
if (closed)
throw new ClosedChannelException();
}
private final void beginWrite() {
rwlock.writeLock().lock();
}
private final void endWrite() {
rwlock.writeLock().unlock();
}
private final void beginRead() {
rwlock.readLock().lock();
}
private final void endRead() {
rwlock.readLock().unlock();
}
private void ensureCapacity(int minCapacity) {
// overflow-conscious code
if (minCapacity - buf.length > 0) {
grow(minCapacity);
}
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = buf.length;
int newCapacity = oldCapacity << 1;
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
buf = Arrays.copyOf(buf, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
}

View File

@ -30,6 +30,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -70,33 +71,34 @@ class ZipFileSystem extends FileSystem {
private final ZipFileSystemProvider provider;
private final Path zfpath;
final ZipCoder zc;
private final boolean noExtt; // see readExtra()
private final ZipPath rootdir;
private boolean readOnly = false; // readonly file system
// configurable by env map
private final boolean noExtt; // see readExtra()
private final boolean useTempFile; // use a temp file for newOS, default
// is to use BAOS for better performance
private boolean readOnly = false; // readonly file system
private static final boolean isWindows = AccessController.doPrivileged(
(PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
.startsWith("Windows"));
private final boolean forceEnd64;
private final int defaultMethod; // METHOD_STORED if "noCompression=true"
// METHOD_DEFLATED otherwise
ZipFileSystem(ZipFileSystemProvider provider,
Path zfpath,
Map<String, ?> env) throws IOException
{
// create a new zip if not exists
boolean createNew = "true".equals(env.get("create"));
// default encoding for name/comment
String nameEncoding = env.containsKey("encoding") ?
(String)env.get("encoding") : "UTF-8";
this.noExtt = "false".equals(env.get("zipinfo-time"));
this.useTempFile = TRUE.equals(env.get("useTempFile"));
this.forceEnd64 = "true".equals(env.get("forceZIP64End"));
this.provider = provider;
this.zfpath = zfpath;
this.useTempFile = isTrue(env, "useTempFile");
this.forceEnd64 = isTrue(env, "forceZIP64End");
this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;
if (Files.notExists(zfpath)) {
if (createNew) {
// create a new zip if not exists
if (isTrue(env, "create")) {
try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
new END().write(os, 0, forceEnd64);
}
@ -122,6 +124,13 @@ class ZipFileSystem extends FileSystem {
}
throw x;
}
this.provider = provider;
this.zfpath = zfpath;
}
// returns true if there is a name=true/"true" setting in env
private static boolean isTrue(Map<String, ?> env, String name) {
return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
}
@Override
@ -254,22 +263,23 @@ class ZipFileSystem extends FileSystem {
try {
if (!isOpen)
return;
isOpen = false; // set closed
isOpen = false; // set closed
} finally {
endWrite();
}
if (!streams.isEmpty()) { // unlock and close all remaining streams
if (!streams.isEmpty()) { // unlock and close all remaining streams
Set<InputStream> copy = new HashSet<>(streams);
for (InputStream is: copy)
is.close();
}
beginWrite(); // lock and sync
beginWrite(); // lock and sync
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
sync(); return null;
});
ch.close(); // close the ch just in case no update
} catch (PrivilegedActionException e) { // and sync dose not close the ch
ch.close(); // close the ch just in case no update
// and sync didn't close the ch
} catch (PrivilegedActionException e) {
throw (IOException)e.getException();
} finally {
endWrite();
@ -316,8 +326,8 @@ class ZipFileSystem extends FileSystem {
IndexNode inode = getInode(path);
if (inode == null)
return null;
e = new Entry(inode.name, inode.isdir); // pseudo directory
e.method = METHOD_STORED; // STORED for dir
// pseudo directory, uses METHOD_STORED
e = new Entry(inode.name, inode.isdir, METHOD_STORED);
e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
}
} finally {
@ -425,8 +435,7 @@ class ZipFileSystem extends FileSystem {
if (dir.length == 0 || exists(dir)) // root dir, or exiting dir
throw new FileAlreadyExistsException(getString(dir));
checkParents(dir);
Entry e = new Entry(dir, Entry.NEW, true);
e.method = METHOD_STORED; // STORED for dir
Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
update(e);
} finally {
endWrite();
@ -467,7 +476,7 @@ class ZipFileSystem extends FileSystem {
checkParents(dst);
}
Entry u = new Entry(eSrc, Entry.COPY); // copy eSrc entry
u.name(dst); // change name
u.name(dst); // change name
if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH)
{
u.type = eSrc.type; // make it the same type
@ -527,7 +536,7 @@ class ZipFileSystem extends FileSystem {
if (hasAppend) {
InputStream is = getInputStream(e);
OutputStream os = getOutputStream(new Entry(e, Entry.NEW));
copyStream(is, os);
is.transferTo(os);
is.close();
return os;
}
@ -536,7 +545,7 @@ class ZipFileSystem extends FileSystem {
if (!hasCreate && !hasCreateNew)
throw new NoSuchFileException(getString(path));
checkParents(path);
return getOutputStream(new Entry(path, Entry.NEW, false));
return getOutputStream(new Entry(path, Entry.NEW, false, defaultMethod));
}
} finally {
endRead();
@ -572,6 +581,37 @@ class ZipFileSystem extends FileSystem {
throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
}
// Returns an output SeekableByteChannel for either
// (1) writing the contents of a new entry, if the entry doesn't exit, or
// (2) updating/replacing the contents of an existing entry.
// Note: The content is not compressed.
private class EntryOutputChannel extends ByteArrayChannel {
Entry e;
EntryOutputChannel(Entry e) throws IOException {
super(e.size > 0? (int)e.size : 8192, false);
this.e = e;
if (e.mtime == -1)
e.mtime = System.currentTimeMillis();
if (e.method == -1)
e.method = defaultMethod;
// store size, compressed size, and crc-32 in datadescriptor
e.flag = FLAG_DATADESCR;
if (zc.isUTF8())
e.flag |= FLAG_USE_UTF8;
}
@Override
public void close() throws IOException {
e.bytes = toByteArray();
e.size = e.bytes.length;
e.crc = -1;
super.close();
update(e);
}
}
// Returns a Writable/ReadByteChannel for now. Might consdier to use
// newFileChannel() instead, which dump the entry data into a regular
// file on the default file system and create a FileChannel on top of
@ -585,57 +625,36 @@ class ZipFileSystem extends FileSystem {
if (options.contains(StandardOpenOption.WRITE) ||
options.contains(StandardOpenOption.APPEND)) {
checkWritable();
beginRead();
beginRead(); // only need a readlock, the "update()" will obtain
// thewritelock when the channel is closed
try {
final WritableByteChannel wbc = Channels.newChannel(
newOutputStream(path, options.toArray(new OpenOption[0])));
long leftover = 0;
if (options.contains(StandardOpenOption.APPEND)) {
Entry e = getEntry(path);
if (e != null && e.size >= 0)
leftover = e.size;
ensureOpen();
Entry e = getEntry(path);
if (e != null) {
if (e.isDir() || options.contains(CREATE_NEW))
throw new FileAlreadyExistsException(getString(path));
SeekableByteChannel sbc =
new EntryOutputChannel(new Entry(e, Entry.NEW));
if (options.contains(APPEND)) {
try (InputStream is = getInputStream(e)) { // copyover
byte[] buf = new byte[8192];
ByteBuffer bb = ByteBuffer.wrap(buf);
int n;
while ((n = is.read(buf)) != -1) {
bb.position(0);
bb.limit(n);
sbc.write(bb);
}
}
}
return sbc;
}
final long offset = leftover;
return new SeekableByteChannel() {
long written = offset;
public boolean isOpen() {
return wbc.isOpen();
}
if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
throw new NoSuchFileException(getString(path));
checkParents(path);
return new EntryOutputChannel(
new Entry(path, Entry.NEW, false, defaultMethod));
public long position() throws IOException {
return written;
}
public SeekableByteChannel position(long pos)
throws IOException
{
throw new UnsupportedOperationException();
}
public int read(ByteBuffer dst) throws IOException {
throw new UnsupportedOperationException();
}
public SeekableByteChannel truncate(long size)
throws IOException
{
throw new UnsupportedOperationException();
}
public int write(ByteBuffer src) throws IOException {
int n = wbc.write(src);
written += n;
return n;
}
public long size() throws IOException {
return written;
}
public void close() throws IOException {
wbc.close();
}
};
} finally {
endRead();
}
@ -646,51 +665,10 @@ class ZipFileSystem extends FileSystem {
Entry e = getEntry(path);
if (e == null || e.isDir())
throw new NoSuchFileException(getString(path));
final ReadableByteChannel rbc =
Channels.newChannel(getInputStream(e));
final long size = e.size;
return new SeekableByteChannel() {
long read = 0;
public boolean isOpen() {
return rbc.isOpen();
}
public long position() throws IOException {
return read;
}
public SeekableByteChannel position(long pos)
throws IOException
{
throw new UnsupportedOperationException();
}
public int read(ByteBuffer dst) throws IOException {
int n = rbc.read(dst);
if (n > 0) {
read += n;
}
return n;
}
public SeekableByteChannel truncate(long size)
throws IOException
{
throw new NonWritableChannelException();
}
public int write (ByteBuffer src) throws IOException {
throw new NonWritableChannelException();
}
public long size() throws IOException {
return size;
}
public void close() throws IOException {
rbc.close();
}
};
try (InputStream is = getInputStream(e)) {
// TBD: if (e.size < NNNNN);
return new ByteArrayChannel(is.readAllBytes(), true);
}
} finally {
endRead();
}
@ -846,10 +824,6 @@ class ZipFileSystem extends FileSystem {
private Set<InputStream> streams =
Collections.synchronizedSet(new HashSet<InputStream>());
// the ex-channel and ex-path that need to close when their outstanding
// input streams are all closed by the obtainers.
private Set<ExChannelCloser> exChClosers = new HashSet<>();
private Set<Path> tmppaths = Collections.synchronizedSet(new HashSet<Path>());
private Path getTempPathForEntry(byte[] path) throws IOException {
Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
@ -1087,8 +1061,9 @@ class ZipFileSystem extends FileSystem {
if (pos + CENHDR + nlen > limit) {
zerror("invalid CEN header (bad header size)");
}
IndexNode inode = new IndexNode(cen, nlen, pos);
IndexNode inode = new IndexNode(cen, pos, nlen);
inodes.put(inode, inode);
// skip ext and comment
pos += (CENHDR + nlen + elen + clen);
}
@ -1205,19 +1180,37 @@ class ZipFileSystem extends FileSystem {
return written;
}
// sync the zip file system, if there is any udpate
private void sync() throws IOException {
// System.out.printf("->sync(%s) starting....!%n", toString());
// check ex-closer
if (!exChClosers.isEmpty()) {
for (ExChannelCloser ecc : exChClosers) {
if (ecc.streams.isEmpty()) {
ecc.ch.close();
Files.delete(ecc.path);
exChClosers.remove(ecc);
private long writeEntry(Entry e, OutputStream os, byte[] buf)
throws IOException {
if (e.bytes == null && e.file == null) // dir, 0-length data
return 0;
long written = 0;
try (OutputStream os2 = e.method == METHOD_STORED ?
new EntryOutputStreamCRC32(e, os) : new EntryOutputStreamDef(e, os)) {
if (e.bytes != null) { // in-memory
os2.write(e.bytes, 0, e.bytes.length);
} else if (e.file != null) { // tmp file
if (e.type == Entry.NEW || e.type == Entry.FILECH) {
try (InputStream is = Files.newInputStream(e.file)) {
is.transferTo(os2);
}
}
Files.delete(e.file);
tmppaths.remove(e.file);
}
}
written += e.csize;
if ((e.flag & FLAG_DATADESCR) != 0) {
written += e.writeEXT(os);
}
return written;
}
// sync the zip file system, if there is any udpate
private void sync() throws IOException {
if (!hasUpdate)
return;
Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
@ -1243,34 +1236,7 @@ class ZipFileSystem extends FileSystem {
} else { // NEW, FILECH or CEN
e.locoff = written;
written += e.writeLOC(os); // write loc header
if (e.bytes != null) { // in-memory, deflated
os.write(e.bytes); // already
written += e.bytes.length;
} else if (e.file != null) { // tmp file
try (InputStream is = Files.newInputStream(e.file)) {
int n;
if (e.type == Entry.NEW) { // deflated already
while ((n = is.read(buf)) != -1) {
os.write(buf, 0, n);
written += n;
}
} else if (e.type == Entry.FILECH) {
// the data are not deflated, use ZEOS
try (OutputStream os2 = new EntryOutputStream(e, os)) {
while ((n = is.read(buf)) != -1) {
os2.write(buf, 0, n);
}
}
written += e.csize;
if ((e.flag & FLAG_DATADESCR) != 0)
written += e.writeEXT(os);
}
}
Files.delete(e.file);
tmppaths.remove(e.file);
} else {
// dir, 0-length data
}
written += writeEntry(e, os, buf);
}
elist.add(e);
} catch (IOException x) {
@ -1303,27 +1269,9 @@ class ZipFileSystem extends FileSystem {
end.cenlen = written - end.cenoff;
end.write(os, written, forceEnd64);
}
if (!streams.isEmpty()) {
//
// TBD: ExChannelCloser should not be necessary if we only
// sync when being closed, all streams should have been
// closed already. Keep the logic here for now.
//
// There are outstanding input streams open on existing "ch",
// so, don't close the "cha" and delete the "file for now, let
// the "ex-channel-closer" to handle them
ExChannelCloser ecc = new ExChannelCloser(
createTempFileInSameDirectoryAs(zfpath),
ch,
streams);
Files.move(zfpath, ecc.path, REPLACE_EXISTING);
exChClosers.add(ecc);
streams = Collections.synchronizedSet(new HashSet<InputStream>());
} else {
ch.close();
Files.delete(zfpath);
}
ch.close();
Files.delete(zfpath);
Files.move(tmpFile, zfpath, REPLACE_EXISTING);
hasUpdate = false; // clear
}
@ -1361,16 +1309,6 @@ class ZipFileSystem extends FileSystem {
}
}
private static void copyStream(InputStream is, OutputStream os)
throws IOException
{
byte[] copyBuf = new byte[8192];
int n;
while ((n = is.read(copyBuf)) != -1) {
os.write(copyBuf, 0, n);
}
}
// Returns an out stream for either
// (1) writing the contents of a new entry, if the entry exits, or
// (2) updating/replacing the contents of the specified existing entry.
@ -1379,9 +1317,9 @@ class ZipFileSystem extends FileSystem {
if (e.mtime == -1)
e.mtime = System.currentTimeMillis();
if (e.method == -1)
e.method = METHOD_DEFLATED; // TBD: use default method
// store size, compressed size, and crc-32 in LOC header
e.flag = 0;
e.method = defaultMethod;
// store size, compressed size, and crc-32 in datadescr
e.flag = FLAG_DATADESCR;
if (zc.isUTF8())
e.flag |= FLAG_USE_UTF8;
OutputStream os;
@ -1394,16 +1332,130 @@ class ZipFileSystem extends FileSystem {
return new EntryOutputStream(e, os);
}
private class EntryOutputStream extends FilterOutputStream {
private Entry e;
private long written;
private boolean isClosed;
EntryOutputStream(Entry e, OutputStream os) throws IOException {
super(os);
this.e = Objects.requireNonNull(e, "Zip entry is null");
// this.written = 0;
}
@Override
public synchronized void write(int b) throws IOException {
out.write(b);
written += 1;
}
@Override
public synchronized void write(byte b[], int off, int len)
throws IOException {
out.write(b, off, len);
written += len;
}
@Override
public synchronized void close() throws IOException {
if (isClosed) {
return;
}
isClosed = true;
e.size = written;
if (out instanceof ByteArrayOutputStream)
e.bytes = ((ByteArrayOutputStream)out).toByteArray();
super.close();
update(e);
}
}
// Wrapper output stream class to write out a "stored" entry.
// (1) this class does not close the underlying out stream when
// being closed.
// (2) no need to be "synchronized", only used by sync()
private class EntryOutputStreamCRC32 extends FilterOutputStream {
private Entry e;
private CRC32 crc;
private long written;
private boolean isClosed;
EntryOutputStreamCRC32(Entry e, OutputStream os) throws IOException {
super(os);
this.e = Objects.requireNonNull(e, "Zip entry is null");
this.crc = new CRC32();
}
@Override
public void write(int b) throws IOException {
out.write(b);
crc.update(b);
written += 1;
}
@Override
public void write(byte b[], int off, int len)
throws IOException {
out.write(b, off, len);
crc.update(b, off, len);
written += len;
}
@Override
public void close() throws IOException {
if (isClosed)
return;
isClosed = true;
e.size = e.csize = written;
e.size = crc.getValue();
}
}
// Wrapper output stream class to write out a "deflated" entry.
// (1) this class does not close the underlying out stream when
// being closed.
// (2) no need to be "synchronized", only used by sync()
private class EntryOutputStreamDef extends DeflaterOutputStream {
private CRC32 crc;
private Entry e;
private boolean isClosed;
EntryOutputStreamDef(Entry e, OutputStream os) throws IOException {
super(os, getDeflater());
this.e = Objects.requireNonNull(e, "Zip entry is null");
this.crc = new CRC32();
}
@Override
public void write(byte b[], int off, int len)
throws IOException {
super.write(b, off, len);
crc.update(b, off, len);
}
@Override
public void close() throws IOException {
if (isClosed)
return;
isClosed = true;
finish();
e.size = def.getBytesRead();
e.csize = def.getBytesWritten();
e.crc = crc.getValue();
}
}
private InputStream getInputStream(Entry e)
throws IOException
{
InputStream eis = null;
if (e.type == Entry.NEW) {
// now bytes & file is uncompressed.
if (e.bytes != null)
eis = new ByteArrayInputStream(e.bytes);
return new ByteArrayInputStream(e.bytes);
else if (e.file != null)
eis = Files.newInputStream(e.file);
return Files.newInputStream(e.file);
else
throw new ZipException("update entry data is missing");
} else if (e.type == Entry.FILECH) {
@ -1569,87 +1621,6 @@ class ZipFileSystem extends FileSystem {
}
}
class EntryOutputStream extends DeflaterOutputStream
{
private CRC32 crc;
private Entry e;
private long written;
private boolean isClosed = false;
EntryOutputStream(Entry e, OutputStream os)
throws IOException
{
super(os, getDeflater());
if (e == null)
throw new NullPointerException("Zip entry is null");
this.e = e;
crc = new CRC32();
}
@Override
public synchronized void write(byte b[], int off, int len)
throws IOException
{
if (e.type != Entry.FILECH) // only from sync
ensureOpen();
if (isClosed) {
throw new IOException("Stream closed");
}
if (off < 0 || len < 0 || off > b.length - len) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
switch (e.method) {
case METHOD_DEFLATED:
super.write(b, off, len);
break;
case METHOD_STORED:
written += len;
out.write(b, off, len);
break;
default:
throw new ZipException("invalid compression method");
}
crc.update(b, off, len);
}
@Override
public synchronized void close() throws IOException {
if (isClosed) {
return;
}
isClosed = true;
// TBD ensureOpen();
switch (e.method) {
case METHOD_DEFLATED:
finish();
e.size = def.getBytesRead();
e.csize = def.getBytesWritten();
e.crc = crc.getValue();
break;
case METHOD_STORED:
// we already know that both e.size and e.csize are the same
e.size = e.csize = written;
e.crc = crc.getValue();
break;
default:
throw new ZipException("invalid compression method");
}
//crc.reset();
if (out instanceof ByteArrayOutputStream)
e.bytes = ((ByteArrayOutputStream)out).toByteArray();
if (e.type == Entry.FILECH) {
releaseDeflater(def);
return;
}
super.close();
releaseDeflater(def);
update(e);
}
}
static void zerror(String msg) throws ZipException {
throw new ZipException(msg);
}
@ -1806,7 +1777,7 @@ class ZipFileSystem extends FileSystem {
}
// constructor for cenInit() (1) remove tailing '/' (2) pad leading '/'
IndexNode(byte[] cen, int nlen, int pos) {
IndexNode(byte[] cen, int pos, int nlen) {
int noff = pos + CENHDR;
if (cen[noff + nlen - 1] == '/') {
isdir = true;
@ -1902,18 +1873,18 @@ class ZipFileSystem extends FileSystem {
Entry() {}
Entry(byte[] name, boolean isdir) {
Entry(byte[] name, boolean isdir, int method) {
name(name);
this.isdir = isdir;
this.mtime = this.ctime = this.atime = System.currentTimeMillis();
this.crc = 0;
this.size = 0;
this.csize = 0;
this.method = METHOD_DEFLATED;
this.method = method;
}
Entry(byte[] name, int type, boolean isdir) {
this(name, isdir);
Entry(byte[] name, int type, boolean isdir, int method) {
this(name, isdir, method);
this.type = type;
}
@ -1941,9 +1912,8 @@ class ZipFileSystem extends FileSystem {
}
Entry (byte[] name, Path file, int type) {
this(name, type, false);
this(name, type, false, METHOD_STORED);
this.file = file;
this.method = METHOD_STORED;
}
int version() throws ZipException {
@ -2422,6 +2392,7 @@ class ZipFileSystem extends FileSystem {
public String toString() {
StringBuilder sb = new StringBuilder(1024);
Formatter fm = new Formatter(sb);
fm.format(" name : %s%n", new String(name));
fm.format(" creationTime : %tc%n", creationTime().toMillis());
fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis());
fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
@ -2439,20 +2410,6 @@ class ZipFileSystem extends FileSystem {
}
}
private static class ExChannelCloser {
Path path;
SeekableByteChannel ch;
Set<InputStream> streams;
ExChannelCloser(Path path,
SeekableByteChannel ch,
Set<InputStream> streams)
{
this.path = path;
this.ch = ch;
this.streams = streams;
}
}
// ZIP directory has two issues:
// (1) ZIP spec does not require the ZIP file to include
// directory entry

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -124,9 +124,6 @@ public class ZipFileSystemProvider extends FileSystemProvider {
public FileSystem newFileSystem(Path path, Map<String, ?> env)
throws IOException
{
if (path.getFileSystem() != FileSystems.getDefault()) {
throw new UnsupportedOperationException();
}
ensureFile(path);
try {
ZipFileSystem zipfs;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,6 +28,7 @@ import java.io.OutputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream;
@ -58,8 +59,10 @@ import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import static java.nio.file.StandardOpenOption.*;
import static java.nio.file.StandardCopyOption.*;
@ -70,7 +73,7 @@ import static java.nio.file.StandardCopyOption.*;
* @test
* @bug 6990846 7009092 7009085 7015391 7014948 7005986 7017840 7007596
* 7157656 8002390 7012868 7012856 8015728 8038500 8040059 8069211
* 8131067
* 8131067 8034802
* @summary Test Zip filesystem provider
* @modules jdk.zipfs
* @run main ZipFSTester
@ -80,23 +83,27 @@ import static java.nio.file.StandardCopyOption.*;
public class ZipFSTester {
public static void main(String[] args) throws Exception {
// create JAR file for test, actual contents don't matter
Path jarFile = Utils.createJarFile("tester.jar",
"META-INF/MANIFEST.MF",
"dir1/foo",
"dir2/bar");
"dir2/bar",
"dir1/dir3/fooo");
try (FileSystem fs = newZipFileSystem(jarFile, Collections.emptyMap())) {
test0(fs);
test1(fs);
test2(fs); // more tests
}
testStreamChannel();
testTime(jarFile);
test8069211();
test8131067();
}
private static Random rdm = new Random();
static void test0(FileSystem fs)
throws Exception
{
@ -121,13 +128,28 @@ public class ZipFSTester {
static void test1(FileSystem fs0)
throws Exception
{
Random rdm = new Random();
// clone a fs and test on it
// prepare a src for testing
Path src = getTempPath();
String tmpName = src.toString();
try (OutputStream os = Files.newOutputStream(src)) {
byte[] bits = new byte[12345];
rdm.nextBytes(bits);
os.write(bits);
}
// clone a fs from fs0 and test on it
Path tmpfsPath = getTempPath();
Map<String, Object> env = new HashMap<String, Object>();
env.put("create", "true");
try (FileSystem copy = newZipFileSystem(tmpfsPath, env)) {
z2zcopy(fs0, copy, "/", 0);
// copy the test jar itself in
Files.copy(Paths.get(fs0.toString()), copy.getPath("/foo.jar"));
Path zpath = copy.getPath("/foo.jar");
try (FileSystem zzfs = FileSystems.newFileSystem(zpath, null)) {
Files.copy(src, zzfs.getPath("/srcInjarjar"));
}
}
try (FileSystem fs = newZipFileSystem(tmpfsPath, new HashMap<String, Object>())) {
@ -142,15 +164,6 @@ public class ZipFSTester {
throw new RuntimeException("newFileSystem(URI...) does not throw exception");
} catch (FileSystemAlreadyExistsException fsaee) {}
// prepare a src
Path src = getTempPath();
String tmpName = src.toString();
OutputStream os = Files.newOutputStream(src);
byte[] bits = new byte[12345];
rdm.nextBytes(bits);
os.write(bits);
os.close();
try {
provider.newFileSystem(new File(System.getProperty("test.src", ".")).toPath(),
new HashMap<String, Object>());
@ -162,6 +175,8 @@ public class ZipFSTester {
throw new RuntimeException("newFileSystem() opens a non-zip file as zipfs");
} catch (UnsupportedOperationException uoe) {}
// walk
walk(fs.getPath("/"));
// copyin
Path dst = getPathWithParents(fs, tmpName);
@ -236,10 +251,29 @@ public class ZipFSTester {
// test channels
channel(fs, dst);
Files.delete(dst);
Files.delete(src);
// test foo.jar in jar/zipfs #8034802
Path jpath = fs.getPath("/foo.jar");
System.out.println("walking: " + jpath);
try (FileSystem zzfs = FileSystems.newFileSystem(jpath, null)) {
walk(zzfs.getPath("/"));
// foojar:/srcInjarjar
checkEqual(src, zzfs.getPath("/srcInjarjar"));
dst = getPathWithParents(zzfs, tmpName);
fchCopy(src, dst);
checkEqual(src, dst);
tmp = Paths.get(tmpName + "_Tmp");
fchCopy(dst, tmp); // out
checkEqual(src, tmp);
Files.delete(tmp);
channel(zzfs, dst);
Files.delete(dst);
}
} finally {
if (Files.exists(tmpfsPath))
Files.delete(tmpfsPath);
Files.deleteIfExists(tmpfsPath);
Files.deleteIfExists(src);
}
}
@ -383,6 +417,158 @@ public class ZipFSTester {
Files.delete(fs3Path);
}
static final int METHOD_STORED = 0;
static final int METHOD_DEFLATED = 8;
static Object[][] getEntries() {
Object[][] entries = new Object[10 + rdm.nextInt(20)][3];
for (int i = 0; i < entries.length; i++) {
entries[i][0] = "entries" + i;
entries[i][1] = rdm.nextInt(10) % 2 == 0 ?
METHOD_STORED : METHOD_DEFLATED;
entries[i][2] = new byte[rdm.nextInt(8192)];
rdm.nextBytes((byte[])entries[i][2]);
}
return entries;
}
// check the content of read from zipfs is equal to the "bytes"
private static void checkRead(Path path, byte[] expected) throws IOException {
//streams
try (InputStream is = Files.newInputStream(path)) {
if (!Arrays.equals(is.readAllBytes(), expected)) {
System.out.printf(" newInputStream <%s> failed...%n", path.toString());
throw new RuntimeException("CHECK FAILED!");
}
}
// channels -- via sun.nio.ch.ChannelInputStream
try (SeekableByteChannel sbc = Files.newByteChannel(path);
InputStream is = Channels.newInputStream(sbc)) {
// check all bytes match
if (!Arrays.equals(is.readAllBytes(), expected)) {
System.out.printf(" newByteChannel <%s> failed...%n", path.toString());
throw new RuntimeException("CHECK FAILED!");
}
// Check if read position is at the end
if (sbc.position() != expected.length) {
System.out.printf("pos [%s]: size=%d, position=%d%n",
path.toString(), expected.length, sbc.position());
throw new RuntimeException("CHECK FAILED!");
}
// Check position(x) + read() at the random/specific pos/len
byte[] buf = new byte[1024];
ByteBuffer bb = ByteBuffer.wrap(buf);
for (int i = 0; i < 10; i++) {
int pos = rdm.nextInt((int)sbc.size());
int len = rdm.nextInt(Math.min(buf.length, expected.length - pos));
// System.out.printf(" --> %d, %d%n", pos, len);
bb.position(0).limit(len); // bb.flip().limit(len);
if (sbc.position(pos).position() != pos ||
sbc.read(bb) != len ||
!Arrays.equals(buf, 0, bb.position(), expected, pos, pos + len)) {
System.out.printf("read()/position() failed%n");
}
}
} catch (IOException x) {
x.printStackTrace();
throw new RuntimeException("CHECK FAILED!");
}
}
// test entry stream/channel reading
static void testStreamChannel() throws Exception {
Path zpath = getTempPath();
try {
var crc = new CRC32();
Object[][] entries = getEntries();
// [1] create zip via ZipOutputStream
try (var os = Files.newOutputStream(zpath);
var zos = new ZipOutputStream(os)) {
for (Object[] entry : entries) {
var ze = new ZipEntry((String)entry[0]);
int method = (int)entry[1];
byte[] bytes = (byte[])entry[2];
if (method == METHOD_STORED) {
ze.setSize(bytes.length);
crc.reset();
crc.update(bytes);
ze.setCrc(crc.getValue());
}
ze.setMethod(method);
zos.putNextEntry(ze);
zos.write(bytes);
zos.closeEntry();
}
}
try (var zfs = newZipFileSystem(zpath, Collections.emptyMap())) {
for (Object[] e : entries) {
Path path = zfs.getPath((String)e[0]);
int method = (int)e[1];
byte[] bytes = (byte[])e[2];
// System.out.printf("checking read [%s, %d, %d]%n",
// path.toString(), bytes.length, method);
checkRead(path, bytes);
}
}
Files.deleteIfExists(zpath);
// [2] create zip via zfs.newByteChannel
try (var zfs = newZipFileSystem(zpath, Map.of("create", "true"))) {
for (Object[] e : entries) {
// tbd: method is not used
try (var sbc = Files.newByteChannel(zfs.getPath((String)e[0]),
CREATE_NEW, WRITE)) {
sbc.write(ByteBuffer.wrap((byte[])e[2]));
}
}
}
try (var zfs = newZipFileSystem(zpath, Collections.emptyMap())) {
for (Object[] e : entries) {
checkRead(zfs.getPath((String)e[0]), (byte[])e[2]);
}
}
Files.deleteIfExists(zpath);
// [3] create zip via Files.write()/newoutputStream/
try (var zfs = newZipFileSystem(zpath, Map.of("create", "true"))) {
for (Object[] e : entries) {
Files.write(zfs.getPath((String)e[0]), (byte[])e[2]);
}
}
try (var zfs = newZipFileSystem(zpath, Collections.emptyMap())) {
for (Object[] e : entries) {
checkRead(zfs.getPath((String)e[0]), (byte[])e[2]);
}
}
Files.deleteIfExists(zpath);
// [4] create zip via zfs.newByteChannel, with "method_stored"
try (var zfs = newZipFileSystem(zpath,
Map.of("create", true, "noCompression", true))) {
for (Object[] e : entries) {
try (var sbc = Files.newByteChannel(zfs.getPath((String)e[0]),
CREATE_NEW, WRITE)) {
sbc.write(ByteBuffer.wrap((byte[])e[2]));
}
}
}
try (var zfs = newZipFileSystem(zpath, Collections.emptyMap())) {
for (Object[] e : entries) {
checkRead(zfs.getPath((String)e[0]), (byte[])e[2]);
}
}
Files.deleteIfExists(zpath);
} finally {
Files.deleteIfExists(zpath);
}
}
// test file stamp
static void testTime(Path src) throws Exception {
BasicFileAttributes attrs = Files
@ -392,34 +578,35 @@ public class ZipFSTester {
Map<String, Object> env = new HashMap<String, Object>();
env.put("create", "true");
Path fsPath = getTempPath();
FileSystem fs = newZipFileSystem(fsPath, env);
try (FileSystem fs = newZipFileSystem(fsPath, env)) {
System.out.println("test copy with timestamps...");
// copyin
Path dst = getPathWithParents(fs, "me");
Files.copy(src, dst, COPY_ATTRIBUTES);
checkEqual(src, dst);
System.out.println("mtime: " + attrs.lastModifiedTime());
System.out.println("ctime: " + attrs.creationTime());
System.out.println("atime: " + attrs.lastAccessTime());
System.out.println(" ==============>");
BasicFileAttributes dstAttrs = Files
.getFileAttributeView(dst, BasicFileAttributeView.class)
.readAttributes();
System.out.println("mtime: " + dstAttrs.lastModifiedTime());
System.out.println("ctime: " + dstAttrs.creationTime());
System.out.println("atime: " + dstAttrs.lastAccessTime());
System.out.println("test copy with timestamps...");
// copyin
Path dst = getPathWithParents(fs, "me");
Files.copy(src, dst, COPY_ATTRIBUTES);
checkEqual(src, dst);
System.out.println("mtime: " + attrs.lastModifiedTime());
System.out.println("ctime: " + attrs.creationTime());
System.out.println("atime: " + attrs.lastAccessTime());
System.out.println(" ==============>");
BasicFileAttributes dstAttrs = Files
.getFileAttributeView(dst, BasicFileAttributeView.class)
.readAttributes();
System.out.println("mtime: " + dstAttrs.lastModifiedTime());
System.out.println("ctime: " + dstAttrs.creationTime());
System.out.println("atime: " + dstAttrs.lastAccessTime());
// 1-second granularity
if (attrs.lastModifiedTime().to(TimeUnit.SECONDS) !=
dstAttrs.lastModifiedTime().to(TimeUnit.SECONDS) ||
attrs.lastAccessTime().to(TimeUnit.SECONDS) !=
dstAttrs.lastAccessTime().to(TimeUnit.SECONDS) ||
attrs.creationTime().to(TimeUnit.SECONDS) !=
dstAttrs.creationTime().to(TimeUnit.SECONDS)) {
throw new RuntimeException("Timestamp Copy Failed!");
// 1-second granularity
if (attrs.lastModifiedTime().to(TimeUnit.SECONDS) !=
dstAttrs.lastModifiedTime().to(TimeUnit.SECONDS) ||
attrs.lastAccessTime().to(TimeUnit.SECONDS) !=
dstAttrs.lastAccessTime().to(TimeUnit.SECONDS) ||
attrs.creationTime().to(TimeUnit.SECONDS) !=
dstAttrs.creationTime().to(TimeUnit.SECONDS)) {
throw new RuntimeException("Timestamp Copy Failed!");
}
} finally {
Files.delete(fsPath);
}
Files.delete(fsPath);
}
static void test8069211() throws Exception {
@ -624,8 +811,8 @@ public class ZipFSTester {
// check the content of two paths are equal
private static void checkEqual(Path src, Path dst) throws IOException
{
//System.out.printf("checking <%s> vs <%s>...%n",
// src.toString(), dst.toString());
System.out.printf("checking <%s> vs <%s>...%n",
src.toString(), dst.toString());
//streams
byte[] bufSrc = new byte[8192];
@ -702,6 +889,21 @@ public class ZipFSTester {
chDst.toString(), chDst.size(), chDst.position());
throw new RuntimeException("CHECK FAILED!");
}
// Check position(x) + read() at the specific pos/len
for (int i = 0; i < 10; i++) {
int pos = rdm.nextInt((int)chSrc.size());
int limit = rdm.nextInt(1024);
if (chSrc.position(pos).position() != chDst.position(pos).position()) {
System.out.printf("dst/src.position(pos failed%n");
}
bbSrc.clear().limit(limit);
bbDst.clear().limit(limit);
if (chSrc.read(bbSrc) != chDst.read(bbDst) ||
!bbSrc.flip().equals(bbDst.flip())) {
System.out.printf("dst/src.read() failed%n");
}
}
} catch (IOException x) {
x.printStackTrace();
}