< prev index next >

src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java

Print this page
rev 55003 : 8213031: (zipfs) Add support for POSIX file permissions

*** 39,51 **** 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.FileAttribute; ! import java.nio.file.attribute.FileTime; ! 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; --- 39,49 ---- 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.FileSystemProvider; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction;
*** 80,92 **** class ZipFileSystem extends FileSystem { // statics private static final boolean isWindows = AccessController.doPrivileged( (PrivilegedAction<Boolean>)()->System.getProperty("os.name") .startsWith("Windows")); - private static final Set<String> supportedFileAttributeViews = - Set.of("basic", "zip"); private static final byte[] ROOTPATH = new byte[] { '/' }; private final ZipFileSystemProvider provider; private final Path zfpath; final ZipCoder zc; private final ZipPath rootdir; --- 78,95 ---- class ZipFileSystem extends FileSystem { // statics private static final boolean isWindows = AccessController.doPrivileged( (PrivilegedAction<Boolean>)()->System.getProperty("os.name") .startsWith("Windows")); private static final byte[] ROOTPATH = new byte[] { '/' }; + private static final String OPT_POSIX = "enablePosixFileAttributes"; + private static final String OPT_DEFAULT_OWNER = "defaultOwner"; + private static final String OPT_DEFAULT_GROUP = "defaultGroup"; + private static final String OPT_DEFAULT_PERMISSIONS = "defaultPermissions"; + + private static final Set<PosixFilePermission> DEFAULT_PERMISSIONS = + PosixFilePermissions.fromString("rwxrwxrwx"); private final ZipFileSystemProvider provider; private final Path zfpath; final ZipCoder zc; private final ZipPath rootdir;
*** 101,110 **** --- 104,121 ---- // is to use BAOS for better performance private final boolean forceEnd64; private final int defaultCompressionMethod; // METHOD_STORED if "noCompression=true" // METHOD_DEFLATED otherwise + // POSIX support + final boolean supportPosix; + private final UserPrincipal defaultOwner; + private final GroupPrincipal defaultGroup; + private final Set<PosixFilePermission> defaultPermissions; + + private final Set<String> supportedFileAttributeViews; + ZipFileSystem(ZipFileSystemProvider provider, Path zfpath, Map<String, ?> env) throws IOException { // default encoding for name/comment
*** 112,121 **** --- 123,138 ---- (String)env.get("encoding") : "UTF-8"; this.noExtt = "false".equals(env.get("zipinfo-time")); this.useTempFile = isTrue(env, "useTempFile"); this.forceEnd64 = isTrue(env, "forceZIP64End"); this.defaultCompressionMethod = isTrue(env, "noCompression") ? METHOD_STORED : METHOD_DEFLATED; + this.supportPosix = isTrue(env, OPT_POSIX); + this.defaultOwner = initOwner(zfpath, env); + this.defaultGroup = initGroup(zfpath, env); + this.defaultPermissions = initPermissions(env); + this.supportedFileAttributeViews = supportPosix ? + Set.of("basic", "posix", "zip") : Set.of("basic", "zip"); if (Files.notExists(zfpath)) { // create a new zip if it doesn't exist if (isTrue(env, "create")) { try (OutputStream os = Files.newOutputStream(zfpath, CREATE_NEW, WRITE)) { new END().write(os, 0, forceEnd64);
*** 149,158 **** --- 166,278 ---- // returns true if there is a name=true/"true" setting in env private static boolean isTrue(Map<String, ?> env, String name) { return "true".equals(env.get(name)) || TRUE.equals(env.get(name)); } + // Initialize the default owner for files inside the zip archive. + // If not specified in env, it is the owner of the archive. If no owner can + // be determined, we try to go with system property "user.name". If that's not + // accessible, we return "<zipfs_default>". + private UserPrincipal initOwner(Path zfpath, Map<String, ?> env) throws IOException { + Object o = env.get(OPT_DEFAULT_OWNER); + if (o == null) { + try { + PrivilegedExceptionAction<UserPrincipal> pa = ()->Files.getOwner(zfpath); + return AccessController.doPrivileged(pa); + } catch (UnsupportedOperationException | PrivilegedActionException e) { + if (e instanceof UnsupportedOperationException || + e.getCause() instanceof NoSuchFileException) + { + PrivilegedAction<String> pa = ()->System.getProperty("user.name"); + String userName = AccessController.doPrivileged(pa); + return ()->userName; + } else { + throw new IOException(e); + } + } + } + if (o instanceof String) { + if (((String)o).isEmpty()) { + throw new IllegalArgumentException("Value for property " + + OPT_DEFAULT_OWNER + " must not be empty."); + } + return ()->(String)o; + } + if (o instanceof UserPrincipal) { + return (UserPrincipal)o; + } + throw new IllegalArgumentException("Value for property " + + OPT_DEFAULT_OWNER + " must be of type " + String.class + + " or " + UserPrincipal.class); + } + + // Initialize the default group for files inside the zip archive. + // If not specified in env, we try to determine the group of the zip archive itself. + // If this is not possible/unsupported, we will return a group principal going by + // the same name as the default owner. + private GroupPrincipal initGroup(Path zfpath, Map<String, ?> env) throws IOException { + Object o = env.get(OPT_DEFAULT_GROUP); + if (o == null) { + try { + PosixFileAttributeView zfpv = Files.getFileAttributeView(zfpath, PosixFileAttributeView.class); + if (zfpv == null) { + return defaultOwner::getName; + } + PrivilegedExceptionAction<GroupPrincipal> pa = ()->zfpv.readAttributes().group(); + return AccessController.doPrivileged(pa); + } catch (UnsupportedOperationException | PrivilegedActionException e) { + if (e instanceof UnsupportedOperationException || + e.getCause() instanceof NoSuchFileException) + { + return defaultOwner::getName; + } else { + throw new IOException(e); + } + } + } + if (o instanceof String) { + if (((String)o).isEmpty()) { + throw new IllegalArgumentException("Value for property " + + OPT_DEFAULT_GROUP + " must not be empty."); + } + return ()->(String)o; + } + if (o instanceof GroupPrincipal) { + return (GroupPrincipal)o; + } + throw new IllegalArgumentException("Value for property " + + OPT_DEFAULT_GROUP + " must be of type " + String.class + + " or " + GroupPrincipal.class); + } + + // Initialize the default permissions for files inside the zip archive. + // If not specified in env, it will return 777. + private Set<PosixFilePermission> initPermissions(Map<String, ?> env) { + Object o = env.get(OPT_DEFAULT_PERMISSIONS); + if (o == null) { + return DEFAULT_PERMISSIONS; + } + if (o instanceof String) { + return PosixFilePermissions.fromString((String)o); + } + if (!(o instanceof Set)) { + throw new IllegalArgumentException("Value for property " + + OPT_DEFAULT_PERMISSIONS + " must be of type " + String.class + + " or " + Set.class); + } + Set<PosixFilePermission> perms = new HashSet<>(); + for (Object o2 : (Set<?>)o) { + if (o2 instanceof PosixFilePermission) { + perms.add((PosixFilePermission)o2); + } else { + throw new IllegalArgumentException(OPT_DEFAULT_PERMISSIONS + + " must only contain objects of type " + PosixFilePermission.class); + } + } + return perms; + } + @Override public FileSystemProvider provider() { return provider; }
*** 336,350 **** return null; } else if (inode instanceof Entry) { return (Entry)inode; } else if (inode.pos == -1) { // pseudo directory, uses METHOD_STORED ! Entry e = new Entry(inode.name, inode.isdir, METHOD_STORED); e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp; return e; } else { ! return new Entry(this, inode); } } finally { endRead(); } } --- 456,472 ---- return null; } else if (inode instanceof Entry) { return (Entry)inode; } else if (inode.pos == -1) { // pseudo directory, uses METHOD_STORED ! Entry e = supportPosix ? ! new PosixEntry(inode.name, inode.isdir, METHOD_STORED) : ! new Entry(inode.name, inode.isdir, METHOD_STORED); e.mtime = e.atime = e.ctime = zfsDefaultTimeStamp; return e; } else { ! return supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode); } } finally { endRead(); } }
*** 385,394 **** --- 507,575 ---- } finally { endWrite(); } } + void setOwner(byte[] path, UserPrincipal owner) throws IOException { + checkWritable(); + beginWrite(); + try { + ensureOpen(); + Entry e = getEntry(path); // ensureOpen checked + if (e == null) { + throw new NoSuchFileException(getString(path)); + } + // as the owner information is not persistent, we don't need to + // change e.type to Entry.COPY + if (e instanceof PosixEntry) { + ((PosixEntry)e).owner = owner; + update(e); + } + } finally { + endWrite(); + } + } + + void setGroup(byte[] path, GroupPrincipal group) throws IOException { + checkWritable(); + beginWrite(); + try { + ensureOpen(); + Entry e = getEntry(path); // ensureOpen checked + if (e == null) { + throw new NoSuchFileException(getString(path)); + } + // as the group information is not persistent, we don't need to + // change e.type to Entry.COPY + if (e instanceof PosixEntry) { + ((PosixEntry)e).group = group; + update(e); + } + } finally { + endWrite(); + } + } + + void setPermissions(byte[] path, Set<PosixFilePermission> perms) throws IOException { + checkWritable(); + beginWrite(); + try { + ensureOpen(); + Entry e = getEntry(path); // ensureOpen checked + if (e == null) { + throw new NoSuchFileException(getString(path)); + } + if (e.type == Entry.CEN) { + e.type = Entry.COPY; // copy e + } + e.posixPerms = perms == null ? -1 : ZipUtils.permsToFlags(perms); + update(e); + } finally { + endWrite(); + } + } + boolean exists(byte[] path) { beginRead(); try { ensureOpen(); return getInode(path) != null;
*** 446,456 **** try { ensureOpen(); if (dir.length == 0 || exists(dir)) // root dir, or existing dir throw new FileAlreadyExistsException(getString(dir)); checkParents(dir); ! Entry e = new Entry(dir, Entry.NEW, true, METHOD_STORED); update(e); } finally { endWrite(); } } --- 627,639 ---- try { ensureOpen(); if (dir.length == 0 || exists(dir)) // root dir, or existing dir throw new FileAlreadyExistsException(getString(dir)); checkParents(dir); ! Entry e = supportPosix ? ! new PosixEntry(dir, Entry.NEW, true, METHOD_STORED, attrs) : ! new Entry(dir, Entry.NEW, true, METHOD_STORED, attrs); update(e); } finally { endWrite(); } }
*** 487,497 **** throw new FileAlreadyExistsException(getString(dst)); } else { checkParents(dst); } // copy eSrc entry and change name ! Entry u = new Entry(eSrc, Entry.COPY); u.name(dst); if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) { u.type = eSrc.type; // make it the same type if (deletesrc) { // if it's a "rename", take the data u.bytes = eSrc.bytes; --- 670,682 ---- throw new FileAlreadyExistsException(getString(dst)); } else { checkParents(dst); } // copy eSrc entry and change name ! Entry u = supportPosix ? ! new PosixEntry((PosixEntry)eSrc, Entry.COPY) : ! new Entry(eSrc, Entry.COPY); u.name(dst); if (eSrc.type == Entry.NEW || eSrc.type == Entry.FILECH) { u.type = eSrc.type; // make it the same type if (deletesrc) { // if it's a "rename", take the data u.bytes = eSrc.bytes;
*** 551,566 **** try (InputStream is = getInputStream(e)) { is.transferTo(os); } return os; } ! return getOutputStream(new Entry(e, Entry.NEW)); } else { if (!hasCreate && !hasCreateNew) throw new NoSuchFileException(getString(path)); checkParents(path); ! return getOutputStream(new Entry(path, Entry.NEW, false, defaultCompressionMethod)); } } finally { endRead(); } } --- 736,754 ---- try (InputStream is = getInputStream(e)) { is.transferTo(os); } return os; } ! return getOutputStream(supportPosix ? ! new PosixEntry((PosixEntry)e, Entry.NEW) : new Entry(e, Entry.NEW)); } else { if (!hasCreate && !hasCreateNew) throw new NoSuchFileException(getString(path)); checkParents(path); ! return getOutputStream(supportPosix ? ! new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod) : ! new Entry(path, Entry.NEW, false, defaultCompressionMethod)); } } finally { endRead(); } }
*** 643,653 **** Entry e = getEntry(path); if (e != null) { if (e.isDir() || options.contains(CREATE_NEW)) throw new FileAlreadyExistsException(getString(path)); SeekableByteChannel sbc = ! new EntryOutputChannel(new Entry(e, Entry.NEW)); if (options.contains(APPEND)) { try (InputStream is = getInputStream(e)) { // copyover byte[] buf = new byte[8192]; ByteBuffer bb = ByteBuffer.wrap(buf); int n; --- 831,843 ---- Entry e = getEntry(path); if (e != null) { if (e.isDir() || options.contains(CREATE_NEW)) throw new FileAlreadyExistsException(getString(path)); SeekableByteChannel sbc = ! new EntryOutputChannel(supportPosix ? ! new PosixEntry((PosixEntry)e, Entry.NEW) : ! new Entry(e, Entry.NEW)); if (options.contains(APPEND)) { try (InputStream is = getInputStream(e)) { // copyover byte[] buf = new byte[8192]; ByteBuffer bb = ByteBuffer.wrap(buf); int n;
*** 662,672 **** } if (!options.contains(CREATE) && !options.contains(CREATE_NEW)) throw new NoSuchFileException(getString(path)); checkParents(path); return new EntryOutputChannel( ! new Entry(path, Entry.NEW, false, defaultCompressionMethod)); } finally { endRead(); } } else { beginRead(); --- 852,864 ---- } if (!options.contains(CREATE) && !options.contains(CREATE_NEW)) throw new NoSuchFileException(getString(path)); checkParents(path); return new EntryOutputChannel( ! supportPosix ? ! new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod, attrs) : ! new Entry(path, Entry.NEW, false, defaultCompressionMethod, attrs)); } finally { endRead(); } } else { beginRead();
*** 726,736 **** final boolean isFCH = (e != null && e.type == Entry.FILECH); final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path); final FileChannel fch = tmpfile.getFileSystem() .provider() .newFileChannel(tmpfile, options, attrs); ! final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH); if (forWrite) { u.flag = FLAG_DATADESCR; u.method = defaultCompressionMethod; } // is there a better way to hook into the FileChannel's close method? --- 918,931 ---- final boolean isFCH = (e != null && e.type == Entry.FILECH); final Path tmpfile = isFCH ? e.file : getTempPathForEntry(path); final FileChannel fch = tmpfile.getFileSystem() .provider() .newFileChannel(tmpfile, options, attrs); ! final Entry u = isFCH ? e : ( ! supportPosix ? ! new PosixEntry(path, tmpfile, Entry.FILECH, attrs) : ! new Entry(path, tmpfile, Entry.FILECH, attrs)); if (forWrite) { u.flag = FLAG_DATADESCR; u.method = defaultCompressionMethod; } // is there a better way to hook into the FileChannel's close method?
*** 1341,1351 **** } if (inode.name.length == 1 && inode.name[0] == '/') { continue; // no root '/' directory even if it // exists in original zip/jar file. } ! e = new Entry(this, inode); try { if (buf == null) buf = new byte[8192]; written += copyLOCEntry(e, false, os, written, buf); elist.add(e); --- 1536,1546 ---- } if (inode.name.length == 1 && inode.name[0] == '/') { continue; // no root '/' directory even if it // exists in original zip/jar file. } ! e = supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode); try { if (buf == null) buf = new byte[8192]; written += copyLOCEntry(e, false, os, written, buf); elist.add(e);
*** 1415,1425 **** IndexNode inode = getInode(path); if (inode instanceof Entry) return (Entry)inode; if (inode == null || inode.pos == -1) return null; ! return new Entry(this, inode); } public void deleteFile(byte[] path, boolean failIfNotExists) throws IOException { --- 1610,1620 ---- IndexNode inode = getInode(path); if (inode instanceof Entry) return (Entry)inode; if (inode == null || inode.pos == -1) return null; ! return supportPosix ? new PosixEntry(this, inode): new Entry(this, inode); } public void deleteFile(byte[] path, boolean failIfNotExists) throws IOException {
*** 2051,2060 **** --- 2246,2256 ---- int type = CEN; // default is the entry read from cen // 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 long ctime = -1; // create time long crc = -1; // crc-32 of entry data
*** 2079,2095 **** this.size = 0; this.csize = 0; this.method = method; } ! Entry(byte[] name, int type, boolean isdir, int method) { this(name, isdir, method); this.type = type; } ! Entry(byte[] name, Path file, int type) { ! this(name, type, false, METHOD_STORED); this.file = file; } Entry(Entry e, int type) { name(e.name); --- 2275,2298 ---- this.size = 0; this.csize = 0; this.method = method; } ! @SuppressWarnings("unchecked") ! Entry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) { this(name, isdir, method); this.type = type; + for (FileAttribute<?> attr : attrs) { + String attrName = attr.name(); + if (attrName.equals("posix:permissions")) { + posixPerms = ZipUtils.permsToFlags((Set<PosixFilePermission>)attr.value()); + } + } } ! Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) { ! this(name, type, false, METHOD_STORED, attrs); this.file = file; } Entry(Entry e, int type) { name(e.name);
*** 2109,2118 **** --- 2312,2322 ---- this.attrs = e.attrs; this.attrsEx = e.attrsEx; */ this.locoff = e.locoff; this.comment = e.comment; + this.posixPerms = e.posixPerms; this.type = type; } Entry(ZipFileSystem zipfs, IndexNode inode) throws IOException { readCEN(zipfs, inode);
*** 2133,2142 **** --- 2337,2355 ---- else if (method == METHOD_STORED) return 10; throw new ZipException("unsupported compression method"); } + /** + * Adds information about compatibility of file attribute information + * to a version value. + */ + private int versionMadeBy(int version) { + return (posixPerms < 0) ? version : + VERSION_BASE_UNIX | (version & 0xff); + } + ///////////////////// CEN ////////////////////// private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException { byte[] cen = zipfs.cen; int pos = inode.pos; if (!cenSigAt(cen, pos))
*** 2155,2164 **** --- 2368,2380 ---- 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;
*** 2221,2231 **** } else { // Extended Timestamp otherwise elenEXTT = 9; // only mtime in cen } } writeInt(os, CENSIG); // CEN header signature ! writeShort(os, 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 writeInt(os, (int)javaToDosTime(mtime)); --- 2437,2447 ---- } else { // Extended Timestamp otherwise elenEXTT = 9; // only mtime in cen } } writeInt(os, CENSIG); // CEN header signature ! 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 writeInt(os, (int)javaToDosTime(mtime));
*** 2240,2250 **** } else { writeShort(os, 0); } writeShort(os, 0); // starting disk number writeShort(os, 0); // internal file attributes (unused) ! writeInt(os, 0); // external file attributes (unused) writeInt(os, locoff0); // relative offset of local header writeBytes(os, zname, 1, nlen); if (zip64) { writeShort(os, EXTID_ZIP64);// Zip64 extra writeShort(os, elen64 - 4); // size of "this" extra block --- 2456,2468 ---- } else { writeShort(os, 0); } writeShort(os, 0); // starting disk number writeShort(os, 0); // internal file attributes (unused) ! writeInt(os, posixPerms > 0 ? posixPerms << 16 : 0); // external file ! // attributes, used for storing posix ! // permissions writeInt(os, locoff0); // relative offset of local header writeBytes(os, zname, 1, nlen); if (zip64) { writeShort(os, EXTID_ZIP64);// Zip64 extra writeShort(os, elen64 - 4); // size of "this" extra block
*** 2525,2534 **** --- 2743,2756 ---- 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()); + Set<PosixFilePermission> permissions = storedPermissions().orElse(null); + if (permissions != null) { + fm.format(" permissions : %s%n", permissions); + } fm.close(); return sb.toString(); } ///////// basic file attributes ///////////
*** 2605,2614 **** --- 2827,2892 ---- public byte[] comment() { if (comment != null) return Arrays.copyOf(comment, comment.length); return null; } + + @Override + public Optional<Set<PosixFilePermission>> storedPermissions() { + Set<PosixFilePermission> perms = null; + if (posixPerms != -1) { + perms = new HashSet<>(PosixFilePermission.values().length); + for (PosixFilePermission perm : PosixFilePermission.values()) { + if ((posixPerms & ZipUtils.permToFlag(perm)) != 0) { + perms.add(perm); + } + } + } + return Optional.ofNullable(perms); + } + } + + final class PosixEntry extends Entry implements PosixFileAttributes { + private UserPrincipal owner = defaultOwner; + private GroupPrincipal group = defaultGroup; + + PosixEntry(byte[] name, boolean isdir, int method) { + super(name, isdir, method); + } + + PosixEntry(byte[] name, int type, boolean isdir, int method, FileAttribute<?>... attrs) { + super(name, type, isdir, method, attrs); + } + + PosixEntry(byte[] name, Path file, int type, FileAttribute<?>... attrs) { + super(name, file, type, attrs); + } + + PosixEntry(PosixEntry e, int type) { + super(e, type); + this.owner = e.owner; + this.group = e.group; + } + + PosixEntry(ZipFileSystem zipfs, IndexNode inode) throws IOException { + super(zipfs, inode); + } + + @Override + public UserPrincipal owner() { + return owner; + } + + @Override + public GroupPrincipal group() { + return group; + } + + @Override + public Set<PosixFilePermission> permissions() { + return storedPermissions().orElse(Set.copyOf(defaultPermissions)); + } } private static class ExistingChannelCloser { private final Path path; private final SeekableByteChannel ch;
< prev index next >