--- old/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java 2019-02-08 16:05:45.528766100 +0100 +++ new/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java 2019-02-08 16:05:44.392249400 +0100 @@ -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; @@ -78,6 +81,26 @@ * @author Xueming Shen */ class ZipFileSystem extends FileSystem { + // statics + private static final boolean isWindows = AccessController.doPrivileged( + (PrivilegedAction)() -> System.getProperty("os.name") + .startsWith("Windows")); + private static final String POSIX_OPT = "posix"; + private static final String DEFAULT_OWNER_OPT = "defaultOwner"; + private static final String DEFAULT_GROUP_OPT = "defaultGroup"; + private static final String DEFAULT_PERMISSIONS_OPT = "defaultPermissions"; + private static final String DEFAULT_PRINCIPAL_NAME = ""; + private static final String DEFAULT_OWNER_NAME = AccessController.doPrivileged( + (PrivilegedAction)() -> System + .getProperty("user.name", DEFAULT_PRINCIPAL_NAME)); + + private static final UserPrincipal DEFAULT_OWNER = () -> DEFAULT_OWNER_NAME; + private static final GroupPrincipal DEFAULT_GROUP = () -> DEFAULT_PRINCIPAL_NAME; + private static final Set DEFAULT_PERMISSIONS = Set.of( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ); + private final ZipFileSystemProvider provider; private final Path zfpath; final ZipCoder zc; @@ -88,13 +111,19 @@ private final boolean noExtt; // see readExtra() private final boolean useTempFile; // use a temp file for newOS, default // is to use BAOS for better performance - private static final boolean isWindows = AccessController.doPrivileged( - (PrivilegedAction)() -> System.getProperty("os.name") - .startsWith("Windows")); + private final boolean forceEnd64; private final int defaultMethod; // METHOD_STORED if "noCompression=true" // METHOD_DEFLATED otherwise + // POSIX support + final boolean supportPosix; + private final UserPrincipal defaultOwner; + private final GroupPrincipal defaultGroup; + private final Set defaultPermissions; + + private final Set supportedFileAttributeViews; + ZipFileSystem(ZipFileSystemProvider provider, Path zfpath, Map env) throws IOException @@ -105,6 +134,60 @@ this.noExtt = "false".equals(env.get("zipinfo-time")); this.useTempFile = isTrue(env, "useTempFile"); this.forceEnd64 = isTrue(env, "forceZIP64End"); + this.supportPosix = isTrue(env, POSIX_OPT); + if (supportPosix) { + Object o = env.get(DEFAULT_OWNER_OPT); + if (o == null) { + defaultOwner = DEFAULT_OWNER; + } else { + if (o instanceof UserPrincipal) { + defaultOwner = (UserPrincipal)o; + } else { + throw new IllegalArgumentException("Value for property " + + DEFAULT_OWNER_OPT + " must be of type " + + UserPrincipal.class); + } + } + o = env.get(DEFAULT_GROUP_OPT); + if (o == null) { + defaultGroup = DEFAULT_GROUP; + } else { + if (o instanceof GroupPrincipal) { + defaultGroup = (GroupPrincipal)o; + } else { + throw new IllegalArgumentException("Value for property " + + DEFAULT_GROUP_OPT + " must be of type " + + GroupPrincipal.class); + } + } + o = env.get(DEFAULT_PERMISSIONS_OPT); + if (o == null) { + defaultPermissions = DEFAULT_PERMISSIONS; + } else { + if (o instanceof Set) { + defaultPermissions = new HashSet(); + Set perms = (Set)o; + for (Object o2 : perms) { + if (o2 instanceof PosixFilePermission) { + defaultPermissions.add((PosixFilePermission)o2); + } else { + throw new IllegalArgumentException(DEFAULT_PERMISSIONS_OPT + + " must only contain objects of type " + + PosixFilePermission.class); + } + } + } else { + throw new IllegalArgumentException("Value for property " + + DEFAULT_PERMISSIONS_OPT + " must be of type " + Set.class); + } + } + supportedFileAttributeViews = Set.of("basic", "posix", "zip"); + } else { + defaultOwner = DEFAULT_OWNER; + defaultGroup = DEFAULT_GROUP; + defaultPermissions = DEFAULT_PERMISSIONS; + supportedFileAttributeViews = Set.of("basic", "zip"); + } this.defaultMethod = isTrue(env, "noCompression") ? METHOD_STORED: METHOD_DEFLATED; if (Files.notExists(zfpath)) { // create a new zip if not exists @@ -119,7 +202,7 @@ // sm and existence check zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ); boolean writeable = AccessController.doPrivileged( - (PrivilegedAction) () -> Files.isWritable(zfpath)); + (PrivilegedAction) () -> Files.isWritable(zfpath)); this.readOnly = !writeable; this.zc = ZipCoder.get(nameEncoding); this.rootdir = new ZipPath(this, new byte[]{'/'}); @@ -218,9 +301,6 @@ return List.of(new ZipFileStore(rootdir)); } - private static final Set supportedFileAttributeViews = - Set.of("basic", "zip"); - @Override public Set supportedFileAttributeViews() { return supportedFileAttributeViews; @@ -371,7 +451,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 +464,63 @@ } } + 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 + e.owner = owner; + update(e); + } finally { + endWrite(); + } + } + + 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(); + } + } + + 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 + e.group = group; + update(e); + } finally { + endWrite(); + } + } + boolean exists(byte[] path) throws IOException { @@ -453,7 +590,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(); @@ -675,7 +812,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(); @@ -740,7 +877,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); @@ -1272,7 +1409,7 @@ continue; // no root '/' directory even it // exits in original zip/jar file. } - e = Entry.readCEN(this, inode); + e = new Entry(inode); try { written += copyLOCEntry(e, false, os, written, buf); elist.add(e); @@ -1310,7 +1447,7 @@ return (Entry)inode; if (inode == null || inode.pos == -1) return null; - return Entry.readCEN(this, inode); + return new Entry(inode); } public void deleteFile(byte[] path, boolean failIfNotExists) @@ -1855,7 +1992,7 @@ IndexNode child; // 1st child } - static class Entry extends IndexNode implements ZipFileAttributes { + class Entry extends IndexNode implements ZipFileAttributes { static final int CEN = 1; // entry read from cen static final int NEW = 2; // updated contents in bytes or file @@ -1869,6 +2006,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 @@ -1888,7 +2026,49 @@ long locoff; byte[] comment; - Entry() {} + // posix support + private UserPrincipal owner = defaultOwner; + private GroupPrincipal group = defaultGroup; + + Entry(IndexNode inode) throws IOException { + int pos = inode.pos; + if (!cenSigAt(cen, pos)) + zerror("invalid CEN header (bad signature)"); + version = CENVER(cen, pos); + flag = CENFLG(cen, pos); + method = CENHOW(cen, pos); + mtime = dosToJavaTime(CENTIM(cen, pos)); + crc = CENCRC(cen, pos); + csize = CENSIZ(cen, pos); + size = CENLEN(cen, pos); + int nlen = CENNAM(cen, pos); + int elen = CENEXT(cen, pos); + int clen = CENCOM(cen, pos); + /* + versionMade = CENVEM(cen, pos); + disk = CENDSK(cen, pos); + 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; + this.isdir = inode.isdir; + this.hashcode = inode.hashcode; + + pos += nlen; + if (elen > 0) { + extra = Arrays.copyOfRange(cen, pos, pos + elen); + pos += elen; + readExtra(); + } + if (clen > 0) { + comment = Arrays.copyOfRange(cen, pos, pos + clen); + } + } Entry(byte[] name, boolean isdir, int method) { name(name); @@ -1900,13 +2080,21 @@ 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)attr.value()); + } + } } - Entry (Entry e, int type) { + Entry(Entry e, int type) { name(e.name); + this.type = type; this.isdir = e.isdir; this.version = e.version; this.ctime = e.ctime; @@ -1925,15 +2113,27 @@ */ this.locoff = e.locoff; this.comment = e.comment; - this.type = type; + this.posixPerms = e.posixPerms; + this.owner = e.owner; + this.group = e.group; } - 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) @@ -1941,56 +2141,18 @@ throw new ZipException("unsupported compression method"); } - ///////////////////// CEN ////////////////////// - static Entry readCEN(ZipFileSystem zipfs, IndexNode inode) - throws IOException - { - return new Entry().cen(zipfs, inode); + /** + * 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); } - private Entry cen(ZipFileSystem zipfs, IndexNode inode) - throws IOException - { - byte[] cen = zipfs.cen; - int pos = inode.pos; - if (!cenSigAt(cen, pos)) - zerror("invalid CEN header (bad signature)"); - version = CENVER(cen, pos); - flag = CENFLG(cen, pos); - method = CENHOW(cen, pos); - mtime = dosToJavaTime(CENTIM(cen, pos)); - crc = CENCRC(cen, pos); - csize = CENSIZ(cen, pos); - size = CENLEN(cen, pos); - int nlen = CENNAM(cen, pos); - int elen = CENEXT(cen, pos); - int clen = CENCOM(cen, pos); - /* - versionMade = CENVEM(cen, pos); - disk = CENDSK(cen, pos); - attrs = CENATT(cen, pos); - attrsEx = CENATX(cen, pos); - */ - locoff = CENOFF(cen, pos); - pos += CENHDR; - this.name = inode.name; - this.isdir = inode.isdir; - this.hashcode = inode.hashcode; - - pos += nlen; - if (elen > 0) { - extra = Arrays.copyOfRange(cen, pos, pos + elen); - pos += elen; - readExtra(zipfs); - } - if (clen > 0) { - comment = Arrays.copyOfRange(cen, pos, pos + clen); - } - return this; - } + ///////////////////// CEN ////////////////////// int writeCEN(OutputStream os) throws IOException { - int version0 = version(); long csize0 = csize; long size0 = size; long locoff0 = locoff; @@ -2021,6 +2183,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); @@ -2037,13 +2201,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 @@ -2061,10 +2220,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) @@ -2103,18 +2264,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 @@ -2127,16 +2288,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 { @@ -2166,7 +2326,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); @@ -2203,7 +2363,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 @@ -2219,7 +2379,7 @@ } // read NTFS, UNIX and ZIP64 data from cen.extra - void readExtra(ZipFileSystem zipfs) throws IOException { + void readExtra() throws IOException { if (extra == null) return; int elen = extra.length; @@ -2274,13 +2434,13 @@ // "zipinfo-time" is not specified to false; // there is performance cost (move up to loc and read) to // access the loc table foreach entry; - if (zipfs.noExtt) { + if (noExtt) { if (sz == 5) mtime = unixToJavaTime(LG(extra, pos + 1)); break; } byte[] buf = new byte[LOCHDR]; - if (zipfs.readFullyAt(buf, 0, buf.length , locoff) + if (readFullyAt(buf, 0, buf.length , locoff) != buf.length) throw new ZipException("loc: reading failed"); if (!locSigAt(buf, 0)) @@ -2291,7 +2451,7 @@ break; int locNlen = LOCNAM(buf); buf = new byte[locElen]; - if (zipfs.readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen) + if (readFullyAt(buf, 0, buf.length , locoff + LOCHDR + locNlen) != buf.length) throw new ZipException("loc extra: reading failed"); int locPos = 0; @@ -2332,6 +2492,30 @@ extra = null; } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(1024); + Formatter fm = new Formatter(sb); + fm.format(" name : %s%n", new String(name)); + fm.format(" creationTime : %tc%n", creationTime().toMillis()); + fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis()); + fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis()); + fm.format(" isRegularFile : %b%n", isRegularFile()); + fm.format(" isDirectory : %b%n", isDirectory()); + fm.format(" isSymbolicLink : %b%n", isSymbolicLink()); + fm.format(" isOther : %b%n", isOther()); + fm.format(" fileKey : %s%n", fileKey()); + fm.format(" size : %d%n", size()); + 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(); + } + ///////// basic file attributes /////////// @Override public FileTime creationTime() { @@ -2378,49 +2562,66 @@ return null; } - ///////// zip entry attributes /////////// + ///////// posix file attributes /////////// + + @Override + public UserPrincipal owner() { + return owner; + } + + @Override + public GroupPrincipal group() { + return group; + } + + @Override + public Set permissions() { + return storedPermissions().orElse(Set.copyOf(defaultPermissions)); + } + + ///////// 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; } - public String toString() { - StringBuilder sb = new StringBuilder(1024); - Formatter fm = new Formatter(sb); - fm.format(" name : %s%n", new String(name)); - fm.format(" creationTime : %tc%n", creationTime().toMillis()); - fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis()); - fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis()); - fm.format(" isRegularFile : %b%n", isRegularFile()); - fm.format(" isDirectory : %b%n", isDirectory()); - fm.format(" isSymbolicLink : %b%n", isSymbolicLink()); - fm.format(" isOther : %b%n", isOther()); - fm.format(" fileKey : %s%n", fileKey()); - fm.format(" size : %d%n", size()); - fm.format(" compressedSize : %d%n", compressedSize()); - fm.format(" crc : %x%n", crc()); - fm.format(" method : %d%n", method()); - fm.close(); - return sb.toString(); + @Override + public Optional> storedPermissions() { + Set 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.of(perms); } }