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