src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
changeset 57665 bf325b739c8a
parent 54944 9f714ef845d5
child 57842 abf6ee4c477c
equal deleted inserted replaced
57664:1d2ea8db7083 57665:bf325b739c8a
    39 import java.nio.channels.FileLock;
    39 import java.nio.channels.FileLock;
    40 import java.nio.channels.ReadableByteChannel;
    40 import java.nio.channels.ReadableByteChannel;
    41 import java.nio.channels.SeekableByteChannel;
    41 import java.nio.channels.SeekableByteChannel;
    42 import java.nio.channels.WritableByteChannel;
    42 import java.nio.channels.WritableByteChannel;
    43 import java.nio.file.*;
    43 import java.nio.file.*;
    44 import java.nio.file.attribute.FileAttribute;
    44 import java.nio.file.attribute.*;
    45 import java.nio.file.attribute.FileTime;
       
    46 import java.nio.file.attribute.UserPrincipalLookupService;
       
    47 import java.nio.file.spi.FileSystemProvider;
    45 import java.nio.file.spi.FileSystemProvider;
    48 import java.security.AccessController;
    46 import java.security.AccessController;
    49 import java.security.PrivilegedAction;
    47 import java.security.PrivilegedAction;
    50 import java.security.PrivilegedActionException;
    48 import java.security.PrivilegedActionException;
    51 import java.security.PrivilegedExceptionAction;
    49 import java.security.PrivilegedExceptionAction;
    80 class ZipFileSystem extends FileSystem {
    78 class ZipFileSystem extends FileSystem {
    81     // statics
    79     // statics
    82     private static final boolean isWindows = AccessController.doPrivileged(
    80     private static final boolean isWindows = AccessController.doPrivileged(
    83         (PrivilegedAction<Boolean>)()->System.getProperty("os.name")
    81         (PrivilegedAction<Boolean>)()->System.getProperty("os.name")
    84                                              .startsWith("Windows"));
    82                                              .startsWith("Windows"));
    85     private static final Set<String> supportedFileAttributeViews =
       
    86         Set.of("basic", "zip");
       
    87     private static final byte[] ROOTPATH = new byte[] { '/' };
    83     private static final byte[] ROOTPATH = new byte[] { '/' };
       
    84     private static final String OPT_POSIX = "enablePosixFileAttributes";
       
    85     private static final String OPT_DEFAULT_OWNER = "defaultOwner";
       
    86     private static final String OPT_DEFAULT_GROUP = "defaultGroup";
       
    87     private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions";
       
    88 
       
    89     private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
       
    90         PosixFilePermissions.fromString("rwxrwxrwx");
    88 
    91 
    89     private final ZipFileSystemProvider provider;
    92     private final ZipFileSystemProvider provider;
    90     private final Path zfpath;
    93     private final Path zfpath;
    91     final ZipCoder zc;
    94     final ZipCoder zc;
    92     private final ZipPath rootdir;
    95     private final ZipPath rootdir;
   100     private final boolean useTempFile;   // use a temp file for newOS, default
   103     private final boolean useTempFile;   // use a temp file for newOS, default
   101                                          // is to use BAOS for better performance
   104                                          // is to use BAOS for better performance
   102     private final boolean forceEnd64;
   105     private final boolean forceEnd64;
   103     private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true"
   106     private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true"
   104                                                 // METHOD_DEFLATED otherwise
   107                                                 // METHOD_DEFLATED otherwise
       
   108 
       
   109     // POSIX support
       
   110     final boolean supportPosix;
       
   111     private final UserPrincipal defaultOwner;
       
   112     private final GroupPrincipal defaultGroup;
       
   113     private final Set<PosixFilePermission> defaultPermissions;
       
   114 
       
   115     private final Set<String> supportedFileAttributeViews;
   105 
   116 
   106     ZipFileSystem(ZipFileSystemProvider provider,
   117     ZipFileSystem(ZipFileSystemProvider provider,
   107                   Path zfpath,
   118                   Path zfpath,
   108                   Map<String, ?> env) throws IOException
   119                   Map<String, ?> env) throws IOException
   109     {
   120     {
   112             (String)env.get("encoding") : "UTF-8";
   123             (String)env.get("encoding") : "UTF-8";
   113         this.noExtt = "false".equals(env.get("zipinfo-time"));
   124         this.noExtt = "false".equals(env.get("zipinfo-time"));
   114         this.useTempFile  = isTrue(env, "useTempFile");
   125         this.useTempFile  = isTrue(env, "useTempFile");
   115         this.forceEnd64 = isTrue(env, "forceZIP64End");
   126         this.forceEnd64 = isTrue(env, "forceZIP64End");
   116         this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED;
   127         this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED;
       
   128         this.supportPosix = isTrue(env, OPT_POSIX);
       
   129         this.defaultOwner = initOwner(zfpath, env);
       
   130         this.defaultGroup = initGroup(zfpath, env);
       
   131         this.defaultPermissions = initPermissions(env);
       
   132         this.supportedFileAttributeViews = supportPosix ?
       
   133             Set.of("basic", "posix", "zip") : Set.of("basic", "zip");
   117         if (Files.notExists(zfpath)) {
   134         if (Files.notExists(zfpath)) {
   118             // create a new zip if it doesn't exist
   135             // create a new zip if it doesn't exist
   119             if (isTrue(env, "create")) {
   136             if (isTrue(env, "create")) {
   120                 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
   137                 try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) {
   121                     new END().write(os, 0, forceEnd64);
   138                     new END().write(os, 0, forceEnd64);
   149     // returns true if there is a name=true/"true" setting in env
   166     // returns true if there is a name=true/"true" setting in env
   150     private static boolean isTrue(Map<String, ?> env, String name) {
   167     private static boolean isTrue(Map<String, ?> env, String name) {
   151         return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
   168         return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
   152     }
   169     }
   153 
   170 
       
   171     // Initialize the default owner for files inside the zip archive.
       
   172     // If not specified in env, it is the owner of the archive. If no owner can
       
   173     // be determined, we try to go with system property "user.name". If that's not
       
   174     // accessible, we return "<zipfs_default>".
       
   175     private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) throws IOException {
       
   176         Object o = env.get(OPT_DEFAULT_OWNER);
       
   177         if (o == null) {
       
   178             try {
       
   179                 PrivilegedExceptionAction<UserPrincipal> pa = ()->Files.getOwner(zfpath);
       
   180                 return AccessController.doPrivileged(pa);
       
   181             } catch (UnsupportedOperationException | PrivilegedActionException e) {
       
   182                 if (e instanceof UnsupportedOperationException ||
       
   183                     e.getCause() instanceof NoSuchFileException)
       
   184                 {
       
   185                     PrivilegedAction<String> pa = ()->System.getProperty("user.name");
       
   186                     String userName = AccessController.doPrivileged(pa);
       
   187                     return ()->userName;
       
   188                 } else {
       
   189                     throw new IOException(e);
       
   190                 }
       
   191             }
       
   192         }
       
   193         if (o instanceof String) {
       
   194             if (((String)o).isEmpty()) {
       
   195                 throw new IllegalArgumentException("Value for property " +
       
   196                     OPT_DEFAULT_OWNER + " must not be empty.");
       
   197             }
       
   198             return ()->(String)o;
       
   199         }
       
   200         if (o instanceof UserPrincipal) {
       
   201             return (UserPrincipal)o;
       
   202         }
       
   203         throw new IllegalArgumentException("Value for property " +
       
   204             OPT_DEFAULT_OWNER + " must be of type " + String.class +
       
   205             " or " + UserPrincipal.class);
       
   206     }
       
   207 
       
   208     // Initialize the default group for files inside the zip archive.
       
   209     // If not specified in env, we try to determine the group of the zip archive itself.
       
   210     // If this is not possible/unsupported, we will return a group principal going by
       
   211     // the same name as the default owner.
       
   212     private GroupPrincipal initGroup(Path zfpath, Map<String, ?> env) throws IOException {
       
   213         Object o = env.get(OPT_DEFAULT_GROUP);
       
   214         if (o == null) {
       
   215             try {
       
   216                 PosixFileAttributeView zfpv = Files.getFileAttributeView(zfpath, PosixFileAttributeView.class);
       
   217                 if (zfpv == null) {
       
   218                     return defaultOwner::getName;
       
   219                 }
       
   220                 PrivilegedExceptionAction<GroupPrincipal> pa = ()->zfpv.readAttributes().group();
       
   221                 return AccessController.doPrivileged(pa);
       
   222             } catch (UnsupportedOperationException | PrivilegedActionException e) {
       
   223                 if (e instanceof UnsupportedOperationException ||
       
   224                     e.getCause() instanceof NoSuchFileException)
       
   225                 {
       
   226                     return defaultOwner::getName;
       
   227                 } else {
       
   228                     throw new IOException(e);
       
   229                 }
       
   230             }
       
   231         }
       
   232         if (o instanceof String) {
       
   233             if (((String)o).isEmpty()) {
       
   234                 throw new IllegalArgumentException("Value for property " +
       
   235                     OPT_DEFAULT_GROUP + " must not be empty.");
       
   236             }
       
   237             return ()->(String)o;
       
   238         }
       
   239         if (o instanceof GroupPrincipal) {
       
   240             return (GroupPrincipal)o;
       
   241         }
       
   242         throw new IllegalArgumentException("Value for property " +
       
   243             OPT_DEFAULT_GROUP + " must be of type " + String.class +
       
   244             " or " + GroupPrincipal.class);
       
   245     }
       
   246 
       
   247     // Initialize the default permissions for files inside the zip archive.
       
   248     // If not specified in env, it will return 777.
       
   249     private Set<PosixFilePermission> initPermissions(Map<String, ?> env) {
       
   250         Object o = env.get(OPT_DEFAULT_PERMISSIONS);
       
   251         if (o == null) {
       
   252             return DEFAULT_PERMISSIONS;
       
   253         }
       
   254         if (o instanceof String) {
       
   255             return PosixFilePermissions.fromString((String)o);
       
   256         }
       
   257         if (!(o instanceof Set)) {
       
   258             throw new IllegalArgumentException("Value for property " +
       
   259                 OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class +
       
   260                 " or " + Set.class);
       
   261         }
       
   262         Set<PosixFilePermission> perms = new HashSet<>();
       
   263         for (Object o2 : (Set<?>)o) {
       
   264             if (o2 instanceof PosixFilePermission) {
       
   265                 perms.add((PosixFilePermission)o2);
       
   266             } else {
       
   267                 throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS +
       
   268                     " must only contain objects of type " + PosixFilePermission.class);
       
   269             }
       
   270         }
       
   271         return perms;
       
   272     }
       
   273 
   154     @Override
   274     @Override
   155     public FileSystemProvider provider() {
   275     public FileSystemProvider provider() {
   156         return provider;
   276         return provider;
   157     }
   277     }
   158 
   278 
   336                 return null;
   456                 return null;
   337             } else if (inode instanceof Entry) {
   457             } else if (inode instanceof Entry) {
   338                 return (Entry)inode;
   458                 return (Entry)inode;
   339             } else if (inode.pos == -1) {
   459             } else if (inode.pos == -1) {
   340                 // pseudo directory, uses METHOD_STORED
   460                 // pseudo directory, uses METHOD_STORED
   341                 Entry e = new Entry(inode.name, inode.isdir, METHOD_STORED);
   461                 Entry e = supportPosix ?
       
   462                     new PosixEntry(inode.name, inode.isdir, METHOD_STORED) :
       
   463                     new Entry(inode.name, inode.isdir, METHOD_STORED);
   342                 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
   464                 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
   343                 return e;
   465                 return e;
   344             } else {
   466             } else {
   345                 return new Entry(this, inode);
   467                 return supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
   346             }
   468             }
   347         } finally {
   469         } finally {
   348             endRead();
   470             endRead();
   349         }
   471         }
   350     }
   472     }
   379                 e.mtime = mtime.toMillis();
   501                 e.mtime = mtime.toMillis();
   380             if (atime != null)
   502             if (atime != null)
   381                 e.atime = atime.toMillis();
   503                 e.atime = atime.toMillis();
   382             if (ctime != null)
   504             if (ctime != null)
   383                 e.ctime = ctime.toMillis();
   505                 e.ctime = ctime.toMillis();
       
   506             update(e);
       
   507         } finally {
       
   508             endWrite();
       
   509         }
       
   510     }
       
   511 
       
   512     void setOwner(byte[] path, UserPrincipal owner) throws IOException {
       
   513         checkWritable();
       
   514         beginWrite();
       
   515         try {
       
   516             ensureOpen();
       
   517             Entry e = getEntry(path);    // ensureOpen checked
       
   518             if (e == null) {
       
   519                 throw new NoSuchFileException(getString(path));
       
   520             }
       
   521             // as the owner information is not persistent, we don't need to
       
   522             // change e.type to Entry.COPY
       
   523             if (e instanceof PosixEntry) {
       
   524                 ((PosixEntry)e).owner = owner;
       
   525                 update(e);
       
   526             }
       
   527         } finally {
       
   528             endWrite();
       
   529         }
       
   530     }
       
   531 
       
   532     void setGroup(byte[] path, GroupPrincipal group) throws IOException {
       
   533         checkWritable();
       
   534         beginWrite();
       
   535         try {
       
   536             ensureOpen();
       
   537             Entry e = getEntry(path);    // ensureOpen checked
       
   538             if (e == null) {
       
   539                 throw new NoSuchFileException(getString(path));
       
   540             }
       
   541             // as the group information is not persistent, we don't need to
       
   542             // change e.type to Entry.COPY
       
   543             if (e instanceof PosixEntry) {
       
   544                 ((PosixEntry)e).group = group;
       
   545                 update(e);
       
   546             }
       
   547         } finally {
       
   548             endWrite();
       
   549         }
       
   550     }
       
   551 
       
   552     void setPermissions(byte[] path, Set<PosixFilePermission> perms) throws IOException {
       
   553         checkWritable();
       
   554         beginWrite();
       
   555         try {
       
   556             ensureOpen();
       
   557             Entry e = getEntry(path);    // ensureOpen checked
       
   558             if (e == null) {
       
   559                 throw new NoSuchFileException(getString(path));
       
   560             }
       
   561             if (e.type == Entry.CEN) {
       
   562                 e.type = Entry.COPY;     // copy e
       
   563             }
       
   564             e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms);
   384             update(e);
   565             update(e);
   385         } finally {
   566         } finally {
   386             endWrite();
   567             endWrite();
   387         }
   568         }
   388     }
   569     }
   446         try {
   627         try {
   447             ensureOpen();
   628             ensureOpen();
   448             if (dir.length == 0 || exists(dir))  // root dir, or existing dir
   629             if (dir.length == 0 || exists(dir))  // root dir, or existing dir
   449                 throw new FileAlreadyExistsException(getString(dir));
   630                 throw new FileAlreadyExistsException(getString(dir));
   450             checkParents(dir);
   631             checkParents(dir);
   451             Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
   632             Entry e = supportPosix ?
       
   633                 new PosixEntry(dir, Entry.NEW, true, METHOD_STORED, attrs) :
       
   634                 new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
   452             update(e);
   635             update(e);
   453         } finally {
   636         } finally {
   454             endWrite();
   637             endWrite();
   455         }
   638         }
   456     }
   639     }
   487                     throw new FileAlreadyExistsException(getString(dst));
   670                     throw new FileAlreadyExistsException(getString(dst));
   488             } else {
   671             } else {
   489                 checkParents(dst);
   672                 checkParents(dst);
   490             }
   673             }
   491             // copy eSrc entry and change name
   674             // copy eSrc entry and change name
   492             Entry u = new Entry(eSrc, Entry.COPY);
   675             Entry u = supportPosix ?
       
   676                 new PosixEntry((PosixEntry)eSrc, Entry.COPY) :
       
   677                 new Entry(eSrc, Entry.COPY);
   493             u.name(dst);
   678             u.name(dst);
   494             if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) {
   679             if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) {
   495                 u.type = eSrc.type;    // make it the same type
   680                 u.type = eSrc.type;    // make it the same type
   496                 if (deletesrc) {       // if it's a "rename", take the data
   681                 if (deletesrc) {       // if it's a "rename", take the data
   497                     u.bytes = eSrc.bytes;
   682                     u.bytes = eSrc.bytes;
   551                     try (InputStream is = getInputStream(e)) {
   736                     try (InputStream is = getInputStream(e)) {
   552                         is.transferTo(os);
   737                         is.transferTo(os);
   553                     }
   738                     }
   554                     return os;
   739                     return os;
   555                 }
   740                 }
   556                 return getOutputStream(new Entry(e, Entry.NEW));
   741                 return getOutputStream(supportPosix ?
       
   742                     new PosixEntry((PosixEntry)e, Entry.NEW) : new Entry(e, Entry.NEW));
   557             } else {
   743             } else {
   558                 if (!hasCreate && !hasCreateNew)
   744                 if (!hasCreate && !hasCreateNew)
   559                     throw new NoSuchFileException(getString(path));
   745                     throw new NoSuchFileException(getString(path));
   560                 checkParents(path);
   746                 checkParents(path);
   561                 return getOutputStream(new Entry(path, Entry.NEW, false, defaultCompressionMethod));
   747                 return getOutputStream(supportPosix ?
       
   748                     new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod) :
       
   749                     new Entry(path, Entry.NEW, false, defaultCompressionMethod));
   562             }
   750             }
   563         } finally {
   751         } finally {
   564             endRead();
   752             endRead();
   565         }
   753         }
   566     }
   754     }
   643                 Entry e = getEntry(path);
   831                 Entry e = getEntry(path);
   644                 if (e != null) {
   832                 if (e != null) {
   645                     if (e.isDir() || options.contains(CREATE_NEW))
   833                     if (e.isDir() || options.contains(CREATE_NEW))
   646                         throw new FileAlreadyExistsException(getString(path));
   834                         throw new FileAlreadyExistsException(getString(path));
   647                     SeekableByteChannel sbc =
   835                     SeekableByteChannel sbc =
   648                             new EntryOutputChannel(new Entry(e, Entry.NEW));
   836                             new EntryOutputChannel(supportPosix ?
       
   837                                 new PosixEntry((PosixEntry)e, Entry.NEW) :
       
   838                                 new Entry(e, Entry.NEW));
   649                     if (options.contains(APPEND)) {
   839                     if (options.contains(APPEND)) {
   650                         try (InputStream is = getInputStream(e)) {  // copyover
   840                         try (InputStream is = getInputStream(e)) {  // copyover
   651                             byte[] buf = new byte[8192];
   841                             byte[] buf = new byte[8192];
   652                             ByteBuffer bb = ByteBuffer.wrap(buf);
   842                             ByteBuffer bb = ByteBuffer.wrap(buf);
   653                             int n;
   843                             int n;
   662                 }
   852                 }
   663                 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
   853                 if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
   664                     throw new NoSuchFileException(getString(path));
   854                     throw new NoSuchFileException(getString(path));
   665                 checkParents(path);
   855                 checkParents(path);
   666                 return new EntryOutputChannel(
   856                 return new EntryOutputChannel(
   667                     new Entry(path, Entry.NEW, false, defaultCompressionMethod));
   857                     supportPosix ?
       
   858                         new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod, attrs) :
       
   859                         new Entry(path, Entry.NEW, false, defaultCompressionMethod, attrs));
   668             } finally {
   860             } finally {
   669                 endRead();
   861                 endRead();
   670             }
   862             }
   671         } else {
   863         } else {
   672             beginRead();
   864             beginRead();
   726             final boolean isFCH = (e != null && e.type == Entry.FILECH);
   918             final boolean isFCH = (e != null && e.type == Entry.FILECH);
   727             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
   919             final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path);
   728             final FileChannel fch = tmpfile.getFileSystem()
   920             final FileChannel fch = tmpfile.getFileSystem()
   729                                            .provider()
   921                                            .provider()
   730                                            .newFileChannel(tmpfile, options, attrs);
   922                                            .newFileChannel(tmpfile, options, attrs);
   731             final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
   923             final Entry u = isFCH ? e : (
       
   924                 supportPosix ?
       
   925                 new PosixEntry(path, tmpfile, Entry.FILECH, attrs) :
       
   926                 new Entry(path, tmpfile, Entry.FILECH, attrs));
   732             if (forWrite) {
   927             if (forWrite) {
   733                 u.flag = FLAG_DATADESCR;
   928                 u.flag = FLAG_DATADESCR;
   734                 u.method = defaultCompressionMethod;
   929                 u.method = defaultCompressionMethod;
   735             }
   930             }
   736             // is there a better way to hook into the FileChannel's close method?
   931             // is there a better way to hook into the FileChannel's close method?
  1341                     }
  1536                     }
  1342                     if (inode.name.length == 1 && inode.name[0] == '/') {
  1537                     if (inode.name.length == 1 && inode.name[0] == '/') {
  1343                         continue;               // no root '/' directory even if it
  1538                         continue;               // no root '/' directory even if it
  1344                                                 // exists in original zip/jar file.
  1539                                                 // exists in original zip/jar file.
  1345                     }
  1540                     }
  1346                     e = new Entry(this, inode);
  1541                     e = supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
  1347                     try {
  1542                     try {
  1348                         if (buf == null)
  1543                         if (buf == null)
  1349                             buf = new byte[8192];
  1544                             buf = new byte[8192];
  1350                         written += copyLOCEntry(e, false, os, written, buf);
  1545                         written += copyLOCEntry(e, false, os, written, buf);
  1351                         elist.add(e);
  1546                         elist.add(e);
  1415         IndexNode inode = getInode(path);
  1610         IndexNode inode = getInode(path);
  1416         if (inode instanceof Entry)
  1611         if (inode instanceof Entry)
  1417             return (Entry)inode;
  1612             return (Entry)inode;
  1418         if (inode == null || inode.pos == -1)
  1613         if (inode == null || inode.pos == -1)
  1419             return null;
  1614             return null;
  1420         return new Entry(this, inode);
  1615         return supportPosix ? new PosixEntry(this, inode): new Entry(this, inode);
  1421     }
  1616     }
  1422 
  1617 
  1423     public void deleteFile(byte[] path, boolean failIfNotExists)
  1618     public void deleteFile(byte[] path, boolean failIfNotExists)
  1424         throws IOException
  1619         throws IOException
  1425     {
  1620     {
  2051         int    type = CEN;            // default is the entry read from cen
  2246         int    type = CEN;            // default is the entry read from cen
  2052 
  2247 
  2053         // entry attributes
  2248         // entry attributes
  2054         int    version;
  2249         int    version;
  2055         int    flag;
  2250         int    flag;
       
  2251         int    posixPerms = -1; // posix permissions
  2056         int    method = -1;    // compression method
  2252         int    method = -1;    // compression method
  2057         long   mtime  = -1;    // last modification time (in DOS time)
  2253         long   mtime  = -1;    // last modification time (in DOS time)
  2058         long   atime  = -1;    // last access time
  2254         long   atime  = -1;    // last access time
  2059         long   ctime  = -1;    // create time
  2255         long   ctime  = -1;    // create time
  2060         long   crc    = -1;    // crc-32 of entry data
  2256         long   crc    = -1;    // crc-32 of entry data
  2079             this.size   = 0;
  2275             this.size   = 0;
  2080             this.csize  = 0;
  2276             this.csize  = 0;
  2081             this.method = method;
  2277             this.method = method;
  2082         }
  2278         }
  2083 
  2279 
  2084         Entry(byte[] name, int type, boolean isdir, int method) {
  2280         @SuppressWarnings("unchecked")
       
  2281         Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
  2085             this(name, isdir, method);
  2282             this(name, isdir, method);
  2086             this.type = type;
  2283             this.type = type;
  2087         }
  2284             for (FileAttribute<?> attr : attrs) {
  2088 
  2285                 String attrName = attr.name();
  2089         Entry(byte[] name, Path file, int type) {
  2286                 if (attrName.equals("posix:permissions")) {
  2090             this(name, type, false, METHOD_STORED);
  2287                     posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
       
  2288                 }
       
  2289             }
       
  2290         }
       
  2291 
       
  2292         Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
       
  2293             this(name, type, false, METHOD_STORED, attrs);
  2091             this.file = file;
  2294             this.file = file;
  2092         }
  2295         }
  2093 
  2296 
  2094         Entry(Entry e, int type) {
  2297         Entry(Entry e, int type) {
  2095             name(e.name);
  2298             name(e.name);
  2109             this.attrs     = e.attrs;
  2312             this.attrs     = e.attrs;
  2110             this.attrsEx   = e.attrsEx;
  2313             this.attrsEx   = e.attrsEx;
  2111             */
  2314             */
  2112             this.locoff    = e.locoff;
  2315             this.locoff    = e.locoff;
  2113             this.comment   = e.comment;
  2316             this.comment   = e.comment;
       
  2317             this.posixPerms = e.posixPerms;
  2114             this.type      = type;
  2318             this.type      = type;
  2115         }
  2319         }
  2116 
  2320 
  2117         Entry(ZipFileSystem zipfs, IndexNode inode) throws IOException {
  2321         Entry(ZipFileSystem zipfs, IndexNode inode) throws IOException {
  2118             readCEN(zipfs, inode);
  2322             readCEN(zipfs, inode);
  2131             if (method == METHOD_DEFLATED)
  2335             if (method == METHOD_DEFLATED)
  2132                 return 20;
  2336                 return 20;
  2133             else if (method == METHOD_STORED)
  2337             else if (method == METHOD_STORED)
  2134                 return 10;
  2338                 return 10;
  2135             throw new ZipException("unsupported compression method");
  2339             throw new ZipException("unsupported compression method");
       
  2340         }
       
  2341 
       
  2342         /**
       
  2343          * Adds information about compatibility of file attribute information
       
  2344          * to a version value.
       
  2345          */
       
  2346         private int versionMadeBy(int version) {
       
  2347             return (posixPerms < 0) ? version :
       
  2348                 VERSION_MADE_BY_BASE_UNIX | (version & 0xff);
  2136         }
  2349         }
  2137 
  2350 
  2138         ///////////////////// CEN //////////////////////
  2351         ///////////////////// CEN //////////////////////
  2139         private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException {
  2352         private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException {
  2140             byte[] cen = zipfs.cen;
  2353             byte[] cen = zipfs.cen;
  2155             versionMade = CENVEM(cen, pos);
  2368             versionMade = CENVEM(cen, pos);
  2156             disk        = CENDSK(cen, pos);
  2369             disk        = CENDSK(cen, pos);
  2157             attrs       = CENATT(cen, pos);
  2370             attrs       = CENATT(cen, pos);
  2158             attrsEx     = CENATX(cen, pos);
  2371             attrsEx     = CENATX(cen, pos);
  2159             */
  2372             */
       
  2373             if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
       
  2374                 posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
       
  2375             }
  2160             locoff      = CENOFF(cen, pos);
  2376             locoff      = CENOFF(cen, pos);
  2161             pos += CENHDR;
  2377             pos += CENHDR;
  2162             this.name = inode.name;
  2378             this.name = inode.name;
  2163             this.isdir = inode.isdir;
  2379             this.isdir = inode.isdir;
  2164             this.hashcode = inode.hashcode;
  2380             this.hashcode = inode.hashcode;
  2221                 } else {                     // Extended Timestamp otherwise
  2437                 } else {                     // Extended Timestamp otherwise
  2222                     elenEXTT = 9;            // only mtime in cen
  2438                     elenEXTT = 9;            // only mtime in cen
  2223                 }
  2439                 }
  2224             }
  2440             }
  2225             writeInt(os, CENSIG);            // CEN header signature
  2441             writeInt(os, CENSIG);            // CEN header signature
  2226             writeShort(os, version0);        // version made by
  2442             writeShort(os, versionMadeBy(version0)); // version made by
  2227             writeShort(os, version0);        // version needed to extract
  2443             writeShort(os, version0);        // version needed to extract
  2228             writeShort(os, flag);            // general purpose bit flag
  2444             writeShort(os, flag);            // general purpose bit flag
  2229             writeShort(os, method);          // compression method
  2445             writeShort(os, method);          // compression method
  2230                                              // last modification time
  2446                                              // last modification time
  2231             writeInt(os, (int)javaToDosTime(mtime));
  2447             writeInt(os, (int)javaToDosTime(mtime));
  2240             } else {
  2456             } else {
  2241                 writeShort(os, 0);
  2457                 writeShort(os, 0);
  2242             }
  2458             }
  2243             writeShort(os, 0);              // starting disk number
  2459             writeShort(os, 0);              // starting disk number
  2244             writeShort(os, 0);              // internal file attributes (unused)
  2460             writeShort(os, 0);              // internal file attributes (unused)
  2245             writeInt(os, 0);                // external file attributes (unused)
  2461             writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
       
  2462                                             // attributes, used for storing posix
       
  2463                                             // permissions
  2246             writeInt(os, locoff0);          // relative offset of local header
  2464             writeInt(os, locoff0);          // relative offset of local header
  2247             writeBytes(os, zname, 1, nlen);
  2465             writeBytes(os, zname, 1, nlen);
  2248             if (zip64) {
  2466             if (zip64) {
  2249                 writeShort(os, EXTID_ZIP64);// Zip64 extra
  2467                 writeShort(os, EXTID_ZIP64);// Zip64 extra
  2250                 writeShort(os, elen64 - 4); // size of "this" extra block
  2468                 writeShort(os, elen64 - 4); // size of "this" extra block
  2525             fm.format("    fileKey         : %s%n", fileKey());
  2743             fm.format("    fileKey         : %s%n", fileKey());
  2526             fm.format("    size            : %d%n", size());
  2744             fm.format("    size            : %d%n", size());
  2527             fm.format("    compressedSize  : %d%n", compressedSize());
  2745             fm.format("    compressedSize  : %d%n", compressedSize());
  2528             fm.format("    crc             : %x%n", crc());
  2746             fm.format("    crc             : %x%n", crc());
  2529             fm.format("    method          : %d%n", method());
  2747             fm.format("    method          : %d%n", method());
       
  2748             Set<PosixFilePermission> permissions = storedPermissions().orElse(null);
       
  2749             if (permissions != null) {
       
  2750                 fm.format("    permissions     : %s%n", permissions);
       
  2751             }
  2530             fm.close();
  2752             fm.close();
  2531             return sb.toString();
  2753             return sb.toString();
  2532         }
  2754         }
  2533 
  2755 
  2534         ///////// basic file attributes ///////////
  2756         ///////// basic file attributes ///////////
  2604         @Override
  2826         @Override
  2605         public byte[] comment() {
  2827         public byte[] comment() {
  2606             if (comment != null)
  2828             if (comment != null)
  2607                 return Arrays.copyOf(comment, comment.length);
  2829                 return Arrays.copyOf(comment, comment.length);
  2608             return null;
  2830             return null;
       
  2831         }
       
  2832 
       
  2833         @Override
       
  2834         public Optional<Set<PosixFilePermission>> storedPermissions() {
       
  2835             Set<PosixFilePermission> perms = null;
       
  2836             if (posixPerms != -1) {
       
  2837                 perms = new HashSet<>(PosixFilePermission.values().length);
       
  2838                 for (PosixFilePermission perm : PosixFilePermission.values()) {
       
  2839                     if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) {
       
  2840                         perms.add(perm);
       
  2841                     }
       
  2842                 }
       
  2843             }
       
  2844             return Optional.ofNullable(perms);
       
  2845         }
       
  2846     }
       
  2847 
       
  2848     final class PosixEntry extends Entry implements PosixFileAttributes {
       
  2849         private UserPrincipal owner = defaultOwner;
       
  2850         private GroupPrincipal group = defaultGroup;
       
  2851 
       
  2852         PosixEntry(byte[] name, boolean isdir, int method) {
       
  2853             super(name, isdir, method);
       
  2854         }
       
  2855 
       
  2856         PosixEntry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
       
  2857             super(name, type, isdir, method, attrs);
       
  2858         }
       
  2859 
       
  2860         PosixEntry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
       
  2861             super(name, file, type, attrs);
       
  2862         }
       
  2863 
       
  2864         PosixEntry(PosixEntry e, int type) {
       
  2865             super(e, type);
       
  2866             this.owner = e.owner;
       
  2867             this.group = e.group;
       
  2868         }
       
  2869 
       
  2870         PosixEntry(ZipFileSystem zipfs, IndexNode inode) throws IOException {
       
  2871             super(zipfs, inode);
       
  2872         }
       
  2873 
       
  2874         @Override
       
  2875         public UserPrincipal owner() {
       
  2876             return owner;
       
  2877         }
       
  2878 
       
  2879         @Override
       
  2880         public GroupPrincipal group() {
       
  2881             return group;
       
  2882         }
       
  2883 
       
  2884         @Override
       
  2885         public Set<PosixFilePermission> permissions() {
       
  2886             return storedPermissions().orElse(Set.copyOf(defaultPermissions));
  2609         }
  2887         }
  2610     }
  2888     }
  2611 
  2889 
  2612     private static class ExistingChannelCloser {
  2890     private static class ExistingChannelCloser {
  2613         private final Path path;
  2891         private final Path path;