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