8213031: (zipfs) Add support for POSIX file permissions
authorclanger
Wed, 07 Aug 2019 11:28:14 +0100
changeset 57665 bf325b739c8a
parent 57664 1d2ea8db7083
child 57666 850f456d1e22
8213031: (zipfs) Add support for POSIX file permissions Reviewed-by: alanb, lancea
src/java.base/share/lib/security/default.policy
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPosixFileAttributeView.java
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java
src/jdk.zipfs/share/classes/module-info.java
test/jdk/jdk/nio/zipfs/TestPosix.java
test/jdk/jdk/nio/zipfs/test.policy.posix
--- a/src/java.base/share/lib/security/default.policy	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/java.base/share/lib/security/default.policy	Wed Aug 07 11:28:14 2019 +0100
@@ -201,8 +201,10 @@
 grant codeBase "jrt:/jdk.zipfs" {
     permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
     permission java.lang.RuntimePermission "fileSystemProvider";
+    permission java.lang.RuntimePermission "accessUserInformation";
     permission java.util.PropertyPermission "os.name", "read";
     permission java.util.PropertyPermission "user.dir", "read";
+    permission java.util.PropertyPermission "user.name", "read";
 };
 
 // permissions needed by applications using java.desktop module
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java	Wed Aug 07 11:28:14 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -65,6 +65,16 @@
     static final int ENDHDR = 22;       // END header size
 
     /*
+     * File attribute compatibility types of CEN field "version made by"
+     */
+    static final int FILE_ATTRIBUTES_UNIX = 3; // Unix
+
+    /*
+     * Base values for CEN field "version made by"
+     */
+    static final int VERSION_MADE_BY_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8; // Unix
+
+    /*
      * Local file (LOC) header field offsets
      */
     static final int LOCVER = 4;        // version needed to extract
@@ -228,22 +238,24 @@
     static final long ZIP64_LOCOFF(byte[] b) { return LL(b, 8);}   // zip64 end offset
 
     // central directory header (CEN) fields
-    static final long CENSIG(byte[] b, int pos) { return LG(b, pos + 0); }
-    static final int  CENVEM(byte[] b, int pos) { return SH(b, pos + 4); }
-    static final int  CENVER(byte[] b, int pos) { return SH(b, pos + 6); }
-    static final int  CENFLG(byte[] b, int pos) { return SH(b, pos + 8); }
-    static final int  CENHOW(byte[] b, int pos) { return SH(b, pos + 10);}
-    static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);}
-    static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);}
-    static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);}
-    static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);}
-    static final int  CENNAM(byte[] b, int pos) { return SH(b, pos + 28);}
-    static final int  CENEXT(byte[] b, int pos) { return SH(b, pos + 30);}
-    static final int  CENCOM(byte[] b, int pos) { return SH(b, pos + 32);}
-    static final int  CENDSK(byte[] b, int pos) { return SH(b, pos + 34);}
-    static final int  CENATT(byte[] b, int pos) { return SH(b, pos + 36);}
-    static final long CENATX(byte[] b, int pos) { return LG(b, pos + 38);}
-    static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);}
+    static final long CENSIG(byte[] b, int pos) { return LG(b, pos + 0); } // signature
+    static final int  CENVEM(byte[] b, int pos) { return SH(b, pos + 4); } // version made by
+    static final int  CENVEM_FA(byte[] b, int pos) { return CH(b, pos + 5); } // file attribute compatibility
+    static final int  CENVER(byte[] b, int pos) { return SH(b, pos + 6); } // version needed to extract
+    static final int  CENFLG(byte[] b, int pos) { return SH(b, pos + 8); } // encrypt, decrypt flags
+    static final int  CENHOW(byte[] b, int pos) { return SH(b, pos + 10);} // compression method
+    static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);} // modification time
+    static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);} // uncompressed file crc-32 value
+    static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);} // compressed size
+    static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);} // uncompressed size
+    static final int  CENNAM(byte[] b, int pos) { return SH(b, pos + 28);} // filename length
+    static final int  CENEXT(byte[] b, int pos) { return SH(b, pos + 30);} // extra field length
+    static final int  CENCOM(byte[] b, int pos) { return SH(b, pos + 32);} // comment length
+    static final int  CENDSK(byte[] b, int pos) { return SH(b, pos + 34);} // disk number start
+    static final int  CENATT(byte[] b, int pos) { return SH(b, pos + 36);} // internal file attributes
+    static final long CENATX(byte[] b, int pos) { return LG(b, pos + 38);} // external file attributes
+    static final int  CENATX_PERMS(byte[] b, int pos) { return SH(b, pos + 40);} // posix permission data
+    static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);} // LOC header offset
 
     /* The END header is followed by a variable length comment of size < 64k. */
     static final long END_MAXLEN = 0xFFFF + ENDHDR;
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java	Wed Aug 07 11:28:14 2019 +0100
@@ -27,15 +27,18 @@
 
 import java.io.IOException;
 import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermission;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal
  */
 class ZipFileAttributeView implements BasicFileAttributeView {
-    private enum AttrID {
+    static enum AttrID {
         size,
         creationTime,
         lastAccessTime,
@@ -47,10 +50,13 @@
         fileKey,
         compressedSize,
         crc,
-        method
+        method,
+        owner,
+        group,
+        permissions
     }
 
-    private final ZipPath path;
+    final ZipPath path;
     private final boolean isZipView;
 
     ZipFileAttributeView(ZipPath path, boolean isZipView) {
@@ -64,7 +70,7 @@
     }
 
     @Override
-    public ZipFileAttributes readAttributes() throws IOException {
+    public BasicFileAttributes readAttributes() throws IOException {
         return path.readAttributes();
     }
 
@@ -77,6 +83,11 @@
         path.setTimes(lastModifiedTime, lastAccessTime, createTime);
     }
 
+    public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
+        path.setPermissions(perms);
+    }
+
+    @SuppressWarnings("unchecked")
     void setAttribute(String attribute, Object value)
         throws IOException
     {
@@ -87,6 +98,8 @@
                 setTimes(null, (FileTime)value, null);
             if (AttrID.valueOf(attribute) == AttrID.creationTime)
                 setTimes(null, null, (FileTime)value);
+            if (AttrID.valueOf(attribute) == AttrID.permissions)
+                setPermissions((Set<PosixFilePermission>)value);
         } catch (IllegalArgumentException x) {
             throw new UnsupportedOperationException("'" + attribute +
                 "' is unknown or read-only attribute");
@@ -96,7 +109,7 @@
     Map<String, Object> readAttributes(String attributes)
         throws IOException
     {
-        ZipFileAttributes zfas = readAttributes();
+        ZipFileAttributes zfas = (ZipFileAttributes)readAttributes();
         LinkedHashMap<String, Object> map = new LinkedHashMap<>();
         if ("*".equals(attributes)) {
             for (AttrID id : AttrID.values()) {
@@ -115,7 +128,7 @@
         return map;
     }
 
-    private Object attribute(AttrID id, ZipFileAttributes zfas) {
+    Object attribute(AttrID id, ZipFileAttributes zfas) {
         switch (id) {
         case size:
             return zfas.size();
@@ -147,6 +160,11 @@
             if (isZipView)
                 return zfas.method();
             break;
+        case permissions:
+            if (isZipView) {
+                return zfas.storedPermissions().orElse(null);
+            }
+            break;
         default:
             break;
         }
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java	Wed Aug 07 11:28:14 2019 +0100
@@ -26,6 +26,9 @@
 package jdk.nio.zipfs;
 
 import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.Optional;
+import java.util.Set;
 
 /**
  * The attributes of a file stored in a zip file.
@@ -38,4 +41,5 @@
     int method();
     byte[] extra();
     byte[] comment();
+    Optional<Set<PosixFilePermission>> storedPermissions();
 }
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java	Wed Aug 07 11:28:14 2019 +0100
@@ -32,7 +32,9 @@
 import java.nio.file.Path;
 import java.nio.file.attribute.BasicFileAttributeView;
 import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileOwnerAttributeView;
 import java.nio.file.attribute.FileStoreAttributeView;
+import java.nio.file.attribute.PosixFileAttributeView;
 
 /**
  * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal
@@ -63,12 +65,15 @@
     @Override
     public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
         return (type == BasicFileAttributeView.class ||
-                type == ZipFileAttributeView.class);
+                type == ZipFileAttributeView.class ||
+                ((type == FileOwnerAttributeView.class ||
+                  type == PosixFileAttributeView.class) && zfs.supportPosix));
     }
 
     @Override
     public boolean supportsFileAttributeView(String name) {
-        return "basic".equals(name) || "zip".equals(name);
+        return "basic".equals(name) || "zip".equals(name) ||
+               (("owner".equals(name) || "posix".equals(name)) && zfs.supportPosix);
     }
 
     @Override
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java	Wed Aug 07 11:28:14 2019 +0100
@@ -41,9 +41,7 @@
 import java.nio.channels.SeekableByteChannel;
 import java.nio.channels.WritableByteChannel;
 import java.nio.file.*;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileTime;
-import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.*;
 import java.nio.file.spi.FileSystemProvider;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -82,9 +80,14 @@
     private static final boolean isWindows = AccessController.doPrivileged(
         (PrivilegedAction<Boolean>)()->System.getProperty("os.name")
                                              .startsWith("Windows"));
-    private static final Set<String> supportedFileAttributeViews =
-        Set.of("basic", "zip");
     private static final byte[] ROOTPATH = new byte[] { '/' };
+    private static final String OPT_POSIX = "enablePosixFileAttributes";
+    private static final String OPT_DEFAULT_OWNER = "defaultOwner";
+    private static final String OPT_DEFAULT_GROUP = "defaultGroup";
+    private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions";
+
+    private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS =
+        PosixFilePermissions.fromString("rwxrwxrwx");
 
     private final ZipFileSystemProvider provider;
     private final Path zfpath;
@@ -103,6 +106,14 @@
     private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true"
                                                 // METHOD_DEFLATED otherwise
 
+    // POSIX support
+    final boolean supportPosix;
+    private final UserPrincipal defaultOwner;
+    private final GroupPrincipal defaultGroup;
+    private final Set<PosixFilePermission> defaultPermissions;
+
+    private final Set<String> supportedFileAttributeViews;
+
     ZipFileSystem(ZipFileSystemProvider provider,
                   Path zfpath,
                   Map<String, ?> env) throws IOException
@@ -114,6 +125,12 @@
         this.useTempFile  = isTrue(env, "useTempFile");
         this.forceEnd64 = isTrue(env, "forceZIP64End");
         this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED;
+        this.supportPosix = isTrue(env, OPT_POSIX);
+        this.defaultOwner = initOwner(zfpath, env);
+        this.defaultGroup = initGroup(zfpath, env);
+        this.defaultPermissions = initPermissions(env);
+        this.supportedFileAttributeViews = supportPosix ?
+            Set.of("basic", "posix", "zip") : Set.of("basic", "zip");
         if (Files.notExists(zfpath)) {
             // create a new zip if it doesn't exist
             if (isTrue(env, "create")) {
@@ -151,6 +168,109 @@
         return "true".equals(env.get(name)) || TRUE.equals(env.get(name));
     }
 
+    // Initialize the default owner for files inside the zip archive.
+    // If not specified in env, it is the owner of the archive. If no owner can
+    // be determined, we try to go with system property "user.name". If that's not
+    // accessible, we return "<zipfs_default>".
+    private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) throws IOException {
+        Object o = env.get(OPT_DEFAULT_OWNER);
+        if (o == null) {
+            try {
+                PrivilegedExceptionAction<UserPrincipal> pa = ()->Files.getOwner(zfpath);
+                return AccessController.doPrivileged(pa);
+            } catch (UnsupportedOperationException | PrivilegedActionException e) {
+                if (e instanceof UnsupportedOperationException ||
+                    e.getCause() instanceof NoSuchFileException)
+                {
+                    PrivilegedAction<String> pa = ()->System.getProperty("user.name");
+                    String userName = AccessController.doPrivileged(pa);
+                    return ()->userName;
+                } else {
+                    throw new IOException(e);
+                }
+            }
+        }
+        if (o instanceof String) {
+            if (((String)o).isEmpty()) {
+                throw new IllegalArgumentException("Value for property " +
+                    OPT_DEFAULT_OWNER + " must not be empty.");
+            }
+            return ()->(String)o;
+        }
+        if (o instanceof UserPrincipal) {
+            return (UserPrincipal)o;
+        }
+        throw new IllegalArgumentException("Value for property " +
+            OPT_DEFAULT_OWNER + " must be of type " + String.class +
+            " or " + UserPrincipal.class);
+    }
+
+    // Initialize the default group for files inside the zip archive.
+    // If not specified in env, we try to determine the group of the zip archive itself.
+    // If this is not possible/unsupported, we will return a group principal going by
+    // the same name as the default owner.
+    private GroupPrincipal initGroup(Path zfpath, Map<String, ?> env) throws IOException {
+        Object o = env.get(OPT_DEFAULT_GROUP);
+        if (o == null) {
+            try {
+                PosixFileAttributeView zfpv = Files.getFileAttributeView(zfpath, PosixFileAttributeView.class);
+                if (zfpv == null) {
+                    return defaultOwner::getName;
+                }
+                PrivilegedExceptionAction<GroupPrincipal> pa = ()->zfpv.readAttributes().group();
+                return AccessController.doPrivileged(pa);
+            } catch (UnsupportedOperationException | PrivilegedActionException e) {
+                if (e instanceof UnsupportedOperationException ||
+                    e.getCause() instanceof NoSuchFileException)
+                {
+                    return defaultOwner::getName;
+                } else {
+                    throw new IOException(e);
+                }
+            }
+        }
+        if (o instanceof String) {
+            if (((String)o).isEmpty()) {
+                throw new IllegalArgumentException("Value for property " +
+                    OPT_DEFAULT_GROUP + " must not be empty.");
+            }
+            return ()->(String)o;
+        }
+        if (o instanceof GroupPrincipal) {
+            return (GroupPrincipal)o;
+        }
+        throw new IllegalArgumentException("Value for property " +
+            OPT_DEFAULT_GROUP + " must be of type " + String.class +
+            " or " + GroupPrincipal.class);
+    }
+
+    // Initialize the default permissions for files inside the zip archive.
+    // If not specified in env, it will return 777.
+    private Set<PosixFilePermission> initPermissions(Map<String, ?> env) {
+        Object o = env.get(OPT_DEFAULT_PERMISSIONS);
+        if (o == null) {
+            return DEFAULT_PERMISSIONS;
+        }
+        if (o instanceof String) {
+            return PosixFilePermissions.fromString((String)o);
+        }
+        if (!(o instanceof Set)) {
+            throw new IllegalArgumentException("Value for property " +
+                OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class +
+                " or " + Set.class);
+        }
+        Set<PosixFilePermission> perms = new HashSet<>();
+        for (Object o2 : (Set<?>)o) {
+            if (o2 instanceof PosixFilePermission) {
+                perms.add((PosixFilePermission)o2);
+            } else {
+                throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS +
+                    " must only contain objects of type " + PosixFilePermission.class);
+            }
+        }
+        return perms;
+    }
+
     @Override
     public FileSystemProvider provider() {
         return provider;
@@ -338,11 +458,13 @@
                 return (Entry)inode;
             } else if (inode.pos == -1) {
                 // pseudo directory, uses METHOD_STORED
-                Entry e = new Entry(inode.name, inode.isdir, METHOD_STORED);
+                Entry e = supportPosix ?
+                    new PosixEntry(inode.name, inode.isdir, METHOD_STORED) :
+                    new Entry(inode.name, inode.isdir, METHOD_STORED);
                 e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp;
                 return e;
             } else {
-                return new Entry(this, inode);
+                return supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
             }
         } finally {
             endRead();
@@ -387,6 +509,65 @@
         }
     }
 
+    void setOwner(byte[] path, UserPrincipal owner) throws IOException {
+        checkWritable();
+        beginWrite();
+        try {
+            ensureOpen();
+            Entry e = getEntry(path);    // ensureOpen checked
+            if (e == null) {
+                throw new NoSuchFileException(getString(path));
+            }
+            // as the owner information is not persistent, we don't need to
+            // change e.type to Entry.COPY
+            if (e instanceof PosixEntry) {
+                ((PosixEntry)e).owner = owner;
+                update(e);
+            }
+        } finally {
+            endWrite();
+        }
+    }
+
+    void setGroup(byte[] path, GroupPrincipal group) throws IOException {
+        checkWritable();
+        beginWrite();
+        try {
+            ensureOpen();
+            Entry e = getEntry(path);    // ensureOpen checked
+            if (e == null) {
+                throw new NoSuchFileException(getString(path));
+            }
+            // as the group information is not persistent, we don't need to
+            // change e.type to Entry.COPY
+            if (e instanceof PosixEntry) {
+                ((PosixEntry)e).group = group;
+                update(e);
+            }
+        } finally {
+            endWrite();
+        }
+    }
+
+    void setPermissions(byte[] path, Set<PosixFilePermission> perms) throws IOException {
+        checkWritable();
+        beginWrite();
+        try {
+            ensureOpen();
+            Entry e = getEntry(path);    // ensureOpen checked
+            if (e == null) {
+                throw new NoSuchFileException(getString(path));
+            }
+            if (e.type == Entry.CEN) {
+                e.type = Entry.COPY;     // copy e
+            }
+            e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms);
+            update(e);
+        } finally {
+            endWrite();
+        }
+    }
+
     boolean exists(byte[] path) {
         beginRead();
         try {
@@ -448,7 +629,9 @@
             if (dir.length == 0 || exists(dir))  // root dir, or existing dir
                 throw new FileAlreadyExistsException(getString(dir));
             checkParents(dir);
-            Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED);
+            Entry e = supportPosix ?
+                new PosixEntry(dir, Entry.NEW, true, METHOD_STORED, attrs) :
+                new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs);
             update(e);
         } finally {
             endWrite();
@@ -489,7 +672,9 @@
                 checkParents(dst);
             }
             // copy eSrc entry and change name
-            Entry u = new Entry(eSrc, Entry.COPY);
+            Entry u = supportPosix ?
+                new PosixEntry((PosixEntry)eSrc, Entry.COPY) :
+                new Entry(eSrc, Entry.COPY);
             u.name(dst);
             if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) {
                 u.type = eSrc.type;    // make it the same type
@@ -553,12 +738,15 @@
                     }
                     return os;
                 }
-                return getOutputStream(new Entry(e, Entry.NEW));
+                return getOutputStream(supportPosix ?
+                    new PosixEntry((PosixEntry)e, Entry.NEW) : new Entry(e, Entry.NEW));
             } else {
                 if (!hasCreate && !hasCreateNew)
                     throw new NoSuchFileException(getString(path));
                 checkParents(path);
-                return getOutputStream(new Entry(path, Entry.NEW, false, defaultCompressionMethod));
+                return getOutputStream(supportPosix ?
+                    new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod) :
+                    new Entry(path, Entry.NEW, false, defaultCompressionMethod));
             }
         } finally {
             endRead();
@@ -645,7 +833,9 @@
                     if (e.isDir() || options.contains(CREATE_NEW))
                         throw new FileAlreadyExistsException(getString(path));
                     SeekableByteChannel sbc =
-                            new EntryOutputChannel(new Entry(e, Entry.NEW));
+                            new EntryOutputChannel(supportPosix ?
+                                new PosixEntry((PosixEntry)e, Entry.NEW) :
+                                new Entry(e, Entry.NEW));
                     if (options.contains(APPEND)) {
                         try (InputStream is = getInputStream(e)) {  // copyover
                             byte[] buf = new byte[8192];
@@ -664,7 +854,9 @@
                     throw new NoSuchFileException(getString(path));
                 checkParents(path);
                 return new EntryOutputChannel(
-                    new Entry(path, Entry.NEW, false, defaultCompressionMethod));
+                    supportPosix ?
+                        new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod, attrs) :
+                        new Entry(path, Entry.NEW, false, defaultCompressionMethod, attrs));
             } finally {
                 endRead();
             }
@@ -728,7 +920,10 @@
             final FileChannel fch = tmpfile.getFileSystem()
                                            .provider()
                                            .newFileChannel(tmpfile, options, attrs);
-            final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH);
+            final Entry u = isFCH ? e : (
+                supportPosix ?
+                new PosixEntry(path, tmpfile, Entry.FILECH, attrs) :
+                new Entry(path, tmpfile, Entry.FILECH, attrs));
             if (forWrite) {
                 u.flag = FLAG_DATADESCR;
                 u.method = defaultCompressionMethod;
@@ -1343,7 +1538,7 @@
                         continue;               // no root '/' directory even if it
                                                 // exists in original zip/jar file.
                     }
-                    e = new Entry(this, inode);
+                    e = supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
                     try {
                         if (buf == null)
                             buf = new byte[8192];
@@ -1417,7 +1612,7 @@
             return (Entry)inode;
         if (inode == null || inode.pos == -1)
             return null;
-        return new Entry(this, inode);
+        return supportPosix ? new PosixEntry(this, inode): new Entry(this, inode);
     }
 
     public void deleteFile(byte[] path, boolean failIfNotExists)
@@ -2053,6 +2248,7 @@
         // entry attributes
         int    version;
         int    flag;
+        int    posixPerms = -1; // posix permissions
         int    method = -1;    // compression method
         long   mtime  = -1;    // last modification time (in DOS time)
         long   atime  = -1;    // last access time
@@ -2081,13 +2277,20 @@
             this.method = method;
         }
 
-        Entry(byte[] name, int type, boolean isdir, int method) {
+        @SuppressWarnings("unchecked")
+        Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
             this(name, isdir, method);
             this.type = type;
+            for (FileAttribute<?> attr : attrs) {
+                String attrName = attr.name();
+                if (attrName.equals("posix:permissions")) {
+                    posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value());
+                }
+            }
         }
 
-        Entry(byte[] name, Path file, int type) {
-            this(name, type, false, METHOD_STORED);
+        Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
+            this(name, type, false, METHOD_STORED, attrs);
             this.file = file;
         }
 
@@ -2111,6 +2314,7 @@
             */
             this.locoff    = e.locoff;
             this.comment   = e.comment;
+            this.posixPerms = e.posixPerms;
             this.type      = type;
         }
 
@@ -2135,6 +2339,15 @@
             throw new ZipException("unsupported compression method");
         }
 
+        /**
+         * Adds information about compatibility of file attribute information
+         * to a version value.
+         */
+        private int versionMadeBy(int version) {
+            return (posixPerms < 0) ? version :
+                VERSION_MADE_BY_BASE_UNIX | (version & 0xff);
+        }
+
         ///////////////////// CEN //////////////////////
         private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException {
             byte[] cen = zipfs.cen;
@@ -2157,6 +2370,9 @@
             attrs       = CENATT(cen, pos);
             attrsEx     = CENATX(cen, pos);
             */
+            if (CENVEM_FA(cen, pos) == FILE_ATTRIBUTES_UNIX) {
+                posixPerms = CENATX_PERMS(cen, pos) & 0xFFF; // 12 bits for setuid, setgid, sticky + perms
+            }
             locoff      = CENOFF(cen, pos);
             pos += CENHDR;
             this.name = inode.name;
@@ -2223,7 +2439,7 @@
                 }
             }
             writeInt(os, CENSIG);            // CEN header signature
-            writeShort(os, version0);        // version made by
+            writeShort(os, versionMadeBy(version0)); // version made by
             writeShort(os, version0);        // version needed to extract
             writeShort(os, flag);            // general purpose bit flag
             writeShort(os, method);          // compression method
@@ -2242,7 +2458,9 @@
             }
             writeShort(os, 0);              // starting disk number
             writeShort(os, 0);              // internal file attributes (unused)
-            writeInt(os, 0);                // external file attributes (unused)
+            writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file
+                                            // attributes, used for storing posix
+                                            // permissions
             writeInt(os, locoff0);          // relative offset of local header
             writeBytes(os, zname, 1, nlen);
             if (zip64) {
@@ -2527,6 +2745,10 @@
             fm.format("    compressedSize  : %d%n", compressedSize());
             fm.format("    crc             : %x%n", crc());
             fm.format("    method          : %d%n", method());
+            Set<PosixFilePermission> permissions = storedPermissions().orElse(null);
+            if (permissions != null) {
+                fm.format("    permissions     : %s%n", permissions);
+            }
             fm.close();
             return sb.toString();
         }
@@ -2607,6 +2829,62 @@
                 return Arrays.copyOf(comment, comment.length);
             return null;
         }
+
+        @Override
+        public Optional<Set<PosixFilePermission>> storedPermissions() {
+            Set<PosixFilePermission> perms = null;
+            if (posixPerms != -1) {
+                perms = new HashSet<>(PosixFilePermission.values().length);
+                for (PosixFilePermission perm : PosixFilePermission.values()) {
+                    if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) {
+                        perms.add(perm);
+                    }
+                }
+            }
+            return Optional.ofNullable(perms);
+        }
+    }
+
+    final class PosixEntry extends Entry implements PosixFileAttributes {
+        private UserPrincipal owner = defaultOwner;
+        private GroupPrincipal group = defaultGroup;
+
+        PosixEntry(byte[] name, boolean isdir, int method) {
+            super(name, isdir, method);
+        }
+
+        PosixEntry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) {
+            super(name, type, isdir, method, attrs);
+        }
+
+        PosixEntry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
+            super(name, file, type, attrs);
+        }
+
+        PosixEntry(PosixEntry e, int type) {
+            super(e, type);
+            this.owner = e.owner;
+            this.group = e.group;
+        }
+
+        PosixEntry(ZipFileSystem zipfs, IndexNode inode) throws IOException {
+            super(zipfs, inode);
+        }
+
+        @Override
+        public UserPrincipal owner() {
+            return owner;
+        }
+
+        @Override
+        public GroupPrincipal group() {
+            return group;
+        }
+
+        @Override
+        public Set<PosixFilePermission> permissions() {
+            return storedPermissions().orElse(Set.copyOf(defaultPermissions));
+        }
     }
 
     private static class ExistingChannelCloser {
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java	Wed Aug 07 11:28:14 2019 +0100
@@ -34,11 +34,7 @@
 import java.nio.channels.SeekableByteChannel;
 import java.nio.file.*;
 import java.nio.file.DirectoryStream.Filter;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileAttribute;
-import java.nio.file.attribute.FileAttributeView;
-import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.*;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Map;
@@ -711,6 +707,12 @@
             return (V)new ZipFileAttributeView(this, false);
         if (type == ZipFileAttributeView.class)
             return (V)new ZipFileAttributeView(this, true);
+        if (zfs.supportPosix) {
+            if (type == PosixFileAttributeView.class)
+                return (V)new ZipPosixFileAttributeView(this, false);
+            if (type == FileOwnerAttributeView.class)
+                return (V)new ZipPosixFileAttributeView(this,true);
+        }
         throw new UnsupportedOperationException("view <" + type + "> is not supported");
     }
 
@@ -721,6 +723,12 @@
             return new ZipFileAttributeView(this, false);
         if ("zip".equals(type))
             return new ZipFileAttributeView(this, true);
+        if (zfs.supportPosix) {
+            if ("posix".equals(type))
+                return new ZipPosixFileAttributeView(this, false);
+            if ("owner".equals(type))
+                return new ZipPosixFileAttributeView(this, true);
+        }
         throw new UnsupportedOperationException("view <" + type + "> is not supported");
     }
 
@@ -764,10 +772,16 @@
 
     @SuppressWarnings("unchecked") // Cast to A
     <A extends BasicFileAttributes> A readAttributes(Class<A> type) throws IOException {
+        // unconditionally support BasicFileAttributes and ZipFileAttributes
         if (type == BasicFileAttributes.class || type == ZipFileAttributes.class) {
             return (A)readAttributes();
         }
 
+        // support PosixFileAttributes when activated
+        if (type == PosixFileAttributes.class && zfs.supportPosix) {
+            return (A)readAttributes();
+        }
+
         throw new UnsupportedOperationException("Attributes of type " +
             type.getName() + " not supported");
     }
@@ -794,9 +808,22 @@
         zfs.setTimes(getResolvedPath(), mtime, atime, ctime);
     }
 
+    void setOwner(UserPrincipal owner) throws IOException {
+        zfs.setOwner(getResolvedPath(), owner);
+    }
+
+    void setPermissions(Set<PosixFilePermission> perms)
+        throws IOException
+    {
+        zfs.setPermissions(getResolvedPath(), perms);
+    }
+
+    void setGroup(GroupPrincipal group) throws IOException {
+        zfs.setGroup(getResolvedPath(), group);
+    }
+
     Map<String, Object> readAttributes(String attributes, LinkOption... options)
         throws IOException
-
     {
         String view;
         String attrs;
@@ -948,12 +975,14 @@
             }
         }
         if (copyAttrs) {
-            BasicFileAttributeView view =
-                target.getFileAttributeView(BasicFileAttributeView.class);
+            ZipFileAttributeView view =
+                target.getFileAttributeView(ZipFileAttributeView.class);
             try {
                 view.setTimes(zfas.lastModifiedTime(),
                               zfas.lastAccessTime(),
                               zfas.creationTime());
+                // copy permissions
+                view.setPermissions(zfas.storedPermissions().orElse(null));
             } catch (IOException x) {
                 // rollback?
                 try {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPosixFileAttributeView.java	Wed Aug 07 11:28:14 2019 +0100
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nio.zipfs;
+
+import java.io.IOException;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.UserPrincipal;
+
+/**
+ * The zip file system attribute view with POSIX support.
+ */
+class ZipPosixFileAttributeView extends ZipFileAttributeView implements PosixFileAttributeView {
+    private final boolean isOwnerView;
+
+    ZipPosixFileAttributeView(ZipPath path, boolean owner) {
+        super(path, true);
+        this.isOwnerView = owner;
+    }
+
+    @Override
+    public String name() {
+        return isOwnerView ? "owner" : "posix";
+    }
+
+    @Override
+    public PosixFileAttributes readAttributes() throws IOException {
+        return (PosixFileAttributes)path.readAttributes();
+    }
+
+    @Override
+    public UserPrincipal getOwner() throws IOException {
+        return readAttributes().owner();
+    }
+
+    @Override
+    public void setOwner(UserPrincipal owner) throws IOException {
+        path.setOwner(owner);
+    }
+
+    @Override
+    public void setGroup(GroupPrincipal group) throws IOException {
+        path.setGroup(group);
+    }
+
+    @Override
+    Object attribute(AttrID id, ZipFileAttributes zfas) {
+        PosixFileAttributes pzfas = (PosixFileAttributes)zfas;
+        switch (id) {
+        case owner:
+            return pzfas.owner();
+        case group:
+            return pzfas.group();
+        case permissions:
+            if (!isOwnerView) {
+                return pzfas.permissions();
+            } else {
+                return super.attribute(id, zfas);
+            }
+        default:
+            return super.attribute(id, zfas);
+        }
+    }
+}
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Wed Aug 07 11:28:14 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,12 +27,14 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.file.attribute.PosixFilePermission;
 import java.time.DateTimeException;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Arrays;
 import java.util.Date;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.PatternSyntaxException;
 
@@ -41,6 +43,102 @@
  */
 class ZipUtils {
 
+    /**
+     * The bit flag used to specify read permission by the owner.
+     */
+    static final int POSIX_USER_READ = 0400;
+
+    /**
+     * The bit flag used to specify write permission by the owner.
+     */
+    static final int POSIX_USER_WRITE = 0200;
+
+    /**
+     * The bit flag used to specify execute permission by the owner.
+     */
+    static final int POSIX_USER_EXECUTE = 0100;
+
+    /**
+     * The bit flag used to specify read permission by the group.
+     */
+    static final int POSIX_GROUP_READ = 040;
+
+    /**
+     * The bit flag used to specify write permission by the group.
+     */
+    static final int POSIX_GROUP_WRITE = 020;
+
+    /**
+     * The bit flag used to specify execute permission by the group.
+     */
+    static final int POSIX_GROUP_EXECUTE = 010;
+
+    /**
+     * The bit flag used to specify read permission by others.
+     */
+    static final int POSIX_OTHER_READ = 04;
+
+    /**
+     * The bit flag used to specify write permission by others.
+     */
+    static final int POSIX_OTHER_WRITE = 02;
+
+    /**
+     * The bit flag used to specify execute permission by others.
+     */
+    static final int POSIX_OTHER_EXECUTE = 01;
+
+    /**
+     * Convert a {@link PosixFilePermission} object into the appropriate bit
+     * flag.
+     *
+     * @param perm The {@link PosixFilePermission} object.
+     * @return The bit flag as int.
+     */
+    static int permToFlag(PosixFilePermission perm) {
+        switch(perm) {
+        case OWNER_READ:
+            return POSIX_USER_READ;
+        case OWNER_WRITE:
+            return POSIX_USER_WRITE;
+        case OWNER_EXECUTE:
+            return POSIX_USER_EXECUTE;
+        case GROUP_READ:
+            return POSIX_GROUP_READ;
+        case GROUP_WRITE:
+            return POSIX_GROUP_WRITE;
+        case GROUP_EXECUTE:
+            return POSIX_GROUP_EXECUTE;
+        case OTHERS_READ:
+            return POSIX_OTHER_READ;
+        case OTHERS_WRITE:
+            return POSIX_OTHER_WRITE;
+        case OTHERS_EXECUTE:
+            return POSIX_OTHER_EXECUTE;
+        default:
+            return 0;
+        }
+    }
+
+    /**
+     * Converts a set of {@link PosixFilePermission}s into an int value where
+     * the according bits are set.
+     *
+     * @param perms A Set of {@link PosixFilePermission} objects.
+     *
+     * @return A bit mask representing the input Set.
+     */
+    static int permsToFlags(Set<PosixFilePermission> perms) {
+        if (perms == null) {
+            return -1;
+        }
+        int flags = 0;
+        for (PosixFilePermission perm : perms) {
+            flags |= permToFlag(perm);
+        }
+        return flags;
+    }
+
     /*
      * Writes a 16-bit short to the output stream in little-endian byte order.
      */
--- a/src/jdk.zipfs/share/classes/module-info.java	Wed Aug 07 12:09:55 2019 +0200
+++ b/src/jdk.zipfs/share/classes/module-info.java	Wed Aug 07 11:28:14 2019 +0100
@@ -23,6 +23,15 @@
  * questions.
  */
 
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.util.Set;
+
 /**
  * Provides the implementation of the Zip file system provider.
  * The Zip file system provider treats the contents of a Zip or JAR file as a file system.
@@ -32,14 +41,91 @@
  * The {@linkplain java.nio.file.FileSystems FileSystems} {@code newFileSystem}
  * static factory methods can be used to:
  * <ul>
- *     <li>Create a Zip file system</li>
- *     <li>Open an existing file as a Zip file system</li>
+ *   <li>Create a Zip file system</li>
+ *   <li>Open an existing file as a Zip file system</li>
  * </ul>
  *
- * <h3>URI Scheme Used to Identify the Zip File System</h3>
+ * <h2>URI Scheme Used to Identify the Zip File System</h2>
  *
  * The URI {@link java.net.URI#getScheme scheme} that identifies the ZIP file system is {@code jar}.
  *
+ * <h2>POSIX file attributes</h2>
+ *
+ * A Zip file system supports a file attribute {@link FileAttributeView view}
+ * named "{@code zip}" that defines the following file attribute:
+ *
+ * <blockquote>
+ * <table class="striped">
+ * <caption style="display:none">Supported attributes</caption>
+ * <thead>
+ *   <tr>
+ *     <th scope="col">Name</th>
+ *     <th scope="col">Type</th>
+ *   </tr>
+ * </thead>
+ * <tbody>
+ *   <tr>
+ *     <th scope="row">permissions</th>
+ *     <td>{@link Set}&lt;{@link PosixFilePermission}&gt;</td>
+ *   </tr>
+ * </tbody>
+ * </table>
+ * </blockquote>
+ *
+ * The "permissions" attribute is the set of access permissions that are optionally
+ * stored for entries in a Zip file. The value of the attribute is {@code null}
+ * for entries that do not have access permissions. Zip file systems do not
+ * enforce access permissions.
+ *
+ * <p> The "permissions" attribute may be read and set using the
+ * {@linkplain Files#getAttribute(Path, String, LinkOption...) Files.getAttribute} and
+ * {@linkplain Files#setAttribute(Path, String, Object, LinkOption...) Files.setAttribute}
+ * methods. The following example uses these methods to read and set the attribute:
+ * <pre> {@code
+ *     Set<PosixFilePermission> perms = Files.getAttribute(entry, "zip:permissions");
+ *     if (perms == null) {
+ *         perms = PosixFilePermissions.fromString("rw-rw-rw-");
+ *         Files.setAttribute(entry, "zip:permissions", perms);
+ *     }
+ * } </pre>
+ *
+ * <p> In addition to the "{@code zip}" view, a Zip file system optionally supports
+ * the {@link PosixFileAttributeView} ("{@code posix}").
+ * This view extends the "{@code basic}" view with type safe access to the
+ * {@link PosixFileAttributes#owner() owner}, {@link PosixFileAttributes#group() group-owner},
+ * and {@link PosixFileAttributes#permissions() permissions} attributes. The
+ * "{@code posix}" view is only supported when the Zip file system is created with
+ * the provider property "{@code enablePosixFileAttributes}" set to "{@code true}".
+ * The following creates a file system with this property and reads the access
+ * permissions of a file:
+ * <pre> {@code
+ *     var env = Map.of("enablePosixFileAttributes", "true");
+ *     try (FileSystem fs = FileSystems.newFileSystem(file, env) {
+ *         Path entry = fs.getPath("entry");
+ *         Set<PosixFilePermission> perms = Files.getPosixFilePermissions(entry);
+ *     }
+ * } </pre>
+ *
+ * <p> The file owner and group owner attributes are not persisted, meaning they are
+ * not stored in the zip file. The "{@code defaultOwner}" and "{@code defaultGroup}"
+ * provider properties (listed below) can be used to configure the default values
+ * for these attributes. If these properties are not set then the file owner
+ * defaults to the owner of the zip file, and the group owner defaults to the
+ * zip file's group owner (or the file owner on platforms that don't support a
+ * group owner).
+ *
+ * <p> The "{@code permissions}" attribute is not optional in the "{@code posix}"
+ * view so a default set of permissions are used for entries that do not have
+ * access permissions stored in the Zip file. The default set of permissions
+ * are
+ * <ul>
+ *   <li>{@link PosixFilePermission#OWNER_READ OWNER_READ}</li>
+ *   <li>{@link PosixFilePermission#OWNER_WRITE OWNER_WRITE}</li>
+ *   <li>{@link PosixFilePermission#GROUP_READ GROUP_READ}</li>
+ * </ul>
+ * The default permissions can be configured with the "{@code defaultPermissions}"
+ * property described below.
+ *
  * <h2>Zip File System Properties</h2>
  *
  * The following properties may be specified when creating a Zip
@@ -50,12 +136,12 @@
  *     a new Zip file system
  * </caption>
  * <thead>
- * <tr>
- * <th scope="col">Property Name</th>
- * <th scope="col">Data Type</th>
- * <th scope="col">Default Value</th>
- * <th scope="col">Description</th>
- * </tr>
+ *   <tr>
+ *     <th scope="col">Property Name</th>
+ *     <th scope="col">Data Type</th>
+ *     <th scope="col">Default Value</th>
+ *     <th scope="col">Description</th>
+ *   </tr>
  * </thead>
  *
  * <tbody>
@@ -77,6 +163,44 @@
  *       names of the entries in the Zip or JAR file.
  *   </td>
  * </tr>
+ * <tr>
+ *   <td scope="row">enablePosixFileAttributes</td>
+ *   <td>java.lang.String</td>
+ *   <td>false</td>
+ *   <td>
+ *       If the value is {@code true}, the Zip file system will support
+ *       the {@link java.nio.file.attribute.PosixFileAttributeView PosixFileAttributeView}.
+ *   </td>
+ * </tr>
+ * <tr>
+ *   <td scope="row">defaultOwner</td>
+ *   <td>{@link java.nio.file.attribute.UserPrincipal UserPrincipal}<br> or java.lang.String</td>
+ *   <td>null/unset</td>
+ *   <td>
+ *       Override the default owner for entries in the Zip file system.<br>
+ *       The value can be a UserPrincipal or a String value that is used as the UserPrincipal's name.
+ *   </td>
+ * </tr>
+ * <tr>
+ *   <td scope="row">defaultGroup</td>
+ *   <td>{@link java.nio.file.attribute.GroupPrincipal GroupPrincipal}<br> or java.lang.String</td>
+ *   <td>null/unset</td>
+ *   <td>
+ *       Override the the default group for entries in the Zip file system.<br>
+ *       The value can be a GroupPrincipal or a String value that is used as the GroupPrincipal's name.
+ *   </td>
+ * </tr>
+ * <tr>
+ *   <td scope="row">defaultPermissions</td>
+ *   <td>{@link java.util.Set Set}&lt;{@link java.nio.file.attribute.PosixFilePermission PosixFilePermission}&gt;<br>
+ *       or java.lang.String</td>
+ *   <td>null/unset</td>
+ *   <td>
+ *       Override the default Set of permissions for entries in the Zip file system.<br>
+ *       The value can be a {@link java.util.Set Set}&lt;{@link java.nio.file.attribute.PosixFilePermission PosixFilePermission}&gt; or<br>
+ *       a String that is parsed by {@link java.nio.file.attribute.PosixFilePermissions#fromString PosixFilePermissions::fromString}
+ *   </td>
+ * </tr>
  * </tbody>
  * </table>
  *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/nio/zipfs/TestPosix.java	Wed Aug 07 11:28:14 2019 +0100
@@ -0,0 +1,725 @@
+/*
+ * Copyright (c) 2019, SAP SE. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.nio.file.attribute.UserPrincipal;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.spi.ToolProvider;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.testng.annotations.Test;
+
+import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
+import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
+import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
+import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+/**
+ * @test
+ * @bug 8213031
+ * @modules jdk.zipfs
+ *          jdk.jartool
+ * @run testng TestPosix
+ * @run testng/othervm/java.security.policy=test.policy.posix TestPosix
+ * @summary Test POSIX zip file operations.
+ */
+public class TestPosix {
+    private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
+        .orElseThrow(()->new RuntimeException("jar tool not found"));
+
+    // files and directories
+    private static final Path ZIP_FILE = Paths.get("testPosix.zip");
+    private static final Path JAR_FILE = Paths.get("testPosix.jar");
+    private static final Path ZIP_FILE_COPY = Paths.get("testPosixCopy.zip");
+    private static final Path UNZIP_DIR = Paths.get("unzip/");
+
+    // permission sets
+    private static final Set<PosixFilePermission> ALLPERMS =
+        PosixFilePermissions.fromString("rwxrwxrwx");
+    private static final Set<PosixFilePermission> EMPTYPERMS =
+        Collections.<PosixFilePermission>emptySet();
+    private static final Set<PosixFilePermission> UR = Set.of(OWNER_READ);
+    private static final Set<PosixFilePermission> UW = Set.of(OWNER_WRITE);
+    private static final Set<PosixFilePermission> UE = Set.of(OWNER_EXECUTE);
+    private static final Set<PosixFilePermission> GR = Set.of(GROUP_READ);
+    private static final Set<PosixFilePermission> GW = Set.of(GROUP_WRITE);
+    private static final Set<PosixFilePermission> GE = Set.of(GROUP_EXECUTE);
+    private static final Set<PosixFilePermission> OR = Set.of(OTHERS_READ);
+    private static final Set<PosixFilePermission> OW = Set.of(OTHERS_WRITE);
+    private static final Set<PosixFilePermission> OE = Set.of(OTHERS_EXECUTE);
+
+    // principals
+    private static final UserPrincipal DUMMY_USER = ()->"defusr";
+    private static final GroupPrincipal DUMMY_GROUP = ()->"defgrp";
+
+    // FS open options
+    private static final Map<String, Object> ENV_DEFAULT = Collections.<String, Object>emptyMap();
+    private static final Map<String, Object> ENV_POSIX = Map.of("enablePosixFileAttributes", true);
+
+    // misc
+    private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES};
+    private static final Map<String, ZipFileEntryInfo> ENTRIES = new HashMap<>();
+
+    private int entriesCreated;
+
+    static enum checkExpects {
+        contentOnly,
+        noPermDataInZip,
+        permsInZip,
+        permsPosix
+    }
+
+    static class ZipFileEntryInfo {
+        // permissions to set initially
+        private final Set<PosixFilePermission> intialPerms;
+        // permissions to set in a later call
+        private final Set<PosixFilePermission> laterPerms;
+        // permissions that should be effective in the zip file
+        private final Set<PosixFilePermission> permsInZip;
+        // permissions that should be returned by zipfs w/Posix support
+        private final Set<PosixFilePermission> permsPosix;
+        // entry is a directory
+        private final boolean isDir;
+        // need additional read flag in copy test
+        private final boolean setReadFlag;
+
+        private ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms,
+            Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)
+        {
+            this.intialPerms = initialPerms;
+            this.laterPerms = laterPerms;
+            this.permsInZip = permsInZip;
+            this.permsPosix = permsZipPosix;
+            this.isDir = isDir;
+            this.setReadFlag = setReadFlag;
+        }
+    }
+
+    static class CopyVisitor extends SimpleFileVisitor<Path> {
+        private Path from, to;
+        private boolean copyPerms;
+
+        CopyVisitor(Path from, Path to) {
+            this.from = from;
+            this.to = to;
+        }
+
+        CopyVisitor(Path from, Path to, boolean copyPerms) {
+            this.from = from;
+            this.to = to;
+            this.copyPerms = copyPerms;
+        }
+
+        @Override
+        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+            FileVisitResult rc = super.preVisitDirectory(dir, attrs);
+            Path target = to.resolve(from.relativize(dir).toString());
+            if (!Files.exists(target)) {
+                Files.copy(dir, target, COPY_ATTRIBUTES);
+                if (copyPerms) {
+                    Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(dir));
+                }
+            }
+            return rc;
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+            FileVisitResult rc = super.visitFile(file, attrs);
+            Path target = to.resolve(from.relativize(file).toString());
+            Files.copy(file, target, COPY_ATTRIBUTES);
+            if (copyPerms) {
+                Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(file));
+            }
+            return rc;
+        }
+    }
+
+    static class DeleteVisitor extends SimpleFileVisitor<Path> {
+        @Override
+        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+            FileVisitResult rc = super.postVisitDirectory(dir, exc);
+            Files.delete(dir);
+            return rc;
+        }
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+            FileVisitResult rc = super.visitFile(file, attrs);
+            Files.delete(file);
+            return rc;
+        }
+    }
+
+    @FunctionalInterface
+    static interface Executor {
+        void doIt() throws IOException;
+    }
+
+    static {
+        ENTRIES.put("dir",        new ZipFileEntryInfo(ALLPERMS,   null, ALLPERMS,   ALLPERMS,   true,  false));
+        ENTRIES.put("uread",      new ZipFileEntryInfo(UR,         null, UR,         UR,         false, false));
+        ENTRIES.put("uwrite",     new ZipFileEntryInfo(UW,         null, UW,         UW,         false, true));
+        ENTRIES.put("uexec",      new ZipFileEntryInfo(UE,         null, UE,         UE,         false, true));
+        ENTRIES.put("gread",      new ZipFileEntryInfo(GR,         null, GR,         GR,         false, true));
+        ENTRIES.put("gwrite",     new ZipFileEntryInfo(GW,         null, GW,         GW,         false, true));
+        ENTRIES.put("gexec",      new ZipFileEntryInfo(GE,         null, GE,         GE,         false, true));
+        ENTRIES.put("oread",      new ZipFileEntryInfo(OR,         null, OR,         OR,         false, true));
+        ENTRIES.put("owrite",     new ZipFileEntryInfo(OW,         null, OW,         OW,         false, true));
+        ENTRIES.put("oexec",      new ZipFileEntryInfo(OE,         null, OE,         OE,         false, true));
+        ENTRIES.put("emptyperms", new ZipFileEntryInfo(EMPTYPERMS, null, EMPTYPERMS, EMPTYPERMS, false, true));
+        ENTRIES.put("noperms",    new ZipFileEntryInfo(null,       null, null,       ALLPERMS,   false, false));
+        ENTRIES.put("permslater", new ZipFileEntryInfo(null,       UR,   UR,         UR,         false, false));
+    }
+
+    private static String expectedDefaultOwner(Path zf) {
+        try {
+            try {
+                PrivilegedExceptionAction<String> pa = ()->Files.getOwner(zf).getName();
+                return AccessController.doPrivileged(pa);
+            } catch (UnsupportedOperationException e) {
+                // if we can't get the owner of the file, we fall back to system property user.name
+                PrivilegedAction<String> pa = ()->System.getProperty("user.name");
+                return AccessController.doPrivileged(pa);
+            }
+        } catch (PrivilegedActionException | SecurityException e) {
+            System.out.println("Caught " + e.getClass().getName() + "(" + e.getMessage() +
+                ") when running a privileged operation to get the default owner.");
+            return null;
+        }
+    }
+
+    private static String expectedDefaultGroup(Path zf, String defaultOwner) {
+        try {
+            try {
+                PosixFileAttributeView zfpv = Files.getFileAttributeView(zf, PosixFileAttributeView.class);
+                if (zfpv == null) {
+                    return defaultOwner;
+                }
+                PrivilegedExceptionAction<String> pa = ()->zfpv.readAttributes().group().getName();
+                return AccessController.doPrivileged(pa);
+            } catch (UnsupportedOperationException e) {
+                return defaultOwner;
+            }
+        } catch (PrivilegedActionException | SecurityException e) {
+            System.out.println("Caught an exception when running a privileged operation to get the default group.");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private void putEntry(FileSystem fs, String name, ZipFileEntryInfo entry) throws IOException {
+        if (entry.isDir) {
+            if (entry.intialPerms == null) {
+                Files.createDirectory(fs.getPath(name));
+            } else {
+                Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
+            }
+
+        } else {
+            if (entry.intialPerms == null) {
+                Files.createFile(fs.getPath(name));
+            } else {
+                Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
+            }
+        }
+        if (entry.laterPerms != null) {
+            Files.setAttribute(fs.getPath(name), "zip:permissions", entry.laterPerms);
+        }
+        entriesCreated++;
+    }
+
+    private FileSystem createTestZipFile(Path zpath, Map<String, Object> env) throws IOException {
+        if (Files.exists(zpath)) {
+            System.out.println("Deleting old " + zpath + "...");
+            Files.delete(zpath);
+        }
+        System.out.println("Creating " + zpath + "...");
+        entriesCreated = 0;
+        var opts = new HashMap<String, Object>();
+        opts.putAll(env);
+        opts.put("create", true);
+        FileSystem fs = FileSystems.newFileSystem(zpath, opts);
+        for (String name : ENTRIES.keySet()) {
+            putEntry(fs, name, ENTRIES.get(name));
+        }
+        return fs;
+    }
+
+    private FileSystem createEmptyZipFile(Path zpath, Map<String, Object> env) throws IOException {
+        if (Files.exists(zpath)) {
+            System.out.println("Deleting old " + zpath + "...");
+            Files.delete(zpath);
+        }
+        System.out.println("Creating " + zpath + "...");
+        var opts = new HashMap<String, Object>();
+        opts.putAll(env);
+        opts.put("create", true);
+        return FileSystems.newFileSystem(zpath, opts);
+    }
+
+    private void delTree(Path p) throws IOException {
+        if (Files.exists(p)) {
+            Files.walkFileTree(p, new DeleteVisitor());
+        }
+    }
+
+    private void addOwnerRead(Path root) throws IOException {
+        for (String name : ENTRIES.keySet()) {
+            ZipFileEntryInfo ei = ENTRIES.get(name);
+            if (!ei.setReadFlag) {
+                continue;
+            }
+            Path setReadOn = root.resolve(name);
+            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(setReadOn);
+            perms.add(OWNER_READ);
+            Files.setPosixFilePermissions(setReadOn, perms);
+        }
+    }
+
+    private void removeOwnerRead(Path root) throws IOException {
+        for (String name : ENTRIES.keySet()) {
+            ZipFileEntryInfo ei = ENTRIES.get(name);
+            if (!ei.setReadFlag) {
+                continue;
+            }
+            Path removeReadFrom = root.resolve(name);
+            Set<PosixFilePermission> perms = Files.getPosixFilePermissions(removeReadFrom);
+            perms.remove(OWNER_READ);
+            Files.setPosixFilePermissions(removeReadFrom, perms);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void checkEntry(Path file, checkExpects expected) {
+        System.out.println("Checking " + file + "...");
+        String name = file.getFileName().toString();
+        ZipFileEntryInfo ei = ENTRIES.get(name);
+        assertNotNull(ei, "Found unknown entry " + name + ".");
+        BasicFileAttributes attrs = null;
+        if (expected == checkExpects.permsPosix) {
+            try {
+                attrs = Files.readAttributes(file, PosixFileAttributes.class);
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("Caught IOException reading file attributes (posix) for " + name + ": " + e.getMessage());
+            }
+        } else {
+            try {
+                attrs = Files.readAttributes(file, BasicFileAttributes.class);
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("Caught IOException reading file attributes (basic) " + name + ": " + e.getMessage());
+            }
+        }
+        assertEquals(Files.isDirectory(file), ei.isDir, "Unexpected directory attribute for:" + System.lineSeparator() + attrs);
+
+        if (expected == checkExpects.contentOnly) {
+            return;
+        }
+
+        Set<PosixFilePermission> permissions;
+        if (expected == checkExpects.permsPosix) {
+            try {
+                permissions = Files.getPosixFilePermissions(file);
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
+                return;
+            }
+            comparePermissions(ei.permsPosix, permissions);
+        } else if (expected == checkExpects.permsInZip || expected == checkExpects.noPermDataInZip) {
+            try {
+                permissions = (Set<PosixFilePermission>)Files.getAttribute(file, "zip:permissions");
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
+                return;
+            }
+            comparePermissions(expected == checkExpects.noPermDataInZip ? null : ei.permsInZip, permissions);
+        }
+    }
+
+    private void doCheckEntries(Path path, checkExpects expected) throws IOException {
+        AtomicInteger entries = new AtomicInteger();
+
+        try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
+            paths.forEach(file -> {
+                entries.getAndIncrement();
+                checkEntry(file, expected);
+            });
+        }
+        System.out.println("Number of entries: " + entries.get() + ".");
+        assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries.");
+    }
+
+    private void checkEntries(FileSystem fs, checkExpects expected) throws IOException {
+        System.out.println("Checking permissions on file system " + fs + "...");
+        doCheckEntries(fs.getPath("/"), expected);
+    }
+
+    private void checkEntries(Path path, checkExpects expected) throws IOException {
+        System.out.println("Checking permissions on path " + path + "...");
+        doCheckEntries(path, expected);
+    }
+
+    private boolean throwsUOE(Executor e) throws IOException {
+        try {
+            e.doIt();
+            return false;
+        } catch (UnsupportedOperationException exc) {
+            return true;
+        }
+    }
+
+    private void comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual) {
+        if (expected == null) {
+            assertNull(actual, "Permissions are not null");
+        } else {
+            assertNotNull(actual, "Permissions are null.");
+            assertEquals(actual.size(), expected.size(), "Unexpected number of permissions (" +
+                actual.size() + " received vs " + expected.size() + " expected).");
+            for (PosixFilePermission p : expected) {
+                assertTrue(actual.contains(p), "Posix permission " + p + " missing.");
+            }
+        }
+    }
+
+    /**
+     * This tests whether the entries in a zip file created w/o
+     * Posix support are correct.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testDefault() throws IOException {
+        // create zip file using zipfs with default options
+        createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
+        // check entries on zipfs with default options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
+            checkEntries(zip, checkExpects.permsInZip);
+        }
+        // check entries on zipfs with posix options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
+            checkEntries(zip, checkExpects.permsPosix);
+        }
+    }
+
+    /**
+     * This tests whether the entries in a zip file created w/
+     * Posix support are correct.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testPosix() throws IOException {
+        // create zip file using zipfs with posix option
+        createTestZipFile(ZIP_FILE, ENV_POSIX).close();
+        // check entries on zipfs with default options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
+            checkEntries(zip, checkExpects.permsInZip);
+        }
+        // check entries on zipfs with posix options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
+            checkEntries(zip, checkExpects.permsPosix);
+        }
+    }
+
+    /**
+     * This tests whether the entries in a zip file copied from another
+     * are correct.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testCopy() throws IOException {
+        // copy zip to zip with default options
+        try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT);
+             FileSystem zipOut = createEmptyZipFile(ZIP_FILE_COPY, ENV_DEFAULT)) {
+            Path from = zipIn.getPath("/");
+            Files.walkFileTree(from, new CopyVisitor(from, zipOut.getPath("/")));
+        }
+        // check entries on copied zipfs with default options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
+            checkEntries(zip, checkExpects.permsInZip);
+        }
+        // check entries on copied zipfs with posix options
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
+            checkEntries(zip, checkExpects.permsPosix);
+        }
+    }
+
+    /**
+     * This tests whether the entries of a zip file look correct after extraction
+     * and re-packing. When not using zipfs with Posix support, we expect the
+     * effective permissions in the resulting zip file to be empty.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testUnzipDefault() throws IOException {
+        delTree(UNZIP_DIR);
+        Files.createDirectory(UNZIP_DIR);
+
+        try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
+            Path from = srcZip.getPath("/");
+            Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR));
+        }
+
+        // we just check that the entries got extracted to file system
+        checkEntries(UNZIP_DIR, checkExpects.contentOnly);
+
+        // the target zip file is opened with Posix support
+        // but we expect no permission data to be copied using the default copy method
+        try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
+            Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/")));
+        }
+
+        // check entries on copied zipfs - no permission data should exist
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
+            checkEntries(zip, checkExpects.noPermDataInZip);
+        }
+    }
+
+    /**
+     * This tests whether the entries of a zip file look correct after extraction
+     * and re-packing. If the default file system supports Posix, we test whether we
+     * correctly carry the Posix permissions. Otherwise there's not much to test in
+     * this method.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testUnzipPosix() throws IOException {
+        delTree(UNZIP_DIR);
+        Files.createDirectory(UNZIP_DIR);
+
+        try {
+            Files.getPosixFilePermissions(UNZIP_DIR);
+        } catch (Exception e) {
+            // if we run into any exception here, be it because of the fact that the file system
+            // is not Posix or if we have insufficient security permissions, we can't do this test.
+            System.out.println("This can't be tested here because of " + e);
+            return;
+        }
+
+        try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_POSIX)) {
+            Path from = srcZip.getPath("/");
+            // copy permissions as well
+            Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR, true));
+        }
+
+        // permissions should have been propagated to file system
+        checkEntries(UNZIP_DIR, checkExpects.permsPosix);
+
+        try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
+            // Make some files owner readable to be able to copy them into the zipfs
+            addOwnerRead(UNZIP_DIR);
+
+            // copy permissions as well
+            Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"), true));
+
+            // Fix back all the files in the target zip file which have been made readable before
+            removeOwnerRead(tgtZip.getPath("/"));
+        }
+
+        // check entries on copied zipfs - permission data should have been propagated
+        try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
+            checkEntries(zip, checkExpects.permsPosix);
+        }
+    }
+
+    /**
+     * Tests POSIX default behavior.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testPosixDefaults() throws IOException {
+        // test with posix = false, expect UnsupportedOperationException
+        try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
+            var entry = zipIn.getPath("/dir");
+            assertTrue(throwsUOE(()->Files.getPosixFilePermissions(entry)));
+            assertTrue(throwsUOE(()->Files.setPosixFilePermissions(entry, UW)));
+            assertTrue(throwsUOE(()->Files.getOwner(entry)));
+            assertTrue(throwsUOE(()->Files.setOwner(entry, DUMMY_USER)));
+            assertTrue(throwsUOE(()->Files.getFileAttributeView(entry, PosixFileAttributeView.class)));
+        }
+
+        // test with posix = true -> default values
+        try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
+            String defaultOwner = expectedDefaultOwner(ZIP_FILE);
+            String defaultGroup = expectedDefaultGroup(ZIP_FILE, defaultOwner);
+            var entry = zipIn.getPath("/noperms");
+            comparePermissions(ALLPERMS, Files.getPosixFilePermissions(entry));
+            var owner = Files.getOwner(entry);
+            assertNotNull(owner, "owner should not be null");
+            if (defaultOwner != null) {
+                assertEquals(owner.getName(), defaultOwner);
+            }
+            Files.setOwner(entry, DUMMY_USER);
+            assertEquals(Files.getOwner(entry), DUMMY_USER);
+            var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
+            var group = view.readAttributes().group();
+            assertNotNull(group, "group must not be null");
+            if (defaultGroup != null) {
+                assertEquals(group.getName(), defaultGroup);
+            }
+            view.setGroup(DUMMY_GROUP);
+            assertEquals(view.readAttributes().group(), DUMMY_GROUP);
+            entry = zipIn.getPath("/uexec");
+            Files.setPosixFilePermissions(entry, GR); // will be persisted
+            comparePermissions(GR, Files.getPosixFilePermissions(entry));
+        }
+
+        // test with posix = true + custom defaults of type String
+        try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
+            "defaultOwner", "auser", "defaultGroup", "agroup", "defaultPermissions", "r--------")))
+        {
+            var entry = zipIn.getPath("/noperms");
+            comparePermissions(UR, Files.getPosixFilePermissions(entry));
+            assertEquals(Files.getOwner(entry).getName(), "auser");
+            var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
+            assertEquals(view.readAttributes().group().getName(), "agroup");
+            // check if the change to permissions of /uexec was persisted
+            comparePermissions(GR, Files.getPosixFilePermissions(zipIn.getPath("/uexec")));
+        }
+
+        // test with posix = true + custom defaults as Objects
+        try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
+            "defaultOwner", DUMMY_USER, "defaultGroup", DUMMY_GROUP, "defaultPermissions", UR)))
+        {
+            var entry = zipIn.getPath("/noperms");
+            comparePermissions(UR, Files.getPosixFilePermissions(entry));
+            assertEquals(Files.getOwner(entry), DUMMY_USER);
+            var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
+            assertEquals(view.readAttributes().group(), DUMMY_GROUP);
+        }
+    }
+
+    /**
+     * Sanity check to test whether the zip file can be unzipped with the java.util.zip API.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testUnzipWithJavaUtilZip() throws IOException {
+        createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
+        delTree(UNZIP_DIR);
+        Files.createDirectory(UNZIP_DIR);
+        File targetDir = UNZIP_DIR.toFile();
+        try (ZipFile zf = new ZipFile(ZIP_FILE.toFile())) {
+            Enumeration<? extends ZipEntry> zenum = zf.entries();
+            while (zenum.hasMoreElements()) {
+                ZipEntry ze = zenum.nextElement();
+                File target = new File(targetDir + File.separator + ze.getName());
+                if (ze.isDirectory()) {
+                    target.mkdir();
+                    continue;
+                }
+                try (InputStream is = zf.getInputStream(ze);
+                     FileOutputStream fos = new FileOutputStream(target))
+                {
+                    while (is.available() > 0) {
+                        fos.write(is.read());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sanity check to test whether a jar file created with zipfs can be
+     * extracted with the java.util.jar API.
+     *
+     * @throws IOException
+     */
+    @Test
+    public void testJarFile() throws IOException {
+        // create jar file using zipfs with default options
+        createTestZipFile(JAR_FILE, ENV_DEFAULT).close();
+
+        // extract it using java.util.jar.JarFile
+        delTree(UNZIP_DIR);
+        Files.createDirectory(UNZIP_DIR);
+        File targetDir = UNZIP_DIR.toFile();
+        try (JarFile jf = new JarFile(ZIP_FILE.toFile())) {
+            Enumeration<? extends JarEntry> zenum = jf.entries();
+            while (zenum.hasMoreElements()) {
+                JarEntry ze = zenum.nextElement();
+                File target = new File(targetDir + File.separator + ze.getName());
+                if (ze.isDirectory()) {
+                    target.mkdir();
+                    continue;
+                }
+                try (InputStream is = jf.getInputStream(ze);
+                     FileOutputStream fos = new FileOutputStream(target))
+                {
+                    while (is.available() > 0) {
+                        fos.write(is.read());
+                    }
+                }
+            }
+        }
+
+        // extract it using the jar tool
+        delTree(UNZIP_DIR);
+        System.out.println("jar xvf " + JAR_FILE);
+
+        // the run method catches IOExceptions, we need to expose them
+        int rc = JAR_TOOL.run(System.out, System.err, "xvf", JAR_FILE.toString());
+        assertEquals(rc, 0, "Return code of jar call is " + rc + " but expected 0");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/nio/zipfs/test.policy.posix	Wed Aug 07 11:28:14 2019 +0100
@@ -0,0 +1,8 @@
+grant {
+    permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
+    permission java.util.PropertyPermission "sun.tools.jar.useExtractionTime","read";
+    permission java.util.PropertyPermission "test.jdk","read";
+    permission java.util.PropertyPermission "test.src","read";
+    permission java.util.PropertyPermission "user.dir","read";
+    permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.module";
+};