# HG changeset patch # User clanger # Date 1540808812 -3600 # Mon Oct 29 11:26:52 2018 +0100 # Node ID eff326424484afd9a400d45c591eec45623fe7f3 # Parent 3b6680f7542fecf542bcc9cbba3f1bc5e8540323 8213031: (zipfs) Add support for POSIX file permissions 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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 @@ -26,10 +26,8 @@ package jdk.nio.zipfs; /** - * * @author Xueming Shen */ - class ZipConstants { /* * Compression methods @@ -232,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);} @@ -245,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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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 @@ -25,17 +25,22 @@ package jdk.nio.zipfs; -import java.nio.file.attribute.*; import java.io.IOException; +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 +/** + * @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,19 +88,28 @@ 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 - { + public ZipFileAttributes readAttributes() throws IOException { return path.getAttributes(); } @@ -104,11 +127,11 @@ { try { if (AttrID.valueOf(attribute) == AttrID.lastModifiedTime) - setTimes ((FileTime)value, null, null); + setTimes((FileTime)value, null, null); if (AttrID.valueOf(attribute) == AttrID.lastAccessTime) - setTimes (null, (FileTime)value, null); + setTimes(null, (FileTime)value, null); if (AttrID.valueOf(attribute) == AttrID.creationTime) - setTimes (null, null, (FileTime)value); + setTimes(null, null, (FileTime)value); return; } catch (IllegalArgumentException x) {} throw new UnsupportedOperationException("'" + attribute + @@ -158,18 +181,47 @@ case fileKey: return zfas.fileKey(); 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; + case permissions: + if (type == ViewType.zip || type == ViewType.posix) { + try { + return zfas.permissions(); + } catch (UnsupportedOperationException e) { + return null; + } + } + break; } return null; } + + @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 { + readAttributes().setPermissions(perms); + } + + @Override + public void setGroup(GroupPrincipal group) throws IOException { + throw new UnsupportedOperationException("ZipFileSystem does not support setGroup."); + } } 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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 @@ -25,18 +25,21 @@ package jdk.nio.zipfs; -import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Set; /** + * The attributes of a file stored in a zip file. * - * @author Xueming Shen, Rajendra Gutupalli,Jaya Hangal + * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal */ - -interface ZipFileAttributes extends BasicFileAttributes { +interface ZipFileAttributes extends PosixFileAttributes { public long compressedSize(); public long crc(); public int method(); public byte[] extra(); public byte[] comment(); public String toString(); + public void setPermissions(Set perms); } 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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 @@ -26,21 +26,18 @@ package jdk.nio.zipfs; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.FileStore; import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileStoreAttributeView; -import java.nio.file.attribute.BasicFileAttributeView; -import java.util.Formatter; +import java.nio.file.attribute.PosixFileAttributeView; -/* - * - * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal +/** + * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal */ - class ZipFileStore extends FileStore { private final ZipFileSystem zfs; @@ -67,16 +64,16 @@ @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 - @SuppressWarnings("unchecked") public V getFileStoreAttributeView(Class type) { if (type == null) throw new NullPointerException(); 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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 @@ -25,49 +25,66 @@ package jdk.nio.zipfs; +import static java.lang.Boolean.TRUE; +import static jdk.nio.zipfs.ZipConstants.*; +import static jdk.nio.zipfs.ZipUtils.*; +import static java.nio.file.StandardOpenOption.*; +import static java.nio.file.StandardCopyOption.*; + import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; -import java.io.File; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; -import java.nio.channels.*; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.channels.WritableByteChannel; import java.nio.file.*; -import java.nio.file.attribute.*; -import java.nio.file.spi.*; +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; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Formatter; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; -import java.util.zip.Deflater; import java.util.zip.InflaterInputStream; -import java.util.zip.DeflaterOutputStream; import java.util.zip.ZipException; -import static java.lang.Boolean.*; -import static jdk.nio.zipfs.ZipConstants.*; -import static jdk.nio.zipfs.ZipUtils.*; -import static java.nio.file.StandardOpenOption.*; -import static java.nio.file.StandardCopyOption.*; /** * A FileSystem built on a zip file * * @author Xueming Shen */ - class ZipFileSystem extends FileSystem { - private final ZipFileSystemProvider provider; private final Path zfpath; final ZipCoder zc; @@ -269,7 +286,7 @@ } if (!streams.isEmpty()) { // unlock and close all remaining streams Set copy = new HashSet<>(streams); - for (InputStream is: copy) + for (InputStream is : copy) is.close(); } beginWrite(); // lock and sync @@ -296,7 +313,7 @@ IOException ioe = null; synchronized (tmppaths) { - for (Path p: tmppaths) { + for (Path p : tmppaths) { try { AccessController.doPrivileged( (PrivilegedExceptionAction)() -> Files.deleteIfExists(p)); @@ -444,7 +461,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(); @@ -521,7 +538,7 @@ boolean hasCreate = false; boolean hasAppend = false; boolean hasTruncate = false; - for (OpenOption opt: options) { + for (OpenOption opt : options) { if (opt == READ) throw new IllegalArgumentException("READ not allowed"); if (opt == CREATE_NEW) @@ -666,7 +683,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(); @@ -731,7 +748,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); @@ -1477,7 +1494,7 @@ // TBD: wrap to hook close() // streams.add(eis); return eis; - } else { // untouced CEN or COPY + } else { // untouched CEN or COPY eis = new EntryInputStream(e, ch); } if (e.method == METHOD_DEFLATED) { @@ -1539,14 +1556,12 @@ // point to a new channel after sync() private long pos; // current position within entry data protected long rem; // number of remaining bytes within entry - protected final long size; // uncompressed size of this entry EntryInputStream(Entry e, SeekableByteChannel zfch) throws IOException { this.zfch = zfch; rem = e.csize; - size = e.size; pos = e.locoff; if (pos == -1) { Entry e2 = getEntry(e.name); @@ -1613,10 +1628,6 @@ return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem; } - public long size() { - return size; - } - public void close() { rem = 0; streams.remove(this); @@ -1672,7 +1683,7 @@ // List of available Deflater objects for compression private final List deflaters = new ArrayList<>(); - // Gets an deflater from the list of available deflaters or allocates + // Gets a deflater from the list of available deflaters or allocates // a new one. private Deflater getDeflater() { synchronized (deflaters) { @@ -1686,18 +1697,6 @@ } } - // Releases the specified inflater to the list of available inflaters. - private void releaseDeflater(Deflater def) { - synchronized (deflaters) { - if (inflaters.size() < MAX_FLATER) { - def.reset(); - deflaters.add(def); - } else { - def.end(); - } - } - } - // End of central directory record static class END { // these 2 fields are not used by anyone and write() uses "0" @@ -1865,6 +1864,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 @@ -1876,7 +1876,7 @@ // cen - // these fields are not used by anyone and writeCEN uses "0" + // these fields are not used // int versionMade; // int disk; // int attrs; @@ -1896,12 +1896,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; @@ -1921,15 +1928,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) @@ -1937,6 +1955,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 @@ -1967,6 +1994,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; @@ -1985,10 +2015,7 @@ return this; } - int writeCEN(OutputStream os) throws IOException - { - int written = CENHDR; - int version0 = version(); + int writeCEN(OutputStream os) throws IOException { long csize0 = csize; long size0 = size; long locoff0 = locoff; @@ -2019,6 +2046,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); @@ -2035,13 +2064,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 @@ -2059,10 +2083,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) @@ -2102,18 +2128,17 @@ int writeLOC(OutputStream os) throws IOException { writeInt(os, LOCSIG); // LOC header signature - int version = 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; if ((flag & FLAG_DATADESCR) != 0) { - writeShort(os, version()); // 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 @@ -2126,16 +2151,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, version()); // 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 { @@ -2165,7 +2189,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); @@ -2418,9 +2442,42 @@ 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(); } + + @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); + } + + @Override + public void setPermissions(Set perms) { + posixPerms = ZipUtils.permsToFlags(perms); + } } // ZIP directory has two issues: @@ -2430,7 +2487,6 @@ // structure. // A possible solution is to build the node tree ourself as // implemented below. - private IndexNode root; // default time stamp for pseudo entries private long zfsDefaultTimeStamp = System.currentTimeMillis(); 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 @@ -25,28 +25,44 @@ package jdk.nio.zipfs; -import java.io.*; -import java.nio.channels.*; -import java.nio.file.*; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.attribute.*; -import java.nio.file.spi.FileSystemProvider; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.ProviderMismatchException; +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; import java.util.Set; +import java.util.concurrent.ExecutorService; import java.util.zip.ZipException; -import java.util.concurrent.ExecutorService; -/* - * - * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal +/** + * @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal */ - public class ZipFileSystemProvider extends FileSystemProvider { - private final Map filesystems = new HashMap<>(); public ZipFileSystemProvider() {} @@ -202,7 +218,6 @@ } @Override - @SuppressWarnings("unchecked") public V getFileAttributeView(Path path, Class type, LinkOption... options) { @@ -286,7 +301,9 @@ 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; } 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2018, 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,21 +27,147 @@ 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; -import java.util.concurrent.TimeUnit; /** - * * @author Xueming Shen */ +class ZipUtils { -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,193 @@ +/* + * 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. + */ + +/** + * @test + * @modules jdk.zipfs + * @run testng TestPosixPerms + * @summary Test zip file operations handling posix permissions. + */ + +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.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributes; +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 org.testng.annotations.Test; + +public class TestPosixPerms { + + private int entries; + + 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, "No posix permissions associated with entry."); + assertNotEquals(expected, null, "Got a set of " + permissions.size() + + " permissions but expected null/UnsupportedOperationException."); + assertEquals(permissions.size(), expected.size(), "Unexpected number of permissions."); + 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)); + } + entries++; + } + + 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)); + } + entries++; + } + + @Test + public void testWriteAndReadArchiveWithPosixPerms() throws Exception { + FileSystemProvider provider = getZipFSProvider(); + assertNotNull(provider, "ZIP filesystem provider is not installed"); + Path zpath = Paths.get(System.getProperty("test.dir", "."), "testPosixPerms.zip"); + System.out.println("Create " + zpath + "..."); + if (Files.exists(zpath)) { + Files.delete(zpath); + } + Map env = new HashMap<>(); + env.put("create", "true"); + try (FileSystem fs = provider.newFileSystem(zpath, env)) { + entries = 0; + 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)); + } + int entriesCreated = entries; + + System.out.println("Test reading " + zpath + "..."); + env.clear(); + try (FileSystem fs = provider.newFileSystem(zpath, env)) { + entries = 0; + try (DirectoryStream paths = Files.newDirectoryStream(fs.getPath("/"))) { + paths.forEach(file->{ + entries++; + 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")) { + 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 + "."); + assertEquals(entries, entriesCreated, "File contained wrong number of entries."); + } +}