< prev index next >
src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipFileSystem.java
Print this page
rev 52310 : 8213031: (zipfs) Add support for POSIX file permissions
@@ -1,7 +1,7 @@
/*
- * 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
@@ -23,53 +23,70 @@
* questions.
*/
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.Inflater;
import java.util.zip.Deflater;
-import java.util.zip.InflaterInputStream;
import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
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;
private final ZipPath rootdir;
private boolean readOnly = false; // readonly file system
@@ -267,11 +284,11 @@
} finally {
endWrite();
}
if (!streams.isEmpty()) { // unlock and close all remaining streams
Set<InputStream> copy = new HashSet<>(streams);
- for (InputStream is: copy)
+ for (InputStream is : copy)
is.close();
}
beginWrite(); // lock and sync
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
@@ -294,11 +311,11 @@
def.end();
}
IOException ioe = null;
synchronized (tmppaths) {
- for (Path p: tmppaths) {
+ for (Path p : tmppaths) {
try {
AccessController.doPrivileged(
(PrivilegedExceptionAction<Boolean>)() -> Files.deleteIfExists(p));
} catch (PrivilegedActionException e) {
IOException x = (IOException)e.getException();
@@ -442,11 +459,11 @@
try {
ensureOpen();
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();
}
}
@@ -519,11 +536,11 @@
checkWritable();
boolean hasCreateNew = false;
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)
hasCreateNew = true;
if (opt == CREATE)
@@ -664,11 +681,11 @@
}
if (!options.contains(CREATE) && !options.contains(CREATE_NEW))
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();
}
} else {
@@ -729,11 +746,11 @@
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);
+ final Entry u = isFCH ? e : new Entry(path, tmpfile, Entry.FILECH, attrs);
if (forWrite) {
u.flag = FLAG_DATADESCR;
u.method = getCompressMethod(attrs);
}
// is there a better way to hook into the FileChannel's close method?
@@ -1475,11 +1492,11 @@
// FILECH result is un-compressed.
eis = Files.newInputStream(e.file);
// 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) {
// MORE: Compute good size for inflater stream:
long bufSize = e.size + 2; // Inflater likes a bit of slack
@@ -1537,18 +1554,16 @@
private class EntryInputStream extends InputStream {
private final SeekableByteChannel zfch; // local ref to zipfs's "ch". zipfs.ch might
// 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);
if (e2 == null) {
throw new ZipException("invalid loc for entry <" + e.name + ">");
@@ -1611,14 +1626,10 @@
public int available() {
return rem > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) rem;
}
- public long size() {
- return size;
- }
-
public void close() {
rem = 0;
streams.remove(this);
}
@@ -1670,11 +1681,11 @@
}
// List of available Deflater objects for compression
private final List<Deflater> 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) {
int size = deflaters.size();
if (size > 0) {
@@ -1684,22 +1695,10 @@
return new Deflater(Deflater.DEFAULT_COMPRESSION, true);
}
}
}
- // 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"
// int disknum;
// int sdisknum;
@@ -1863,10 +1862,11 @@
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
@@ -1874,11 +1874,11 @@
long size = -1; // uncompressed size of entry data
byte[] extra;
// cen
- // these fields are not used by anyone and writeCEN uses "0"
+ // these fields are not used
// int versionMade;
// int disk;
// int attrs;
// long attrsEx;
long locoff;
@@ -1894,16 +1894,23 @@
this.size = 0;
this.csize = 0;
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<PosixFilePermission>)attr.value());
+ }
+ }
}
- Entry (Entry e, int type) {
+ Entry(Entry e, int type) {
name(e.name);
this.isdir = e.isdir;
this.version = e.version;
this.ctime = e.ctime;
this.atime = e.atime;
@@ -1919,26 +1926,46 @@
this.attrs = e.attrs;
this.attrsEx = e.attrsEx;
*/
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<PosixFilePermission>)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)
return 10;
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
{
return new Entry().cen(zipfs, inode);
@@ -1965,10 +1992,13 @@
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;
@@ -1983,14 +2013,11 @@
comment = Arrays.copyOfRange(cen, pos, pos + clen);
}
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;
int elen64 = 0; // extra for ZIP64
int elenNTFS = 0; // extra for NTFS (a/c/mtime)
@@ -2017,10 +2044,12 @@
elen64 += 8; // offset(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);
if (tag == EXTID_EXTT || tag == EXTID_NTFS) {
foundExtraTime = true;
@@ -2033,17 +2062,12 @@
} else { // Extended Timestamp otherwise
elenEXTT = 9; // only mtime in cen
}
}
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
writeInt(os, (int)javaToDosTime(mtime));
writeInt(os, crc); // crc-32
@@ -2057,14 +2081,16 @@
} 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, 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)
writeLong(os, size);
if (csize0 == ZIP64_MINVAL)
@@ -2100,22 +2126,21 @@
///////////////////// LOC //////////////////////
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
writeInt(os, (int)javaToDosTime(mtime));
// store size, uncompressed size, and crc-32 in data descriptor
@@ -2124,20 +2149,19 @@
writeInt(os, 0);
writeInt(os, 0);
} 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 {
writeInt(os, csize); // compressed size
writeInt(os, size); // uncompressed size
@@ -2163,11 +2187,11 @@
}
}
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);
writeLong(os, csize);
}
@@ -2416,23 +2440,55 @@
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();
}
+
+ @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<PosixFilePermission> 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<PosixFilePermission> perms) {
+ posixPerms = ZipUtils.permsToFlags(perms);
+ }
}
// ZIP directory has two issues:
// (1) ZIP spec does not require the ZIP file to include
// directory entry
// (2) all entries are not stored/organized in a "tree"
// 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();
private void removeFromTree(IndexNode inode) {
< prev index next >