1 /*
2 * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.nio.zipfs;
27
28 import java.io.BufferedOutputStream;
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.EOFException;
32 import java.io.File;
33 import java.io.FilterOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.OutputStream;
37 import java.nio.ByteBuffer;
38 import java.nio.MappedByteBuffer;
39 import java.nio.channels.*;
40 import java.nio.file.*;
41 import java.nio.file.attribute.*;
42 import java.nio.file.spi.*;
43 import java.security.AccessController;
44 import java.security.PrivilegedAction;
45 import java.security.PrivilegedActionException;
46 import java.security.PrivilegedExceptionAction;
47 import java.util.*;
48 import java.util.concurrent.locks.ReadWriteLock;
49 import java.util.concurrent.locks.ReentrantReadWriteLock;
50 import java.util.regex.Pattern;
51 import java.util.zip.CRC32;
52 import java.util.zip.Inflater;
53 import java.util.zip.Deflater;
54 import java.util.zip.InflaterInputStream;
55 import java.util.zip.DeflaterOutputStream;
56 import java.util.zip.ZipException;
57 import static java.lang.Boolean.*;
58 import static jdk.nio.zipfs.ZipConstants.*;
59 import static jdk.nio.zipfs.ZipUtils.*;
60 import static java.nio.file.StandardOpenOption.*;
61 import static java.nio.file.StandardCopyOption.*;
62
63 /**
64 * A FileSystem built on a zip file
65 *
66 * @author Xueming Shen
67 */
68
69 class ZipFileSystem extends FileSystem {
70
71 private final ZipFileSystemProvider provider;
72 private final Path zfpath;
73 final ZipCoder zc;
74 private final ZipPath rootdir;
75 private boolean readOnly = false; // readonly file system
76
77 // configurable by env map
78 private final boolean noExtt; // see readExtra()
79 private final boolean useTempFile; // use a temp file for newOS, default
80 // is to use BAOS for better performance
81 private static final boolean isWindows = AccessController.doPrivileged(
82 (PrivilegedAction<Boolean>) () -> System.getProperty("os.name")
83 .startsWith("Windows"));
84 private final boolean forceEnd64;
85 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
86 // METHOD_DEFLATED otherwise
87
88 ZipFileSystem(ZipFileSystemProvider provider,
89 Path zfpath,
90 Map<String, ?> env) throws IOException
91 {
92 // default encoding for name/comment
93 String nameEncoding = env.containsKey("encoding") ?
94 (String)env.get("encoding") : "UTF-8";
95 this.noExtt = "false".equals(env.get("zipinfo-time"));
96 this.useTempFile = isTrue(env, "useTempFile");
97 this.forceEnd64 = isTrue(env, "forceZIP64End");
98 this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;
99 if (Files.notExists(zfpath)) {
100 // create a new zip if not exists
101 if (isTrue(env, "create")) {
102 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
103 new END().write(os, 0, forceEnd64);
104 }
105 } else {
106 throw new FileSystemNotFoundException(zfpath.toString());
107 }
108 }
109 // sm and existence check
110 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
252 return new PathMatcher() {
253 @Override
254 public boolean matches(Path path) {
255 return pattern.matcher(path.toString()).matches();
256 }
257 };
258 }
259
260 @Override
261 public void close() throws IOException {
262 beginWrite();
263 try {
264 if (!isOpen)
265 return;
266 isOpen = false; // set closed
267 } finally {
268 endWrite();
269 }
270 if (!streams.isEmpty()) { // unlock and close all remaining streams
271 Set<InputStream> copy = new HashSet<>(streams);
272 for (InputStream is: copy)
273 is.close();
274 }
275 beginWrite(); // lock and sync
276 try {
277 AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
278 sync(); return null;
279 });
280 ch.close(); // close the ch just in case no update
281 // and sync didn't close the ch
282 } catch (PrivilegedActionException e) {
283 throw (IOException)e.getException();
284 } finally {
285 endWrite();
286 }
287
288 synchronized (inflaters) {
289 for (Inflater inf : inflaters)
290 inf.end();
291 }
292 synchronized (deflaters) {
293 for (Deflater def : deflaters)
294 def.end();
295 }
296
297 IOException ioe = null;
298 synchronized (tmppaths) {
299 for (Path p: tmppaths) {
300 try {
301 AccessController.doPrivileged(
302 (PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
303 } catch (PrivilegedActionException e) {
304 IOException x = (IOException)e.getException();
305 if (ioe == null)
306 ioe = x;
307 else
308 ioe.addSuppressed(x);
309 }
310 }
311 }
312 provider.removeFileSystem(zfpath, this);
313 if (ioe != null)
314 throw ioe;
315 }
316
317 ZipFileAttributes getFileAttributes(byte[] path)
318 throws IOException
319 {
504 if (!hasCopyAttrs)
505 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
506 update(u);
507 if (deletesrc)
508 updateDelete(eSrc);
509 } finally {
510 endWrite();
511 }
512 }
513
514 // Returns an output stream for writing the contents into the specified
515 // entry.
516 OutputStream newOutputStream(byte[] path, OpenOption... options)
517 throws IOException
518 {
519 checkWritable();
520 boolean hasCreateNew = false;
521 boolean hasCreate = false;
522 boolean hasAppend = false;
523 boolean hasTruncate = false;
524 for (OpenOption opt: options) {
525 if (opt == READ)
526 throw new IllegalArgumentException("READ not allowed");
527 if (opt == CREATE_NEW)
528 hasCreateNew = true;
529 if (opt == CREATE)
530 hasCreate = true;
531 if (opt == APPEND)
532 hasAppend = true;
533 if (opt == TRUNCATE_EXISTING)
534 hasTruncate = true;
535 }
536 if (hasAppend && hasTruncate)
537 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
538 beginRead(); // only need a readlock, the "update()" will
539 try { // try to obtain a writelock when the os is
540 ensureOpen(); // being closed.
541 Entry e = getEntry(path);
542 if (e != null) {
543 if (e.isDir() || hasCreateNew)
544 throw new FileAlreadyExistsException(getString(path));
1460
1461 private InputStream getInputStream(Entry e)
1462 throws IOException
1463 {
1464 InputStream eis = null;
1465
1466 if (e.type == Entry.NEW) {
1467 // now bytes & file is uncompressed.
1468 if (e.bytes != null)
1469 return new ByteArrayInputStream(e.bytes);
1470 else if (e.file != null)
1471 return Files.newInputStream(e.file);
1472 else
1473 throw new ZipException("update entry data is missing");
1474 } else if (e.type == Entry.FILECH) {
1475 // FILECH result is un-compressed.
1476 eis = Files.newInputStream(e.file);
1477 // TBD: wrap to hook close()
1478 // streams.add(eis);
1479 return eis;
1480 } else { // untouced CEN or COPY
1481 eis = new EntryInputStream(e, ch);
1482 }
1483 if (e.method == METHOD_DEFLATED) {
1484 // MORE: Compute good size for inflater stream:
1485 long bufSize = e.size + 2; // Inflater likes a bit of slack
1486 if (bufSize > 65536)
1487 bufSize = 8192;
1488 final long size = e.size;
1489 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1490 private boolean isClosed = false;
1491 public void close() throws IOException {
1492 if (!isClosed) {
1493 releaseInflater(inf);
1494 this.in.close();
1495 isClosed = true;
1496 streams.remove(this);
1497 }
1498 }
1499 // Override fill() method to provide an extra "dummy" byte
1500 // at the end of the input stream. This is required when
1522 return avail > (long) Integer.MAX_VALUE ?
1523 Integer.MAX_VALUE : (int) avail;
1524 }
1525 };
1526 } else if (e.method == METHOD_STORED) {
1527 // TBD: wrap/ it does not seem necessary
1528 } else {
1529 throw new ZipException("invalid compression method");
1530 }
1531 streams.add(eis);
1532 return eis;
1533 }
1534
1535 // Inner class implementing the input stream used to read
1536 // a (possibly compressed) zip file entry.
1537 private class EntryInputStream extends InputStream {
1538 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1539 // point to a new channel after sync()
1540 private long pos; // current position within entry data
1541 protected long rem; // number of remaining bytes within entry
1542 protected final long size; // uncompressed size of this entry
1543
1544 EntryInputStream(Entry e, SeekableByteChannel zfch)
1545 throws IOException
1546 {
1547 this.zfch = zfch;
1548 rem = e.csize;
1549 size = e.size;
1550 pos = e.locoff;
1551 if (pos == -1) {
1552 Entry e2 = getEntry(e.name);
1553 if (e2 == null) {
1554 throw new ZipException("invalid loc for entry <" + e.name + ">");
1555 }
1556 pos = e2.locoff;
1557 }
1558 pos = -pos; // lazy initialize the real data offset
1559 }
1560
1561 public int read(byte b[], int off, int len) throws IOException {
1562 ensureOpen();
1563 initDataPos();
1564 if (rem == 0) {
1565 return -1;
1566 }
1567 if (len <= 0) {
1568 return 0;
1569 }
1596 return -1;
1597 }
1598 }
1599
1600 public long skip(long n) throws IOException {
1601 ensureOpen();
1602 if (n > rem)
1603 n = rem;
1604 pos += n;
1605 rem -= n;
1606 if (rem == 0) {
1607 close();
1608 }
1609 return n;
1610 }
1611
1612 public int available() {
1613 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1614 }
1615
1616 public long size() {
1617 return size;
1618 }
1619
1620 public void close() {
1621 rem = 0;
1622 streams.remove(this);
1623 }
1624
1625 private void initDataPos() throws IOException {
1626 if (pos <= 0) {
1627 pos = -pos + locpos;
1628 byte[] buf = new byte[LOCHDR];
1629 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1630 throw new ZipException("invalid loc " + pos + " for entry reading");
1631 }
1632 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1633 }
1634 }
1635 }
1636
1637 static void zerror(String msg) throws ZipException {
1638 throw new ZipException(msg);
1639 }
1655 return new Inflater(true);
1656 }
1657 }
1658 }
1659
1660 // Releases the specified inflater to the list of available inflaters.
1661 private void releaseInflater(Inflater inf) {
1662 synchronized (inflaters) {
1663 if (inflaters.size() < MAX_FLATER) {
1664 inf.reset();
1665 inflaters.add(inf);
1666 } else {
1667 inf.end();
1668 }
1669 }
1670 }
1671
1672 // List of available Deflater objects for compression
1673 private final List<Deflater> deflaters = new ArrayList<>();
1674
1675 // Gets an deflater from the list of available deflaters or allocates
1676 // a new one.
1677 private Deflater getDeflater() {
1678 synchronized (deflaters) {
1679 int size = deflaters.size();
1680 if (size > 0) {
1681 Deflater def = deflaters.remove(size - 1);
1682 return def;
1683 } else {
1684 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1685 }
1686 }
1687 }
1688
1689 // Releases the specified inflater to the list of available inflaters.
1690 private void releaseDeflater(Deflater def) {
1691 synchronized (deflaters) {
1692 if (inflaters.size() < MAX_FLATER) {
1693 def.reset();
1694 deflaters.add(def);
1695 } else {
1696 def.end();
1697 }
1698 }
1699 }
1700
1701 // End of central directory record
1702 static class END {
1703 // these 2 fields are not used by anyone and write() uses "0"
1704 // int disknum;
1705 // int sdisknum;
1706 int endsub; // endsub
1707 int centot; // 4 bytes
1708 long cenlen; // 4 bytes
1709 long cenoff; // 4 bytes
1710 int comlen; // comment length
1711 byte[] comment;
1712
1713 /* members of Zip64 end of central directory locator */
1714 // int diskNum;
1715 long endpos;
1716 // int disktot;
1717
1718 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1719 boolean hasZip64 = forceEnd64; // false;
1720 long xlen = cenlen;
1968 attrsEx = CENATX(cen, pos);
1969 */
1970 locoff = CENOFF(cen, pos);
1971 pos += CENHDR;
1972 this.name = inode.name;
1973 this.isdir = inode.isdir;
1974 this.hashcode = inode.hashcode;
1975
1976 pos += nlen;
1977 if (elen > 0) {
1978 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1979 pos += elen;
1980 readExtra(zipfs);
1981 }
1982 if (clen > 0) {
1983 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1984 }
1985 return this;
1986 }
1987
1988 int writeCEN(OutputStream os) throws IOException
1989 {
1990 int written = CENHDR;
1991 int version0 = version();
1992 long csize0 = csize;
1993 long size0 = size;
1994 long locoff0 = locoff;
1995 int elen64 = 0; // extra for ZIP64
1996 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
1997 int elenEXTT = 0; // extra for Extended Timestamp
1998 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
1999
2000 byte[] zname = isdir ? toDirectoryPath(name) : name;
2001
2002 // confirm size/length
2003 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
2004 int elen = (extra != null) ? extra.length : 0;
2005 int eoff = 0;
2006 int clen = (comment != null) ? comment.length : 0;
2007 if (csize >= ZIP64_MINVAL) {
2008 csize0 = ZIP64_MINVAL;
2009 elen64 += 8; // csize(8)
2010 }
2085 if (elenEXTT != 0) {
2086 writeShort(os, EXTID_EXTT);
2087 writeShort(os, elenEXTT - 4);
2088 if (ctime == -1)
2089 os.write(0x3); // mtime and atime
2090 else
2091 os.write(0x7); // mtime, atime and ctime
2092 writeInt(os, javaToUnixTime(mtime));
2093 }
2094 if (extra != null) // whatever not recognized
2095 writeBytes(os, extra);
2096 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2097 writeBytes(os, comment);
2098 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2099 }
2100
2101 ///////////////////// LOC //////////////////////
2102
2103 int writeLOC(OutputStream os) throws IOException {
2104 writeInt(os, LOCSIG); // LOC header signature
2105 int version = version();
2106
2107 byte[] zname = isdir ? toDirectoryPath(name) : name;
2108 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2109 int elen = (extra != null) ? extra.length : 0;
2110 boolean foundExtraTime = false; // if extra timestamp present
2111 int eoff = 0;
2112 int elen64 = 0;
2113 int elenEXTT = 0;
2114 int elenNTFS = 0;
2115 if ((flag & FLAG_DATADESCR) != 0) {
2116 writeShort(os, version()); // version needed to extract
2117 writeShort(os, flag); // general purpose bit flag
2118 writeShort(os, method); // compression method
2119 // last modification time
2120 writeInt(os, (int)javaToDosTime(mtime));
2121 // store size, uncompressed size, and crc-32 in data descriptor
2122 // immediately following compressed entry data
2123 writeInt(os, 0);
2124 writeInt(os, 0);
2125 writeInt(os, 0);
2126 } else {
2413 fm.format(" isDirectory : %b%n", isDirectory());
2414 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2415 fm.format(" isOther : %b%n", isOther());
2416 fm.format(" fileKey : %s%n", fileKey());
2417 fm.format(" size : %d%n", size());
2418 fm.format(" compressedSize : %d%n", compressedSize());
2419 fm.format(" crc : %x%n", crc());
2420 fm.format(" method : %d%n", method());
2421 fm.close();
2422 return sb.toString();
2423 }
2424 }
2425
2426 // ZIP directory has two issues:
2427 // (1) ZIP spec does not require the ZIP file to include
2428 // directory entry
2429 // (2) all entries are not stored/organized in a "tree"
2430 // structure.
2431 // A possible solution is to build the node tree ourself as
2432 // implemented below.
2433 private IndexNode root;
2434
2435 // default time stamp for pseudo entries
2436 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2437
2438 private void removeFromTree(IndexNode inode) {
2439 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2440 IndexNode child = parent.child;
2441 if (child.equals(inode)) {
2442 parent.child = child.sibling;
2443 } else {
2444 IndexNode last = child;
2445 while ((child = child.sibling) != null) {
2446 if (child.equals(inode)) {
2447 last.sibling = child.sibling;
2448 break;
2449 } else {
2450 last = child;
2451 }
2452 }
2453 }
|
1 /*
2 * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.nio.zipfs;
27
28 import static java.lang.Boolean.TRUE;
29 import static jdk.nio.zipfs.ZipConstants.*;
30 import static jdk.nio.zipfs.ZipUtils.*;
31 import static java.nio.file.StandardOpenOption.*;
32 import static java.nio.file.StandardCopyOption.*;
33
34 import java.io.BufferedOutputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.EOFException;
38 import java.io.FilterOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.nio.ByteBuffer;
43 import java.nio.MappedByteBuffer;
44 import java.nio.channels.FileChannel;
45 import java.nio.channels.FileLock;
46 import java.nio.channels.ReadableByteChannel;
47 import java.nio.channels.SeekableByteChannel;
48 import java.nio.channels.WritableByteChannel;
49 import java.nio.file.*;
50 import java.nio.file.attribute.FileAttribute;
51 import java.nio.file.attribute.FileTime;
52 import java.nio.file.attribute.UserPrincipalLookupService;
53 import java.nio.file.spi.FileSystemProvider;
54 import java.security.AccessController;
55 import java.security.PrivilegedAction;
56 import java.security.PrivilegedActionException;
57 import java.security.PrivilegedExceptionAction;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.Formatter;
62 import java.util.HashSet;
63 import java.util.Iterator;
64 import java.util.LinkedHashMap;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.Set;
69 import java.util.concurrent.locks.ReadWriteLock;
70 import java.util.concurrent.locks.ReentrantReadWriteLock;
71 import java.util.regex.Pattern;
72 import java.util.zip.CRC32;
73 import java.util.zip.Deflater;
74 import java.util.zip.DeflaterOutputStream;
75 import java.util.zip.Inflater;
76 import java.util.zip.InflaterInputStream;
77 import java.util.zip.ZipException;
78
79 /**
80 * A FileSystem built on a zip file
81 *
82 * @author Xueming Shen
83 */
84 class ZipFileSystem extends FileSystem {
85 private final ZipFileSystemProvider provider;
86 private final Path zfpath;
87 final ZipCoder zc;
88 private final ZipPath rootdir;
89 private boolean readOnly = false; // readonly file system
90
91 // configurable by env map
92 private final boolean noExtt; // see readExtra()
93 private final boolean useTempFile; // use a temp file for newOS, default
94 // is to use BAOS for better performance
95 private static final boolean isWindows = AccessController.doPrivileged(
96 (PrivilegedAction<Boolean>)() -> System.getProperty("os.name")
97 .startsWith("Windows"));
98 private final boolean forceEnd64;
99 private final int defaultMethod; // METHOD_STORED if "noCompression=true"
100 // METHOD_DEFLATED otherwise
101
102 ZipFileSystem(ZipFileSystemProvider provider,
103 Path zfpath,
104 Map<String, ?> env) throws IOException
105 {
106 // default encoding for name/comment
107 String nameEncoding = env.containsKey("encoding") ?
108 (String)env.get("encoding") : "UTF-8";
109 this.noExtt = "false".equals(env.get("zipinfo-time"));
110 this.useTempFile = isTrue(env, "useTempFile");
111 this.forceEnd64 = isTrue(env, "forceZIP64End");
112 this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;
113 if (Files.notExists(zfpath)) {
114 // create a new zip if not exists
115 if (isTrue(env, "create")) {
116 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
117 new END().write(os, 0, forceEnd64);
118 }
119 } else {
120 throw new FileSystemNotFoundException(zfpath.toString());
121 }
122 }
123 // sm and existence check
124 zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
266 return new PathMatcher() {
267 @Override
268 public boolean matches(Path path) {
269 return pattern.matcher(path.toString()).matches();
270 }
271 };
272 }
273
274 @Override
275 public void close() throws IOException {
276 beginWrite();
277 try {
278 if (!isOpen)
279 return;
280 isOpen = false; // set closed
281 } finally {
282 endWrite();
283 }
284 if (!streams.isEmpty()) { // unlock and close all remaining streams
285 Set<InputStream> copy = new HashSet<>(streams);
286 for (InputStream is : copy)
287 is.close();
288 }
289 beginWrite(); // lock and sync
290 try {
291 AccessController.doPrivileged((PrivilegedExceptionAction<Void>)() -> {
292 sync(); return null;
293 });
294 ch.close(); // close the ch just in case no update
295 // and sync didn't close the ch
296 } catch (PrivilegedActionException e) {
297 throw (IOException)e.getException();
298 } finally {
299 endWrite();
300 }
301
302 synchronized (inflaters) {
303 for (Inflater inf : inflaters)
304 inf.end();
305 }
306 synchronized (deflaters) {
307 for (Deflater def : deflaters)
308 def.end();
309 }
310
311 IOException ioe = null;
312 synchronized (tmppaths) {
313 for (Path p : tmppaths) {
314 try {
315 AccessController.doPrivileged(
316 (PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
317 } catch (PrivilegedActionException e) {
318 IOException x = (IOException)e.getException();
319 if (ioe == null)
320 ioe = x;
321 else
322 ioe.addSuppressed(x);
323 }
324 }
325 }
326 provider.removeFileSystem(zfpath, this);
327 if (ioe != null)
328 throw ioe;
329 }
330
331 ZipFileAttributes getFileAttributes(byte[] path)
332 throws IOException
333 {
518 if (!hasCopyAttrs)
519 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
520 update(u);
521 if (deletesrc)
522 updateDelete(eSrc);
523 } finally {
524 endWrite();
525 }
526 }
527
528 // Returns an output stream for writing the contents into the specified
529 // entry.
530 OutputStream newOutputStream(byte[] path, OpenOption... options)
531 throws IOException
532 {
533 checkWritable();
534 boolean hasCreateNew = false;
535 boolean hasCreate = false;
536 boolean hasAppend = false;
537 boolean hasTruncate = false;
538 for (OpenOption opt : options) {
539 if (opt == READ)
540 throw new IllegalArgumentException("READ not allowed");
541 if (opt == CREATE_NEW)
542 hasCreateNew = true;
543 if (opt == CREATE)
544 hasCreate = true;
545 if (opt == APPEND)
546 hasAppend = true;
547 if (opt == TRUNCATE_EXISTING)
548 hasTruncate = true;
549 }
550 if (hasAppend && hasTruncate)
551 throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
552 beginRead(); // only need a readlock, the "update()" will
553 try { // try to obtain a writelock when the os is
554 ensureOpen(); // being closed.
555 Entry e = getEntry(path);
556 if (e != null) {
557 if (e.isDir() || hasCreateNew)
558 throw new FileAlreadyExistsException(getString(path));
1474
1475 private InputStream getInputStream(Entry e)
1476 throws IOException
1477 {
1478 InputStream eis = null;
1479
1480 if (e.type == Entry.NEW) {
1481 // now bytes & file is uncompressed.
1482 if (e.bytes != null)
1483 return new ByteArrayInputStream(e.bytes);
1484 else if (e.file != null)
1485 return Files.newInputStream(e.file);
1486 else
1487 throw new ZipException("update entry data is missing");
1488 } else if (e.type == Entry.FILECH) {
1489 // FILECH result is un-compressed.
1490 eis = Files.newInputStream(e.file);
1491 // TBD: wrap to hook close()
1492 // streams.add(eis);
1493 return eis;
1494 } else { // untouched CEN or COPY
1495 eis = new EntryInputStream(e, ch);
1496 }
1497 if (e.method == METHOD_DEFLATED) {
1498 // MORE: Compute good size for inflater stream:
1499 long bufSize = e.size + 2; // Inflater likes a bit of slack
1500 if (bufSize > 65536)
1501 bufSize = 8192;
1502 final long size = e.size;
1503 eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1504 private boolean isClosed = false;
1505 public void close() throws IOException {
1506 if (!isClosed) {
1507 releaseInflater(inf);
1508 this.in.close();
1509 isClosed = true;
1510 streams.remove(this);
1511 }
1512 }
1513 // Override fill() method to provide an extra "dummy" byte
1514 // at the end of the input stream. This is required when
1536 return avail > (long) Integer.MAX_VALUE ?
1537 Integer.MAX_VALUE : (int) avail;
1538 }
1539 };
1540 } else if (e.method == METHOD_STORED) {
1541 // TBD: wrap/ it does not seem necessary
1542 } else {
1543 throw new ZipException("invalid compression method");
1544 }
1545 streams.add(eis);
1546 return eis;
1547 }
1548
1549 // Inner class implementing the input stream used to read
1550 // a (possibly compressed) zip file entry.
1551 private class EntryInputStream extends InputStream {
1552 private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1553 // point to a new channel after sync()
1554 private long pos; // current position within entry data
1555 protected long rem; // number of remaining bytes within entry
1556
1557 EntryInputStream(Entry e, SeekableByteChannel zfch)
1558 throws IOException
1559 {
1560 this.zfch = zfch;
1561 rem = e.csize;
1562 pos = e.locoff;
1563 if (pos == -1) {
1564 Entry e2 = getEntry(e.name);
1565 if (e2 == null) {
1566 throw new ZipException("invalid loc for entry <" + e.name + ">");
1567 }
1568 pos = e2.locoff;
1569 }
1570 pos = -pos; // lazy initialize the real data offset
1571 }
1572
1573 public int read(byte b[], int off, int len) throws IOException {
1574 ensureOpen();
1575 initDataPos();
1576 if (rem == 0) {
1577 return -1;
1578 }
1579 if (len <= 0) {
1580 return 0;
1581 }
1608 return -1;
1609 }
1610 }
1611
1612 public long skip(long n) throws IOException {
1613 ensureOpen();
1614 if (n > rem)
1615 n = rem;
1616 pos += n;
1617 rem -= n;
1618 if (rem == 0) {
1619 close();
1620 }
1621 return n;
1622 }
1623
1624 public int available() {
1625 return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1626 }
1627
1628 public void close() {
1629 rem = 0;
1630 streams.remove(this);
1631 }
1632
1633 private void initDataPos() throws IOException {
1634 if (pos <= 0) {
1635 pos = -pos + locpos;
1636 byte[] buf = new byte[LOCHDR];
1637 if (readFullyAt(buf, 0, buf.length, pos) != LOCHDR) {
1638 throw new ZipException("invalid loc " + pos + " for entry reading");
1639 }
1640 pos += LOCHDR + LOCNAM(buf) + LOCEXT(buf);
1641 }
1642 }
1643 }
1644
1645 static void zerror(String msg) throws ZipException {
1646 throw new ZipException(msg);
1647 }
1663 return new Inflater(true);
1664 }
1665 }
1666 }
1667
1668 // Releases the specified inflater to the list of available inflaters.
1669 private void releaseInflater(Inflater inf) {
1670 synchronized (inflaters) {
1671 if (inflaters.size() < MAX_FLATER) {
1672 inf.reset();
1673 inflaters.add(inf);
1674 } else {
1675 inf.end();
1676 }
1677 }
1678 }
1679
1680 // List of available Deflater objects for compression
1681 private final List<Deflater> deflaters = new ArrayList<>();
1682
1683 // Gets a deflater from the list of available deflaters or allocates
1684 // a new one.
1685 private Deflater getDeflater() {
1686 synchronized (deflaters) {
1687 int size = deflaters.size();
1688 if (size > 0) {
1689 Deflater def = deflaters.remove(size - 1);
1690 return def;
1691 } else {
1692 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1693 }
1694 }
1695 }
1696
1697 // End of central directory record
1698 static class END {
1699 // these 2 fields are not used by anyone and write() uses "0"
1700 // int disknum;
1701 // int sdisknum;
1702 int endsub; // endsub
1703 int centot; // 4 bytes
1704 long cenlen; // 4 bytes
1705 long cenoff; // 4 bytes
1706 int comlen; // comment length
1707 byte[] comment;
1708
1709 /* members of Zip64 end of central directory locator */
1710 // int diskNum;
1711 long endpos;
1712 // int disktot;
1713
1714 void write(OutputStream os, long offset, boolean forceEnd64) throws IOException {
1715 boolean hasZip64 = forceEnd64; // false;
1716 long xlen = cenlen;
1964 attrsEx = CENATX(cen, pos);
1965 */
1966 locoff = CENOFF(cen, pos);
1967 pos += CENHDR;
1968 this.name = inode.name;
1969 this.isdir = inode.isdir;
1970 this.hashcode = inode.hashcode;
1971
1972 pos += nlen;
1973 if (elen > 0) {
1974 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1975 pos += elen;
1976 readExtra(zipfs);
1977 }
1978 if (clen > 0) {
1979 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1980 }
1981 return this;
1982 }
1983
1984 int writeCEN(OutputStream os) throws IOException {
1985 int version0 = version();
1986 long csize0 = csize;
1987 long size0 = size;
1988 long locoff0 = locoff;
1989 int elen64 = 0; // extra for ZIP64
1990 int elenNTFS = 0; // extra for NTFS (a/c/mtime)
1991 int elenEXTT = 0; // extra for Extended Timestamp
1992 boolean foundExtraTime = false; // if time stamp NTFS, EXTT present
1993
1994 byte[] zname = isdir ? toDirectoryPath(name) : name;
1995
1996 // confirm size/length
1997 int nlen = (zname != null) ? zname.length - 1 : 0; // name has [0] as "slash"
1998 int elen = (extra != null) ? extra.length : 0;
1999 int eoff = 0;
2000 int clen = (comment != null) ? comment.length : 0;
2001 if (csize >= ZIP64_MINVAL) {
2002 csize0 = ZIP64_MINVAL;
2003 elen64 += 8; // csize(8)
2004 }
2079 if (elenEXTT != 0) {
2080 writeShort(os, EXTID_EXTT);
2081 writeShort(os, elenEXTT - 4);
2082 if (ctime == -1)
2083 os.write(0x3); // mtime and atime
2084 else
2085 os.write(0x7); // mtime, atime and ctime
2086 writeInt(os, javaToUnixTime(mtime));
2087 }
2088 if (extra != null) // whatever not recognized
2089 writeBytes(os, extra);
2090 if (comment != null) //TBD: 0, Math.min(commentBytes.length, 0xffff));
2091 writeBytes(os, comment);
2092 return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2093 }
2094
2095 ///////////////////// LOC //////////////////////
2096
2097 int writeLOC(OutputStream os) throws IOException {
2098 writeInt(os, LOCSIG); // LOC header signature
2099 byte[] zname = isdir ? toDirectoryPath(name) : name;
2100 int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2101 int elen = (extra != null) ? extra.length : 0;
2102 boolean foundExtraTime = false; // if extra timestamp present
2103 int eoff = 0;
2104 int elen64 = 0;
2105 int elenEXTT = 0;
2106 int elenNTFS = 0;
2107 if ((flag & FLAG_DATADESCR) != 0) {
2108 writeShort(os, version()); // version needed to extract
2109 writeShort(os, flag); // general purpose bit flag
2110 writeShort(os, method); // compression method
2111 // last modification time
2112 writeInt(os, (int)javaToDosTime(mtime));
2113 // store size, uncompressed size, and crc-32 in data descriptor
2114 // immediately following compressed entry data
2115 writeInt(os, 0);
2116 writeInt(os, 0);
2117 writeInt(os, 0);
2118 } else {
2405 fm.format(" isDirectory : %b%n", isDirectory());
2406 fm.format(" isSymbolicLink : %b%n", isSymbolicLink());
2407 fm.format(" isOther : %b%n", isOther());
2408 fm.format(" fileKey : %s%n", fileKey());
2409 fm.format(" size : %d%n", size());
2410 fm.format(" compressedSize : %d%n", compressedSize());
2411 fm.format(" crc : %x%n", crc());
2412 fm.format(" method : %d%n", method());
2413 fm.close();
2414 return sb.toString();
2415 }
2416 }
2417
2418 // ZIP directory has two issues:
2419 // (1) ZIP spec does not require the ZIP file to include
2420 // directory entry
2421 // (2) all entries are not stored/organized in a "tree"
2422 // structure.
2423 // A possible solution is to build the node tree ourself as
2424 // implemented below.
2425
2426 // default time stamp for pseudo entries
2427 private long zfsDefaultTimeStamp = System.currentTimeMillis();
2428
2429 private void removeFromTree(IndexNode inode) {
2430 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2431 IndexNode child = parent.child;
2432 if (child.equals(inode)) {
2433 parent.child = child.sibling;
2434 } else {
2435 IndexNode last = child;
2436 while ((child = child.sibling) != null) {
2437 if (child.equals(inode)) {
2438 last.sibling = child.sibling;
2439 break;
2440 } else {
2441 last = child;
2442 }
2443 }
2444 }
|