< prev index next >

src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java

Print this page
rev 54573 : 8222532: (zipfs) Performance regression when writing ZipFileSystem entries in parallel
Reviewed-by: TBD


  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.FilterOutputStream;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.OutputStream;
  36 import java.nio.ByteBuffer;
  37 import java.nio.MappedByteBuffer;

  38 import java.nio.channels.FileChannel;
  39 import java.nio.channels.FileLock;

  40 import java.nio.channels.ReadableByteChannel;
  41 import java.nio.channels.SeekableByteChannel;
  42 import java.nio.channels.WritableByteChannel;
  43 import java.nio.file.*;
  44 import java.nio.file.attribute.FileAttribute;
  45 import java.nio.file.attribute.FileTime;
  46 import java.nio.file.attribute.UserPrincipalLookupService;
  47 import java.nio.file.spi.FileSystemProvider;
  48 import java.security.AccessController;
  49 import java.security.PrivilegedAction;
  50 import java.security.PrivilegedActionException;
  51 import java.security.PrivilegedExceptionAction;
  52 import java.util.*;
  53 import java.util.concurrent.locks.ReadWriteLock;
  54 import java.util.concurrent.locks.ReentrantReadWriteLock;
  55 import java.util.regex.Pattern;
  56 import java.util.zip.CRC32;
  57 import java.util.zip.Deflater;
  58 import java.util.zip.DeflaterOutputStream;
  59 import java.util.zip.Inflater;


 582             if (e.isDir())
 583                 throw new FileSystemException(getString(path), "is a directory", null);
 584             return getInputStream(e);
 585         } finally {
 586             endRead();
 587         }
 588     }
 589 
 590     private void checkOptions(Set<? extends OpenOption> options) {
 591         // check for options of null type and option is an intance of StandardOpenOption
 592         for (OpenOption option : options) {
 593             if (option == null)
 594                 throw new NullPointerException();
 595             if (!(option instanceof StandardOpenOption))
 596                 throw new IllegalArgumentException();
 597         }
 598         if (options.contains(APPEND) && options.contains(TRUNCATE_EXISTING))
 599             throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
 600     }
 601 
 602 
 603     // Returns an output SeekableByteChannel for either
 604     // (1) writing the contents of a new entry, if the entry doesn't exit, or
 605     // (2) updating/replacing the contents of an existing entry.
 606     // Note: The content is not compressed.

 607     private class EntryOutputChannel extends ByteArrayChannel {
 608         Entry e;
 609 
 610         EntryOutputChannel(Entry e) throws IOException {
 611             super(e.size > 0? (int)e.size : 8192, false);
 612             this.e = e;
 613             if (e.mtime == -1)
 614                 e.mtime = System.currentTimeMillis();
 615             if (e.method == -1)
 616                 e.method = defaultMethod;
 617             // store size, compressed size, and crc-32 in datadescriptor
 618             e.flag = FLAG_DATADESCR;
 619             if (zc.isUTF8())
 620                 e.flag |= FLAG_USE_UTF8;
 621         }
 622 
 623         @Override
 624         public void close() throws IOException {
 625             e.bytes = toByteArray();
 626             e.size = e.bytes.length;
 627             e.crc = -1;
 628             super.close();
 629             update(e);
 630         }
 631     }
 632 
 633     private int getCompressMethod(FileAttribute<?>... attrs) {
 634          return defaultMethod;
 635     }
 636 
 637     // Returns a Writable/ReadByteChannel for now. Might consdier to use
 638     // newFileChannel() instead, which dump the entry data into a regular
 639     // file on the default file system and create a FileChannel on top of
 640     // it.
 641     SeekableByteChannel newByteChannel(byte[] path,
 642                                        Set<? extends OpenOption> options,
 643                                        FileAttribute<?>... attrs)
 644         throws IOException
 645     {
 646         checkOptions(options);
 647         if (options.contains(StandardOpenOption.WRITE) ||
 648             options.contains(StandardOpenOption.APPEND)) {
 649             checkWritable();
 650             beginRead();    // only need a readlock, the "update()" will obtain
 651                             // thewritelock when the channel is closed
 652             try {
 653                 ensureOpen();
 654                 Entry e = getEntry(path);
 655                 if (e != null) {
 656                     if (e.isDir() || options.contains(CREATE_NEW))
 657                         throw new FileAlreadyExistsException(getString(path));
 658                     SeekableByteChannel sbc =
 659                             new EntryOutputChannel(new Entry(e, Entry.NEW));
 660                     if (options.contains(APPEND)) {
 661                         try (InputStream is = getInputStream(e)) {  // copyover
 662                             byte[] buf = new byte[8192];
 663                             ByteBuffer bb = ByteBuffer.wrap(buf);
 664                             int n;
 665                             while ((n = is.read(buf)) != -1) {
 666                                 bb.position(0);
 667                                 bb.limit(n);
 668                                 sbc.write(bb);
 669                             }
 670                         }
 671                     }
 672                     return sbc;
 673                 }
 674                 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
 675                     throw new NoSuchFileException(getString(path));
 676                 checkParents(path);
 677                 return new EntryOutputChannel(
 678                     new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));
 679 
 680             } finally {
 681                 endRead();
 682             }
 683         } else {
 684             beginRead();
 685             try {
 686                 ensureOpen();
 687                 Entry e = getEntry(path);
 688                 if (e == null || e.isDir())
 689                     throw new NoSuchFileException(getString(path));
 690                 try (InputStream is = getInputStream(e)) {
 691                     // TBD: if (e.size < NNNNN);
 692                     return new ByteArrayChannel(is.readAllBytes(), true);
 693                 }
 694             } finally {
 695                 endRead();
 696             }
 697         }
 698     }
 699 


 726                         throw new FileAlreadyExistsException(getString(path));
 727                     }
 728                     if (e.isDir())
 729                         throw new FileAlreadyExistsException("directory <"
 730                             + getString(path) + "> exists");
 731                 }
 732                 options = new HashSet<>(options);
 733                 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
 734             } else if (e == null || e.isDir()) {
 735                 throw new NoSuchFileException(getString(path));
 736             }
 737 
 738             final boolean isFCH = (e != null && e.type == Entry.FILECH);
 739             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
 740             final FileChannel fch = tmpfile.getFileSystem()
 741                                            .provider()
 742                                            .newFileChannel(tmpfile, options, attrs);
 743             final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
 744             if (forWrite) {
 745                 u.flag = FLAG_DATADESCR;
 746                 u.method = getCompressMethod(attrs);
 747             }
 748             // is there a better way to hook into the FileChannel's close method?
 749             return new FileChannel() {
 750                 public int write(ByteBuffer src) throws IOException {
 751                     return fch.write(src);
 752                 }
 753                 public long write(ByteBuffer[] srcs, int offset, int length)
 754                     throws IOException
 755                 {
 756                     return fch.write(srcs, offset, length);
 757                 }
 758                 public long position() throws IOException {
 759                     return fch.position();
 760                 }
 761                 public FileChannel position(long newPosition)
 762                     throws IOException
 763                 {
 764                     fch.position(newPosition);
 765                     return this;
 766                 }


 827                 protected void implCloseChannel() throws IOException {
 828                     fch.close();
 829                     if (forWrite) {
 830                         u.mtime = System.currentTimeMillis();
 831                         u.size = Files.size(u.file);
 832 
 833                         update(u);
 834                     } else {
 835                         if (!isFCH)    // if this is a new fch for reading
 836                             removeTempPathForEntry(tmpfile);
 837                     }
 838                }
 839             };
 840         } finally {
 841             endRead();
 842         }
 843     }
 844 
 845     // the outstanding input streams that need to be closed
 846     private Set<InputStream> streams =
 847         Collections.synchronizedSet(new HashSet<InputStream>());




 848 
 849     private Set<Path> tmppaths = Collections.synchronizedSet(new HashSet<Path>());
 850     private Path getTempPathForEntry(byte[] path) throws IOException {
 851         Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
 852         if (path != null) {
 853             Entry e = getEntry(path);
 854             if (e != null) {
 855                 try (InputStream is = newInputStream(path)) {
 856                     Files.copy(is, tmpPath, REPLACE_EXISTING);
 857                 }
 858             }
 859         }
 860         return tmpPath;
 861     }
 862 
 863     private void removeTempPathForEntry(Path path) throws IOException {
 864         Files.delete(path);
 865         tmppaths.remove(path);
 866     }
 867 


1185             os.write(buf, 0, LOCHDR);    // write out the loc header
1186             locoff += LOCHDR;
1187             // use e.csize,  LOCSIZ(buf) is zero if FLAG_DATADESCR is on
1188             // size += LOCNAM(buf) + LOCEXT(buf) + LOCSIZ(buf);
1189             size += LOCNAM(buf) + LOCEXT(buf) + e.csize;
1190             written = LOCHDR + size;
1191         }
1192         int n;
1193         while (size > 0 &&
1194             (n = (int)readFullyAt(buf, 0, buf.length, locoff)) != -1)
1195         {
1196             if (size < n)
1197                 n = (int)size;
1198             os.write(buf, 0, n);
1199             size -= n;
1200             locoff += n;
1201         }
1202         return written;
1203     }
1204 
1205     private long writeEntry(Entry e, OutputStream os, byte[] buf)
1206         throws IOException {
1207 
1208         if (e.bytes == null && e.file == null)    // dir, 0-length data
1209             return 0;
1210 
1211         long written = 0;
1212         try (OutputStream os2 = e.method == METHOD_STORED ?




1213             new EntryOutputStreamCRC32(e, os) : new EntryOutputStreamDef(e, os)) {
1214             if (e.bytes != null) {                 // in-memory
1215                 os2.write(e.bytes, 0, e.bytes.length);
1216             } else if (e.file != null) {           // tmp file
1217                 if (e.type == Entry.NEW || e.type == Entry.FILECH) {
1218                     try (InputStream is = Files.newInputStream(e.file)) {
1219                         is.transferTo(os2);
1220                     }
1221                 }
1222                 Files.delete(e.file);
1223                 tmppaths.remove(e.file);
1224             }
1225         }
1226         written += e.csize;
1227         if ((e.flag & FLAG_DATADESCR) != 0) {
1228             written += e.writeEXT(os);
1229         }
1230         return written;
1231     }
1232 














1233     // sync the zip file system, if there is any udpate
1234     private void sync() throws IOException {
1235 









1236         if (!hasUpdate)
1237             return;
1238         Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
1239         try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(tmpFile, WRITE)))
1240         {
1241             ArrayList<Entry> elist = new ArrayList<>(inodes.size());
1242             long written = 0;
1243             byte[] buf = new byte[8192];
1244             Entry e = null;
1245 
1246             // write loc
1247             for (IndexNode inode : inodes.values()) {
1248                 if (inode instanceof Entry) {    // an updated inode
1249                     e = (Entry)inode;
1250                     try {
1251                         if (e.type == Entry.COPY) {
1252                             // entry copy: the only thing changed is the "name"
1253                             // and "nlen" in LOC header, so we udpate/rewrite the
1254                             // LOC in new file and simply copy the rest (data and
1255                             // ext) without enflating/deflating from the old zip
1256                             // file LOC entry.


1257                             written += copyLOCEntry(e, true, os, written, buf);
1258                         } else {                          // NEW, FILECH or CEN
1259                             e.locoff = written;
1260                             written += e.writeLOC(os);    // write loc header
1261                             written += writeEntry(e, os, buf);
1262                         }
1263                         elist.add(e);
1264                     } catch (IOException x) {
1265                         x.printStackTrace();    // skip any in-accurate entry
1266                     }
1267                 } else {                        // unchanged inode
1268                     if (inode.pos == -1) {
1269                         continue;               // pseudo directory node
1270                     }
1271                     if (inode.name.length == 1 && inode.name[0] == '/') {
1272                         continue;               // no root '/' directory even it
1273                                                 // exits in original zip/jar file.
1274                     }
1275                     e = Entry.readCEN(this, inode);
1276                     try {


1277                         written += copyLOCEntry(e, false, os, written, buf);
1278                         elist.add(e);
1279                     } catch (IOException x) {
1280                         x.printStackTrace();    // skip any wrong entry
1281                     }
1282                 }
1283             }
1284 
1285             // now write back the cen and end table
1286             end.cenoff = written;
1287             for (Entry entry : elist) {
1288                 written += entry.writeCEN(os);
1289             }
1290             end.centot = elist.size();
1291             end.cenlen = written - end.cenoff;
1292             end.write(os, written, forceEnd64);
1293         }
1294 












1295         ch.close();
1296         Files.delete(zfpath);


1297         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1298         hasUpdate = false;    // clear
1299     }
1300 
1301     IndexNode getInode(byte[] path) {
1302         if (path == null)
1303             throw new NullPointerException("path");
1304         return inodes.get(IndexNode.keyOf(path));
1305     }
1306 
1307     Entry getEntry(byte[] path) throws IOException {
1308         IndexNode inode = getInode(path);
1309         if (inode instanceof Entry)
1310             return (Entry)inode;
1311         if (inode == null || inode.pos == -1)
1312             return null;
1313         return Entry.readCEN(this, inode);
1314     }
1315 
1316     public void deleteFile(byte[] path, boolean failIfNotExists)


1334     // Returns an out stream for either
1335     // (1) writing the contents of a new entry, if the entry exits, or
1336     // (2) updating/replacing the contents of the specified existing entry.
1337     private OutputStream getOutputStream(Entry e) throws IOException {
1338 
1339         if (e.mtime == -1)
1340             e.mtime = System.currentTimeMillis();
1341         if (e.method == -1)
1342             e.method = defaultMethod;
1343         // store size, compressed size, and crc-32 in datadescr
1344         e.flag = FLAG_DATADESCR;
1345         if (zc.isUTF8())
1346             e.flag |= FLAG_USE_UTF8;
1347         OutputStream os;
1348         if (useTempFile) {
1349             e.file = getTempPathForEntry(null);
1350             os = Files.newOutputStream(e.file, WRITE);
1351         } else {
1352             os = new ByteArrayOutputStream((e.size > 0)? (int)e.size : 8192);
1353         }



1354         return new EntryOutputStream(e, os);
1355     }

1356 
1357     private class EntryOutputStream extends FilterOutputStream {
1358         private Entry e;
1359         private long written;
1360         private boolean isClosed;
1361 
1362         EntryOutputStream(Entry e, OutputStream os) throws IOException {
1363             super(os);
1364             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1365             // this.written = 0;
1366         }
1367 
1368         @Override
1369         public synchronized void write(int b) throws IOException {
1370             out.write(b);
1371             written += 1;
1372         }
1373 
1374         @Override
1375         public synchronized void write(byte b[], int off, int len)
1376                 throws IOException {
1377             out.write(b, off, len);
1378             written += len;
1379         }
1380 
1381         @Override
1382         public synchronized void close() throws IOException {
1383             if (isClosed) {
1384                 return;
1385             }
1386             isClosed = true;
1387             e.size = written;
1388             if (out instanceof ByteArrayOutputStream)
1389                 e.bytes = ((ByteArrayOutputStream)out).toByteArray();
1390             super.close();
1391             update(e);
1392         }
1393     }
1394 











































1395     // Wrapper output stream class to write out a "stored" entry.
1396     // (1) this class does not close the underlying out stream when
1397     //     being closed.
1398     // (2) no need to be "synchronized", only used by sync()
1399     private class EntryOutputStreamCRC32 extends FilterOutputStream {
1400         private Entry e;
1401         private CRC32 crc;
1402         private long written;
1403         private boolean isClosed;
1404 
1405         EntryOutputStreamCRC32(Entry e, OutputStream os) throws IOException {
1406             super(os);
1407             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1408             this.crc = new CRC32();
1409         }
1410 
1411         @Override
1412         public void write(int b) throws IOException {
1413             out.write(b);
1414             crc.update(b);
1415             written += 1;
1416         }
1417 
1418         @Override
1419         public void write(byte b[], int off, int len)
1420                 throws IOException {
1421             out.write(b, off, len);
1422             crc.update(b, off, len);
1423             written += len;
1424         }
1425 
1426         @Override
1427         public void close() throws IOException {
1428             if (isClosed)
1429                 return;
1430             isClosed = true;
1431             e.size = e.csize = written;
1432             e.crc = crc.getValue();
1433         }
1434     }
1435 
1436     // Wrapper output stream class to write out a "deflated" entry.
1437     // (1) this class does not close the underlying out stream when
1438     //     being closed.
1439     // (2) no need to be "synchronized", only used by sync()
1440     private class EntryOutputStreamDef extends DeflaterOutputStream {
1441         private CRC32 crc;
1442         private Entry e;
1443         private boolean isClosed;
1444 
1445         EntryOutputStreamDef(Entry e, OutputStream os) throws IOException {
1446             super(os, getDeflater());
1447             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1448             this.crc = new CRC32();
1449         }
1450 
1451         @Override
1452         public void write(byte b[], int off, int len)
1453                 throws IOException {
1454             super.write(b, off, len);
1455             crc.update(b, off, len);
1456         }
1457 
1458         @Override
1459         public void close() throws IOException {
1460             if (isClosed)
1461                 return;
1462             isClosed = true;
1463             finish();
1464             e.size  = def.getBytesRead();
1465             e.csize = def.getBytesWritten();
1466             e.crc = crc.getValue();
1467             releaseDeflater(def);
1468         }
1469     }
1470 
1471     private InputStream getInputStream(Entry e)
1472         throws IOException
1473     {
1474         InputStream eis = null;
1475 
1476         if (e.type == Entry.NEW) {
1477             // now bytes & file is uncompressed.
1478             if (e.bytes != null)
1479                 return new ByteArrayInputStream(e.bytes);
1480             else if (e.file != null)
1481                 return Files.newInputStream(e.file);
1482             else
1483                 throw new ZipException("update entry data is missing");
1484         } else if (e.type == Entry.FILECH) {
1485             // FILECH result is un-compressed.
1486             eis = Files.newInputStream(e.file);
1487             // TBD: wrap to hook close()
1488             // streams.add(eis);
1489             return eis;
1490         } else {  // untouched CEN or COPY
1491             eis = new EntryInputStream(e, ch);
1492         }
1493         if (e.method == METHOD_DEFLATED) {
1494             // MORE: Compute good size for inflater stream:
1495             long bufSize = e.size + 2; // Inflater likes a bit of slack
1496             if (bufSize > 65536)
1497                 bufSize = 8192;
1498             final long size = e.size;
1499             eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1500                 private boolean isClosed = false;
1501                 public void close() throws IOException {


1562                     throw new ZipException("invalid loc for entry <" + e.name + ">");
1563                 }
1564                 pos = e2.locoff;
1565             }
1566             pos = -pos;  // lazy initialize the real data offset
1567         }
1568 
1569         public int read(byte b[], int off, int len) throws IOException {
1570             ensureOpen();
1571             initDataPos();
1572             if (rem == 0) {
1573                 return -1;
1574             }
1575             if (len <= 0) {
1576                 return 0;
1577             }
1578             if (len > rem) {
1579                 len = (int) rem;
1580             }
1581             // readFullyAt()
1582             long n = 0;
1583             ByteBuffer bb = ByteBuffer.wrap(b);
1584             bb.position(off);
1585             bb.limit(off + len);
1586             synchronized(zfch) {
1587                 n = zfch.position(pos).read(bb);
1588             }
1589             if (n > 0) {
1590                 pos += n;
1591                 rem -= n;
1592             }
1593             if (rem == 0) {
1594                 close();
1595             }
1596             return (int)n;
1597         }
1598 
1599         public int read() throws IOException {
1600             byte[] b = new byte[1];
1601             if (read(b, 0, 1) == 1) {
1602                 return b[0] & 0xff;


1888         long   locoff;
1889         byte[] comment;
1890 
1891         Entry() {}
1892 
1893         Entry(byte[] name, boolean isdir, int method) {
1894             name(name);
1895             this.isdir = isdir;
1896             this.mtime  = this.ctime = this.atime = System.currentTimeMillis();
1897             this.crc    = 0;
1898             this.size   = 0;
1899             this.csize  = 0;
1900             this.method = method;
1901         }
1902 
1903         Entry(byte[] name, int type, boolean isdir, int method) {
1904             this(name, isdir, method);
1905             this.type = type;
1906         }
1907 
1908         Entry (Entry e, int type) {
1909             name(e.name);
1910             this.isdir     = e.isdir;
1911             this.version   = e.version;
1912             this.ctime     = e.ctime;
1913             this.atime     = e.atime;
1914             this.mtime     = e.mtime;
1915             this.crc       = e.crc;
1916             this.size      = e.size;
1917             this.csize     = e.csize;
1918             this.method    = e.method;
1919             this.extra     = e.extra;
1920             /*
1921             this.versionMade = e.versionMade;
1922             this.disk      = e.disk;
1923             this.attrs     = e.attrs;
1924             this.attrsEx   = e.attrsEx;
1925             */
1926             this.locoff    = e.locoff;
1927             this.comment   = e.comment;
1928             this.type      = type;
1929         }
1930 
1931         Entry (byte[] name, Path file, int type) {
1932             this(name, type, false, METHOD_STORED);
1933             this.file = file;
1934         }
1935 
1936         int version() throws ZipException {
1937             if (method == METHOD_DEFLATED)
1938                 return 20;
1939             else if (method == METHOD_STORED)
1940                 return 10;
1941             throw new ZipException("unsupported compression method");
1942         }
1943 
1944         ///////////////////// CEN //////////////////////
1945         static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1946             throws IOException
1947         {
1948             return new Entry().cen(zipfs, inode);
1949         }
1950 
1951         private Entry cen(ZipFileSystem zipfs, IndexNode inode)


2404         }
2405 
2406         public String toString() {
2407             StringBuilder sb = new StringBuilder(1024);
2408             Formatter fm = new Formatter(sb);
2409             fm.format("    name            : %s%n", new String(name));
2410             fm.format("    creationTime    : %tc%n", creationTime().toMillis());
2411             fm.format("    lastAccessTime  : %tc%n", lastAccessTime().toMillis());
2412             fm.format("    lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2413             fm.format("    isRegularFile   : %b%n", isRegularFile());
2414             fm.format("    isDirectory     : %b%n", isDirectory());
2415             fm.format("    isSymbolicLink  : %b%n", isSymbolicLink());
2416             fm.format("    isOther         : %b%n", isOther());
2417             fm.format("    fileKey         : %s%n", fileKey());
2418             fm.format("    size            : %d%n", size());
2419             fm.format("    compressedSize  : %d%n", compressedSize());
2420             fm.format("    crc             : %x%n", crc());
2421             fm.format("    method          : %d%n", method());
2422             fm.close();
2423             return sb.toString();














2424         }
2425     }
2426 
2427     // ZIP directory has two issues:
2428     // (1) ZIP spec does not require the ZIP file to include
2429     //     directory entry
2430     // (2) all entries are not stored/organized in a "tree"
2431     //     structure.
2432     // A possible solution is to build the node tree ourself as
2433     // implemented below.
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 {




  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.FilterOutputStream;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.OutputStream;
  36 import java.nio.ByteBuffer;
  37 import java.nio.MappedByteBuffer;
  38 import java.nio.channels.Channels;
  39 import java.nio.channels.FileChannel;
  40 import java.nio.channels.FileLock;
  41 import java.nio.channels.NonWritableChannelException;
  42 import java.nio.channels.ReadableByteChannel;
  43 import java.nio.channels.SeekableByteChannel;
  44 import java.nio.channels.WritableByteChannel;
  45 import java.nio.file.*;
  46 import java.nio.file.attribute.FileAttribute;
  47 import java.nio.file.attribute.FileTime;
  48 import java.nio.file.attribute.UserPrincipalLookupService;
  49 import java.nio.file.spi.FileSystemProvider;
  50 import java.security.AccessController;
  51 import java.security.PrivilegedAction;
  52 import java.security.PrivilegedActionException;
  53 import java.security.PrivilegedExceptionAction;
  54 import java.util.*;
  55 import java.util.concurrent.locks.ReadWriteLock;
  56 import java.util.concurrent.locks.ReentrantReadWriteLock;
  57 import java.util.regex.Pattern;
  58 import java.util.zip.CRC32;
  59 import java.util.zip.Deflater;
  60 import java.util.zip.DeflaterOutputStream;
  61 import java.util.zip.Inflater;


 584             if (e.isDir())
 585                 throw new FileSystemException(getString(path), "is a directory", null);
 586             return getInputStream(e);
 587         } finally {
 588             endRead();
 589         }
 590     }
 591 
 592     private void checkOptions(Set<? extends OpenOption> options) {
 593         // check for options of null type and option is an intance of StandardOpenOption
 594         for (OpenOption option : options) {
 595             if (option == null)
 596                 throw new NullPointerException();
 597             if (!(option instanceof StandardOpenOption))
 598                 throw new IllegalArgumentException();
 599         }
 600         if (options.contains(APPEND) && options.contains(TRUNCATE_EXISTING))
 601             throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed");
 602     }
 603 

 604     // Returns an output SeekableByteChannel for either
 605     // (1) writing the contents of a new entry, if the entry doesn't exit, or
 606     // (2) updating/replacing the contents of an existing entry.
 607     // Note: The content of the channel is not compressed until the
 608     // channel is closed
 609     private class EntryOutputChannel extends ByteArrayChannel {
 610         Entry e;
 611 
 612         EntryOutputChannel(Entry e) throws IOException {
 613             super(e.size > 0? (int)e.size : 8192, false);
 614             this.e = e;
 615             if (e.mtime == -1)
 616                 e.mtime = System.currentTimeMillis();
 617             if (e.method == -1)
 618                 e.method = defaultMethod;
 619             // store size, compressed size, and crc-32 in datadescriptor
 620             e.flag = FLAG_DATADESCR;
 621             if (zc.isUTF8())
 622                 e.flag |= FLAG_USE_UTF8;
 623         }
 624 
 625         @Override
 626         public void close() throws IOException {
 627             OutputStream os = getOutputStream(e);
 628             os.write(toByteArray());
 629             os.close(); // will update the entry
 630             super.close();

 631         }
 632     }
 633 
 634     private int getCompressMethod(FileAttribute<?>... attrs) {
 635          return defaultMethod;
 636     }
 637 
 638     // Returns a Writable/ReadByteChannel for now. Might consider to use
 639     // newFileChannel() instead, which dump the entry data into a regular
 640     // file on the default file system and create a FileChannel on top of
 641     // it.
 642     SeekableByteChannel newByteChannel(byte[] path,
 643                                        Set<? extends OpenOption> options,
 644                                        FileAttribute<?>... attrs)
 645         throws IOException
 646     {
 647         checkOptions(options);
 648         if (options.contains(StandardOpenOption.WRITE) ||
 649             options.contains(StandardOpenOption.APPEND)) {
 650             checkWritable();
 651             beginRead();    // only need a read lock, the "update()" will obtain
 652                             // the write lock when the channel is closed
 653             try {

 654                 Entry e = getEntry(path);
 655                 if (e != null) {
 656                     if (e.isDir() || options.contains(CREATE_NEW))
 657                         throw new FileAlreadyExistsException(getString(path));
 658                     SeekableByteChannel sbc =
 659                             new EntryOutputChannel(new Entry(e, Entry.NEW));
 660                     if (options.contains(APPEND)) {
 661                         try (InputStream is = getInputStream(e)) {  // copyover
 662                             byte[] buf = new byte[8192];
 663                             ByteBuffer bb = ByteBuffer.wrap(buf);
 664                             int n;
 665                             while ((n = is.read(buf)) != -1) {
 666                                 bb.position(0);
 667                                 bb.limit(n);
 668                                 sbc.write(bb);
 669                             }
 670                         }
 671                     }
 672                     return sbc;
 673                 }
 674                 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
 675                     throw new NoSuchFileException(getString(path));
 676                 checkParents(path);
 677                 return new EntryOutputChannel(
 678                     new Entry(path, Entry.NEW, false, getCompressMethod(attrs)));

 679             } finally {
 680                 endRead();
 681             }
 682         } else {
 683             beginRead();
 684             try {
 685                 ensureOpen();
 686                 Entry e = getEntry(path);
 687                 if (e == null || e.isDir())
 688                     throw new NoSuchFileException(getString(path));
 689                 try (InputStream is = getInputStream(e)) {
 690                     // TBD: if (e.size < NNNNN);
 691                     return new ByteArrayChannel(is.readAllBytes(), true);
 692                 }
 693             } finally {
 694                 endRead();
 695             }
 696         }
 697     }
 698 


 725                         throw new FileAlreadyExistsException(getString(path));
 726                     }
 727                     if (e.isDir())
 728                         throw new FileAlreadyExistsException("directory <"
 729                             + getString(path) + "> exists");
 730                 }
 731                 options = new HashSet<>(options);
 732                 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
 733             } else if (e == null || e.isDir()) {
 734                 throw new NoSuchFileException(getString(path));
 735             }
 736 
 737             final boolean isFCH = (e != null && e.type == Entry.FILECH);
 738             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
 739             final FileChannel fch = tmpfile.getFileSystem()
 740                                            .provider()
 741                                            .newFileChannel(tmpfile, options, attrs);
 742             final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
 743             if (forWrite) {
 744                 u.flag = FLAG_DATADESCR;
 745                 u.method = getCompressMethod();
 746             }
 747             // is there a better way to hook into the FileChannel's close method?
 748             return new FileChannel() {
 749                 public int write(ByteBuffer src) throws IOException {
 750                     return fch.write(src);
 751                 }
 752                 public long write(ByteBuffer[] srcs, int offset, int length)
 753                     throws IOException
 754                 {
 755                     return fch.write(srcs, offset, length);
 756                 }
 757                 public long position() throws IOException {
 758                     return fch.position();
 759                 }
 760                 public FileChannel position(long newPosition)
 761                     throws IOException
 762                 {
 763                     fch.position(newPosition);
 764                     return this;
 765                 }


 826                 protected void implCloseChannel() throws IOException {
 827                     fch.close();
 828                     if (forWrite) {
 829                         u.mtime = System.currentTimeMillis();
 830                         u.size = Files.size(u.file);
 831 
 832                         update(u);
 833                     } else {
 834                         if (!isFCH)    // if this is a new fch for reading
 835                             removeTempPathForEntry(tmpfile);
 836                     }
 837                }
 838             };
 839         } finally {
 840             endRead();
 841         }
 842     }
 843 
 844     // the outstanding input streams that need to be closed
 845     private Set<InputStream> streams =
 846         Collections.synchronizedSet(new HashSet<>());
 847 
 848     // the ex-channel and ex-path that need to close when their outstanding
 849     // input streams are all closed by the obtainers.
 850     private Set<ExChannelCloser> exChClosers = new HashSet<>();
 851 
 852     private Set<Path> tmppaths = Collections.synchronizedSet(new HashSet<Path>());
 853     private Path getTempPathForEntry(byte[] path) throws IOException {
 854         Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
 855         if (path != null) {
 856             Entry e = getEntry(path);
 857             if (e != null) {
 858                 try (InputStream is = newInputStream(path)) {
 859                     Files.copy(is, tmpPath, REPLACE_EXISTING);
 860                 }
 861             }
 862         }
 863         return tmpPath;
 864     }
 865 
 866     private void removeTempPathForEntry(Path path) throws IOException {
 867         Files.delete(path);
 868         tmppaths.remove(path);
 869     }
 870 


1188             os.write(buf, 0, LOCHDR);    // write out the loc header
1189             locoff += LOCHDR;
1190             // use e.csize,  LOCSIZ(buf) is zero if FLAG_DATADESCR is on
1191             // size += LOCNAM(buf) + LOCEXT(buf) + LOCSIZ(buf);
1192             size += LOCNAM(buf) + LOCEXT(buf) + e.csize;
1193             written = LOCHDR + size;
1194         }
1195         int n;
1196         while (size > 0 &&
1197             (n = (int)readFullyAt(buf, 0, buf.length, locoff)) != -1)
1198         {
1199             if (size < n)
1200                 n = (int)size;
1201             os.write(buf, 0, n);
1202             size -= n;
1203             locoff += n;
1204         }
1205         return written;
1206     }
1207 
1208     private long writeEntry(Entry e, OutputStream os)
1209         throws IOException {
1210 
1211         if (e.bytes == null && e.file == null)    // dir, 0-length data
1212             return 0;
1213 
1214         long written = 0;
1215         if (e.crc != 0 && e.csize > 0) {
1216             // pre-compressed entry, write directly to output stream
1217             writeTo(e, os);
1218         } else {
1219             try (OutputStream os2 = (e.method == METHOD_STORED) ?
1220                     new EntryOutputStreamCRC32(e, os) : new EntryOutputStreamDef(e, os)) {
1221                 writeTo(e, os2);









1222             }
1223         }
1224         written += e.csize;
1225         if ((e.flag & FLAG_DATADESCR) != 0) {
1226             written += e.writeEXT(os);
1227         }
1228         return written;
1229     }
1230 
1231     private void writeTo(Entry e, OutputStream os) throws IOException {
1232         if (e.bytes != null) {
1233             os.write(e.bytes, 0, e.bytes.length);
1234         } else if (e.file != null) {
1235             if (e.type == Entry.NEW || e.type == Entry.FILECH) {
1236                 try (InputStream is = Files.newInputStream(e.file)) {
1237                     is.transferTo(os);
1238                 }
1239             }
1240             Files.delete(e.file);
1241             tmppaths.remove(e.file);
1242         }
1243     }
1244 
1245     // sync the zip file system, if there is any udpate
1246     private void sync() throws IOException {
1247         // check ex-closer
1248         if (!exChClosers.isEmpty()) {
1249             for (ExChannelCloser ecc : exChClosers) {
1250                 if (ecc.streams.isEmpty()) {
1251                     ecc.ch.close();
1252                     Files.delete(ecc.path);
1253                     exChClosers.remove(ecc);
1254                 }
1255             }
1256         }
1257         if (!hasUpdate)
1258             return;
1259         Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
1260         try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(tmpFile, WRITE))) {

1261             ArrayList<Entry> elist = new ArrayList<>(inodes.size());
1262             long written = 0;
1263             byte[] buf = null;
1264             Entry e;
1265 
1266             // write loc
1267             for (IndexNode inode : inodes.values()) {
1268                 if (inode instanceof Entry) {    // an updated inode
1269                     e = (Entry)inode;
1270                     try {
1271                         if (e.type == Entry.COPY) {
1272                             // entry copy: the only thing changed is the "name"
1273                             // and "nlen" in LOC header, so we udpate/rewrite the
1274                             // LOC in new file and simply copy the rest (data and
1275                             // ext) without enflating/deflating from the old zip
1276                             // file LOC entry.
1277                             if (buf == null)
1278                                 buf = new byte[8192];
1279                             written += copyLOCEntry(e, true, os, written, buf);
1280                         } else {                          // NEW, FILECH or CEN
1281                             e.locoff = written;
1282                             written += e.writeLOC(os);    // write loc header
1283                             written += writeEntry(e, os);
1284                         }
1285                         elist.add(e);
1286                     } catch (IOException x) {
1287                         x.printStackTrace();    // skip any in-accurate entry
1288                     }
1289                 } else {                        // unchanged inode
1290                     if (inode.pos == -1) {
1291                         continue;               // pseudo directory node
1292                     }
1293                     if (inode.name.length == 1 && inode.name[0] == '/') {
1294                         continue;               // no root '/' directory even it
1295                                                 // exits in original zip/jar file.
1296                     }
1297                     e = Entry.readCEN(this, inode);
1298                     try {
1299                         if (buf == null)
1300                             buf = new byte[8192];
1301                         written += copyLOCEntry(e, false, os, written, buf);
1302                         elist.add(e);
1303                     } catch (IOException x) {
1304                         x.printStackTrace();    // skip any wrong entry
1305                     }
1306                 }
1307             }
1308 
1309             // now write back the cen and end table
1310             end.cenoff = written;
1311             for (Entry entry : elist) {
1312                 written += entry.writeCEN(os);
1313             }
1314             end.centot = elist.size();
1315             end.cenlen = written - end.cenoff;
1316             end.write(os, written, forceEnd64);
1317         }
1318         if (!streams.isEmpty()) {
1319             //
1320             // There are outstanding input streams open on existing "ch",
1321             // so, don't close the "cha" and delete the "file for now, let
1322             // the "ex-channel-closer" to handle them
1323             ExChannelCloser ecc = new ExChannelCloser(
1324                                       createTempFileInSameDirectoryAs(zfpath),
1325                                       ch,
1326                                       streams);
1327             Files.move(zfpath, ecc.path, REPLACE_EXISTING);
1328             exChClosers.add(ecc);
1329             streams = Collections.synchronizedSet(new HashSet<InputStream>());
1330         } else {
1331             ch.close();
1332             Files.delete(zfpath);
1333         }
1334 
1335         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1336         hasUpdate = false;    // clear
1337     }
1338 
1339     IndexNode getInode(byte[] path) {
1340         if (path == null)
1341             throw new NullPointerException("path");
1342         return inodes.get(IndexNode.keyOf(path));
1343     }
1344 
1345     Entry getEntry(byte[] path) throws IOException {
1346         IndexNode inode = getInode(path);
1347         if (inode instanceof Entry)
1348             return (Entry)inode;
1349         if (inode == null || inode.pos == -1)
1350             return null;
1351         return Entry.readCEN(this, inode);
1352     }
1353 
1354     public void deleteFile(byte[] path, boolean failIfNotExists)


1372     // Returns an out stream for either
1373     // (1) writing the contents of a new entry, if the entry exits, or
1374     // (2) updating/replacing the contents of the specified existing entry.
1375     private OutputStream getOutputStream(Entry e) throws IOException {
1376 
1377         if (e.mtime == -1)
1378             e.mtime = System.currentTimeMillis();
1379         if (e.method == -1)
1380             e.method = defaultMethod;
1381         // store size, compressed size, and crc-32 in datadescr
1382         e.flag = FLAG_DATADESCR;
1383         if (zc.isUTF8())
1384             e.flag |= FLAG_USE_UTF8;
1385         OutputStream os;
1386         if (useTempFile) {
1387             e.file = getTempPathForEntry(null);
1388             os = Files.newOutputStream(e.file, WRITE);
1389         } else {
1390             os = new ByteArrayOutputStream((e.size > 0)? (int)e.size : 8192);
1391         }
1392         if (e.method == METHOD_DEFLATED) {
1393             return new DeflatingEntryOutputStream(e, os);
1394         } else {
1395             return new EntryOutputStream(e, os);
1396         }
1397     }
1398 
1399     private class EntryOutputStream extends FilterOutputStream {
1400         private final Entry e;
1401         private long written;
1402         private boolean isClosed;
1403 
1404         EntryOutputStream(Entry e, OutputStream os) throws IOException {
1405             super(os);
1406             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1407             // this.written = 0;
1408         }
1409 
1410         @Override
1411         public synchronized void write(int b) throws IOException {
1412             out.write(b);
1413             written += 1;
1414         }
1415 
1416         @Override
1417         public synchronized void write(byte b[], int off, int len)
1418                 throws IOException {
1419             out.write(b, off, len);
1420             written += len;
1421         }
1422 
1423         @Override
1424         public synchronized void close() throws IOException {
1425             if (isClosed) {
1426                 return;
1427             }
1428             isClosed = true;
1429             e.size = written;
1430             if (out instanceof ByteArrayOutputStream)
1431                 e.bytes = ((ByteArrayOutputStream)out).toByteArray();
1432             super.close();
1433             update(e);
1434         }
1435     }
1436 
1437     // Output stream returned when writing "deflated" entries into memory,
1438     // to enable eager (possibly parallel) deflation and reduce memory required.
1439     private class DeflatingEntryOutputStream extends DeflaterOutputStream {
1440         private final CRC32 crc;
1441         private final Entry e;
1442         private boolean isClosed;
1443 
1444         DeflatingEntryOutputStream(Entry e, OutputStream os) throws IOException {
1445             super(os, getDeflater());
1446             this.e = Objects.requireNonNull(e, "Zip entry is null");
1447             this.crc = new CRC32();
1448         }
1449 
1450         @Override
1451         public synchronized void write(int b) throws IOException {
1452             super.write(b);
1453             crc.update(b);
1454         }
1455 
1456         @Override
1457         public synchronized void write(byte b[], int off, int len)
1458                 throws IOException {
1459             super.write(b, off, len);
1460             crc.update(b, off, len);
1461         }
1462 
1463         @Override
1464         public synchronized void close() throws IOException {
1465             if (isClosed)
1466                 return;
1467             isClosed = true;
1468             finish();
1469             e.size  = def.getBytesRead();
1470             e.csize = def.getBytesWritten();
1471             e.crc = crc.getValue();
1472             if (out instanceof ByteArrayOutputStream)
1473                 e.bytes = ((ByteArrayOutputStream)out).toByteArray();
1474             super.close();
1475             update(e);
1476             releaseDeflater(def);
1477         }
1478     }
1479 
1480     // Wrapper output stream class to write out a "stored" entry.
1481     // (1) this class does not close the underlying out stream when
1482     //     being closed.
1483     // (2) no need to be "synchronized", only used by sync()
1484     private class EntryOutputStreamCRC32 extends FilterOutputStream {
1485         private final CRC32 crc;
1486         private final Entry e;
1487         private long written;
1488         private boolean isClosed;
1489 
1490         EntryOutputStreamCRC32(Entry e, OutputStream os) throws IOException {
1491             super(os);
1492             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1493             this.crc = new CRC32();
1494         }
1495 
1496         @Override
1497         public void write(int b) throws IOException {
1498             out.write(b);
1499             crc.update(b);
1500             written += 1;
1501         }
1502 
1503         @Override
1504         public void write(byte b[], int off, int len)
1505                 throws IOException {
1506             out.write(b, off, len);
1507             crc.update(b, off, len);
1508             written += len;
1509         }
1510 
1511         @Override
1512         public void close() throws IOException {
1513             if (isClosed)
1514                 return;
1515             isClosed = true;
1516             e.size = e.csize = written;
1517             e.crc = crc.getValue();
1518         }
1519     }
1520 
1521     // Wrapper output stream class to write out a "deflated" entry.
1522     // (1) this class does not close the underlying out stream when
1523     //     being closed.
1524     // (2) no need to be "synchronized", only used by sync()
1525     private class EntryOutputStreamDef extends DeflaterOutputStream {
1526         private final CRC32 crc;
1527         private final Entry e;
1528         private boolean isClosed;
1529 
1530         EntryOutputStreamDef(Entry e, OutputStream os) throws IOException {
1531             super(os, getDeflater());
1532             this.e =  Objects.requireNonNull(e, "Zip entry is null");
1533             this.crc = new CRC32();
1534         }
1535 
1536         @Override
1537         public void write(byte b[], int off, int len)
1538                 throws IOException {
1539             super.write(b, off, len);
1540             crc.update(b, off, len);
1541         }
1542 
1543         @Override
1544         public void close() throws IOException {
1545             if (isClosed)
1546                 return;
1547             isClosed = true;
1548             finish();
1549             e.size  = def.getBytesRead();
1550             e.csize = def.getBytesWritten();
1551             e.crc = crc.getValue();
1552             releaseDeflater(def);
1553         }
1554     }
1555 
1556     private InputStream getInputStream(Entry e)
1557         throws IOException
1558     {
1559         InputStream eis;

1560         if (e.type == Entry.NEW) {

1561             if (e.bytes != null)
1562                 eis = new ByteArrayInputStream(e.bytes);
1563             else if (e.file != null)
1564                 eis = Files.newInputStream(e.file);
1565             else
1566                 throw new ZipException("update entry data is missing");
1567         } else if (e.type == Entry.FILECH) {
1568             // FILECH result is un-compressed.
1569             eis = Files.newInputStream(e.file);
1570             // TBD: wrap to hook close()
1571             // streams.add(eis);
1572             return eis;
1573         } else {  // untouched CEN or COPY
1574             eis = new EntryInputStream(e, ch);
1575         }
1576         if (e.method == METHOD_DEFLATED) {
1577             // MORE: Compute good size for inflater stream:
1578             long bufSize = e.size + 2; // Inflater likes a bit of slack
1579             if (bufSize > 65536)
1580                 bufSize = 8192;
1581             final long size = e.size;
1582             eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1583                 private boolean isClosed = false;
1584                 public void close() throws IOException {


1645                     throw new ZipException("invalid loc for entry <" + e.name + ">");
1646                 }
1647                 pos = e2.locoff;
1648             }
1649             pos = -pos;  // lazy initialize the real data offset
1650         }
1651 
1652         public int read(byte b[], int off, int len) throws IOException {
1653             ensureOpen();
1654             initDataPos();
1655             if (rem == 0) {
1656                 return -1;
1657             }
1658             if (len <= 0) {
1659                 return 0;
1660             }
1661             if (len > rem) {
1662                 len = (int) rem;
1663             }
1664             // readFullyAt()
1665             long n;
1666             ByteBuffer bb = ByteBuffer.wrap(b);
1667             bb.position(off);
1668             bb.limit(off + len);
1669             synchronized(zfch) {
1670                 n = zfch.position(pos).read(bb);
1671             }
1672             if (n > 0) {
1673                 pos += n;
1674                 rem -= n;
1675             }
1676             if (rem == 0) {
1677                 close();
1678             }
1679             return (int)n;
1680         }
1681 
1682         public int read() throws IOException {
1683             byte[] b = new byte[1];
1684             if (read(b, 0, 1) == 1) {
1685                 return b[0] & 0xff;


1971         long   locoff;
1972         byte[] comment;
1973 
1974         Entry() {}
1975 
1976         Entry(byte[] name, boolean isdir, int method) {
1977             name(name);
1978             this.isdir = isdir;
1979             this.mtime  = this.ctime = this.atime = System.currentTimeMillis();
1980             this.crc    = 0;
1981             this.size   = 0;
1982             this.csize  = 0;
1983             this.method = method;
1984         }
1985 
1986         Entry(byte[] name, int type, boolean isdir, int method) {
1987             this(name, isdir, method);
1988             this.type = type;
1989         }
1990 
1991         Entry(Entry e, int type) {
1992             name(e.name);
1993             this.isdir     = e.isdir;
1994             this.version   = e.version;
1995             this.ctime     = e.ctime;
1996             this.atime     = e.atime;
1997             this.mtime     = e.mtime;
1998             this.crc       = e.crc;
1999             this.size      = e.size;
2000             this.csize     = e.csize;
2001             this.method    = e.method;
2002             this.extra     = e.extra;
2003             /*
2004             this.versionMade = e.versionMade;
2005             this.disk      = e.disk;
2006             this.attrs     = e.attrs;
2007             this.attrsEx   = e.attrsEx;
2008             */
2009             this.locoff    = e.locoff;
2010             this.comment   = e.comment;
2011             this.type      = type;
2012         }
2013 
2014         Entry(byte[] name, Path file, int type) {
2015             this(name, type, false, METHOD_STORED);
2016             this.file = file;
2017         }
2018 
2019         int version() throws ZipException {
2020             if (method == METHOD_DEFLATED)
2021                 return 20;
2022             else if (method == METHOD_STORED)
2023                 return 10;
2024             throw new ZipException("unsupported compression method");
2025         }
2026 
2027         ///////////////////// CEN //////////////////////
2028         static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
2029             throws IOException
2030         {
2031             return new Entry().cen(zipfs, inode);
2032         }
2033 
2034         private Entry cen(ZipFileSystem zipfs, IndexNode inode)


2487         }
2488 
2489         public String toString() {
2490             StringBuilder sb = new StringBuilder(1024);
2491             Formatter fm = new Formatter(sb);
2492             fm.format("    name            : %s%n", new String(name));
2493             fm.format("    creationTime    : %tc%n", creationTime().toMillis());
2494             fm.format("    lastAccessTime  : %tc%n", lastAccessTime().toMillis());
2495             fm.format("    lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2496             fm.format("    isRegularFile   : %b%n", isRegularFile());
2497             fm.format("    isDirectory     : %b%n", isDirectory());
2498             fm.format("    isSymbolicLink  : %b%n", isSymbolicLink());
2499             fm.format("    isOther         : %b%n", isOther());
2500             fm.format("    fileKey         : %s%n", fileKey());
2501             fm.format("    size            : %d%n", size());
2502             fm.format("    compressedSize  : %d%n", compressedSize());
2503             fm.format("    crc             : %x%n", crc());
2504             fm.format("    method          : %d%n", method());
2505             fm.close();
2506             return sb.toString();
2507         }
2508     }
2509 
2510     private static class ExChannelCloser  {
2511         Path path;
2512         SeekableByteChannel ch;
2513         Set<InputStream> streams;
2514         ExChannelCloser(Path path,
2515                         SeekableByteChannel ch,
2516                         Set<InputStream> streams)
2517         {
2518             this.path = path;
2519             this.ch = ch;
2520             this.streams = streams;
2521         }
2522     }
2523 
2524     // ZIP directory has two issues:
2525     // (1) ZIP spec does not require the ZIP file to include
2526     //     directory entry
2527     // (2) all entries are not stored/organized in a "tree"
2528     //     structure.
2529     // A possible solution is to build the node tree ourself as
2530     // implemented below.
2531 
2532     // default time stamp for pseudo entries
2533     private long zfsDefaultTimeStamp = System.currentTimeMillis();
2534 
2535     private void removeFromTree(IndexNode inode) {
2536         IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2537         IndexNode child = parent.child;
2538         if (child.equals(inode)) {
2539             parent.child = child.sibling;
2540         } else {


< prev index next >