1 /*
   2  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 package com.sun.nio.zipfs;
  33 
  34 import java.io.ByteArrayInputStream;
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.EOFException;
  37 import java.io.File;
  38 import java.io.IOException;
  39 import java.io.InputStream;
  40 import java.io.OutputStream;
  41 import java.nio.ByteBuffer;
  42 import java.nio.MappedByteBuffer;
  43 import java.nio.channels.*;
  44 import java.nio.file.*;
  45 import java.nio.file.attribute.*;
  46 import java.nio.file.spi.*;
  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 java.util.zip.ZipError;
  58 import static java.lang.Boolean.*;
  59 import static com.sun.nio.zipfs.ZipConstants.*;
  60 import static com.sun.nio.zipfs.ZipUtils.*;
  61 import static java.nio.file.StandardOpenOption.*;
  62 import static java.nio.file.StandardCopyOption.*;
  63 
  64 /**
  65  * A FileSystem built on a zip file
  66  *
  67  * @author Xueming Shen
  68  */
  69 
  70 public class ZipFileSystem extends FileSystem {
  71 
  72     private final ZipFileSystemProvider provider;
  73     private final ZipPath defaultdir;
  74     private boolean readOnly = false;
  75     private final Path zfpath;
  76     private final ZipCoder zc;
  77 
  78     // configurable by env map
  79     private final String  defaultDir;    // default dir for the file system
  80     private final String  nameEncoding;  // default encoding for name/comment
  81     private final boolean useTempFile;   // use a temp file for newOS, default
  82                                          // is to use BAOS for better performance
  83     private final boolean createNew;     // create a new zip if not exists
  84     private static final boolean isWindows =
  85         System.getProperty("os.name").startsWith("Windows");
  86 
  87     ZipFileSystem(ZipFileSystemProvider provider,
  88                   Path zfpath,
  89                   Map<String, ?> env)
  90         throws IOException
  91     {
  92         // configurable env setup
  93         this.createNew    = "true".equals(env.get("create"));
  94         this.nameEncoding = env.containsKey("encoding") ?
  95                             (String)env.get("encoding") : "UTF-8";
  96         this.useTempFile  = TRUE.equals(env.get("useTempFile"));
  97         this.defaultDir   = env.containsKey("default.dir") ?
  98                             (String)env.get("default.dir") : "/";
  99         if (this.defaultDir.charAt(0) != '/')
 100             throw new IllegalArgumentException("default dir should be absolute");
 101 
 102         this.provider = provider;
 103         this.zfpath = zfpath;
 104         if (Files.notExists(zfpath)) {
 105             if (createNew) {
 106                 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
 107                     new END().write(os, 0);
 108                 }
 109             } else {
 110                 throw new FileSystemNotFoundException(zfpath.toString());
 111             }
 112         }
 113         // sm and existence check
 114         zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
 115         if (!Files.isWritable(zfpath))
 116             this.readOnly = true;
 117         this.zc = ZipCoder.get(nameEncoding);
 118         this.defaultdir = new ZipPath(this, getBytes(defaultDir));
 119         this.ch = Files.newByteChannel(zfpath, READ);
 120         this.cen = initCEN();
 121     }
 122 
 123     @Override
 124     public FileSystemProvider provider() {
 125         return provider;
 126     }
 127 
 128     @Override
 129     public String getSeparator() {
 130         return "/";
 131     }
 132 
 133     @Override
 134     public boolean isOpen() {
 135         return isOpen;
 136     }
 137 
 138     @Override
 139     public boolean isReadOnly() {
 140         return readOnly;
 141     }
 142 
 143     private void checkWritable() throws IOException {
 144         if (readOnly)
 145             throw new ReadOnlyFileSystemException();
 146     }
 147 
 148     @Override
 149     public Iterable<Path> getRootDirectories() {
 150         ArrayList<Path> pathArr = new ArrayList<>();
 151         pathArr.add(new ZipPath(this, new byte[]{'/'}));
 152         return pathArr;
 153     }
 154 
 155     ZipPath getDefaultDir() {  // package private
 156         return defaultdir;
 157     }
 158 
 159     @Override
 160     public ZipPath getPath(String first, String... more) {
 161         String path;
 162         if (more.length == 0) {
 163             path = first;
 164         } else {
 165             StringBuilder sb = new StringBuilder();
 166             sb.append(first);
 167             for (String segment: more) {
 168                 if (segment.length() > 0) {
 169                     if (sb.length() > 0)
 170                         sb.append('/');
 171                     sb.append(segment);
 172                 }
 173             }
 174             path = sb.toString();
 175         }
 176         return new ZipPath(this, getBytes(path));
 177     }
 178 
 179     @Override
 180     public UserPrincipalLookupService getUserPrincipalLookupService() {
 181         throw new UnsupportedOperationException();
 182     }
 183 
 184     @Override
 185     public WatchService newWatchService() {
 186         throw new UnsupportedOperationException();
 187     }
 188 
 189     FileStore getFileStore(ZipPath path) {
 190         return new ZipFileStore(path);
 191     }
 192 
 193     @Override
 194     public Iterable<FileStore> getFileStores() {
 195         ArrayList<FileStore> list = new ArrayList<>(1);
 196         list.add(new ZipFileStore(new ZipPath(this, new byte[]{'/'})));
 197         return list;
 198     }
 199 
 200     private static final Set<String> supportedFileAttributeViews =
 201             Collections.unmodifiableSet(
 202                 new HashSet<String>(Arrays.asList("basic", "zip")));
 203 
 204     @Override
 205     public Set<String> supportedFileAttributeViews() {
 206         return supportedFileAttributeViews;
 207     }
 208 
 209     @Override
 210     public String toString() {
 211         return zfpath.toString();
 212     }
 213 
 214     Path getZipFile() {
 215         return zfpath;
 216     }
 217 
 218     private static final String GLOB_SYNTAX = "glob";
 219     private static final String REGEX_SYNTAX = "regex";
 220 
 221     @Override
 222     public PathMatcher getPathMatcher(String syntaxAndInput) {
 223         int pos = syntaxAndInput.indexOf(':');
 224         if (pos <= 0 || pos == syntaxAndInput.length()) {
 225             throw new IllegalArgumentException();
 226         }
 227         String syntax = syntaxAndInput.substring(0, pos);
 228         String input = syntaxAndInput.substring(pos + 1);
 229         String expr;
 230         if (syntax.equals(GLOB_SYNTAX)) {
 231             expr = toRegexPattern(input);
 232         } else {
 233             if (syntax.equals(REGEX_SYNTAX)) {
 234                 expr = input;
 235             } else {
 236                 throw new UnsupportedOperationException("Syntax '" + syntax +
 237                     "' not recognized");
 238             }
 239         }
 240         // return matcher
 241         final Pattern pattern = Pattern.compile(expr);
 242         return new PathMatcher() {
 243             @Override
 244             public boolean matches(Path path) {
 245                 return pattern.matcher(path.toString()).matches();
 246             }
 247         };
 248     }
 249 
 250     @Override
 251     public void close() throws IOException {
 252         beginWrite();
 253         try {
 254             if (!isOpen)
 255                 return;
 256             isOpen = false;             // set closed
 257         } finally {
 258             endWrite();
 259         }
 260         if (!streams.isEmpty()) {       // unlock and close all remaining streams
 261             Set<InputStream> copy = new HashSet<>(streams);
 262             for (InputStream is: copy)
 263                 is.close();
 264         }
 265         beginWrite();                   // lock and sync
 266         try {
 267             sync();
 268             ch.close();                 // close the ch just in case no update
 269         } finally {                     // and sync dose not close the ch
 270             endWrite();
 271         }
 272 
 273         synchronized (inflaters) {
 274             for (Inflater inf : inflaters)
 275                 inf.end();
 276         }
 277         synchronized (deflaters) {
 278             for (Deflater def : deflaters)
 279                 def.end();
 280         }
 281 
 282         IOException ioe = null;
 283         synchronized (tmppaths) {
 284             for (Path p: tmppaths) {
 285                 try {
 286                     Files.deleteIfExists(p);
 287                 } catch (IOException x) {
 288                     if (ioe == null)
 289                         ioe = x;
 290                     else
 291                         ioe.addSuppressed(x);
 292                 }
 293             }
 294         }
 295         provider.removeFileSystem(zfpath, this);
 296         if (ioe != null)
 297            throw ioe;
 298     }
 299 
 300     ZipFileAttributes getFileAttributes(byte[] path)
 301         throws IOException
 302     {
 303         Entry e;
 304         beginRead();
 305         try {
 306             ensureOpen();
 307             e = getEntry0(path);
 308             if (e == null) {
 309                 IndexNode inode = getInode(path);
 310                 if (inode == null)
 311                     return null;
 312                 e = new Entry(inode.name);       // pseudo directory
 313                 e.method = METHOD_STORED;        // STORED for dir
 314                 e.mtime = e.atime = e.ctime = -1;// -1 for all times
 315             }
 316         } finally {
 317             endRead();
 318         }
 319         return new ZipFileAttributes(e);
 320     }
 321 
 322     void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
 323         throws IOException
 324     {
 325         checkWritable();
 326         beginWrite();
 327         try {
 328             ensureOpen();
 329             Entry e = getEntry0(path);    // ensureOpen checked
 330             if (e == null)
 331                 throw new NoSuchFileException(getString(path));
 332             if (e.type == Entry.CEN)
 333                 e.type = Entry.COPY;      // copy e
 334             if (mtime != null)
 335                 e.mtime = mtime.toMillis();
 336             if (atime != null)
 337                 e.atime = atime.toMillis();
 338             if (ctime != null)
 339                 e.ctime = ctime.toMillis();
 340             update(e);
 341         } finally {
 342             endWrite();
 343         }
 344     }
 345 
 346     boolean exists(byte[] path)
 347         throws IOException
 348     {
 349         beginRead();
 350         try {
 351             ensureOpen();
 352             return getInode(path) != null;
 353         } finally {
 354             endRead();
 355         }
 356     }
 357 
 358     boolean isDirectory(byte[] path)
 359         throws IOException
 360     {
 361         beginRead();
 362         try {
 363             IndexNode n = getInode(path);
 364             return n != null && n.isDir();
 365         } finally {
 366             endRead();
 367         }
 368     }
 369 
 370     private ZipPath toZipPath(byte[] path) {
 371         // make it absolute
 372         byte[] p = new byte[path.length + 1];
 373         p[0] = '/';
 374         System.arraycopy(path, 0, p, 1, path.length);
 375         return new ZipPath(this, p);
 376     }
 377 
 378     // returns the list of child paths of "path"
 379     Iterator<Path> iteratorOf(byte[] path,
 380                               DirectoryStream.Filter<? super Path> filter)
 381         throws IOException
 382     {
 383         beginWrite();    // iteration of inodes needs exclusive lock
 384         try {
 385             ensureOpen();
 386             IndexNode inode = getInode(path);
 387             if (inode == null)
 388                 throw new NotDirectoryException(getString(path));
 389             List<Path> list = new ArrayList<>();
 390             IndexNode child = inode.child;
 391             while (child != null) {
 392                 ZipPath zp = toZipPath(child.name);
 393                 if (filter == null || filter.accept(zp))
 394                     list.add(zp);
 395                 child = child.sibling;
 396             }
 397             return list.iterator();
 398         } finally {
 399             endWrite();
 400         }
 401     }
 402 
 403     void createDirectory(byte[] dir, FileAttribute<?>... attrs)
 404         throws IOException
 405     {
 406         checkWritable();
 407         dir = toDirectoryPath(dir);
 408         beginWrite();
 409         try {
 410             ensureOpen();
 411             if (dir.length == 0 || exists(dir))  // root dir, or exiting dir
 412                 throw new FileAlreadyExistsException(getString(dir));
 413             checkParents(dir);
 414             Entry e = new Entry(dir, Entry.NEW);
 415             e.method = METHOD_STORED;            // STORED for dir
 416             update(e);
 417         } finally {
 418             endWrite();
 419         }
 420     }
 421 
 422     void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
 423         throws IOException
 424     {
 425         checkWritable();
 426         if (Arrays.equals(src, dst))
 427             return;    // do nothing, src and dst are the same
 428 
 429         beginWrite();
 430         try {
 431             ensureOpen();
 432             Entry eSrc = getEntry0(src);  // ensureOpen checked
 433             if (eSrc == null)
 434                 throw new NoSuchFileException(getString(src));
 435             if (eSrc.isDir()) {    // spec says to create dst dir
 436                 createDirectory(dst);
 437                 return;
 438             }
 439             boolean hasReplace = false;
 440             boolean hasCopyAttrs = false;
 441             for (CopyOption opt : options) {
 442                 if (opt == REPLACE_EXISTING)
 443                     hasReplace = true;
 444                 else if (opt == COPY_ATTRIBUTES)
 445                     hasCopyAttrs = true;
 446             }
 447             Entry eDst = getEntry0(dst);
 448             if (eDst != null) {
 449                 if (!hasReplace)
 450                     throw new FileAlreadyExistsException(getString(dst));
 451             } else {
 452                 checkParents(dst);
 453             }
 454             Entry u = new Entry(eSrc, Entry.COPY);    // copy eSrc entry
 455             u.name(dst);                              // change name
 456             if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH)
 457             {
 458                 u.type = eSrc.type;    // make it the same type
 459                 if (!deletesrc) {      // if it's not "rename", just take the data
 460                     if (eSrc.bytes != null)
 461                         u.bytes = Arrays.copyOf(eSrc.bytes, eSrc.bytes.length);
 462                     else if (eSrc.file != null) {
 463                         u.file = getTempPathForEntry(null);
 464                         Files.copy(eSrc.file, u.file, REPLACE_EXISTING);
 465                     }
 466                 }
 467             }
 468             if (!hasCopyAttrs)
 469                 u.mtime = u.atime= u.ctime = System.currentTimeMillis();
 470             update(u);
 471             if (deletesrc)
 472                 updateDelete(eSrc);
 473         } finally {
 474             endWrite();
 475         }
 476     }
 477 
 478     // Returns an output stream for writing the contents into the specified
 479     // entry.
 480     OutputStream newOutputStream(byte[] path, OpenOption... options)
 481         throws IOException
 482     {
 483         checkWritable();
 484         boolean hasCreateNew = false;
 485         boolean hasCreate = false;
 486         boolean hasAppend = false;
 487         for (OpenOption opt: options) {
 488             if (opt == READ)
 489                 throw new IllegalArgumentException("READ not allowed");
 490             if (opt == CREATE_NEW)
 491                 hasCreateNew = true;
 492             if (opt == CREATE)
 493                 hasCreate = true;
 494             if (opt == APPEND)
 495                 hasAppend = true;
 496         }
 497         beginRead();                 // only need a readlock, the "update()" will
 498         try {                        // try to obtain a writelock when the os is
 499             ensureOpen();            // being closed.
 500             Entry e = getEntry0(path);
 501             if (e != null) {
 502                 if (e.isDir() || hasCreateNew)
 503                     throw new FileAlreadyExistsException(getString(path));
 504                 if (hasAppend) {
 505                     InputStream is = getInputStream(e);
 506                     OutputStream os = getOutputStream(new Entry(e, Entry.NEW));
 507                     copyStream(is, os);
 508                     is.close();
 509                     return os;
 510                 }
 511                 return getOutputStream(new Entry(e, Entry.NEW));
 512             } else {
 513                 if (!hasCreate && !hasCreateNew)
 514                     throw new NoSuchFileException(getString(path));
 515                 checkParents(path);
 516                 return getOutputStream(new Entry(path, Entry.NEW));
 517             }
 518         } finally {
 519             endRead();
 520         }
 521     }
 522 
 523     // Returns an input stream for reading the contents of the specified
 524     // file entry.
 525     InputStream newInputStream(byte[] path) throws IOException {
 526         beginRead();
 527         try {
 528             ensureOpen();
 529             Entry e = getEntry0(path);
 530             if (e == null)
 531                 throw new NoSuchFileException(getString(path));
 532             if (e.isDir())
 533                 throw new FileSystemException(getString(path), "is a directory", null);
 534             return getInputStream(e);
 535         } finally {
 536             endRead();
 537         }
 538     }
 539 
 540     private void checkOptions(Set<? extends OpenOption> options) {
 541         // check for options of null type and option is an intance of StandardOpenOption
 542         for (OpenOption option : options) {
 543             if (option == null)
 544                 throw new NullPointerException();
 545             if (!(option instanceof StandardOpenOption))
 546                 throw new IllegalArgumentException();
 547         }
 548     }
 549 
 550     // Returns a Writable/ReadByteChannel for now. Might consdier to use
 551     // newFileChannel() instead, which dump the entry data into a regular
 552     // file on the default file system and create a FileChannel on top of
 553     // it.
 554     SeekableByteChannel newByteChannel(byte[] path,
 555                                        Set<? extends OpenOption> options,
 556                                        FileAttribute<?>... attrs)
 557         throws IOException
 558     {
 559         checkOptions(options);
 560         if (options.contains(StandardOpenOption.WRITE) ||
 561             options.contains(StandardOpenOption.APPEND)) {
 562             checkWritable();
 563             beginRead();
 564             try {
 565                 final WritableByteChannel wbc = Channels.newChannel(
 566                     newOutputStream(path, options.toArray(new OpenOption[0])));
 567                 long leftover = 0;
 568                 if (options.contains(StandardOpenOption.APPEND)) {
 569                     Entry e = getEntry0(path);
 570                     if (e != null && e.size >= 0)
 571                         leftover = e.size;
 572                 }
 573                 final long offset = leftover;
 574                 return new SeekableByteChannel() {
 575                     long written = offset;
 576                     public boolean isOpen() {
 577                         return wbc.isOpen();
 578                     }
 579 
 580                     public long position() throws IOException {
 581                         return written;
 582                     }
 583 
 584                     public SeekableByteChannel position(long pos)
 585                         throws IOException
 586                     {
 587                         throw new UnsupportedOperationException();
 588                     }
 589 
 590                     public int read(ByteBuffer dst) throws IOException {
 591                         throw new UnsupportedOperationException();
 592                     }
 593 
 594                     public SeekableByteChannel truncate(long size)
 595                         throws IOException
 596                     {
 597                         throw new UnsupportedOperationException();
 598                     }
 599 
 600                     public int write(ByteBuffer src) throws IOException {
 601                         int n = wbc.write(src);
 602                         written += n;
 603                         return n;
 604                     }
 605 
 606                     public long size() throws IOException {
 607                         return written;
 608                     }
 609 
 610                     public void close() throws IOException {
 611                         wbc.close();
 612                     }
 613                 };
 614             } finally {
 615                 endRead();
 616             }
 617         } else {
 618             beginRead();
 619             try {
 620                 ensureOpen();
 621                 Entry e = getEntry0(path);
 622                 if (e == null || e.isDir())
 623                     throw new NoSuchFileException(getString(path));
 624                 final ReadableByteChannel rbc =
 625                     Channels.newChannel(getInputStream(e));
 626                 final long size = e.size;
 627                 return new SeekableByteChannel() {
 628                     long read = 0;
 629                     public boolean isOpen() {
 630                         return rbc.isOpen();
 631                     }
 632 
 633                     public long position() throws IOException {
 634                         return read;
 635                     }
 636 
 637                     public SeekableByteChannel position(long pos)
 638                         throws IOException
 639                     {
 640                         throw new UnsupportedOperationException();
 641                     }
 642 
 643                     public int read(ByteBuffer dst) throws IOException {
 644                         return rbc.read(dst);
 645                     }
 646 
 647                     public SeekableByteChannel truncate(long size)
 648                     throws IOException
 649                     {
 650                         throw new NonWritableChannelException();
 651                     }
 652 
 653                     public int write (ByteBuffer src) throws IOException {
 654                         throw new NonWritableChannelException();
 655                     }
 656 
 657                     public long size() throws IOException {
 658                         return size;
 659                     }
 660 
 661                     public void close() throws IOException {
 662                         rbc.close();
 663                     }
 664                 };
 665             } finally {
 666                 endRead();
 667             }
 668         }
 669     }
 670 
 671     // Returns a FileChannel of the specified entry.
 672     //
 673     // This implementation creates a temporary file on the default file system,
 674     // copy the entry data into it if the entry exists, and then create a
 675     // FileChannel on top of it.
 676     FileChannel newFileChannel(byte[] path,
 677                                Set<? extends OpenOption> options,
 678                                FileAttribute<?>... attrs)
 679         throws IOException
 680     {
 681         checkOptions(options);
 682         final  boolean forWrite = (options.contains(StandardOpenOption.WRITE) ||
 683                                    options.contains(StandardOpenOption.APPEND));
 684         beginRead();
 685         try {
 686             ensureOpen();
 687             Entry e = getEntry0(path);
 688             if (forWrite) {
 689                 checkWritable();
 690                 if (e == null) {
 691                 if (!options.contains(StandardOpenOption.CREATE_NEW))
 692                     throw new NoSuchFileException(getString(path));
 693                 } else {
 694                     if (options.contains(StandardOpenOption.CREATE_NEW))
 695                         throw new FileAlreadyExistsException(getString(path));
 696                     if (e.isDir())
 697                         throw new FileAlreadyExistsException("directory <"
 698                             + getString(path) + "> exists");
 699                 }
 700                 options.remove(StandardOpenOption.CREATE_NEW); // for tmpfile
 701             } else if (e == null || e.isDir()) {
 702                 throw new NoSuchFileException(getString(path));
 703             }
 704 
 705             final boolean isFCH = (e != null && e.type == Entry.FILECH);
 706             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
 707             final FileChannel fch = tmpfile.getFileSystem()
 708                                            .provider()
 709                                            .newFileChannel(tmpfile, options, attrs);
 710             final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
 711             if (forWrite) {
 712                 u.flag = FLAG_DATADESCR;
 713                 u.method = METHOD_DEFLATED;
 714             }
 715             // is there a better way to hook into the FileChannel's close method?
 716             return new FileChannel() {
 717                 public int write(ByteBuffer src) throws IOException {
 718                     return fch.write(src);
 719                 }
 720                 public long write(ByteBuffer[] srcs, int offset, int length)
 721                     throws IOException
 722                 {
 723                     return fch.write(srcs, offset, length);
 724                 }
 725                 public long position() throws IOException {
 726                     return fch.position();
 727                 }
 728                 public FileChannel position(long newPosition)
 729                     throws IOException
 730                 {
 731                     fch.position(newPosition);
 732                     return this;
 733                 }
 734                 public long size() throws IOException {
 735                     return fch.size();
 736                 }
 737                 public FileChannel truncate(long size)
 738                     throws IOException
 739                 {
 740                     fch.truncate(size);
 741                     return this;
 742                 }
 743                 public void force(boolean metaData)
 744                     throws IOException
 745                 {
 746                     fch.force(metaData);
 747                 }
 748                 public long transferTo(long position, long count,
 749                                        WritableByteChannel target)
 750                     throws IOException
 751                 {
 752                     return fch.transferTo(position, count, target);
 753                 }
 754                 public long transferFrom(ReadableByteChannel src,
 755                                          long position, long count)
 756                     throws IOException
 757                 {
 758                     return fch.transferFrom(src, position, count);
 759                 }
 760                 public int read(ByteBuffer dst) throws IOException {
 761                     return fch.read(dst);
 762                 }
 763                 public int read(ByteBuffer dst, long position)
 764                     throws IOException
 765                 {
 766                     return fch.read(dst, position);
 767                 }
 768                 public long read(ByteBuffer[] dsts, int offset, int length)
 769                     throws IOException
 770                 {
 771                     return fch.read(dsts, offset, length);
 772                 }
 773                 public int write(ByteBuffer src, long position)
 774                     throws IOException
 775                     {
 776                    return fch.write(src, position);
 777                 }
 778                 public MappedByteBuffer map(MapMode mode,
 779                                             long position, long size)
 780                     throws IOException
 781                 {
 782                     throw new UnsupportedOperationException();
 783                 }
 784                 public FileLock lock(long position, long size, boolean shared)
 785                     throws IOException
 786                 {
 787                     return fch.lock(position, size, shared);
 788                 }
 789                 public FileLock tryLock(long position, long size, boolean shared)
 790                     throws IOException
 791                 {
 792                     return fch.tryLock(position, size, shared);
 793                 }
 794                 protected void implCloseChannel() throws IOException {
 795                     fch.close();
 796                     if (forWrite) {
 797                         u.mtime = System.currentTimeMillis();
 798                         u.size = Files.size(u.file);
 799 
 800                         update(u);
 801                     } else {
 802                         if (!isFCH)    // if this is a new fch for reading
 803                             removeTempPathForEntry(tmpfile);
 804                     }
 805                }
 806             };
 807         } finally {
 808             endRead();
 809         }
 810     }
 811 
 812     // the outstanding input streams that need to be closed
 813     private Set<InputStream> streams =
 814         Collections.synchronizedSet(new HashSet<InputStream>());
 815 
 816     // the ex-channel and ex-path that need to close when their outstanding
 817     // input streams are all closed by the obtainers.
 818     private Set<ExChannelCloser> exChClosers = new HashSet<>();
 819 
 820     private Set<Path> tmppaths = Collections.synchronizedSet(new HashSet<Path>());
 821     private Path getTempPathForEntry(byte[] path) throws IOException {
 822         Path tmpPath = createTempFileInSameDirectoryAs(zfpath);
 823         if (path != null) {
 824             Entry e = getEntry0(path);
 825             if (e != null) {
 826                 try (InputStream is = newInputStream(path)) {
 827                     Files.copy(is, tmpPath, REPLACE_EXISTING);
 828                 }
 829             }
 830         }
 831         return tmpPath;
 832     }
 833 
 834     private void removeTempPathForEntry(Path path) throws IOException {
 835         Files.delete(path);
 836         tmppaths.remove(path);
 837     }
 838 
 839     // check if all parents really exit. ZIP spec does not require
 840     // the existence of any "parent directory".
 841     private void checkParents(byte[] path) throws IOException {
 842         beginRead();
 843         try {
 844             while ((path = getParent(path)) != null && path.length != 0) {
 845                 if (!inodes.containsKey(IndexNode.keyOf(path))) {
 846                     throw new NoSuchFileException(getString(path));
 847                 }
 848             }
 849         } finally {
 850             endRead();
 851         }
 852     }
 853 
 854     private static byte[] ROOTPATH = new byte[0];
 855     private static byte[] getParent(byte[] path) {
 856         int off = path.length - 1;
 857         if (off > 0 && path[off] == '/')  // isDirectory
 858             off--;
 859         while (off > 0 && path[off] != '/') { off--; }
 860         if (off <= 0)
 861             return ROOTPATH;
 862         return Arrays.copyOf(path, off + 1);
 863     }
 864 
 865     private final void beginWrite() {
 866         rwlock.writeLock().lock();
 867     }
 868 
 869     private final void endWrite() {
 870         rwlock.writeLock().unlock();
 871     }
 872 
 873     private final void beginRead() {
 874         rwlock.readLock().lock();
 875     }
 876 
 877     private final void endRead() {
 878         rwlock.readLock().unlock();
 879     }
 880 
 881     ///////////////////////////////////////////////////////////////////
 882 
 883     private volatile boolean isOpen = true;
 884     private final SeekableByteChannel ch; // channel to the zipfile
 885     final byte[]  cen;     // CEN & ENDHDR
 886     private END  end;
 887     private long locpos;   // position of first LOC header (usually 0)
 888 
 889     private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
 890 
 891     // name -> pos (in cen), IndexNode itself can be used as a "key"
 892     private LinkedHashMap<IndexNode, IndexNode> inodes;
 893 
 894     final byte[] getBytes(String name) {
 895         return zc.getBytes(name);
 896     }
 897 
 898     final String getString(byte[] name) {
 899         return zc.toString(name);
 900     }
 901 
 902     protected void finalize() throws IOException {
 903         close();
 904     }
 905 
 906     private long getDataPos(Entry e) throws IOException {
 907         if (e.locoff == -1) {
 908             Entry e2 = getEntry0(e.name);
 909             if (e2 == null)
 910                 throw new ZipException("invalid loc for entry <" + e.name + ">");
 911             e.locoff = e2.locoff;
 912         }
 913         byte[] buf = new byte[LOCHDR];
 914         if (readFullyAt(buf, 0, buf.length, e.locoff) != buf.length)
 915             throw new ZipException("invalid loc for entry <" + e.name + ">");
 916         return locpos + e.locoff + LOCHDR + LOCNAM(buf) + LOCEXT(buf);
 917     }
 918 
 919     // Reads len bytes of data from the specified offset into buf.
 920     // Returns the total number of bytes read.
 921     // Each/every byte read from here (except the cen, which is mapped).
 922     final long readFullyAt(byte[] buf, int off, long len, long pos)
 923         throws IOException
 924     {
 925         ByteBuffer bb = ByteBuffer.wrap(buf);
 926         bb.position(off);
 927         bb.limit((int)(off + len));
 928         return readFullyAt(bb, pos);
 929     }
 930 
 931     private final long readFullyAt(ByteBuffer bb, long pos)
 932         throws IOException
 933     {
 934         synchronized(ch) {
 935             return ch.position(pos).read(bb);
 936         }
 937     }
 938 
 939     // Searches for end of central directory (END) header. The contents of
 940     // the END header will be read and placed in endbuf. Returns the file
 941     // position of the END header, otherwise returns -1 if the END header
 942     // was not found or an error occurred.
 943     private END findEND() throws IOException
 944     {
 945         byte[] buf = new byte[READBLOCKSZ];
 946         long ziplen = ch.size();
 947         long minHDR = (ziplen - END_MAXLEN) > 0 ? ziplen - END_MAXLEN : 0;
 948         long minPos = minHDR - (buf.length - ENDHDR);
 949 
 950         for (long pos = ziplen - buf.length; pos >= minPos; pos -= (buf.length - ENDHDR))
 951         {
 952             int off = 0;
 953             if (pos < 0) {
 954                 // Pretend there are some NUL bytes before start of file
 955                 off = (int)-pos;
 956                 Arrays.fill(buf, 0, off, (byte)0);
 957             }
 958             int len = buf.length - off;
 959             if (readFullyAt(buf, off, len, pos + off) != len)
 960                 zerror("zip END header not found");
 961 
 962             // Now scan the block backwards for END header signature
 963             for (int i = buf.length - ENDHDR; i >= 0; i--) {
 964                 if (buf[i+0] == (byte)'P'    &&
 965                     buf[i+1] == (byte)'K'    &&
 966                     buf[i+2] == (byte)'\005' &&
 967                     buf[i+3] == (byte)'\006' &&
 968                     (pos + i + ENDHDR + ENDCOM(buf, i) == ziplen)) {
 969                     // Found END header
 970                     buf = Arrays.copyOfRange(buf, i, i + ENDHDR);
 971                     END end = new END();
 972                     end.endsub = ENDSUB(buf);
 973                     end.centot = ENDTOT(buf);
 974                     end.cenlen = ENDSIZ(buf);
 975                     end.cenoff = ENDOFF(buf);
 976                     end.comlen = ENDCOM(buf);
 977                     end.endpos = pos + i;
 978                     if (end.cenlen == ZIP64_MINVAL ||
 979                         end.cenoff == ZIP64_MINVAL ||
 980                         end.centot == ZIP64_MINVAL32)
 981                     {
 982                         // need to find the zip64 end;
 983                         byte[] loc64 = new byte[ZIP64_LOCHDR];
 984                         if (readFullyAt(loc64, 0, loc64.length, end.endpos - ZIP64_LOCHDR)
 985                             != loc64.length) {
 986                             return end;
 987                         }
 988                         long end64pos = ZIP64_LOCOFF(loc64);
 989                         byte[] end64buf = new byte[ZIP64_ENDHDR];
 990                         if (readFullyAt(end64buf, 0, end64buf.length, end64pos)
 991                             != end64buf.length) {
 992                             return end;
 993                         }
 994                         // end64 found, re-calcualte everything.
 995                         end.cenlen = ZIP64_ENDSIZ(end64buf);
 996                         end.cenoff = ZIP64_ENDOFF(end64buf);
 997                         end.centot = (int)ZIP64_ENDTOT(end64buf); // assume total < 2g
 998                         end.endpos = end64pos;
 999                     }
1000                     return end;
1001                 }
1002             }
1003         }
1004         zerror("zip END header not found");
1005         return null; //make compiler happy
1006     }
1007 
1008     // Reads zip file central directory. Returns the file position of first
1009     // CEN header, otherwise returns -1 if an error occured. If zip->msg != NULL
1010     // then the error was a zip format error and zip->msg has the error text.
1011     // Always pass in -1 for knownTotal; it's used for a recursive call.
1012     private byte[] initCEN() throws IOException {
1013         end = findEND();
1014         if (end.endpos == 0) {
1015             inodes = new LinkedHashMap<>(10);
1016             locpos = 0;
1017             buildNodeTree();
1018             return null;         // only END header present
1019         }
1020         if (end.cenlen > end.endpos)
1021             zerror("invalid END header (bad central directory size)");
1022         long cenpos = end.endpos - end.cenlen;     // position of CEN table
1023 
1024         // Get position of first local file (LOC) header, taking into
1025         // account that there may be a stub prefixed to the zip file.
1026         locpos = cenpos - end.cenoff;
1027         if (locpos < 0)
1028             zerror("invalid END header (bad central directory offset)");
1029 
1030         // read in the CEN and END
1031         byte[] cen = new byte[(int)(end.cenlen + ENDHDR)];
1032         if (readFullyAt(cen, 0, cen.length, cenpos) != end.cenlen + ENDHDR) {
1033             zerror("read CEN tables failed");
1034         }
1035         // Iterate through the entries in the central directory
1036         inodes = new LinkedHashMap<>(end.centot + 1);
1037         int pos = 0;
1038         int limit = cen.length - ENDHDR;
1039         while (pos < limit) {
1040             if (CENSIG(cen, pos) != CENSIG)
1041                 zerror("invalid CEN header (bad signature)");
1042             int method = CENHOW(cen, pos);
1043             int nlen   = CENNAM(cen, pos);
1044             int elen   = CENEXT(cen, pos);
1045             int clen   = CENCOM(cen, pos);
1046             if ((CENFLG(cen, pos) & 1) != 0)
1047                 zerror("invalid CEN header (encrypted entry)");
1048             if (method != METHOD_STORED && method != METHOD_DEFLATED)
1049                 zerror("invalid CEN header (unsupported compression method: " + method + ")");
1050             if (pos + CENHDR + nlen > limit)
1051                 zerror("invalid CEN header (bad header size)");
1052             byte[] name = Arrays.copyOfRange(cen, pos + CENHDR, pos + CENHDR + nlen);
1053             IndexNode inode = new IndexNode(name, pos);
1054             inodes.put(inode, inode);
1055             // skip ext and comment
1056             pos += (CENHDR + nlen + elen + clen);
1057         }
1058         if (pos + ENDHDR != cen.length) {
1059             zerror("invalid CEN header (bad header size)");
1060         }
1061         buildNodeTree();
1062         return cen;
1063     }
1064 
1065     private void ensureOpen() throws IOException {
1066         if (!isOpen)
1067             throw new ClosedFileSystemException();
1068     }
1069 
1070     // Creates a new empty temporary file in the same directory as the
1071     // specified file.  A variant of File.createTempFile.
1072     private Path createTempFileInSameDirectoryAs(Path path)
1073         throws IOException
1074     {
1075         Path parent = path.toAbsolutePath().getParent();
1076         String dir = (parent == null)? "." : parent.toString();
1077         Path tmpPath = File.createTempFile("zipfstmp", null, new File(dir)).toPath();
1078         tmppaths.add(tmpPath);
1079         return tmpPath;
1080     }
1081 
1082     ////////////////////update & sync //////////////////////////////////////
1083 
1084     private boolean hasUpdate = false;
1085 
1086     // shared key. consumer guarantees the "writeLock" before use it.
1087     private final IndexNode LOOKUPKEY = IndexNode.keyOf(null);
1088 
1089     private void updateDelete(IndexNode inode) {
1090         beginWrite();
1091         try {
1092             removeFromTree(inode);
1093             inodes.remove(inode);
1094             hasUpdate = true;
1095         } finally {
1096              endWrite();
1097         }
1098     }
1099 
1100     private void update(Entry e) {
1101         beginWrite();
1102         try {
1103             IndexNode old = inodes.put(e, e);
1104             if (old != null) {
1105                 removeFromTree(old);
1106             }
1107             if (e.type == Entry.NEW || e.type == Entry.FILECH) {
1108                 IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(e.name)));
1109                 e.sibling = parent.child;
1110                 parent.child = e;
1111             }
1112             hasUpdate = true;
1113         } finally {
1114             endWrite();
1115         }
1116     }
1117 
1118     // copy over the whole LOC entry (header if necessary, data and ext) from
1119     // old zip to the new one.
1120     private long copyLOCEntry(Entry e, boolean updateHeader,
1121                               OutputStream os,
1122                               long written, byte[] buf)
1123         throws IOException
1124     {
1125         long locoff = e.locoff;  // where to read
1126         e.locoff = written;      // update the e.locoff with new value
1127 
1128         // calculate the size need to write out
1129         long size = 0;
1130         //  if there is A ext
1131         if ((e.flag & FLAG_DATADESCR) != 0) {
1132             if (e.size >= ZIP64_MINVAL || e.csize >= ZIP64_MINVAL)
1133                 size = 24;
1134             else
1135                 size = 16;
1136         }
1137         // read loc, use the original loc.elen/nlen
1138         if (readFullyAt(buf, 0, LOCHDR , locoff) != LOCHDR)
1139             throw new ZipException("loc: reading failed");
1140         if (updateHeader) {
1141             locoff += LOCHDR + LOCNAM(buf) + LOCEXT(buf);  // skip header
1142             size += e.csize;
1143             written = e.writeLOC(os) + size;
1144         } else {
1145             os.write(buf, 0, LOCHDR);    // write out the loc header
1146             locoff += LOCHDR;
1147             // use e.csize,  LOCSIZ(buf) is zero if FLAG_DATADESCR is on
1148             // size += LOCNAM(buf) + LOCEXT(buf) + LOCSIZ(buf);
1149             size += LOCNAM(buf) + LOCEXT(buf) + e.csize;
1150             written = LOCHDR + size;
1151         }
1152         int n;
1153         while (size > 0 &&
1154             (n = (int)readFullyAt(buf, 0, buf.length, locoff)) != -1)
1155         {
1156             if (size < n)
1157                 n = (int)size;
1158             os.write(buf, 0, n);
1159             size -= n;
1160             locoff += n;
1161         }
1162         return written;
1163     }
1164 
1165     // sync the zip file system, if there is any udpate
1166     private void sync() throws IOException {
1167         //System.out.printf("->sync(%s) starting....!%n", toString());
1168 
1169         // check ex-closer
1170         if (!exChClosers.isEmpty()) {
1171             for (ExChannelCloser ecc : exChClosers) {
1172                 if (ecc.streams.isEmpty()) {
1173                     ecc.ch.close();
1174                     Files.delete(ecc.path);
1175                     exChClosers.remove(ecc);
1176                 }
1177             }
1178         }
1179         if (!hasUpdate)
1180             return;
1181         Path tmpFile = createTempFileInSameDirectoryAs(zfpath);
1182         OutputStream os = Files.newOutputStream(tmpFile, WRITE);
1183         ArrayList<Entry> elist = new ArrayList<>(inodes.size());
1184         long written = 0;
1185         byte[] buf = new byte[8192];
1186         Entry e = null;
1187 
1188         // write loc
1189         for (IndexNode inode : inodes.values()) {
1190             if (inode instanceof Entry) {    // an updated inode
1191                 e = (Entry)inode;
1192                 try {
1193                     if (e.type == Entry.COPY) {
1194                         // entry copy: the only thing changed is the "name"
1195                         // and "nlen" in LOC header, so we udpate/rewrite the
1196                         // LOC in new file and simply copy the rest (data and
1197                         // ext) without enflating/deflating from the old zip
1198                         // file LOC entry.
1199                         written += copyLOCEntry(e, true, os, written, buf);
1200                     } else {                          // NEW, FILECH or CEN
1201                         e.locoff = written;
1202                         written += e.writeLOC(os);    // write loc header
1203                         if (e.bytes != null) {        // in-memory, deflated
1204                             os.write(e.bytes);        // already
1205                             written += e.bytes.length;
1206                         } else if (e.file != null) {  // tmp file
1207                             try (InputStream is = Files.newInputStream(e.file)) {
1208                                 int n;
1209                                 if (e.type == Entry.NEW) {  // deflated already
1210                                     while ((n = is.read(buf)) != -1) {
1211                                         os.write(buf, 0, n);
1212                                         written += n;
1213                                     }
1214                                 } else if (e.type == Entry.FILECH) {
1215                                     // the data are not deflated, use ZEOS
1216                                     try (OutputStream os2 = new EntryOutputStream(e, os)) {
1217                                         while ((n = is.read(buf)) != -1) {
1218                                             os2.write(buf, 0, n);
1219                                         }
1220                                     }
1221                                     written += e.csize;
1222                                     if ((e.flag & FLAG_DATADESCR) != 0)
1223                                         written += e.writeEXT(os);
1224                                 }
1225                             }
1226                             Files.delete(e.file);
1227                             tmppaths.remove(e.file);
1228                         } else {
1229                             // dir, 0-length data
1230                         }
1231                     }
1232                     elist.add(e);
1233                 } catch (IOException x) {
1234                     x.printStackTrace();    // skip any in-accurate entry
1235                 }
1236             } else {                        // unchanged inode
1237                 if (inode.pos == -1) {
1238                     continue;               // pseudo directory node
1239                 }
1240                 e = Entry.readCEN(this, inode.pos);
1241                 try {
1242                     written += copyLOCEntry(e, false, os, written, buf);
1243                     elist.add(e);
1244                 } catch (IOException x) {
1245                     x.printStackTrace();    // skip any wrong entry
1246                 }
1247             }
1248         }
1249 
1250         // now write back the cen and end table
1251         end.cenoff = written;
1252         for (Entry entry : elist) {
1253             written += entry.writeCEN(os);
1254         }
1255         end.centot = elist.size();
1256         end.cenlen = written - end.cenoff;
1257         end.write(os, written);
1258         os.close();
1259 
1260         if (!streams.isEmpty()) {
1261             //
1262             // TBD: ExChannelCloser should not be necessary if we only
1263             // sync when being closed, all streams should have been
1264             // closed already. Keep the logic here for now.
1265             //
1266             // There are outstanding input streams open on existing "ch",
1267             // so, don't close the "cha" and delete the "file for now, let
1268             // the "ex-channel-closer" to handle them
1269             ExChannelCloser ecc = new ExChannelCloser(
1270                                       createTempFileInSameDirectoryAs(zfpath),
1271                                       ch,
1272                                       streams);
1273             Files.move(zfpath, ecc.path, REPLACE_EXISTING);
1274             exChClosers.add(ecc);
1275             streams = Collections.synchronizedSet(new HashSet<InputStream>());
1276         } else {
1277             ch.close();
1278             Files.delete(zfpath);
1279         }
1280 
1281         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1282         hasUpdate = false;    // clear
1283         /*
1284         if (isOpen) {
1285             ch = zfpath.newByteChannel(READ); // re-fresh "ch" and "cen"
1286             cen = initCEN();
1287         }
1288          */
1289         //System.out.printf("->sync(%s) done!%n", toString());
1290     }
1291 
1292     private IndexNode getInode(byte[] path) {
1293         if (path == null)
1294             throw new NullPointerException("path");
1295         IndexNode key = IndexNode.keyOf(path);
1296         IndexNode inode = inodes.get(key);
1297         if (inode == null &&
1298             (path.length == 0 || path[path.length -1] != '/')) {
1299             // if does not ends with a slash
1300             path = Arrays.copyOf(path, path.length + 1);
1301             path[path.length - 1] = '/';
1302             inode = inodes.get(key.as(path));
1303         }
1304         return inode;
1305     }
1306 
1307     private Entry getEntry0(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.pos);
1314     }
1315 
1316     public void deleteFile(byte[] path, boolean failIfNotExists)
1317         throws IOException
1318     {
1319         checkWritable();
1320 
1321         IndexNode inode = getInode(path);
1322         if (inode == null) {
1323             if (path != null && path.length == 0)
1324                 throw new ZipException("root directory </> can't not be delete");
1325             if (failIfNotExists)
1326                 throw new NoSuchFileException(getString(path));
1327         } else {
1328             if (inode.isDir() && inode.child != null)
1329                 throw new DirectoryNotEmptyException(getString(path));
1330             updateDelete(inode);
1331         }
1332     }
1333 
1334     private static void copyStream(InputStream is, OutputStream os)
1335         throws IOException
1336     {
1337         byte[] copyBuf = new byte[8192];
1338         int n;
1339         while ((n = is.read(copyBuf)) != -1) {
1340             os.write(copyBuf, 0, n);
1341         }
1342     }
1343 
1344     // Returns an out stream for either
1345     // (1) writing the contents of a new entry, if the entry exits, or
1346     // (2) updating/replacing the contents of the specified existing entry.
1347     private OutputStream getOutputStream(Entry e) throws IOException {
1348 
1349         if (e.mtime == -1)
1350             e.mtime = System.currentTimeMillis();
1351         if (e.method == -1)
1352             e.method = METHOD_DEFLATED;  // TBD:  use default method
1353         // store size, compressed size, and crc-32 in LOC header
1354         e.flag = 0;
1355         if (zc.isUTF8())
1356             e.flag |= FLAG_EFS;
1357         OutputStream os;
1358         if (useTempFile) {
1359             e.file = getTempPathForEntry(null);
1360             os = Files.newOutputStream(e.file, WRITE);
1361         } else {
1362             os = new ByteArrayOutputStream((e.size > 0)? (int)e.size : 8192);
1363         }
1364         return new EntryOutputStream(e, os);
1365     }
1366 
1367     private InputStream getInputStream(Entry e)
1368         throws IOException
1369     {
1370         InputStream eis = null;
1371 
1372         if (e.type == Entry.NEW) {
1373             if (e.bytes != null)
1374                 eis = new ByteArrayInputStream(e.bytes);
1375             else if (e.file != null)
1376                 eis = Files.newInputStream(e.file);
1377             else
1378                 throw new ZipException("update entry data is missing");
1379         } else if (e.type == Entry.FILECH) {
1380             // FILECH result is un-compressed.
1381             eis = Files.newInputStream(e.file);
1382             // TBD: wrap to hook close()
1383             // streams.add(eis);
1384             return eis;
1385         } else {  // untouced  CEN or COPY
1386             eis = new EntryInputStream(e, ch);
1387         }
1388         if (e.method == METHOD_DEFLATED) {
1389             // MORE: Compute good size for inflater stream:
1390             long bufSize = e.size + 2; // Inflater likes a bit of slack
1391             if (bufSize > 65536)
1392                 bufSize = 8192;
1393             final long size = e.size;
1394             eis = new InflaterInputStream(eis, getInflater(), (int)bufSize) {
1395 
1396                 private boolean isClosed = false;
1397                 public void close() throws IOException {
1398                     if (!isClosed) {
1399                         releaseInflater(inf);
1400                         this.in.close();
1401                         isClosed = true;
1402                         streams.remove(this);
1403                     }
1404                 }
1405                 // Override fill() method to provide an extra "dummy" byte
1406                 // at the end of the input stream. This is required when
1407                 // using the "nowrap" Inflater option. (it appears the new
1408                 // zlib in 7 does not need it, but keep it for now)
1409                 protected void fill() throws IOException {
1410                     if (eof) {
1411                         throw new EOFException(
1412                             "Unexpected end of ZLIB input stream");
1413                     }
1414                     len = this.in.read(buf, 0, buf.length);
1415                     if (len == -1) {
1416                         buf[0] = 0;
1417                         len = 1;
1418                         eof = true;
1419                     }
1420                     inf.setInput(buf, 0, len);
1421                 }
1422                 private boolean eof;
1423 
1424                 public int available() throws IOException {
1425                     if (isClosed)
1426                         return 0;
1427                     long avail = size - inf.getBytesWritten();
1428                     return avail > (long) Integer.MAX_VALUE ?
1429                         Integer.MAX_VALUE : (int) avail;
1430                 }
1431             };
1432         } else if (e.method == METHOD_STORED) {
1433             // TBD: wrap/ it does not seem necessary
1434         } else {
1435             throw new ZipException("invalid compression method");
1436         }
1437         streams.add(eis);
1438         return eis;
1439     }
1440 
1441     // Inner class implementing the input stream used to read
1442     // a (possibly compressed) zip file entry.
1443     private class EntryInputStream extends InputStream {
1444         private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
1445                                           // point to a new channel after sync()
1446         private   long pos;               // current position within entry data
1447         protected long rem;               // number of remaining bytes within entry
1448         protected final long size;        // uncompressed size of this entry
1449 
1450         EntryInputStream(Entry e, SeekableByteChannel zfch)
1451             throws IOException
1452         {
1453             this.zfch = zfch;
1454             rem = e.csize;
1455             size = e.size;
1456             pos = getDataPos(e);
1457         }
1458         public int read(byte b[], int off, int len) throws IOException {
1459             ensureOpen();
1460             if (rem == 0) {
1461                 return -1;
1462             }
1463             if (len <= 0) {
1464                 return 0;
1465             }
1466             if (len > rem) {
1467                 len = (int) rem;
1468             }
1469             // readFullyAt()
1470             long n = 0;
1471             ByteBuffer bb = ByteBuffer.wrap(b);
1472             bb.position(off);
1473             bb.limit(off + len);
1474             synchronized(zfch) {
1475                 n = zfch.position(pos).read(bb);
1476             }
1477             if (n > 0) {
1478                 pos += n;
1479                 rem -= n;
1480             }
1481             if (rem == 0) {
1482                 close();
1483             }
1484             return (int)n;
1485         }
1486         public int read() throws IOException {
1487             byte[] b = new byte[1];
1488             if (read(b, 0, 1) == 1) {
1489                 return b[0] & 0xff;
1490             } else {
1491                 return -1;
1492             }
1493         }
1494         public long skip(long n) throws IOException {
1495             ensureOpen();
1496             if (n > rem)
1497                 n = rem;
1498             pos += n;
1499             rem -= n;
1500             if (rem == 0) {
1501                 close();
1502             }
1503             return n;
1504         }
1505         public int available() {
1506             return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
1507         }
1508         public long size() {
1509             return size;
1510         }
1511         public void close() {
1512             rem = 0;
1513             streams.remove(this);
1514         }
1515     }
1516 
1517     class EntryOutputStream extends DeflaterOutputStream
1518     {
1519         private CRC32 crc;
1520         private Entry e;
1521         private long written;
1522 
1523         EntryOutputStream(Entry e, OutputStream os)
1524             throws IOException
1525         {
1526             super(os, getDeflater());
1527             if (e == null)
1528                 throw new NullPointerException("Zip entry is null");
1529             this.e = e;
1530             crc = new CRC32();
1531         }
1532 
1533         @Override
1534         public void write(byte b[], int off, int len) throws IOException {
1535             if (e.type != Entry.FILECH)    // only from sync
1536                 ensureOpen();
1537             if (off < 0 || len < 0 || off > b.length - len) {
1538                 throw new IndexOutOfBoundsException();
1539             } else if (len == 0) {
1540                 return;
1541             }
1542             switch (e.method) {
1543             case METHOD_DEFLATED:
1544                 super.write(b, off, len);
1545                 break;
1546             case METHOD_STORED:
1547                 written += len;
1548                 out.write(b, off, len);
1549                 break;
1550             default:
1551                 throw new ZipException("invalid compression method");
1552             }
1553             crc.update(b, off, len);
1554         }
1555 
1556         @Override
1557         public void close() throws IOException {
1558             // TBD ensureOpen();
1559             switch (e.method) {
1560             case METHOD_DEFLATED:
1561                 finish();
1562                 e.size  = def.getBytesRead();
1563                 e.csize = def.getBytesWritten();
1564                 e.crc = crc.getValue();
1565                 break;
1566             case METHOD_STORED:
1567                 // we already know that both e.size and e.csize are the same
1568                 e.size = e.csize = written;
1569                 e.crc = crc.getValue();
1570                 break;
1571             default:
1572                 throw new ZipException("invalid compression method");
1573             }
1574             //crc.reset();
1575             if (out instanceof ByteArrayOutputStream)
1576                 e.bytes = ((ByteArrayOutputStream)out).toByteArray();
1577 
1578             if (e.type == Entry.FILECH) {
1579                 releaseDeflater(def);
1580                 return;
1581             }
1582             super.close();
1583             releaseDeflater(def);
1584             update(e);
1585         }
1586     }
1587 
1588     static void zerror(String msg) {
1589         throw new ZipError(msg);
1590     }
1591 
1592     // Maxmum number of de/inflater we cache
1593     private final int MAX_FLATER = 20;
1594     // List of available Inflater objects for decompression
1595     private final List<Inflater> inflaters = new ArrayList<>();
1596 
1597     // Gets an inflater from the list of available inflaters or allocates
1598     // a new one.
1599     private Inflater getInflater() {
1600         synchronized (inflaters) {
1601             int size = inflaters.size();
1602             if (size > 0) {
1603                 Inflater inf = (Inflater)inflaters.remove(size - 1);
1604                 return inf;
1605             } else {
1606                 return new Inflater(true);
1607             }
1608         }
1609     }
1610 
1611     // Releases the specified inflater to the list of available inflaters.
1612     private void releaseInflater(Inflater inf) {
1613         synchronized (inflaters) {
1614             if (inflaters.size() < MAX_FLATER) {
1615                 inf.reset();
1616                 inflaters.add(inf);
1617             } else {
1618                 inf.end();
1619             }
1620         }
1621     }
1622 
1623     // List of available Deflater objects for compression
1624     private final List<Deflater> deflaters = new ArrayList<>();
1625 
1626     // Gets an deflater from the list of available deflaters or allocates
1627     // a new one.
1628     private Deflater getDeflater() {
1629         synchronized (deflaters) {
1630             int size = deflaters.size();
1631             if (size > 0) {
1632                 Deflater def = (Deflater)deflaters.remove(size - 1);
1633                 return def;
1634             } else {
1635                 return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
1636             }
1637         }
1638     }
1639 
1640     // Releases the specified inflater to the list of available inflaters.
1641     private void releaseDeflater(Deflater def) {
1642         synchronized (deflaters) {
1643             if (inflaters.size() < MAX_FLATER) {
1644                def.reset();
1645                deflaters.add(def);
1646             } else {
1647                def.end();
1648             }
1649         }
1650     }
1651 
1652     // End of central directory record
1653     static class END {
1654         int  disknum;
1655         int  sdisknum;
1656         int  endsub;     // endsub
1657         int  centot;     // 4 bytes
1658         long cenlen;     // 4 bytes
1659         long cenoff;     // 4 bytes
1660         int  comlen;     // comment length
1661         byte[] comment;
1662 
1663         /* members of Zip64 end of central directory locator */
1664         int diskNum;
1665         long endpos;
1666         int disktot;
1667 
1668         void write(OutputStream os, long offset) throws IOException {
1669             boolean hasZip64 = false;
1670             long xlen = cenlen;
1671             long xoff = cenoff;
1672             if (xlen >= ZIP64_MINVAL) {
1673                 xlen = ZIP64_MINVAL;
1674                 hasZip64 = true;
1675             }
1676             if (xoff >= ZIP64_MINVAL) {
1677                 xoff = ZIP64_MINVAL;
1678                 hasZip64 = true;
1679             }
1680             int count = centot;
1681             if (count >= ZIP64_MINVAL32) {
1682                 count = ZIP64_MINVAL32;
1683                 hasZip64 = true;
1684             }
1685             if (hasZip64) {
1686                 long off64 = offset;
1687                 //zip64 end of central directory record
1688                 writeInt(os, ZIP64_ENDSIG);       // zip64 END record signature
1689                 writeLong(os, ZIP64_ENDHDR - 12); // size of zip64 end
1690                 writeShort(os, 45);               // version made by
1691                 writeShort(os, 45);               // version needed to extract
1692                 writeInt(os, 0);                  // number of this disk
1693                 writeInt(os, 0);                  // central directory start disk
1694                 writeLong(os, centot);            // number of directory entires on disk
1695                 writeLong(os, centot);            // number of directory entires
1696                 writeLong(os, cenlen);            // length of central directory
1697                 writeLong(os, cenoff);            // offset of central directory
1698 
1699                 //zip64 end of central directory locator
1700                 writeInt(os, ZIP64_LOCSIG);       // zip64 END locator signature
1701                 writeInt(os, 0);                  // zip64 END start disk
1702                 writeLong(os, off64);             // offset of zip64 END
1703                 writeInt(os, 1);                  // total number of disks (?)
1704             }
1705             writeInt(os, ENDSIG);                 // END record signature
1706             writeShort(os, 0);                    // number of this disk
1707             writeShort(os, 0);                    // central directory start disk
1708             writeShort(os, count);                // number of directory entries on disk
1709             writeShort(os, count);                // total number of directory entries
1710             writeInt(os, xlen);                   // length of central directory
1711             writeInt(os, xoff);                   // offset of central directory
1712             if (comment != null) {            // zip file comment
1713                 writeShort(os, comment.length);
1714                 writeBytes(os, comment);
1715             } else {
1716                 writeShort(os, 0);
1717             }
1718         }
1719     }
1720 
1721     // Internal node that links a "name" to its pos in cen table.
1722     // The node itself can be used as a "key" to lookup itself in
1723     // the HashMap inodes.
1724     static class IndexNode {
1725         byte[] name;
1726         int    hashcode;  // node is hashable/hashed by its name
1727         int    pos = -1;  // postion in cen table, -1 menas the
1728                           // entry does not exists in zip file
1729         IndexNode(byte[] name, int pos) {
1730             name(name);
1731             this.pos = pos;
1732         }
1733 
1734         final static IndexNode keyOf(byte[] name) { // get a lookup key;
1735             return new IndexNode(name, -1);
1736         }
1737 
1738         final void name(byte[] name) {
1739             this.name = name;
1740             this.hashcode = Arrays.hashCode(name);
1741         }
1742 
1743         final IndexNode as(byte[] name) {           // reuse the node, mostly
1744             name(name);                             // as a lookup "key"
1745             return this;
1746         }
1747 
1748         boolean isDir() {
1749             return name != null &&
1750                    (name.length == 0 || name[name.length - 1] == '/');
1751         }
1752 
1753         public boolean equals(Object other) {
1754             if (!(other instanceof IndexNode)) {
1755                 return false;
1756             }
1757             return Arrays.equals(name, ((IndexNode)other).name);
1758         }
1759 
1760         public int hashCode() {
1761             return hashcode;
1762         }
1763 
1764         IndexNode() {}
1765         IndexNode sibling;
1766         IndexNode child;  // 1st child
1767     }
1768 
1769     static class Entry extends IndexNode {
1770 
1771         static final int CEN    = 1;    // entry read from cen
1772         static final int NEW    = 2;    // updated contents in bytes or file
1773         static final int FILECH = 3;    // fch update in "file"
1774         static final int COPY   = 4;    // copy of a CEN entry
1775 
1776 
1777         byte[] bytes;      // updated content bytes
1778         Path   file;       // use tmp file to store bytes;
1779         int    type = CEN; // default is the entry read from cen
1780 
1781         // entry attributes
1782         int    version;
1783         int    flag;
1784         int    method = -1;    // compression method
1785         long   mtime  = -1;    // last modification time (in DOS time)
1786         long   atime  = -1;    // last access time
1787         long   ctime  = -1;    // create time
1788         long   crc    = -1;    // crc-32 of entry data
1789         long   csize  = -1;    // compressed size of entry data
1790         long   size   = -1;    // uncompressed size of entry data
1791         byte[] extra;
1792 
1793         // cen
1794         int    versionMade;
1795         int    disk;
1796         int    attrs;
1797         long   attrsEx;
1798         long   locoff;
1799         byte[] comment;
1800 
1801         Entry() {}
1802 
1803         Entry(byte[] name) {
1804             name(name);
1805             this.mtime  = System.currentTimeMillis();
1806             this.crc    = 0;
1807             this.size   = 0;
1808             this.csize  = 0;
1809             this.method = METHOD_DEFLATED;
1810         }
1811 
1812         Entry(byte[] name, int type) {
1813             this(name);
1814             this.type = type;
1815         }
1816 
1817         Entry (Entry e, int type) {
1818             name(e.name);
1819             this.version   = e.version;
1820             this.ctime     = e.ctime;
1821             this.atime     = e.atime;
1822             this.mtime     = e.mtime;
1823             this.crc       = e.crc;
1824             this.size      = e.size;
1825             this.csize     = e.csize;
1826             this.method    = e.method;
1827             this.extra     = e.extra;
1828             this.versionMade = e.versionMade;
1829             this.disk      = e.disk;
1830             this.attrs     = e.attrs;
1831             this.attrsEx   = e.attrsEx;
1832             this.locoff    = e.locoff;
1833             this.comment   = e.comment;
1834             this.type      = type;
1835         }
1836 
1837         Entry (byte[] name, Path file, int type) {
1838             this(name, type);
1839             this.file = file;
1840             this.method = METHOD_STORED;
1841         }
1842 
1843         int version() throws ZipException {
1844             if (method == METHOD_DEFLATED)
1845                 return 20;
1846             else if (method == METHOD_STORED)
1847                 return 10;
1848             throw new ZipException("unsupported compression method");
1849         }
1850 
1851         ///////////////////// CEN //////////////////////
1852         static Entry readCEN(ZipFileSystem zipfs, int pos)
1853             throws IOException
1854         {
1855             return new Entry().cen(zipfs, pos);
1856         }
1857 
1858         private Entry cen(ZipFileSystem zipfs, int pos)
1859             throws IOException
1860         {
1861             byte[] cen = zipfs.cen;
1862             if (CENSIG(cen, pos) != CENSIG)
1863                 zerror("invalid CEN header (bad signature)");
1864             versionMade = CENVEM(cen, pos);
1865             version     = CENVER(cen, pos);
1866             flag        = CENFLG(cen, pos);
1867             method      = CENHOW(cen, pos);
1868             mtime       = dosToJavaTime(CENTIM(cen, pos));
1869             crc         = CENCRC(cen, pos);
1870             csize       = CENSIZ(cen, pos);
1871             size        = CENLEN(cen, pos);
1872             int nlen    = CENNAM(cen, pos);
1873             int elen    = CENEXT(cen, pos);
1874             int clen    = CENCOM(cen, pos);
1875             disk        = CENDSK(cen, pos);
1876             attrs       = CENATT(cen, pos);
1877             attrsEx     = CENATX(cen, pos);
1878             locoff      = CENOFF(cen, pos);
1879 
1880             pos += CENHDR;
1881             name(Arrays.copyOfRange(cen, pos, pos + nlen));
1882 
1883             pos += nlen;
1884             if (elen > 0) {
1885                 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1886                 pos += elen;
1887                 readExtra(zipfs);
1888             }
1889             if (clen > 0) {
1890                 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1891             }
1892             return this;
1893         }
1894 
1895         int writeCEN(OutputStream os) throws IOException
1896         {
1897             int written  = CENHDR;
1898             int version0 = version();
1899 
1900             long csize0  = csize;
1901             long size0   = size;
1902             long locoff0 = locoff;
1903             int elen64   = 0;                // extra for ZIP64
1904             int elenNTFS = 0;                // extra for NTFS (a/c/mtime)
1905             int elenEXTT = 0;                // extra for Extended Timestamp
1906 
1907             // confirm size/length
1908             int nlen = (name != null) ? name.length : 0;
1909             int elen = (extra != null) ? extra.length : 0;
1910             int clen = (comment != null) ? comment.length : 0;
1911             if (csize >= ZIP64_MINVAL) {
1912                 csize0 = ZIP64_MINVAL;
1913                 elen64 += 8;                 // csize(8)
1914             }
1915             if (size >= ZIP64_MINVAL) {
1916                 size0 = ZIP64_MINVAL;        // size(8)
1917                 elen64 += 8;
1918             }
1919             if (locoff >= ZIP64_MINVAL) {
1920                 locoff0 = ZIP64_MINVAL;
1921                 elen64 += 8;                 // offset(8)
1922             }
1923             if (elen64 != 0)
1924                 elen64 += 4;                 // header and data sz 4 bytes
1925 
1926             if (atime != -1) {
1927                 if (isWindows)               // use NTFS
1928                     elenNTFS = 36;           // total 36 bytes
1929                 else                         // Extended Timestamp otherwise
1930                     elenEXTT = 9;            // only mtime in cen
1931             }
1932             writeInt(os, CENSIG);            // CEN header signature
1933             if (elen64 != 0) {
1934                 writeShort(os, 45);          // ver 4.5 for zip64
1935                 writeShort(os, 45);
1936             } else {
1937                 writeShort(os, version0);    // version made by
1938                 writeShort(os, version0);    // version needed to extract
1939             }
1940             writeShort(os, flag);            // general purpose bit flag
1941             writeShort(os, method);          // compression method
1942                                              // last modification time
1943             writeInt(os, (int)javaToDosTime(mtime));
1944             writeInt(os, crc);               // crc-32
1945             writeInt(os, csize0);            // compressed size
1946             writeInt(os, size0);             // uncompressed size
1947             writeShort(os, name.length);
1948             writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
1949 
1950             if (comment != null) {
1951                 writeShort(os, Math.min(clen, 0xffff));
1952             } else {
1953                 writeShort(os, 0);
1954             }
1955             writeShort(os, 0);              // starting disk number
1956             writeShort(os, 0);              // internal file attributes (unused)
1957             writeInt(os, 0);                // external file attributes (unused)
1958             writeInt(os, locoff0);          // relative offset of local header
1959             writeBytes(os, name);
1960             if (elen64 != 0) {
1961                 writeShort(os, EXTID_ZIP64);// Zip64 extra
1962                 writeShort(os, elen64);     // size of "this" extra block
1963                 if (size0 == ZIP64_MINVAL)
1964                     writeLong(os, size);
1965                 if (csize0 == ZIP64_MINVAL)
1966                     writeLong(os, csize);
1967                 if (locoff0 == ZIP64_MINVAL)
1968                     writeLong(os, locoff);
1969             }
1970             if (elenNTFS != 0) {
1971                 // System.out.println("writing NTFS:" + elenNTFS);
1972                 writeShort(os, EXTID_NTFS);
1973                 writeShort(os, elenNTFS - 4);
1974                 writeInt(os, 0);            // reserved
1975                 writeShort(os, 0x0001);     // NTFS attr tag
1976                 writeShort(os, 24);
1977                 writeLong(os, javaToWinTime(mtime));
1978                 writeLong(os, javaToWinTime(atime));
1979                 writeLong(os, javaToWinTime(ctime));
1980             }
1981             if (elenEXTT != 0) {
1982                 writeShort(os, EXTID_EXTT);
1983                 writeShort(os, elenEXTT - 4);
1984                 if (ctime == -1)
1985                     os.write(0x3);          // mtime and atime
1986                 else
1987                     os.write(0x7);          // mtime, atime and ctime
1988                 writeInt(os, javaToUnixTime(mtime));
1989             }
1990             if (extra != null)              // whatever not recognized
1991                 writeBytes(os, extra);
1992             if (comment != null)            //TBD: 0, Math.min(commentBytes.length, 0xffff));
1993                 writeBytes(os, comment);
1994             return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
1995         }
1996 
1997         ///////////////////// LOC //////////////////////
1998         static Entry readLOC(ZipFileSystem zipfs, long pos)
1999             throws IOException
2000         {
2001             return readLOC(zipfs, pos, new byte[1024]);
2002         }
2003 
2004         static Entry readLOC(ZipFileSystem zipfs, long pos, byte[] buf)
2005             throws IOException
2006         {
2007             return new Entry().loc(zipfs, pos, buf);
2008         }
2009 
2010         Entry loc(ZipFileSystem zipfs, long pos, byte[] buf)
2011             throws IOException
2012         {
2013             assert (buf.length >= LOCHDR);
2014             if (zipfs.readFullyAt(buf, 0, LOCHDR , pos) != LOCHDR)
2015                 throw new ZipException("loc: reading failed");
2016             if (LOCSIG(buf) != LOCSIG)
2017                 throw new ZipException("loc: wrong sig ->"
2018                                        + Long.toString(LOCSIG(buf), 16));
2019             //startPos = pos;
2020             version  = LOCVER(buf);
2021             flag     = LOCFLG(buf);
2022             method   = LOCHOW(buf);
2023             mtime    = dosToJavaTime(LOCTIM(buf));
2024             crc      = LOCCRC(buf);
2025             csize    = LOCSIZ(buf);
2026             size     = LOCLEN(buf);
2027             int nlen = LOCNAM(buf);
2028             int elen = LOCEXT(buf);
2029 
2030             name = new byte[nlen];
2031             if (zipfs.readFullyAt(name, 0, nlen, pos + LOCHDR) != nlen) {
2032                 throw new ZipException("loc: name reading failed");
2033             }
2034             if (elen > 0) {
2035                 extra = new byte[elen];
2036                 if (zipfs.readFullyAt(extra, 0, elen, pos + LOCHDR + nlen)
2037                     != elen) {
2038                     throw new ZipException("loc: ext reading failed");
2039                 }
2040             }
2041             pos += (LOCHDR + nlen + elen);
2042             if ((flag & FLAG_DATADESCR) != 0) {
2043                 // Data Descriptor
2044                 Entry e = zipfs.getEntry0(name);  // get the size/csize from cen
2045                 if (e == null)
2046                     throw new ZipException("loc: name not found in cen");
2047                 size = e.size;
2048                 csize = e.csize;
2049                 pos += (method == METHOD_STORED ? size : csize);
2050                 if (size >= ZIP64_MINVAL || csize >= ZIP64_MINVAL)
2051                     pos += 24;
2052                 else
2053                     pos += 16;
2054             } else {
2055                 if (extra != null &&
2056                     (size == ZIP64_MINVAL || csize == ZIP64_MINVAL)) {
2057                     // zip64 ext: must include both size and csize
2058                     int off = 0;
2059                     while (off + 20 < elen) {    // HeaderID+DataSize+Data
2060                         int sz = SH(extra, off + 2);
2061                         if (SH(extra, off) == EXTID_ZIP64 && sz == 16) {
2062                             size = LL(extra, off + 4);
2063                             csize = LL(extra, off + 12);
2064                             break;
2065                         }
2066                         off += (sz + 4);
2067                     }
2068                 }
2069                 pos += (method == METHOD_STORED ? size : csize);
2070             }
2071             return this;
2072         }
2073 
2074         int writeLOC(OutputStream os)
2075             throws IOException
2076         {
2077             writeInt(os, LOCSIG);               // LOC header signature
2078             int version = version();
2079 
2080             int nlen = (name != null) ? name.length : 0;
2081             int elen = (extra != null) ? extra.length : 0;
2082             int elen64 = 0;
2083             int elenEXTT = 0;
2084             if ((flag & FLAG_DATADESCR) != 0) {
2085                 writeShort(os, version());      // version needed to extract
2086                 writeShort(os, flag);           // general purpose bit flag
2087                 writeShort(os, method);         // compression method
2088                 // last modification time
2089                 writeInt(os, (int)javaToDosTime(mtime));
2090                 // store size, uncompressed size, and crc-32 in data descriptor
2091                 // immediately following compressed entry data
2092                 writeInt(os, 0);
2093                 writeInt(os, 0);
2094                 writeInt(os, 0);
2095             } else {
2096                 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2097                     elen64 = 20;    //headid(2) + size(2) + size(8) + csize(8)
2098                     writeShort(os, 45);         // ver 4.5 for zip64
2099                 } else {
2100                     writeShort(os, version());  // version needed to extract
2101                 }
2102                 writeShort(os, flag);           // general purpose bit flag
2103                 writeShort(os, method);         // compression method
2104                                                 // last modification time
2105                 writeInt(os, (int)javaToDosTime(mtime));
2106                 writeInt(os, crc);              // crc-32
2107                 if (elen64 != 0) {
2108                     writeInt(os, ZIP64_MINVAL);
2109                     writeInt(os, ZIP64_MINVAL);
2110                 } else {
2111                     writeInt(os, csize);        // compressed size
2112                     writeInt(os, size);         // uncompressed size
2113                 }
2114             }
2115             if (atime != -1 && !isWindows) {    // on unix use "ext time"
2116                 if (ctime == -1)
2117                     elenEXTT = 13;
2118                 else
2119                     elenEXTT = 17;
2120             }
2121             writeShort(os, name.length);
2122             writeShort(os, elen + elen64 + elenEXTT);
2123             writeBytes(os, name);
2124             if (elen64 != 0) {
2125                 writeShort(os, EXTID_ZIP64);
2126                 writeShort(os, 16);
2127                 writeLong(os, size);
2128                 writeLong(os, csize);
2129             }
2130             if (elenEXTT != 0) {
2131                 writeShort(os, EXTID_EXTT);
2132                 writeShort(os, elenEXTT - 4);// size for the folowing data block
2133                 if (ctime == -1)
2134                     os.write(0x3);           // mtime and atime
2135                 else
2136                     os.write(0x7);           // mtime, atime and ctime
2137                 writeInt(os, javaToUnixTime(mtime));
2138                 writeInt(os, javaToUnixTime(atime));
2139                 if (ctime != -1)
2140                     writeInt(os, javaToUnixTime(ctime));
2141             }
2142             if (extra != null) {
2143                 writeBytes(os, extra);
2144             }
2145             return LOCHDR + name.length + elen + elen64 + elenEXTT;
2146         }
2147 
2148         // Data Descriptior
2149         int writeEXT(OutputStream os)
2150             throws IOException
2151         {
2152             writeInt(os, EXTSIG);           // EXT header signature
2153             writeInt(os, crc);              // crc-32
2154             if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2155                 writeLong(os, csize);
2156                 writeLong(os, size);
2157                 return 24;
2158             } else {
2159                 writeInt(os, csize);        // compressed size
2160                 writeInt(os, size);         // uncompressed size
2161                 return 16;
2162             }
2163         }
2164 
2165         // read NTFS, UNIX and ZIP64 data from cen.extra
2166         void readExtra(ZipFileSystem zipfs) throws IOException {
2167             if (extra == null)
2168                 return;
2169             int elen = extra.length;
2170             int off = 0;
2171             int newOff = 0;
2172             while (off + 4 < elen) {
2173                 // extra spec: HeaderID+DataSize+Data
2174                 int pos = off;
2175                 int tag = SH(extra, pos);
2176                 int sz = SH(extra, pos + 2);
2177                 pos += 4;
2178                 if (pos + sz > elen)         // invalid data
2179                     break;
2180                 switch (tag) {
2181                 case EXTID_ZIP64 :
2182                     if (size == ZIP64_MINVAL) {
2183                         if (pos + 8 > elen)  // invalid zip64 extra
2184                             break;           // fields, just skip
2185                         size = LL(extra, pos);
2186                         pos += 8;
2187                     }
2188                     if (csize == ZIP64_MINVAL) {
2189                         if (pos + 8 > elen)
2190                             break;
2191                         csize = LL(extra, pos);
2192                         pos += 8;
2193                     }
2194                     if (locoff == ZIP64_MINVAL) {
2195                         if (pos + 8 > elen)
2196                             break;
2197                         locoff = LL(extra, pos);
2198                         pos += 8;
2199                     }
2200                     break;
2201                 case EXTID_NTFS:
2202                     pos += 4;    // reserved 4 bytes
2203                     if (SH(extra, pos) !=  0x0001)
2204                         break;
2205                     if (SH(extra, pos + 2) != 24)
2206                         break;
2207                     // override the loc field, datatime here is
2208                     // more "accurate"
2209                     mtime  = winToJavaTime(LL(extra, pos + 4));
2210                     atime  = winToJavaTime(LL(extra, pos + 12));
2211                     ctime  = winToJavaTime(LL(extra, pos + 20));
2212                     break;
2213                 case EXTID_EXTT:
2214                     // spec says the Extened timestamp in cen only has mtime
2215                     // need to read the loc to get the extra a/ctime
2216                     byte[] buf = new byte[LOCHDR];
2217                     if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
2218                         != buf.length)
2219                         throw new ZipException("loc: reading failed");
2220                     if (LOCSIG(buf) != LOCSIG)
2221                         throw new ZipException("loc: wrong sig ->"
2222                                            + Long.toString(LOCSIG(buf), 16));
2223 
2224                     int locElen = LOCEXT(buf);
2225                     if (locElen < 9)    // EXTT is at lease 9 bytes
2226                         break;
2227                     int locNlen = LOCNAM(buf);
2228                     buf = new byte[locElen];
2229                     if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
2230                         != buf.length)
2231                         throw new ZipException("loc extra: reading failed");
2232                     int locPos = 0;
2233                     while (locPos + 4 < buf.length) {
2234                         int locTag = SH(buf, locPos);
2235                         int locSZ  = SH(buf, locPos + 2);
2236                         locPos += 4;
2237                         if (locTag  != EXTID_EXTT) {
2238                             locPos += locSZ;
2239                              continue;
2240                         }
2241                         int flag = CH(buf, locPos++);
2242                         if ((flag & 0x1) != 0) {
2243                             mtime = unixToJavaTime(LG(buf, locPos));
2244                             locPos += 4;
2245                         }
2246                         if ((flag & 0x2) != 0) {
2247                             atime = unixToJavaTime(LG(buf, locPos));
2248                             locPos += 4;
2249                         }
2250                         if ((flag & 0x4) != 0) {
2251                             ctime = unixToJavaTime(LG(buf, locPos));
2252                             locPos += 4;
2253                         }
2254                         break;
2255                     }
2256                     break;
2257                 default:    // unknown tag
2258                     System.arraycopy(extra, off, extra, newOff, sz + 4);
2259                     newOff += (sz + 4);
2260                 }
2261                 off += (sz + 4);
2262             }
2263             if (newOff != 0 && newOff != extra.length)
2264                 extra = Arrays.copyOf(extra, newOff);
2265             else
2266                 extra = null;
2267         }
2268     }
2269 
2270     private static class ExChannelCloser  {
2271         Path path;
2272         SeekableByteChannel ch;
2273         Set<InputStream> streams;
2274         ExChannelCloser(Path path,
2275                         SeekableByteChannel ch,
2276                         Set<InputStream> streams)
2277         {
2278             this.path = path;
2279             this.ch = ch;
2280             this.streams = streams;
2281         }
2282     }
2283 
2284     // ZIP directory has two issues:
2285     // (1) ZIP spec does not require the ZIP file to include
2286     //     directory entry
2287     // (2) all entries are not stored/organized in a "tree"
2288     //     structure.
2289     // A possible solution is to build the node tree ourself as
2290     // implemented below.
2291     private IndexNode root;
2292 
2293     private void addToTree(IndexNode inode, HashSet<IndexNode> dirs) {
2294         if (dirs.contains(inode)) {
2295             return;
2296         }
2297         IndexNode parent;
2298         byte[] name = inode.name;
2299         byte[] pname = getParent(name);
2300         if (inodes.containsKey(LOOKUPKEY.as(pname))) {
2301             parent = inodes.get(LOOKUPKEY);
2302         } else {    // pseudo directory entry
2303             parent = new IndexNode(pname, -1);
2304             inodes.put(parent, parent);
2305         }
2306         addToTree(parent, dirs);
2307         inode.sibling = parent.child;
2308         parent.child = inode;
2309         if (name[name.length -1] == '/')
2310             dirs.add(inode);
2311     }
2312 
2313     private void removeFromTree(IndexNode inode) {
2314         IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2315         IndexNode child = parent.child;
2316         if (child == inode) {
2317             parent.child = child.sibling;
2318         } else {
2319             IndexNode last = child;
2320             while ((child = child.sibling) != null) {
2321                 if (child == inode) {
2322                     last.sibling = child.sibling;
2323                     break;
2324                 } else {
2325                     last = child;
2326                 }
2327             }
2328         }
2329     }
2330 
2331     private void buildNodeTree() throws IOException {
2332         beginWrite();
2333         try {
2334             HashSet<IndexNode> dirs = new HashSet<>();
2335             IndexNode root = new IndexNode(ROOTPATH, -1);
2336             inodes.put(root, root);
2337             dirs.add(root);
2338             for (IndexNode node : inodes.keySet().toArray(new IndexNode[0])) {
2339                 addToTree(node, dirs);
2340             }
2341         } finally {
2342             endWrite();
2343         }
2344     }
2345 }