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