< prev index next >

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

Print this page
rev 54189 : 8213031: (zipfs) Add support for POSIX file permissions


  26 package jdk.nio.zipfs;
  27 
  28 import java.io.BufferedOutputStream;
  29 import java.io.ByteArrayInputStream;
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.EOFException;
  32 import java.io.FilterOutputStream;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.io.OutputStream;
  36 import java.nio.ByteBuffer;
  37 import java.nio.MappedByteBuffer;
  38 import java.nio.channels.FileChannel;
  39 import java.nio.channels.FileLock;
  40 import java.nio.channels.ReadableByteChannel;
  41 import java.nio.channels.SeekableByteChannel;
  42 import java.nio.channels.WritableByteChannel;
  43 import java.nio.file.*;
  44 import java.nio.file.attribute.FileAttribute;
  45 import java.nio.file.attribute.FileTime;




  46 import java.nio.file.attribute.UserPrincipalLookupService;
  47 import java.nio.file.spi.FileSystemProvider;
  48 import java.security.AccessController;
  49 import java.security.PrivilegedAction;
  50 import java.security.PrivilegedActionException;
  51 import java.security.PrivilegedExceptionAction;
  52 import java.util.*;
  53 import java.util.concurrent.locks.ReadWriteLock;
  54 import java.util.concurrent.locks.ReentrantReadWriteLock;
  55 import java.util.regex.Pattern;
  56 import java.util.zip.CRC32;
  57 import java.util.zip.Deflater;
  58 import java.util.zip.DeflaterOutputStream;
  59 import java.util.zip.Inflater;
  60 import java.util.zip.InflaterInputStream;
  61 import java.util.zip.ZipException;
  62 
  63 import static java.lang.Boolean.TRUE;
  64 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
  65 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  66 import static java.nio.file.StandardOpenOption.APPEND;
  67 import static java.nio.file.StandardOpenOption.CREATE;
  68 import static java.nio.file.StandardOpenOption.CREATE_NEW;
  69 import static java.nio.file.StandardOpenOption.READ;
  70 import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
  71 import static java.nio.file.StandardOpenOption.WRITE;
  72 import static jdk.nio.zipfs.ZipConstants.*;
  73 import static jdk.nio.zipfs.ZipUtils.*;
  74 
  75 /**
  76  * A FileSystem built on a zip file
  77  *
  78  * @author Xueming Shen
  79  */
  80 class ZipFileSystem extends FileSystem {













  81     private final ZipFileSystemProvider provider;
  82     private final Path zfpath;
  83     final ZipCoder zc;
  84     private final ZipPath rootdir;
  85     private boolean readOnly = false;    // readonly file system
  86 
  87     // configurable by env map
  88     private final boolean noExtt;        // see readExtra()
  89     private final boolean useTempFile;   // use a temp file for newOS, default
  90                                          // is to use BAOS for better performance
  91     private static final boolean isWindows = AccessController.doPrivileged(
  92             (PrivilegedAction<Boolean>)() -> System.getProperty("os.name")
  93                                                    .startsWith("Windows"));
  94     private final boolean forceEnd64;
  95     private final int defaultMethod;     // METHOD_STORED if "noCompression=true"
  96                                          // METHOD_DEFLATED otherwise
  97 








  98     ZipFileSystem(ZipFileSystemProvider provider,
  99                   Path zfpath,
 100                   Map<String, ?> env) throws IOException
 101     {
 102         // default encoding for name/comment
 103         String nameEncoding = env.containsKey("encoding") ?
 104                               (String)env.get("encoding") : "UTF-8";
 105         this.noExtt = "false".equals(env.get("zipinfo-time"));
 106         this.useTempFile  = isTrue(env, "useTempFile");
 107         this.forceEnd64 = isTrue(env, "forceZIP64End");
 108         this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED;






 109         if (Files.notExists(zfpath)) {
 110             // create a new zip if not exists
 111             if (isTrue(env, "create")) {
 112                 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
 113                     new END().write(os, 0, forceEnd64);
 114                 }
 115             } else {
 116                 throw new FileSystemNotFoundException(zfpath.toString());
 117             }
 118         }
 119         // sm and existence check
 120         zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
 121         boolean writeable = AccessController.doPrivileged(
 122             (PrivilegedAction<Boolean>) () ->  Files.isWritable(zfpath));
 123         this.readOnly = !writeable;
 124         this.zc = ZipCoder.get(nameEncoding);
 125         this.rootdir = new ZipPath(this, new byte[]{'/'});
 126         this.ch = Files.newByteChannel(zfpath, READ);
 127         try {
 128             this.cen = initCEN();
 129         } catch (IOException x) {
 130             try {
 131                 this.ch.close();
 132             } catch (IOException xx) {
 133                 x.addSuppressed(xx);
 134             }
 135             throw x;
 136         }
 137         this.provider = provider;
 138         this.zfpath = zfpath;
 139     }
 140 
 141     // returns true if there is a name=true/"true" setting in env
 142     private static boolean isTrue(Map<String, ?> env, String name) {
 143         return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
 144     }
 145 




















































































 146     @Override
 147     public FileSystemProvider provider() {
 148         return provider;
 149     }
 150 
 151     @Override
 152     public String getSeparator() {
 153         return "/";
 154     }
 155 
 156     @Override
 157     public boolean isOpen() {
 158         return isOpen;
 159     }
 160 
 161     @Override
 162     public boolean isReadOnly() {
 163         return readOnly;
 164     }
 165 


 201 
 202     @Override
 203     public UserPrincipalLookupService getUserPrincipalLookupService() {
 204         throw new UnsupportedOperationException();
 205     }
 206 
 207     @Override
 208     public WatchService newWatchService() {
 209         throw new UnsupportedOperationException();
 210     }
 211 
 212     FileStore getFileStore(ZipPath path) {
 213         return new ZipFileStore(path);
 214     }
 215 
 216     @Override
 217     public Iterable<FileStore> getFileStores() {
 218         return List.of(new ZipFileStore(rootdir));
 219     }
 220 
 221     private static final Set<String> supportedFileAttributeViews =
 222             Set.of("basic", "zip");
 223 
 224     @Override
 225     public Set<String> supportedFileAttributeViews() {
 226         return supportedFileAttributeViews;
 227     }
 228 
 229     @Override
 230     public String toString() {
 231         return zfpath.toString();
 232     }
 233 
 234     Path getZipFile() {
 235         return zfpath;
 236     }
 237 
 238     private static final String GLOB_SYNTAX = "glob";
 239     private static final String REGEX_SYNTAX = "regex";
 240 
 241     @Override
 242     public PathMatcher getPathMatcher(String syntaxAndInput) {
 243         int pos = syntaxAndInput.indexOf(':');


 354             if (getInode(path) == null) {
 355                 throw new NoSuchFileException(toString());
 356             }
 357 
 358         } finally {
 359             endRead();
 360         }
 361     }
 362 
 363     void setTimes(byte[] path, FileTime mtime, FileTime atime, FileTime ctime)
 364         throws IOException
 365     {
 366         checkWritable();
 367         beginWrite();
 368         try {
 369             ensureOpen();
 370             Entry e = getEntry(path);    // ensureOpen checked
 371             if (e == null)
 372                 throw new NoSuchFileException(getString(path));
 373             if (e.type == Entry.CEN)
 374                 e.type = Entry.COPY;      // copy e
 375             if (mtime != null)
 376                 e.mtime = mtime.toMillis();
 377             if (atime != null)
 378                 e.atime = atime.toMillis();
 379             if (ctime != null)
 380                 e.ctime = ctime.toMillis();
 381             update(e);
 382         } finally {
 383             endWrite();
 384         }
 385     }
 386 

























































 387     boolean exists(byte[] path)
 388         throws IOException
 389     {
 390         beginRead();
 391         try {
 392             ensureOpen();
 393             return getInode(path) != null;
 394         } finally {
 395             endRead();
 396         }
 397     }
 398 
 399     boolean isDirectory(byte[] path)
 400         throws IOException
 401     {
 402         beginRead();
 403         try {
 404             IndexNode n = getInode(path);
 405             return n != null && n.isDir();
 406         } finally {


 436                     list.add(zpath);
 437                 child = child.sibling;
 438             }
 439             return list.iterator();
 440         } finally {
 441             endWrite();
 442         }
 443     }
 444 
 445     void createDirectory(byte[] dir, FileAttribute<?>... attrs)
 446         throws IOException
 447     {
 448         checkWritable();
 449         //  dir = toDirectoryPath(dir);
 450         beginWrite();
 451         try {
 452             ensureOpen();
 453             if (dir.length == 0 || exists(dir))  // root dir, or exiting dir
 454                 throw new FileAlreadyExistsException(getString(dir));
 455             checkParents(dir);
 456             Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
 457             update(e);
 458         } finally {
 459             endWrite();
 460         }
 461     }
 462 
 463     void copyFile(boolean deletesrc, byte[]src, byte[] dst, CopyOption... options)
 464         throws IOException
 465     {
 466         checkWritable();
 467         if (Arrays.equals(src, dst))
 468             return;    // do nothing, src and dst are the same
 469 
 470         beginWrite();
 471         try {
 472             ensureOpen();
 473             Entry eSrc = getEntry(src);  // ensureOpen checked
 474 
 475             if (eSrc == null)
 476                 throw new NoSuchFileException(getString(src));


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


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


1252                             // entry copy: the only thing changed is the "name"
1253                             // and "nlen" in LOC header, so we udpate/rewrite the
1254                             // LOC in new file and simply copy the rest (data and
1255                             // ext) without enflating/deflating from the old zip
1256                             // file LOC entry.
1257                             written += copyLOCEntry(e, true, os, written, buf);
1258                         } else {                          // NEW, FILECH or CEN
1259                             e.locoff = written;
1260                             written += e.writeLOC(os);    // write loc header
1261                             written += writeEntry(e, os, buf);
1262                         }
1263                         elist.add(e);
1264                     } catch (IOException x) {
1265                         x.printStackTrace();    // skip any in-accurate entry
1266                     }
1267                 } else {                        // unchanged inode
1268                     if (inode.pos == -1) {
1269                         continue;               // pseudo directory node
1270                     }
1271                     if (inode.name.length == 1 && inode.name[0] == '/') {
1272                         continue;               // no root '/' directory even it
1273                                                 // exits in original zip/jar file.
1274                     }
1275                     e = Entry.readCEN(this, inode);
1276                     try {
1277                         written += copyLOCEntry(e, false, os, written, buf);
1278                         elist.add(e);
1279                     } catch (IOException x) {
1280                         x.printStackTrace();    // skip any wrong entry
1281                     }
1282                 }
1283             }
1284 
1285             // now write back the cen and end table
1286             end.cenoff = written;
1287             for (Entry entry : elist) {
1288                 written += entry.writeCEN(os);
1289             }
1290             end.centot = elist.size();
1291             end.cenlen = written - end.cenoff;
1292             end.write(os, written, forceEnd64);
1293         }
1294 
1295         ch.close();
1296         Files.delete(zfpath);
1297         Files.move(tmpFile, zfpath, REPLACE_EXISTING);
1298         hasUpdate = false;    // clear
1299     }
1300 
1301     IndexNode getInode(byte[] path) {
1302         if (path == null)
1303             throw new NullPointerException("path");
1304         return inodes.get(IndexNode.keyOf(path));
1305     }
1306 
1307     Entry getEntry(byte[] path) throws IOException {
1308         IndexNode inode = getInode(path);
1309         if (inode instanceof Entry)
1310             return (Entry)inode;
1311         if (inode == null || inode.pos == -1)
1312             return null;
1313         return Entry.readCEN(this, inode);
1314     }
1315 
1316     public void deleteFile(byte[] path, boolean failIfNotExists)
1317         throws IOException
1318     {
1319         checkWritable();
1320 
1321         IndexNode inode = getInode(path);
1322         if (inode == null) {
1323             if (path != null && path.length == 0)
1324                 throw new ZipException("root directory </> can't not be delete");
1325             if (failIfNotExists)
1326                 throw new NoSuchFileException(getString(path));
1327         } else {
1328             if (inode.isDir() && inode.child != null)
1329                 throw new DirectoryNotEmptyException(getString(path));
1330             updateDelete(inode);
1331         }
1332     }
1333 


1761             writeShort(os, 0);                    // central directory start disk
1762             writeShort(os, count);                // number of directory entries on disk
1763             writeShort(os, count);                // total number of directory entries
1764             writeInt(os, xlen);                   // length of central directory
1765             writeInt(os, xoff);                   // offset of central directory
1766             if (comment != null) {            // zip file comment
1767                 writeShort(os, comment.length);
1768                 writeBytes(os, comment);
1769             } else {
1770                 writeShort(os, 0);
1771             }
1772         }
1773     }
1774 
1775     // Internal node that links a "name" to its pos in cen table.
1776     // The node itself can be used as a "key" to lookup itself in
1777     // the HashMap inodes.
1778     static class IndexNode {
1779         byte[] name;
1780         int    hashcode;  // node is hashable/hashed by its name
1781         int    pos = -1;  // position in cen table, -1 menas the
1782                           // entry does not exists in zip file
1783         boolean isdir;
1784 
1785         IndexNode(byte[] name, boolean isdir) {
1786             name(name);
1787             this.isdir = isdir;
1788             this.pos = -1;
1789         }
1790 
1791         IndexNode(byte[] name, int pos) {
1792             name(name);
1793             this.pos = pos;
1794         }
1795 
1796         // constructor for cenInit() (1) remove tailing '/' (2) pad leading '/'
1797         IndexNode(byte[] cen, int pos, int nlen) {
1798             int noff = pos + CENHDR;
1799             if (cen[noff + nlen - 1] == '/') {
1800                 isdir = true;
1801                 nlen--;
1802             }


1838 
1839         public boolean equals(Object other) {
1840             if (!(other instanceof IndexNode)) {
1841                 return false;
1842             }
1843             if (other instanceof ParentLookup) {
1844                 return ((ParentLookup)other).equals(this);
1845             }
1846             return Arrays.equals(name, ((IndexNode)other).name);
1847         }
1848 
1849         public int hashCode() {
1850             return hashcode;
1851         }
1852 
1853         IndexNode() {}
1854         IndexNode sibling;
1855         IndexNode child;  // 1st child
1856     }
1857 
1858     static class Entry extends IndexNode implements ZipFileAttributes {
1859 
1860         static final int CEN    = 1;  // entry read from cen
1861         static final int NEW    = 2;  // updated contents in bytes or file
1862         static final int FILECH = 3;  // fch update in "file"
1863         static final int COPY   = 4;  // copy of a CEN entry
1864 
1865         byte[] bytes;                 // updated content bytes
1866         Path   file;                  // use tmp file to store bytes;
1867         int    type = CEN;            // default is the entry read from cen
1868 
1869         // entry attributes
1870         int    version;
1871         int    flag;

1872         int    method = -1;    // compression method
1873         long   mtime  = -1;    // last modification time (in DOS time)
1874         long   atime  = -1;    // last access time
1875         long   ctime  = -1;    // create time
1876         long   crc    = -1;    // crc-32 of entry data
1877         long   csize  = -1;    // compressed size of entry data
1878         long   size   = -1;    // uncompressed size of entry data
1879         byte[] extra;
1880 
1881         // cen
1882 
1883         // these fields are not used by anyone and writeCEN uses "0"
1884         // int    versionMade;
1885         // int    disk;
1886         // int    attrs;
1887         // long   attrsEx;
1888         long   locoff;
1889         byte[] comment;
1890 




1891         Entry() {}
1892 
1893         Entry(byte[] name, boolean isdir, int method) {
1894             name(name);
1895             this.isdir = isdir;
1896             this.mtime  = this.ctime = this.atime = System.currentTimeMillis();
1897             this.crc    = 0;
1898             this.size   = 0;
1899             this.csize  = 0;
1900             this.method = method;
1901         }
1902 
1903         Entry(byte[] name, int type, boolean isdir, int method) {

1904             this(name, isdir, method);
1905             this.type = type;






1906         }
1907 
1908         Entry (Entry e, int type) {
1909             name(e.name);
1910             this.isdir     = e.isdir;
1911             this.version   = e.version;
1912             this.ctime     = e.ctime;
1913             this.atime     = e.atime;
1914             this.mtime     = e.mtime;
1915             this.crc       = e.crc;
1916             this.size      = e.size;
1917             this.csize     = e.csize;
1918             this.method    = e.method;
1919             this.extra     = e.extra;
1920             /*
1921             this.versionMade = e.versionMade;
1922             this.disk      = e.disk;
1923             this.attrs     = e.attrs;
1924             this.attrsEx   = e.attrsEx;
1925             */
1926             this.locoff    = e.locoff;
1927             this.comment   = e.comment;



1928             this.type      = type;
1929         }
1930 
1931         Entry (byte[] name, Path file, int type) {

1932             this(name, type, false, METHOD_STORED);
1933             this.file = file;






1934         }
1935 
1936         int version() throws ZipException {
1937             if (method == METHOD_DEFLATED)
1938                 return 20;
1939             else if (method == METHOD_STORED)
1940                 return 10;
1941             throw new ZipException("unsupported compression method");
1942         }
1943 
1944         ///////////////////// CEN //////////////////////
1945         static Entry readCEN(ZipFileSystem zipfs, IndexNode inode)
1946             throws IOException
1947         {
1948             return new Entry().cen(zipfs, inode);
1949         }
1950 
1951         private Entry cen(ZipFileSystem zipfs, IndexNode inode)
1952             throws IOException
1953         {
1954             byte[] cen = zipfs.cen;
1955             int pos = inode.pos;
1956             if (!cenSigAt(cen, pos))
1957                 zerror("invalid CEN header (bad signature)");
1958             version     = CENVER(cen, pos);
1959             flag        = CENFLG(cen, pos);
1960             method      = CENHOW(cen, pos);
1961             mtime       = dosToJavaTime(CENTIM(cen, pos));
1962             crc         = CENCRC(cen, pos);
1963             csize       = CENSIZ(cen, pos);
1964             size        = CENLEN(cen, pos);
1965             int nlen    = CENNAM(cen, pos);
1966             int elen    = CENEXT(cen, pos);
1967             int clen    = CENCOM(cen, pos);
1968             /*
1969             versionMade = CENVEM(cen, pos);
1970             disk        = CENDSK(cen, pos);
1971             attrs       = CENATT(cen, pos);
1972             attrsEx     = CENATX(cen, pos);
1973             */



1974             locoff      = CENOFF(cen, pos);
1975             pos += CENHDR;
1976             this.name = inode.name;
1977             this.isdir = inode.isdir;
1978             this.hashcode = inode.hashcode;
1979 
1980             pos += nlen;
1981             if (elen > 0) {
1982                 extra = Arrays.copyOfRange(cen, pos, pos + elen);
1983                 pos += elen;
1984                 readExtra(zipfs);
1985             }
1986             if (clen > 0) {
1987                 comment = Arrays.copyOfRange(cen, pos, pos + clen);
1988             }
1989             return this;
1990         }
1991 





















1992         int writeCEN(OutputStream os) throws IOException {
1993             int version0 = version();
1994             long csize0  = csize;
1995             long size0   = size;
1996             long locoff0 = locoff;
1997             int elen64   = 0;                // extra for ZIP64
1998             int elenNTFS = 0;                // extra for NTFS (a/c/mtime)
1999             int elenEXTT = 0;                // extra for Extended Timestamp
2000             boolean foundExtraTime = false;  // if time stamp NTFS, EXTT present
2001 
2002             byte[] zname = isdir ? toDirectoryPath(name) : name;
2003 
2004             // confirm size/length
2005             int nlen = (zname != null) ? zname.length - 1 : 0;  // name has [0] as "slash"
2006             int elen = (extra != null) ? extra.length : 0;
2007             int eoff = 0;
2008             int clen = (comment != null) ? comment.length : 0;
2009             if (csize >= ZIP64_MINVAL) {
2010                 csize0 = ZIP64_MINVAL;
2011                 elen64 += 8;                 // csize(8)
2012             }
2013             if (size >= ZIP64_MINVAL) {
2014                 size0 = ZIP64_MINVAL;        // size(8)
2015                 elen64 += 8;
2016             }
2017             if (locoff >= ZIP64_MINVAL) {
2018                 locoff0 = ZIP64_MINVAL;
2019                 elen64 += 8;                 // offset(8)
2020             }
2021             if (elen64 != 0) {
2022                 elen64 += 4;                 // header and data sz 4 bytes
2023             }


2024             while (eoff + 4 < elen) {
2025                 int tag = SH(extra, eoff);
2026                 int sz = SH(extra, eoff + 2);
2027                 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2028                     foundExtraTime = true;
2029                 }
2030                 eoff += (4 + sz);
2031             }
2032             if (!foundExtraTime) {
2033                 if (isWindows) {             // use NTFS
2034                     elenNTFS = 36;           // total 36 bytes
2035                 } else {                     // Extended Timestamp otherwise
2036                     elenEXTT = 9;            // only mtime in cen
2037                 }
2038             }
2039             writeInt(os, CENSIG);            // CEN header signature
2040             if (elen64 != 0) {
2041                 writeShort(os, 45);          // ver 4.5 for zip64
2042                 writeShort(os, 45);
2043             } else {
2044                 writeShort(os, version0);    // version made by
2045                 writeShort(os, version0);    // version needed to extract
2046             }
2047             writeShort(os, flag);            // general purpose bit flag
2048             writeShort(os, method);          // compression method
2049                                              // last modification time
2050             writeInt(os, (int)javaToDosTime(mtime));
2051             writeInt(os, crc);               // crc-32
2052             writeInt(os, csize0);            // compressed size
2053             writeInt(os, size0);             // uncompressed size
2054             writeShort(os, nlen);
2055             writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2056 
2057             if (comment != null) {
2058                 writeShort(os, Math.min(clen, 0xffff));
2059             } else {
2060                 writeShort(os, 0);
2061             }
2062             writeShort(os, 0);              // starting disk number
2063             writeShort(os, 0);              // internal file attributes (unused)
2064             writeInt(os, 0);                // external file attributes (unused)


2065             writeInt(os, locoff0);          // relative offset of local header
2066             writeBytes(os, zname, 1, nlen);
2067             if (elen64 != 0) {
2068                 writeShort(os, EXTID_ZIP64);// Zip64 extra
2069                 writeShort(os, elen64 - 4); // size of "this" extra block
2070                 if (size0 == ZIP64_MINVAL)
2071                     writeLong(os, size);
2072                 if (csize0 == ZIP64_MINVAL)
2073                     writeLong(os, csize);
2074                 if (locoff0 == ZIP64_MINVAL)
2075                     writeLong(os, locoff);
2076             }
2077             if (elenNTFS != 0) {
2078                 writeShort(os, EXTID_NTFS);
2079                 writeShort(os, elenNTFS - 4);
2080                 writeInt(os, 0);            // reserved
2081                 writeShort(os, 0x0001);     // NTFS attr tag
2082                 writeShort(os, 24);
2083                 writeLong(os, javaToWinTime(mtime));
2084                 writeLong(os, javaToWinTime(atime));
2085                 writeLong(os, javaToWinTime(ctime));
2086             }
2087             if (elenEXTT != 0) {
2088                 writeShort(os, EXTID_EXTT);
2089                 writeShort(os, elenEXTT - 4);
2090                 if (ctime == -1)
2091                     os.write(0x3);          // mtime and atime
2092                 else
2093                     os.write(0x7);          // mtime, atime and ctime
2094                 writeInt(os, javaToUnixTime(mtime));
2095             }
2096             if (extra != null)              // whatever not recognized
2097                 writeBytes(os, extra);
2098             if (comment != null)            //TBD: 0, Math.min(commentBytes.length, 0xffff));
2099                 writeBytes(os, comment);
2100             return CENHDR + nlen + elen + clen + elen64 + elenNTFS + elenEXTT;
2101         }
2102 
2103         ///////////////////// LOC //////////////////////
2104 
2105         int writeLOC(OutputStream os) throws IOException {
2106             int version0 = version();
2107             byte[] zname = isdir ? toDirectoryPath(name) : name;
2108             int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash
2109             int elen = (extra != null) ? extra.length : 0;
2110             boolean foundExtraTime = false;     // if extra timestamp present
2111             int eoff = 0;
2112             int elen64 = 0;

2113             int elenEXTT = 0;
2114             int elenNTFS = 0;
2115             writeInt(os, LOCSIG);               // LOC header signature
2116             if ((flag & FLAG_DATADESCR) != 0) {
2117                 writeShort(os, version0);       // version needed to extract
2118                 writeShort(os, flag);           // general purpose bit flag
2119                 writeShort(os, method);         // compression method
2120                 // last modification time
2121                 writeInt(os, (int)javaToDosTime(mtime));
2122                 // store size, uncompressed size, and crc-32 in data descriptor
2123                 // immediately following compressed entry data
2124                 writeInt(os, 0);
2125                 writeInt(os, 0);
2126                 writeInt(os, 0);
2127             } else {
2128                 if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2129                     elen64 = 20;    //headid(2) + size(2) + size(8) + csize(8)
2130                     writeShort(os, 45);         // ver 4.5 for zip64
2131                 } else {
2132                     writeShort(os, version0);   // version needed to extract
2133                 }

2134                 writeShort(os, flag);           // general purpose bit flag
2135                 writeShort(os, method);         // compression method
2136                                                 // last modification time
2137                 writeInt(os, (int)javaToDosTime(mtime));
2138                 writeInt(os, crc);              // crc-32
2139                 if (elen64 != 0) {
2140                     writeInt(os, ZIP64_MINVAL);
2141                     writeInt(os, ZIP64_MINVAL);
2142                 } else {
2143                     writeInt(os, csize);        // compressed size
2144                     writeInt(os, size);         // uncompressed size
2145                 }
2146             }
2147             while (eoff + 4 < elen) {
2148                 int tag = SH(extra, eoff);
2149                 int sz = SH(extra, eoff + 2);
2150                 if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
2151                     foundExtraTime = true;
2152                 }
2153                 eoff += (4 + sz);
2154             }
2155             if (!foundExtraTime) {
2156                 if (isWindows) {
2157                     elenNTFS = 36;              // NTFS, total 36 bytes
2158                 } else {                        // on unix use "ext time"
2159                     elenEXTT = 9;
2160                     if (atime != -1)
2161                         elenEXTT += 4;
2162                     if (ctime != -1)
2163                         elenEXTT += 4;
2164                 }
2165             }
2166             writeShort(os, nlen);
2167             writeShort(os, elen + elen64 + elenNTFS + elenEXTT);
2168             writeBytes(os, zname, 1, nlen);
2169             if (elen64 != 0) {
2170                 writeShort(os, EXTID_ZIP64);
2171                 writeShort(os, 16);
2172                 writeLong(os, size);
2173                 writeLong(os, csize);
2174             }
2175             if (elenNTFS != 0) {
2176                 writeShort(os, EXTID_NTFS);
2177                 writeShort(os, elenNTFS - 4);
2178                 writeInt(os, 0);            // reserved
2179                 writeShort(os, 0x0001);     // NTFS attr tag
2180                 writeShort(os, 24);
2181                 writeLong(os, javaToWinTime(mtime));
2182                 writeLong(os, javaToWinTime(atime));
2183                 writeLong(os, javaToWinTime(ctime));
2184             }
2185             if (elenEXTT != 0) {
2186                 writeShort(os, EXTID_EXTT);
2187                 writeShort(os, elenEXTT - 4);// size for the folowing data block
2188                 int fbyte = 0x1;
2189                 if (atime != -1)           // mtime and atime
2190                     fbyte |= 0x2;
2191                 if (ctime != -1)           // mtime, atime and ctime
2192                     fbyte |= 0x4;
2193                 os.write(fbyte);           // flags byte
2194                 writeInt(os, javaToUnixTime(mtime));
2195                 if (atime != -1)
2196                     writeInt(os, javaToUnixTime(atime));
2197                 if (ctime != -1)
2198                     writeInt(os, javaToUnixTime(ctime));
2199             }
2200             if (extra != null) {
2201                 writeBytes(os, extra);
2202             }
2203             return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT;
2204         }
2205 
2206         // Data Descriptior
2207         int writeEXT(OutputStream os) throws IOException {
2208             writeInt(os, EXTSIG);           // EXT header signature
2209             writeInt(os, crc);              // crc-32
2210             if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) {
2211                 writeLong(os, csize);
2212                 writeLong(os, size);
2213                 return 24;
2214             } else {
2215                 writeInt(os, csize);        // compressed size
2216                 writeInt(os, size);         // uncompressed size
2217                 return 16;
2218             }
2219         }
2220 
2221         // read NTFS, UNIX and ZIP64 data from cen.extra
2222         void readExtra(ZipFileSystem zipfs) throws IOException {
2223             if (extra == null)
2224                 return;
2225             int elen = extra.length;
2226             int off = 0;


2263                     if (SH(extra, pos + 2) != 24)
2264                         break;
2265                     // override the loc field, datatime here is
2266                     // more "accurate"
2267                     mtime  = winToJavaTime(LL(extra, pos + 4));
2268                     atime  = winToJavaTime(LL(extra, pos + 12));
2269                     ctime  = winToJavaTime(LL(extra, pos + 20));
2270                     break;
2271                 case EXTID_EXTT:
2272                     // spec says the Extened timestamp in cen only has mtime
2273                     // need to read the loc to get the extra a/ctime, if flag
2274                     // "zipinfo-time" is not specified to false;
2275                     // there is performance cost (move up to loc and read) to
2276                     // access the loc table foreach entry;
2277                     if (zipfs.noExtt) {
2278                         if (sz == 5)
2279                             mtime = unixToJavaTime(LG(extra, pos + 1));
2280                          break;
2281                     }
2282                     byte[] buf = new byte[LOCHDR];
2283                     if (zipfs.readFullyAt(buf, 0, buf.length , locoff)
2284                         != buf.length)
2285                         throw new ZipException("loc: reading failed");
2286                     if (!locSigAt(buf, 0))
2287                         throw new ZipException("loc: wrong sig ->"
2288                                            + Long.toString(getSig(buf, 0), 16));
2289                     int locElen = LOCEXT(buf);
2290                     if (locElen < 9)    // EXTT is at lease 9 bytes
2291                         break;
2292                     int locNlen = LOCNAM(buf);
2293                     buf = new byte[locElen];
2294                     if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen)
2295                         != buf.length)
2296                         throw new ZipException("loc extra: reading failed");
2297                     int locPos = 0;
2298                     while (locPos + 4 < buf.length) {
2299                         int locTag = SH(buf, locPos);
2300                         int locSZ  = SH(buf, locPos + 2);
2301                         locPos += 4;
2302                         if (locTag  != EXTID_EXTT) {
2303                             locPos += locSZ;
2304                              continue;
2305                         }
2306                         int end = locPos + locSZ - 4;
2307                         int flag = CH(buf, locPos++);
2308                         if ((flag & 0x1) != 0 && locPos <= end) {
2309                             mtime = unixToJavaTime(LG(buf, locPos));
2310                             locPos += 4;
2311                         }
2312                         if ((flag & 0x2) != 0 && locPos <= end) {
2313                             atime = unixToJavaTime(LG(buf, locPos));
2314                             locPos += 4;
2315                         }
2316                         if ((flag & 0x4) != 0 && locPos <= end) {
2317                             ctime = unixToJavaTime(LG(buf, locPos));
2318                             locPos += 4;
2319                         }
2320                         break;
2321                     }
2322                     break;
2323                 default:    // unknown tag
2324                     System.arraycopy(extra, off, extra, newOff, sz + 4);
2325                     newOff += (sz + 4);
2326                 }
2327                 off += (sz + 4);
2328             }
2329             if (newOff != 0 && newOff != extra.length)
2330                 extra = Arrays.copyOf(extra, newOff);
2331             else
2332                 extra = null;
2333         }
2334 
























2335         ///////// basic file attributes ///////////
2336         @Override
2337         public FileTime creationTime() {
2338             return FileTime.fromMillis(ctime == -1 ? mtime : ctime);
2339         }
2340 
2341         @Override
2342         public boolean isDirectory() {
2343             return isDir();
2344         }
2345 
2346         @Override
2347         public boolean isOther() {
2348             return false;
2349         }
2350 
2351         @Override
2352         public boolean isRegularFile() {
2353             return !isDir();
2354         }


2361         @Override
2362         public FileTime lastModifiedTime() {
2363             return FileTime.fromMillis(mtime);
2364         }
2365 
2366         @Override
2367         public long size() {
2368             return size;
2369         }
2370 
2371         @Override
2372         public boolean isSymbolicLink() {
2373             return false;
2374         }
2375 
2376         @Override
2377         public Object fileKey() {
2378             return null;
2379         }
2380 
2381         ///////// zip entry attributes ///////////



















2382         public long compressedSize() {
2383             return csize;
2384         }
2385 

2386         public long crc() {
2387             return crc;
2388         }
2389 

2390         public int method() {
2391             return method;
2392         }
2393 

2394         public byte[] extra() {
2395             if (extra != null)
2396                 return Arrays.copyOf(extra, extra.length);
2397             return null;
2398         }
2399 

2400         public byte[] comment() {
2401             if (comment != null)
2402                 return Arrays.copyOf(comment, comment.length);
2403             return null;
2404         }
2405 
2406         public String toString() {
2407             StringBuilder sb = new StringBuilder(1024);
2408             Formatter fm = new Formatter(sb);
2409             fm.format("    name            : %s%n", new String(name));
2410             fm.format("    creationTime    : %tc%n", creationTime().toMillis());
2411             fm.format("    lastAccessTime  : %tc%n", lastAccessTime().toMillis());
2412             fm.format("    lastModifiedTime: %tc%n", lastModifiedTime().toMillis());
2413             fm.format("    isRegularFile   : %b%n", isRegularFile());
2414             fm.format("    isDirectory     : %b%n", isDirectory());
2415             fm.format("    isSymbolicLink  : %b%n", isSymbolicLink());
2416             fm.format("    isOther         : %b%n", isOther());
2417             fm.format("    fileKey         : %s%n", fileKey());
2418             fm.format("    size            : %d%n", size());
2419             fm.format("    compressedSize  : %d%n", compressedSize());
2420             fm.format("    crc             : %x%n", crc());
2421             fm.format("    method          : %d%n", method());
2422             fm.close();
2423             return sb.toString();
2424         }
2425     }
2426 
2427     // ZIP directory has two issues:
2428     // (1) ZIP spec does not require the ZIP file to include
2429     //     directory entry
2430     // (2) all entries are not stored/organized in a "tree"
2431     //     structure.
2432     // A possible solution is to build the node tree ourself as
2433     // implemented below.
2434 
2435     // default time stamp for pseudo entries
2436     private long zfsDefaultTimeStamp = System.currentTimeMillis();
2437 
2438     private void removeFromTree(IndexNode inode) {
2439         IndexNode parent = inodes.get(LOOKUPKEY.as(getParent(inode.name)));
2440         IndexNode child = parent.child;
2441         if (child.equals(inode)) {
2442             parent.child = child.sibling;
2443         } else {




  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 


 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(':');


 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 {


 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));


 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     }


 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                 {


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 


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             }


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;


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         }


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 {


< prev index next >