# HG changeset patch # User clanger # Date 1545401846 0 # Fri Dec 21 14:17:26 2018 +0000 # Node ID 3777445fd0afc5b1ea55eb770d81f70ca88fca99 # Parent 74d33d22a8df70c639a67996261d9b10d049b502 8213031: (zipfs) Add support for POSIX file permissions Reviewed-by: simonis diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipConstants.java @@ -230,6 +230,7 @@ // 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 CENVEM_FA(byte[] b, int pos) { return CH(b, pos + 5); } 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);} @@ -243,6 +244,7 @@ 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 int CENATX_PERMS(byte[] b, int pos) { return SH(b, pos + 40);} static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);} /* The END header is followed by a variable length comment of size < 64k. */ diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributeView.java @@ -29,13 +29,18 @@ import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; /** * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal */ -class ZipFileAttributeView implements BasicFileAttributeView { +class ZipFileAttributeView implements PosixFileAttributeView { private static enum AttrID { size, creationTime, @@ -48,15 +53,22 @@ fileKey, compressedSize, crc, - method + method, + permissions }; + private static enum ViewType { + zip, + posix, + basic + } + private final ZipPath path; - private final boolean isZipView; + private final ViewType type; - private ZipFileAttributeView(ZipPath path, boolean isZipView) { + private ZipFileAttributeView(ZipPath path, ViewType type) { this.path = path; - this.isZipView = isZipView; + this.type = type; } @SuppressWarnings("unchecked") // Cast to V @@ -64,9 +76,11 @@ if (type == null) throw new NullPointerException(); if (type == BasicFileAttributeView.class) - return (V)new ZipFileAttributeView(path, false); + return (V)new ZipFileAttributeView(path, ViewType.basic); + if (type == PosixFileAttributeView.class) + return (V)new ZipFileAttributeView(path, ViewType.posix); if (type == ZipFileAttributeView.class) - return (V)new ZipFileAttributeView(path, true); + return (V)new ZipFileAttributeView(path, ViewType.zip); return null; } @@ -74,15 +88,25 @@ if (type == null) throw new NullPointerException(); if (type.equals("basic")) - return new ZipFileAttributeView(path, false); + return new ZipFileAttributeView(path, ViewType.basic); + if (type.equals("posix")) + return new ZipFileAttributeView(path, ViewType.posix); if (type.equals("zip")) - return new ZipFileAttributeView(path, true); + return new ZipFileAttributeView(path, ViewType.zip); return null; } @Override public String name() { - return isZipView ? "zip" : "basic"; + switch (type) { + case zip: + return "zip"; + case posix: + return "posix"; + case basic: + default: + return "basic"; + } } public ZipFileAttributes readAttributes() throws IOException { @@ -98,6 +122,27 @@ path.setTimes(lastModifiedTime, lastAccessTime, createTime); } + @Override + public UserPrincipal getOwner() throws IOException { + throw new UnsupportedOperationException("ZipFileSystem does not support getOwner."); + } + + @Override + public void setOwner(UserPrincipal owner) throws IOException { + throw new UnsupportedOperationException("ZipFileSystem does not support setOwner."); + } + + @Override + public void setPermissions(Set perms) throws IOException { + path.setPermissions(perms); + } + + @Override + public void setGroup(GroupPrincipal group) throws IOException { + throw new UnsupportedOperationException("ZipFileSystem does not support setGroup."); + } + + @SuppressWarnings("unchecked") void setAttribute(String attribute, Object value) throws IOException { @@ -108,10 +153,13 @@ setTimes(null, (FileTime)value, null); if (AttrID.valueOf(attribute) == AttrID.creationTime) setTimes(null, null, (FileTime)value); + if (AttrID.valueOf(attribute) == AttrID.permissions) + setPermissions((Set)value); return; - } catch (IllegalArgumentException x) {} - throw new UnsupportedOperationException("'" + attribute + - "' is unknown or read-only attribute"); + } catch (IllegalArgumentException x) { + throw new UnsupportedOperationException("'" + attribute + + "' is unknown or read-only attribute"); + } } Map readAttributes(String attributes) @@ -136,7 +184,7 @@ return map; } - Object attribute(AttrID id, ZipFileAttributes zfas) { + private Object attribute(AttrID id, ZipFileAttributes zfas) { switch (id) { case size: return zfas.size(); @@ -156,17 +204,29 @@ return zfas.isOther(); case fileKey: return zfas.fileKey(); + case permissions: + if (type == ViewType.posix || type == ViewType.zip) { + try { + return zfas.permissions(); + } catch (UnsupportedOperationException e) { + return null; + } + } + break; case compressedSize: - if (isZipView) + if (type == ViewType.zip) { return zfas.compressedSize(); + } break; case crc: - if (isZipView) + if (type == ViewType.zip) { return zfas.crc(); + } break; case method: - if (isZipView) + if (type == ViewType.zip) { return zfas.method(); + } break; } return null; diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileAttributes.java @@ -25,14 +25,14 @@ package jdk.nio.zipfs; -import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributes; /** * The attributes of a file stored in a zip file. * * @author Xueming Shen, Rajendra Gutupalli,Jaya Hangal */ -interface ZipFileAttributes extends BasicFileAttributes { +interface ZipFileAttributes extends PosixFileAttributes { public long compressedSize(); public long crc(); public int method(); diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileStore.java @@ -33,6 +33,7 @@ import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileStoreAttributeView; +import java.nio.file.attribute.PosixFileAttributeView; /** * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal @@ -63,12 +64,13 @@ @Override public boolean supportsFileAttributeView(Class type) { return (type == BasicFileAttributeView.class || + type == PosixFileAttributeView.class || type == ZipFileAttributeView.class); } @Override public boolean supportsFileAttributeView(String name) { - return name.equals("basic") || name.equals("zip"); + return name.equals("basic") || name.equals("posix") || name.equals("zip"); } @Override diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java @@ -43,6 +43,9 @@ import java.nio.file.*; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; import java.security.AccessController; @@ -371,7 +374,7 @@ if (e == null) throw new NoSuchFileException(getString(path)); if (e.type == Entry.CEN) - e.type = Entry.COPY; // copy e + e.type = Entry.COPY; // copy e if (mtime != null) e.mtime = mtime.toMillis(); if (atime != null) @@ -384,6 +387,27 @@ } } + void setPermissions(byte[] path, Set 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) throws IOException { @@ -454,7 +478,7 @@ if (dir.length == 0 || exists(dir)) // root dir, or exiting dir throw new FileAlreadyExistsException(getString(dir)); checkParents(dir); - Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED); + Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs); update(e); } finally { endWrite(); @@ -676,7 +700,7 @@ throw new NoSuchFileException(getString(path)); checkParents(path); return new EntryOutputChannel( - new Entry(path, Entry.NEW, false, getCompressMethod(attrs))); + new Entry(path, Entry.NEW, false, getCompressMethod(attrs), attrs)); } finally { endRead(); @@ -741,7 +765,7 @@ 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 : new Entry(path, tmpfile, Entry.FILECH, attrs); if (forWrite) { u.flag = FLAG_DATADESCR; u.method = getCompressMethod(attrs); @@ -1870,6 +1894,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 @@ -1901,12 +1926,19 @@ 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") || attrName.equals("unix:permissions")) { + posixPerms = ZipUtils.permsToFlags((Set)attr.value()); + } + } } - Entry (Entry e, int type) { + Entry(Entry e, int type) { name(e.name); this.isdir = e.isdir; this.version = e.version; @@ -1926,15 +1958,26 @@ */ this.locoff = e.locoff; this.comment = e.comment; + this.posixPerms = e.posixPerms; this.type = type; } - Entry (byte[] name, Path file, int type) { + @SuppressWarnings("unchecked") + Entry(byte[] name, Path file, int type, FileAttribute... attrs) { this(name, type, false, METHOD_STORED); this.file = file; + for (FileAttribute attr : attrs) { + String attrName = attr.name(); + if (attrName.equals("posix:permissions") || attrName.equals("unix:permissions")) { + posixPerms = ZipUtils.permsToFlags((Set)attr.value()); + } + } } - int version() throws ZipException { + int version(boolean zip64) throws ZipException { + if (zip64) { + return 45; + } if (method == METHOD_DEFLATED) return 20; else if (method == METHOD_STORED) @@ -1942,6 +1985,15 @@ throw new ZipException("unsupported compression method"); } + /** + * Adds information about compatibility of file attribute information + * to a version value. + */ + int versionMadeBy(int version) { + return (posixPerms < 0) ? version : + VERSION_BASE_UNIX | (version & 0xff); + } + ///////////////////// CEN ////////////////////// static Entry readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException @@ -1972,6 +2024,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; @@ -1991,7 +2046,6 @@ } int writeCEN(OutputStream os) throws IOException { - int version0 = version(); long csize0 = csize; long size0 = size; long locoff0 = locoff; @@ -2022,6 +2076,8 @@ if (elen64 != 0) { elen64 += 4; // header and data sz 4 bytes } + boolean zip64 = (elen64 != 0); + int version0 = version(zip64); while (eoff + 4 < elen) { int tag = SH(extra, eoff); int sz = SH(extra, eoff + 2); @@ -2038,13 +2094,8 @@ } } writeInt(os, CENSIG); // CEN header signature - if (elen64 != 0) { - writeShort(os, 45); // ver 4.5 for zip64 - writeShort(os, 45); - } else { - writeShort(os, version0); // version made by - writeShort(os, version0); // version needed to extract - } + 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 // last modification time @@ -2062,10 +2113,12 @@ } 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 (elen64 != 0) { + if (zip64) { writeShort(os, EXTID_ZIP64);// Zip64 extra writeShort(os, elen64 - 4); // size of "this" extra block if (size0 == ZIP64_MINVAL) @@ -2104,18 +2157,18 @@ ///////////////////// LOC ////////////////////// int writeLOC(OutputStream os) throws IOException { - int version0 = version(); byte[] zname = isdir ? toDirectoryPath(name) : name; int nlen = (zname != null) ? zname.length - 1 : 0; // [0] is slash int elen = (extra != null) ? extra.length : 0; boolean foundExtraTime = false; // if extra timestamp present int eoff = 0; int elen64 = 0; + boolean zip64 = false; int elenEXTT = 0; int elenNTFS = 0; writeInt(os, LOCSIG); // LOC header signature if ((flag & FLAG_DATADESCR) != 0) { - writeShort(os, version0); // version needed to extract + writeShort(os, version(zip64)); // version needed to extract writeShort(os, flag); // general purpose bit flag writeShort(os, method); // compression method // last modification time @@ -2128,16 +2181,15 @@ } else { if (csize >= ZIP64_MINVAL || size >= ZIP64_MINVAL) { elen64 = 20; //headid(2) + size(2) + size(8) + csize(8) - writeShort(os, 45); // ver 4.5 for zip64 - } else { - writeShort(os, version0); // version needed to extract + zip64 = true; } + writeShort(os, version(zip64)); // version needed to extract writeShort(os, flag); // general purpose bit flag writeShort(os, method); // compression method // last modification time writeInt(os, (int)javaToDosTime(mtime)); writeInt(os, crc); // crc-32 - if (elen64 != 0) { + if (zip64) { writeInt(os, ZIP64_MINVAL); writeInt(os, ZIP64_MINVAL); } else { @@ -2167,7 +2219,7 @@ writeShort(os, nlen); writeShort(os, elen + elen64 + elenNTFS + elenEXTT); writeBytes(os, zname, 1, nlen); - if (elen64 != 0) { + if (zip64) { writeShort(os, EXTID_ZIP64); writeShort(os, 16); writeLong(os, size); @@ -2204,7 +2256,7 @@ return LOCHDR + nlen + elen + elen64 + elenNTFS + elenEXTT; } - // Data Descriptior + // Data Descriptor int writeEXT(OutputStream os) throws IOException { writeInt(os, EXTSIG); // EXT header signature writeInt(os, crc); // crc-32 @@ -2379,31 +2431,65 @@ return null; } - ///////// zip entry attributes /////////// + ///////// posix file attributes /////////// + + @Override + public UserPrincipal owner() { + throw new UnsupportedOperationException( + "ZipFileSystem does not support owner."); + } + + @Override + public GroupPrincipal group() { + throw new UnsupportedOperationException( + "ZipFileSystem does not support group."); + } + + @Override + public Set permissions() { + if (posixPerms == -1) { + // in case there are no Posix permissions associated with the + // entry, we should not return an empty set of permissions + // because that would be an explicit set of permissions meaning + // no permissions for anyone + throw new UnsupportedOperationException( + "No posix permissions associated with zip entry."); + } + return ZipUtils.permsFromFlags(posixPerms); + } + + ///////// zip file attributes /////////// + + @Override public long compressedSize() { return csize; } + @Override public long crc() { return crc; } + @Override public int method() { return method; } + @Override public byte[] extra() { if (extra != null) return Arrays.copyOf(extra, extra.length); return null; } + @Override public byte[] comment() { if (comment != null) return Arrays.copyOf(comment, comment.length); return null; } + @Override public String toString() { StringBuilder sb = new StringBuilder(1024); Formatter fm = new Formatter(sb); @@ -2420,6 +2506,9 @@ fm.format(" compressedSize : %d%n", compressedSize()); fm.format(" crc : %x%n", crc()); fm.format(" method : %d%n", method()); + if (posixPerms != -1) { + fm.format(" permissions : %s%n", permissions()); + } fm.close(); return sb.toString(); } diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystemProvider.java @@ -38,6 +38,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.spi.FileSystemProvider; import java.util.HashMap; import java.util.Map; @@ -288,17 +289,19 @@ readAttributes(Path path, Class type, LinkOption... options) throws IOException { - if (type == BasicFileAttributes.class || type == ZipFileAttributes.class) + if (type == BasicFileAttributes.class || + type == PosixFileAttributes.class || + type == ZipFileAttributes.class) return (A)toZipPath(path).getAttributes(); return null; } @Override public Map - readAttributes(Path path, String attribute, LinkOption... options) + readAttributes(Path path, String attributes, LinkOption... options) throws IOException { - return toZipPath(path).readAttributes(attribute, options); + return toZipPath(path).readAttributes(attributes, options); } @Override diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipPath.java @@ -34,9 +34,9 @@ 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.FileAttribute; import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFilePermission; import java.util.Arrays; import java.util.Iterator; import java.util.Map; @@ -768,9 +768,14 @@ zfs.setTimes(getResolvedPath(), mtime, atime, ctime); } + void setPermissions(Set perms) + throws IOException + { + zfs.setPermissions(getResolvedPath(), perms); + } + Map readAttributes(String attributes, LinkOption... options) throws IOException - { String view = null; String attrs = null; @@ -935,12 +940,18 @@ } } if (copyAttrs) { - BasicFileAttributeView view = - ZipFileAttributeView.get(target, BasicFileAttributeView.class); + ZipFileAttributeView view = + ZipFileAttributeView.get(target, ZipFileAttributeView.class); try { view.setTimes(zfas.lastModifiedTime(), zfas.lastAccessTime(), zfas.creationTime()); + // copy permissions if available + try { + view.setPermissions(zfas.permissions()); + } catch (UnsupportedOperationException uoe) { + // the current entry may have no permissions. + } } catch (IOException x) { // rollback? try { diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java @@ -27,12 +27,15 @@ 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.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.regex.PatternSyntaxException; @@ -41,6 +44,131 @@ */ class ZipUtils { + /** + * The value indicating unix file attributes in CEN field "version made by". + */ + static final int FILE_ATTRIBUTES_UNIX = 3; + + /** + * Constant used to calculate "version made by". + */ + static final int VERSION_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8; + + /** + * 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. + */ + private 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 perms) { + if (perms == null) { + return -1; + } + int flags = 0; + for (PosixFilePermission perm : perms) { + flags |= permToFlag(perm); + } + return flags; + } + + /** + * Converts a bit mask of Posix file permissions into a mutable + * set of {@link PosixFilePermission} objects. + * + * @param flags The bit mask containing the flags. + * + * @return A set of {@link PosixFilePermission} objects matching the input + * flags. + */ + static Set permsFromFlags(int flags) { + Set perms = new HashSet<>(PosixFilePermission.values().length); + for (PosixFilePermission perm : PosixFilePermission.values()) { + if ((flags & permToFlag(perm)) != 0) { + perms.add(perm); + } + } + return perms; + } + /* * Writes a 16-bit short to the output stream in little-endian byte order. */ diff --git a/test/jdk/jdk/nio/zipfs/TestPosixPerms.java b/test/jdk/jdk/nio/zipfs/TestPosixPerms.java new file mode 100644 --- /dev/null +++ b/test/jdk/jdk/nio/zipfs/TestPosixPerms.java @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2018, 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. 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 SAP SE, Dietmar-Hopp-Allee 16, 69190 Walldorf, Germany + * or visit www.sap.com if you need additional information or have any + * questions. + */ + +import static java.nio.file.attribute.PosixFilePermission.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.IOException; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.spi.FileSystemProvider; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.testng.annotations.Test; + +/** + * @test + * @modules jdk.zipfs + * @run testng TestPosixPerms + * @summary Test zip file operations handling POSIX permissions. + */ +public class TestPosixPerms { + + private static final String ZIP_FILE = "testPosixPerms.zip"; + private static final String ZIP_FILE_COPY = "testPosixPermsCopy.zip"; + private static final String ZIP_FILE_NEW = "testPosixPermsNew.zip"; + private static final String UNZIP_DIR = "unzip/"; + private static final Map CREATE_TRUE = new HashMap<>(); + private static final FileSystemProvider zipFS; + private static final CopyOption[] NO_OPTIONS = {}; + private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES}; + + public static class CopyVisitor extends SimpleFileVisitor { + private Path from, to; + private final CopyOption[] options; + + public CopyVisitor(Path from, Path to, boolean manualCopyPosixPerms, CopyOption... options) { + this.from = from; + this.to = to; + this.options = manualCopyPosixPerms ? NO_OPTIONS : COPY_ATTRIBUTES; + } + + private static void copyPermissions(Path source, Path target) throws IOException { + PosixFileAttributeView targetAttrs; + if ((targetAttrs = Files.getFileAttributeView(target, PosixFileAttributeView.class)) != null) { + try { + PosixFileAttributes sourceAttrs = Files.readAttributes(source, PosixFileAttributes.class); + if (sourceAttrs != null) { + targetAttrs.setPermissions(sourceAttrs.permissions()); + } + } catch (UnsupportedOperationException uoe) { + // Just ignore if source has no Posix file permissions. + } + } + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path target = to.resolve(from.relativize(dir).toString()); + if (!Files.exists(target)) { + Files.copy(dir, target, options); + if (options == NO_OPTIONS) { + copyPermissions(dir, target); + } + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path target = to.resolve(from.relativize(file).toString()); + Files.copy(file, target, options); + if (options == NO_OPTIONS) { + copyPermissions(file, target); + } + return FileVisitResult.CONTINUE; + } + } + + private int entriesCreated; + + static { + zipFS = getZipFSProvider(); + assertNotNull(zipFS, "ZIP filesystem provider is not installed"); + CREATE_TRUE.put("create", "true"); + } + + private static FileSystemProvider getZipFSProvider() { + for (FileSystemProvider provider : FileSystemProvider.installedProviders()) { + if ("jar".equals(provider.getScheme())) + return provider; + } + return null; + } + + private void checkPermissionsOfEntry(Path file, boolean directory, Set expected) { + System.out.println("Checking " + file + "..."); + assertEquals(Files.isDirectory(file), directory, "Unexpected directory attribute."); + try { + System.out.println(Files.readAttributes(file, PosixFileAttributes.class).toString()); + } catch (IOException e) { + fail("Failed to list file attributes (posix) for entry.", e); + } + try { + Set permissions = Files.getPosixFilePermissions(file); + assertNotEquals(permissions, null, "Retruned permissions of entry are null. " + + "This should not happen but we should rather see an UnsupportedOperationException."); + assertNotEquals(expected, null, "Got a set of " + permissions.size() + + " permissions but expected null/UnsupportedOperationException."); + assertEquals(permissions.size(), expected.size(), "Unexpected number of permissions( " + + permissions.size() + " received vs " + expected.size() + " expected)."); + for (PosixFilePermission p : expected) { + assertTrue(permissions.contains(p), "Posix permission " + p + " missing."); + } + } catch (UnsupportedOperationException e) { + if (expected != null) { + fail("Unexpected: No posix permissions associated with entry."); + } + } catch (IOException e) { + fail("Caught unexpected exception obtaining posix file permissions.", e); + } + } + + private void putFile(FileSystem fs, String name, Set perms) throws IOException { + if (perms == null) { + Files.createFile(fs.getPath(name)); + } else { + Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(perms)); + } + entriesCreated++; + } + + private void putDirectory(FileSystem fs, String name, Set perms) throws IOException { + if (perms == null) { + Files.createDirectory(fs.getPath(name)); + } else { + Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(perms)); + } + entriesCreated++; + } + + private FileSystem openOrcreateZipFile(Path zpath) throws Exception { + if (Files.exists(zpath)) { + FileSystem fs = zipFS.newFileSystem(zpath, new HashMap<>()); + return fs; + } else { + System.out.println("Create " + zpath + "..."); + FileSystem fs = zipFS.newFileSystem(zpath, CREATE_TRUE); + putDirectory(fs, "dir", Set.of( + OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, + GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, + OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE)); + putFile(fs, "uread", Set.of(OWNER_READ)); + putFile(fs, "uwrite", Set.of(OWNER_WRITE)); + putFile(fs, "uexec", Set.of(OWNER_EXECUTE)); + putFile(fs, "gread", Set.of(GROUP_READ)); + putFile(fs, "gwrite", Set.of(GROUP_WRITE)); + putFile(fs, "gexec", Set.of(GROUP_EXECUTE)); + putFile(fs, "oread", Set.of(OTHERS_READ)); + putFile(fs, "owrite", Set.of(OTHERS_WRITE)); + putFile(fs, "oexec", Set.of(OTHERS_EXECUTE)); + putFile(fs, "emptyperms", Collections.emptySet()); + putFile(fs, "noperms", null); + putFile(fs, "permsaddedlater", null); + Files.setPosixFilePermissions(fs.getPath("permsaddedlater"), Set.of(OWNER_READ)); + return fs; + } + } + + private void checkPosixPerms(Path path) throws Exception { + AtomicInteger entries = new AtomicInteger(); + + try (DirectoryStream paths = Files.newDirectoryStream(path)) { + paths.forEach(file -> { + entries.getAndIncrement(); + String name = file.getFileName().toString(); + if (name.startsWith("dir")) { + checkPermissionsOfEntry(file, true, Set.of( + OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, + GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, + OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE)); + } else if (name.equals("uread")) { + checkPermissionsOfEntry(file, false, Set.of(OWNER_READ)); + } else if (name.equals("uwrite")) { + checkPermissionsOfEntry(file, false, Set.of(OWNER_WRITE)); + } else if (name.equals("uexec")) { + checkPermissionsOfEntry(file, false, Set.of(OWNER_EXECUTE)); + } else if (name.equals("gread")) { + checkPermissionsOfEntry(file, false, Set.of(GROUP_READ)); + } else if (name.equals("gwrite")) { + checkPermissionsOfEntry(file, false, Set.of(GROUP_WRITE)); + } else if (name.equals("gexec")) { + checkPermissionsOfEntry(file, false, Set.of(GROUP_EXECUTE)); + } else if (name.equals("oread")) { + checkPermissionsOfEntry(file, false, Set.of(OTHERS_READ)); + } else if (name.equals("owrite")) { + checkPermissionsOfEntry(file, false, Set.of(OTHERS_WRITE)); + } else if (name.equals("oexec")) { + checkPermissionsOfEntry(file, false, Set.of(OTHERS_EXECUTE)); + } else if (name.equals("emptyperms")) { + checkPermissionsOfEntry(file, false, Collections.emptySet()); + } else if (name.equals("noperms")) { + // Only check for "no permissions" in files in the zip file system + // If such a file gets extracted into a "normal" file system it will + // get the system default permissions (i.e. "umask") + if ("jar".equals(path.getFileSystem().provider().getScheme())) { + checkPermissionsOfEntry(file, false, null); + } + } else if (name.equals("permsaddedlater")) { + checkPermissionsOfEntry(file, false, Set.of(OWNER_READ)); + } else { + fail("Found unknown entry " + name + "."); + } + }); + } + System.out.println("Number of entries: " + entries.get() + "."); + assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries."); + } + + @Test(priority=0) + public void testWriteAndReadArchiveWithPosixPerms() throws Exception { + Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE); + + try (FileSystem zipIn = openOrcreateZipFile(in)) { + System.out.println("Test reading " + in + "..."); + checkPosixPerms(zipIn.getPath("/")); + } + } + + @Test(priority=1) + public void testPosixPermsAfterCopy() throws Exception { + Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE); + Path out = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE_COPY); + + try (FileSystem zipIn = openOrcreateZipFile(in); + FileSystem zipOut = zipFS.newFileSystem(out, CREATE_TRUE)) { + Path from = zipIn.getPath("/"); + Path to = zipOut.getPath("/"); + Files.walkFileTree(from, new CopyVisitor(from, to, false, StandardCopyOption.COPY_ATTRIBUTES)); + System.out.println("Test reading " + out + "..."); + checkPosixPerms(to); + } + } + + @Test(priority=2) + public void testPosixPermsAfterUnzip() throws Exception { + Path in = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE); + Path out = Files.createDirectory(Paths.get(System.getProperty("test.dir", "."), UNZIP_DIR)); + boolean posixFS; + try { + Files.getPosixFilePermissions(out); + posixFS = true; + } catch (UnsupportedOperationException e) { + posixFS = false; + } + + try (FileSystem zipIn = openOrcreateZipFile(in)) { + Path from = zipIn.getPath("/"); + Files.walkFileTree(from, new CopyVisitor(from, out, true, StandardCopyOption.COPY_ATTRIBUTES)); + System.out.println("Test reading " + out + "..."); + if (posixFS) { + checkPosixPerms(out); + } + } + } + + @Test(priority=3) + public void testPosixPermsAfterZip() throws Exception { + Path in = Paths.get(System.getProperty("test.dir", "."), UNZIP_DIR); + Path out = Paths.get(System.getProperty("test.dir", "."), ZIP_FILE_NEW); + boolean posixFS; + try { + Files.getPosixFilePermissions(in); + posixFS = true; + } catch (UnsupportedOperationException e) { + posixFS = false; + } + + try (FileSystem zipOut = zipFS.newFileSystem(out, CREATE_TRUE)) { + out = zipOut.getPath("/"); + + // Have to hack this manually otherwise we won't be able to read these files at all + Set emptyperms_p = null, gexec_p = null, gwrite_p = null, gread_p = null, + oread_p = null, oexec_p = null, owrite_p = null, uexec_p = null, uwrite_p = null; + if (posixFS) { + Path emptyperms = in.resolve("emptyperms"); + emptyperms_p = Files.getPosixFilePermissions(emptyperms); + emptyperms_p.add(OWNER_READ); + Files.setPosixFilePermissions(emptyperms, emptyperms_p); + Path gexec = in.resolve("gexec"); + gexec_p = Files.getPosixFilePermissions(gexec); + gexec_p.add(OWNER_READ); + Files.setPosixFilePermissions(gexec, gexec_p); + Path gwrite = in.resolve("gwrite"); + gwrite_p = Files.getPosixFilePermissions(gwrite); + gwrite_p.add(OWNER_READ); + Files.setPosixFilePermissions(gwrite, gwrite_p); + Path gread = in.resolve("gread"); + gread_p = Files.getPosixFilePermissions(gread); + gread_p.add(OWNER_READ); + Files.setPosixFilePermissions(gread, gread_p); + Path oread = in.resolve("oread"); + oread_p = Files.getPosixFilePermissions(oread); + oread_p.add(OWNER_READ); + Files.setPosixFilePermissions(oread, oread_p); + Path oexec = in.resolve("oexec"); + oexec_p = Files.getPosixFilePermissions(oexec); + oexec_p.add(OWNER_READ); + Files.setPosixFilePermissions(oexec, oexec_p); + Path owrite = in.resolve("owrite"); + owrite_p = Files.getPosixFilePermissions(owrite); + owrite_p.add(OWNER_READ); + Files.setPosixFilePermissions(owrite, owrite_p); + Path uexec = in.resolve("uexec"); + uexec_p = Files.getPosixFilePermissions(uexec); + uexec_p.add(OWNER_READ); + Files.setPosixFilePermissions(uexec, uexec_p); + Path uwrite = in.resolve("uwrite"); + uwrite_p = Files.getPosixFilePermissions(uwrite); + uwrite_p.add(OWNER_READ); + Files.setPosixFilePermissions(uwrite, uwrite_p); + } + + Files.walkFileTree(in, new CopyVisitor(in, out, true, StandardCopyOption.COPY_ATTRIBUTES)); + + if (posixFS) { + // Fix back all the files in the target zip file which have been made readable before + emptyperms_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("emptyperms"), emptyperms_p); + gexec_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("gexec"), gexec_p); + gwrite_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("gwrite"), gwrite_p); + gread_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("gread"), gread_p); + oread_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("oread"), oread_p); + oexec_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("oexec"), oexec_p); + owrite_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("owrite"), owrite_p); + uexec_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("uexec"), uexec_p); + uwrite_p.remove(OWNER_READ); + Files.setPosixFilePermissions(zipOut.getPath("uwrite"), uwrite_p); + // Fix "noperms" which has the default permissions after unzipping in 'testPosixPermsAfterUnzip()' + Files.setPosixFilePermissions(zipOut.getPath("noperms"), null); + } + + System.out.println("Test reading " + out + "..."); + if (posixFS) { + checkPosixPerms(out); + } + } + } +}