mirror of
https://github.com/darlinghq/darling-openjdk.git
synced 2025-03-03 05:26:55 +00:00
8213031: (zipfs) Add support for POSIX file permissions
Reviewed-by: alanb, lancea
This commit is contained in:
parent
ac2ea95788
commit
302a162704
@ -201,8 +201,10 @@ grant codeBase "jrt:/jdk.security.jgss" {
|
||||
grant codeBase "jrt:/jdk.zipfs" {
|
||||
permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete";
|
||||
permission java.lang.RuntimePermission "fileSystemProvider";
|
||||
permission java.lang.RuntimePermission "accessUserInformation";
|
||||
permission java.util.PropertyPermission "os.name", "read";
|
||||
permission java.util.PropertyPermission "user.dir", "read";
|
||||
permission java.util.PropertyPermission "user.name", "read";
|
||||
};
|
||||
|
||||
// permissions needed by applications using java.desktop module
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 2019, 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
|
||||
@ -64,6 +64,16 @@ class ZipConstants {
|
||||
static final int CENHDR = 46; // CEN header size
|
||||
static final int ENDHDR = 22; // END header size
|
||||
|
||||
/*
|
||||
* File attribute compatibility types of CEN field "version made by"
|
||||
*/
|
||||
static final int FILE_ATTRIBUTES_UNIX = 3; // Unix
|
||||
|
||||
/*
|
||||
* Base values for CEN field "version made by"
|
||||
*/
|
||||
static final int VERSION_MADE_BY_BASE_UNIX = FILE_ATTRIBUTES_UNIX << 8; // Unix
|
||||
|
||||
/*
|
||||
* Local file (LOC) header field offsets
|
||||
*/
|
||||
@ -228,22 +238,24 @@ class ZipConstants {
|
||||
static final long ZIP64_LOCOFF(byte[] b) { return LL(b, 8);} // zip64 end offset
|
||||
|
||||
// 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 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);}
|
||||
static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);}
|
||||
static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);}
|
||||
static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);}
|
||||
static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);}
|
||||
static final int CENNAM(byte[] b, int pos) { return SH(b, pos + 28);}
|
||||
static final int CENEXT(byte[] b, int pos) { return SH(b, pos + 30);}
|
||||
static final int CENCOM(byte[] b, int pos) { return SH(b, pos + 32);}
|
||||
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 long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);}
|
||||
static final long CENSIG(byte[] b, int pos) { return LG(b, pos + 0); } // signature
|
||||
static final int CENVEM(byte[] b, int pos) { return SH(b, pos + 4); } // version made by
|
||||
static final int CENVEM_FA(byte[] b, int pos) { return CH(b, pos + 5); } // file attribute compatibility
|
||||
static final int CENVER(byte[] b, int pos) { return SH(b, pos + 6); } // version needed to extract
|
||||
static final int CENFLG(byte[] b, int pos) { return SH(b, pos + 8); } // encrypt, decrypt flags
|
||||
static final int CENHOW(byte[] b, int pos) { return SH(b, pos + 10);} // compression method
|
||||
static final long CENTIM(byte[] b, int pos) { return LG(b, pos + 12);} // modification time
|
||||
static final long CENCRC(byte[] b, int pos) { return LG(b, pos + 16);} // uncompressed file crc-32 value
|
||||
static final long CENSIZ(byte[] b, int pos) { return LG(b, pos + 20);} // compressed size
|
||||
static final long CENLEN(byte[] b, int pos) { return LG(b, pos + 24);} // uncompressed size
|
||||
static final int CENNAM(byte[] b, int pos) { return SH(b, pos + 28);} // filename length
|
||||
static final int CENEXT(byte[] b, int pos) { return SH(b, pos + 30);} // extra field length
|
||||
static final int CENCOM(byte[] b, int pos) { return SH(b, pos + 32);} // comment length
|
||||
static final int CENDSK(byte[] b, int pos) { return SH(b, pos + 34);} // disk number start
|
||||
static final int CENATT(byte[] b, int pos) { return SH(b, pos + 36);} // internal file attributes
|
||||
static final long CENATX(byte[] b, int pos) { return LG(b, pos + 38);} // external file attributes
|
||||
static final int CENATX_PERMS(byte[] b, int pos) { return SH(b, pos + 40);} // posix permission data
|
||||
static final long CENOFF(byte[] b, int pos) { return LG(b, pos + 42);} // LOC header offset
|
||||
|
||||
/* The END header is followed by a variable length comment of size < 64k. */
|
||||
static final long END_MAXLEN = 0xFFFF + ENDHDR;
|
||||
|
@ -27,15 +27,18 @@ package jdk.nio.zipfs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal
|
||||
*/
|
||||
class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
private enum AttrID {
|
||||
static enum AttrID {
|
||||
size,
|
||||
creationTime,
|
||||
lastAccessTime,
|
||||
@ -47,10 +50,13 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
fileKey,
|
||||
compressedSize,
|
||||
crc,
|
||||
method
|
||||
method,
|
||||
owner,
|
||||
group,
|
||||
permissions
|
||||
}
|
||||
|
||||
private final ZipPath path;
|
||||
final ZipPath path;
|
||||
private final boolean isZipView;
|
||||
|
||||
ZipFileAttributeView(ZipPath path, boolean isZipView) {
|
||||
@ -64,7 +70,7 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipFileAttributes readAttributes() throws IOException {
|
||||
public BasicFileAttributes readAttributes() throws IOException {
|
||||
return path.readAttributes();
|
||||
}
|
||||
|
||||
@ -77,6 +83,11 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
path.setTimes(lastModifiedTime, lastAccessTime, createTime);
|
||||
}
|
||||
|
||||
public void setPermissions(Set<PosixFilePermission> perms) throws IOException {
|
||||
path.setPermissions(perms);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void setAttribute(String attribute, Object value)
|
||||
throws IOException
|
||||
{
|
||||
@ -87,6 +98,8 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
setTimes(null, (FileTime)value, null);
|
||||
if (AttrID.valueOf(attribute) == AttrID.creationTime)
|
||||
setTimes(null, null, (FileTime)value);
|
||||
if (AttrID.valueOf(attribute) == AttrID.permissions)
|
||||
setPermissions((Set<PosixFilePermission>)value);
|
||||
} catch (IllegalArgumentException x) {
|
||||
throw new UnsupportedOperationException("'" + attribute +
|
||||
"' is unknown or read-only attribute");
|
||||
@ -96,7 +109,7 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
Map<String, Object> readAttributes(String attributes)
|
||||
throws IOException
|
||||
{
|
||||
ZipFileAttributes zfas = readAttributes();
|
||||
ZipFileAttributes zfas = (ZipFileAttributes)readAttributes();
|
||||
LinkedHashMap<String, Object> map = new LinkedHashMap<>();
|
||||
if ("*".equals(attributes)) {
|
||||
for (AttrID id : AttrID.values()) {
|
||||
@ -115,7 +128,7 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
return map;
|
||||
}
|
||||
|
||||
private Object attribute(AttrID id, ZipFileAttributes zfas) {
|
||||
Object attribute(AttrID id, ZipFileAttributes zfas) {
|
||||
switch (id) {
|
||||
case size:
|
||||
return zfas.size();
|
||||
@ -147,6 +160,11 @@ class ZipFileAttributeView implements BasicFileAttributeView {
|
||||
if (isZipView)
|
||||
return zfas.method();
|
||||
break;
|
||||
case permissions:
|
||||
if (isZipView) {
|
||||
return zfas.storedPermissions().orElse(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -26,6 +26,9 @@
|
||||
package jdk.nio.zipfs;
|
||||
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The attributes of a file stored in a zip file.
|
||||
@ -38,4 +41,5 @@ interface ZipFileAttributes extends BasicFileAttributes {
|
||||
int method();
|
||||
byte[] extra();
|
||||
byte[] comment();
|
||||
Optional<Set<PosixFilePermission>> storedPermissions();
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.FileAttributeView;
|
||||
import java.nio.file.attribute.FileOwnerAttributeView;
|
||||
import java.nio.file.attribute.FileStoreAttributeView;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
|
||||
/**
|
||||
* @author Xueming Shen, Rajendra Gutupalli, Jaya Hangal
|
||||
@ -63,12 +65,15 @@ class ZipFileStore extends FileStore {
|
||||
@Override
|
||||
public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
|
||||
return (type == BasicFileAttributeView.class ||
|
||||
type == ZipFileAttributeView.class);
|
||||
type == ZipFileAttributeView.class ||
|
||||
((type == FileOwnerAttributeView.class ||
|
||||
type == PosixFileAttributeView.class) && zfs.supportPosix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFileAttributeView(String name) {
|
||||
return "basic".equals(name) || "zip".equals(name);
|
||||
return "basic".equals(name) || "zip".equals(name) ||
|
||||
(("owner".equals(name) || "posix".equals(name)) && zfs.supportPosix);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,9 +41,7 @@ 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.attribute.*;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
@ -82,9 +80,14 @@ class ZipFileSystem extends FileSystem {
|
||||
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 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;
|
||||
@ -103,6 +106,14 @@ class ZipFileSystem extends FileSystem {
|
||||
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
|
||||
@ -114,6 +125,12 @@ class ZipFileSystem extends FileSystem {
|
||||
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")) {
|
||||
@ -151,6 +168,109 @@ class ZipFileSystem extends FileSystem {
|
||||
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;
|
||||
@ -338,11 +458,13 @@ class ZipFileSystem extends FileSystem {
|
||||
return (Entry)inode;
|
||||
} else if (inode.pos == -1) {
|
||||
// pseudo directory, uses METHOD_STORED
|
||||
Entry e = new Entry(inode.name, inode.isdir, 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 new Entry(this, inode);
|
||||
return supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
|
||||
}
|
||||
} finally {
|
||||
endRead();
|
||||
@ -387,6 +509,65 @@ class ZipFileSystem extends FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -448,7 +629,9 @@ class ZipFileSystem extends FileSystem {
|
||||
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);
|
||||
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();
|
||||
@ -489,7 +672,9 @@ class ZipFileSystem extends FileSystem {
|
||||
checkParents(dst);
|
||||
}
|
||||
// copy eSrc entry and change name
|
||||
Entry u = new Entry(eSrc, Entry.COPY);
|
||||
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
|
||||
@ -553,12 +738,15 @@ class ZipFileSystem extends FileSystem {
|
||||
}
|
||||
return os;
|
||||
}
|
||||
return getOutputStream(new Entry(e, Entry.NEW));
|
||||
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(new Entry(path, Entry.NEW, false, defaultCompressionMethod));
|
||||
return getOutputStream(supportPosix ?
|
||||
new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod) :
|
||||
new Entry(path, Entry.NEW, false, defaultCompressionMethod));
|
||||
}
|
||||
} finally {
|
||||
endRead();
|
||||
@ -645,7 +833,9 @@ class ZipFileSystem extends FileSystem {
|
||||
if (e.isDir() || options.contains(CREATE_NEW))
|
||||
throw new FileAlreadyExistsException(getString(path));
|
||||
SeekableByteChannel sbc =
|
||||
new EntryOutputChannel(new Entry(e, Entry.NEW));
|
||||
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];
|
||||
@ -664,7 +854,9 @@ class ZipFileSystem extends FileSystem {
|
||||
throw new NoSuchFileException(getString(path));
|
||||
checkParents(path);
|
||||
return new EntryOutputChannel(
|
||||
new Entry(path, Entry.NEW, false, defaultCompressionMethod));
|
||||
supportPosix ?
|
||||
new PosixEntry(path, Entry.NEW, false, defaultCompressionMethod, attrs) :
|
||||
new Entry(path, Entry.NEW, false, defaultCompressionMethod, attrs));
|
||||
} finally {
|
||||
endRead();
|
||||
}
|
||||
@ -728,7 +920,10 @@ class ZipFileSystem extends FileSystem {
|
||||
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 : (
|
||||
supportPosix ?
|
||||
new PosixEntry(path, tmpfile, Entry.FILECH, attrs) :
|
||||
new Entry(path, tmpfile, Entry.FILECH, attrs));
|
||||
if (forWrite) {
|
||||
u.flag = FLAG_DATADESCR;
|
||||
u.method = defaultCompressionMethod;
|
||||
@ -1343,7 +1538,7 @@ class ZipFileSystem extends FileSystem {
|
||||
continue; // no root '/' directory even if it
|
||||
// exists in original zip/jar file.
|
||||
}
|
||||
e = new Entry(this, inode);
|
||||
e = supportPosix ? new PosixEntry(this, inode) : new Entry(this, inode);
|
||||
try {
|
||||
if (buf == null)
|
||||
buf = new byte[8192];
|
||||
@ -1417,7 +1612,7 @@ class ZipFileSystem extends FileSystem {
|
||||
return (Entry)inode;
|
||||
if (inode == null || inode.pos == -1)
|
||||
return null;
|
||||
return new Entry(this, inode);
|
||||
return supportPosix ? new PosixEntry(this, inode): new Entry(this, inode);
|
||||
}
|
||||
|
||||
public void deleteFile(byte[] path, boolean failIfNotExists)
|
||||
@ -2053,6 +2248,7 @@ class ZipFileSystem extends FileSystem {
|
||||
// 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
|
||||
@ -2081,13 +2277,20 @@ class ZipFileSystem extends FileSystem {
|
||||
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<PosixFilePermission>)attr.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Entry(byte[] name, Path file, int type) {
|
||||
this(name, type, false, METHOD_STORED);
|
||||
Entry(byte[] name, Path file, int type, FileAttribute<?>... attrs) {
|
||||
this(name, type, false, METHOD_STORED, attrs);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@ -2111,6 +2314,7 @@ class ZipFileSystem extends FileSystem {
|
||||
*/
|
||||
this.locoff = e.locoff;
|
||||
this.comment = e.comment;
|
||||
this.posixPerms = e.posixPerms;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@ -2135,6 +2339,15 @@ class ZipFileSystem extends FileSystem {
|
||||
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_MADE_BY_BASE_UNIX | (version & 0xff);
|
||||
}
|
||||
|
||||
///////////////////// CEN //////////////////////
|
||||
private void readCEN(ZipFileSystem zipfs, IndexNode inode) throws IOException {
|
||||
byte[] cen = zipfs.cen;
|
||||
@ -2157,6 +2370,9 @@ class ZipFileSystem extends FileSystem {
|
||||
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;
|
||||
@ -2223,7 +2439,7 @@ class ZipFileSystem extends FileSystem {
|
||||
}
|
||||
}
|
||||
writeInt(os, CENSIG); // CEN header signature
|
||||
writeShort(os, version0); // version made by
|
||||
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
|
||||
@ -2242,7 +2458,9 @@ class ZipFileSystem extends FileSystem {
|
||||
}
|
||||
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 (zip64) {
|
||||
@ -2527,6 +2745,10 @@ class ZipFileSystem extends FileSystem {
|
||||
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();
|
||||
}
|
||||
@ -2607,6 +2829,62 @@ class ZipFileSystem extends FileSystem {
|
||||
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 {
|
||||
|
@ -34,11 +34,7 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.DirectoryStream.Filter;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.FileAttributeView;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.nio.file.attribute.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@ -711,6 +707,12 @@ final class ZipPath implements Path {
|
||||
return (V)new ZipFileAttributeView(this, false);
|
||||
if (type == ZipFileAttributeView.class)
|
||||
return (V)new ZipFileAttributeView(this, true);
|
||||
if (zfs.supportPosix) {
|
||||
if (type == PosixFileAttributeView.class)
|
||||
return (V)new ZipPosixFileAttributeView(this, false);
|
||||
if (type == FileOwnerAttributeView.class)
|
||||
return (V)new ZipPosixFileAttributeView(this,true);
|
||||
}
|
||||
throw new UnsupportedOperationException("view <" + type + "> is not supported");
|
||||
}
|
||||
|
||||
@ -721,6 +723,12 @@ final class ZipPath implements Path {
|
||||
return new ZipFileAttributeView(this, false);
|
||||
if ("zip".equals(type))
|
||||
return new ZipFileAttributeView(this, true);
|
||||
if (zfs.supportPosix) {
|
||||
if ("posix".equals(type))
|
||||
return new ZipPosixFileAttributeView(this, false);
|
||||
if ("owner".equals(type))
|
||||
return new ZipPosixFileAttributeView(this, true);
|
||||
}
|
||||
throw new UnsupportedOperationException("view <" + type + "> is not supported");
|
||||
}
|
||||
|
||||
@ -764,10 +772,16 @@ final class ZipPath implements Path {
|
||||
|
||||
@SuppressWarnings("unchecked") // Cast to A
|
||||
<A extends BasicFileAttributes> A readAttributes(Class<A> type) throws IOException {
|
||||
// unconditionally support BasicFileAttributes and ZipFileAttributes
|
||||
if (type == BasicFileAttributes.class || type == ZipFileAttributes.class) {
|
||||
return (A)readAttributes();
|
||||
}
|
||||
|
||||
// support PosixFileAttributes when activated
|
||||
if (type == PosixFileAttributes.class && zfs.supportPosix) {
|
||||
return (A)readAttributes();
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Attributes of type " +
|
||||
type.getName() + " not supported");
|
||||
}
|
||||
@ -794,9 +808,22 @@ final class ZipPath implements Path {
|
||||
zfs.setTimes(getResolvedPath(), mtime, atime, ctime);
|
||||
}
|
||||
|
||||
void setOwner(UserPrincipal owner) throws IOException {
|
||||
zfs.setOwner(getResolvedPath(), owner);
|
||||
}
|
||||
|
||||
void setPermissions(Set<PosixFilePermission> perms)
|
||||
throws IOException
|
||||
{
|
||||
zfs.setPermissions(getResolvedPath(), perms);
|
||||
}
|
||||
|
||||
void setGroup(GroupPrincipal group) throws IOException {
|
||||
zfs.setGroup(getResolvedPath(), group);
|
||||
}
|
||||
|
||||
Map<String, Object> readAttributes(String attributes, LinkOption... options)
|
||||
throws IOException
|
||||
|
||||
{
|
||||
String view;
|
||||
String attrs;
|
||||
@ -948,12 +975,14 @@ final class ZipPath implements Path {
|
||||
}
|
||||
}
|
||||
if (copyAttrs) {
|
||||
BasicFileAttributeView view =
|
||||
target.getFileAttributeView(BasicFileAttributeView.class);
|
||||
ZipFileAttributeView view =
|
||||
target.getFileAttributeView(ZipFileAttributeView.class);
|
||||
try {
|
||||
view.setTimes(zfas.lastModifiedTime(),
|
||||
zfas.lastAccessTime(),
|
||||
zfas.creationTime());
|
||||
// copy permissions
|
||||
view.setPermissions(zfas.storedPermissions().orElse(null));
|
||||
} catch (IOException x) {
|
||||
// rollback?
|
||||
try {
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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
|
||||
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nio.zipfs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.attribute.GroupPrincipal;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.UserPrincipal;
|
||||
|
||||
/**
|
||||
* The zip file system attribute view with POSIX support.
|
||||
*/
|
||||
class ZipPosixFileAttributeView extends ZipFileAttributeView implements PosixFileAttributeView {
|
||||
private final boolean isOwnerView;
|
||||
|
||||
ZipPosixFileAttributeView(ZipPath path, boolean owner) {
|
||||
super(path, true);
|
||||
this.isOwnerView = owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return isOwnerView ? "owner" : "posix";
|
||||
}
|
||||
|
||||
@Override
|
||||
public PosixFileAttributes readAttributes() throws IOException {
|
||||
return (PosixFileAttributes)path.readAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserPrincipal getOwner() throws IOException {
|
||||
return readAttributes().owner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(UserPrincipal owner) throws IOException {
|
||||
path.setOwner(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroup(GroupPrincipal group) throws IOException {
|
||||
path.setGroup(group);
|
||||
}
|
||||
|
||||
@Override
|
||||
Object attribute(AttrID id, ZipFileAttributes zfas) {
|
||||
PosixFileAttributes pzfas = (PosixFileAttributes)zfas;
|
||||
switch (id) {
|
||||
case owner:
|
||||
return pzfas.owner();
|
||||
case group:
|
||||
return pzfas.group();
|
||||
case permissions:
|
||||
if (!isOwnerView) {
|
||||
return pzfas.permissions();
|
||||
} else {
|
||||
return super.attribute(id, zfas);
|
||||
}
|
||||
default:
|
||||
return super.attribute(id, zfas);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2009, 2019, 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,12 +27,14 @@ package jdk.nio.zipfs;
|
||||
|
||||
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.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
@ -41,6 +43,102 @@ import java.util.regex.PatternSyntaxException;
|
||||
*/
|
||||
class ZipUtils {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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<PosixFilePermission> perms) {
|
||||
if (perms == null) {
|
||||
return -1;
|
||||
}
|
||||
int flags = 0;
|
||||
for (PosixFilePermission perm : perms) {
|
||||
flags |= permToFlag(perm);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes a 16-bit short to the output stream in little-endian byte order.
|
||||
*/
|
||||
|
@ -23,6 +23,15 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileAttributeView;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides the implementation of the Zip file system provider.
|
||||
* The Zip file system provider treats the contents of a Zip or JAR file as a file system.
|
||||
@ -32,14 +41,91 @@
|
||||
* The {@linkplain java.nio.file.FileSystems FileSystems} {@code newFileSystem}
|
||||
* static factory methods can be used to:
|
||||
* <ul>
|
||||
* <li>Create a Zip file system</li>
|
||||
* <li>Open an existing file as a Zip file system</li>
|
||||
* <li>Create a Zip file system</li>
|
||||
* <li>Open an existing file as a Zip file system</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>URI Scheme Used to Identify the Zip File System</h3>
|
||||
* <h2>URI Scheme Used to Identify the Zip File System</h2>
|
||||
*
|
||||
* The URI {@link java.net.URI#getScheme scheme} that identifies the ZIP file system is {@code jar}.
|
||||
*
|
||||
* <h2>POSIX file attributes</h2>
|
||||
*
|
||||
* A Zip file system supports a file attribute {@link FileAttributeView view}
|
||||
* named "{@code zip}" that defines the following file attribute:
|
||||
*
|
||||
* <blockquote>
|
||||
* <table class="striped">
|
||||
* <caption style="display:none">Supported attributes</caption>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th scope="col">Name</th>
|
||||
* <th scope="col">Type</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr>
|
||||
* <th scope="row">permissions</th>
|
||||
* <td>{@link Set}<{@link PosixFilePermission}></td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
* </blockquote>
|
||||
*
|
||||
* The "permissions" attribute is the set of access permissions that are optionally
|
||||
* stored for entries in a Zip file. The value of the attribute is {@code null}
|
||||
* for entries that do not have access permissions. Zip file systems do not
|
||||
* enforce access permissions.
|
||||
*
|
||||
* <p> The "permissions" attribute may be read and set using the
|
||||
* {@linkplain Files#getAttribute(Path, String, LinkOption...) Files.getAttribute} and
|
||||
* {@linkplain Files#setAttribute(Path, String, Object, LinkOption...) Files.setAttribute}
|
||||
* methods. The following example uses these methods to read and set the attribute:
|
||||
* <pre> {@code
|
||||
* Set<PosixFilePermission> perms = Files.getAttribute(entry, "zip:permissions");
|
||||
* if (perms == null) {
|
||||
* perms = PosixFilePermissions.fromString("rw-rw-rw-");
|
||||
* Files.setAttribute(entry, "zip:permissions", perms);
|
||||
* }
|
||||
* } </pre>
|
||||
*
|
||||
* <p> In addition to the "{@code zip}" view, a Zip file system optionally supports
|
||||
* the {@link PosixFileAttributeView} ("{@code posix}").
|
||||
* This view extends the "{@code basic}" view with type safe access to the
|
||||
* {@link PosixFileAttributes#owner() owner}, {@link PosixFileAttributes#group() group-owner},
|
||||
* and {@link PosixFileAttributes#permissions() permissions} attributes. The
|
||||
* "{@code posix}" view is only supported when the Zip file system is created with
|
||||
* the provider property "{@code enablePosixFileAttributes}" set to "{@code true}".
|
||||
* The following creates a file system with this property and reads the access
|
||||
* permissions of a file:
|
||||
* <pre> {@code
|
||||
* var env = Map.of("enablePosixFileAttributes", "true");
|
||||
* try (FileSystem fs = FileSystems.newFileSystem(file, env) {
|
||||
* Path entry = fs.getPath("entry");
|
||||
* Set<PosixFilePermission> perms = Files.getPosixFilePermissions(entry);
|
||||
* }
|
||||
* } </pre>
|
||||
*
|
||||
* <p> The file owner and group owner attributes are not persisted, meaning they are
|
||||
* not stored in the zip file. The "{@code defaultOwner}" and "{@code defaultGroup}"
|
||||
* provider properties (listed below) can be used to configure the default values
|
||||
* for these attributes. If these properties are not set then the file owner
|
||||
* defaults to the owner of the zip file, and the group owner defaults to the
|
||||
* zip file's group owner (or the file owner on platforms that don't support a
|
||||
* group owner).
|
||||
*
|
||||
* <p> The "{@code permissions}" attribute is not optional in the "{@code posix}"
|
||||
* view so a default set of permissions are used for entries that do not have
|
||||
* access permissions stored in the Zip file. The default set of permissions
|
||||
* are
|
||||
* <ul>
|
||||
* <li>{@link PosixFilePermission#OWNER_READ OWNER_READ}</li>
|
||||
* <li>{@link PosixFilePermission#OWNER_WRITE OWNER_WRITE}</li>
|
||||
* <li>{@link PosixFilePermission#GROUP_READ GROUP_READ}</li>
|
||||
* </ul>
|
||||
* The default permissions can be configured with the "{@code defaultPermissions}"
|
||||
* property described below.
|
||||
*
|
||||
* <h2>Zip File System Properties</h2>
|
||||
*
|
||||
* The following properties may be specified when creating a Zip
|
||||
@ -50,12 +136,12 @@
|
||||
* a new Zip file system
|
||||
* </caption>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th scope="col">Property Name</th>
|
||||
* <th scope="col">Data Type</th>
|
||||
* <th scope="col">Default Value</th>
|
||||
* <th scope="col">Description</th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <th scope="col">Property Name</th>
|
||||
* <th scope="col">Data Type</th>
|
||||
* <th scope="col">Default Value</th>
|
||||
* <th scope="col">Description</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
*
|
||||
* <tbody>
|
||||
@ -77,6 +163,44 @@
|
||||
* names of the entries in the Zip or JAR file.
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td scope="row">enablePosixFileAttributes</td>
|
||||
* <td>java.lang.String</td>
|
||||
* <td>false</td>
|
||||
* <td>
|
||||
* If the value is {@code true}, the Zip file system will support
|
||||
* the {@link java.nio.file.attribute.PosixFileAttributeView PosixFileAttributeView}.
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td scope="row">defaultOwner</td>
|
||||
* <td>{@link java.nio.file.attribute.UserPrincipal UserPrincipal}<br> or java.lang.String</td>
|
||||
* <td>null/unset</td>
|
||||
* <td>
|
||||
* Override the default owner for entries in the Zip file system.<br>
|
||||
* The value can be a UserPrincipal or a String value that is used as the UserPrincipal's name.
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td scope="row">defaultGroup</td>
|
||||
* <td>{@link java.nio.file.attribute.GroupPrincipal GroupPrincipal}<br> or java.lang.String</td>
|
||||
* <td>null/unset</td>
|
||||
* <td>
|
||||
* Override the the default group for entries in the Zip file system.<br>
|
||||
* The value can be a GroupPrincipal or a String value that is used as the GroupPrincipal's name.
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td scope="row">defaultPermissions</td>
|
||||
* <td>{@link java.util.Set Set}<{@link java.nio.file.attribute.PosixFilePermission PosixFilePermission}><br>
|
||||
* or java.lang.String</td>
|
||||
* <td>null/unset</td>
|
||||
* <td>
|
||||
* Override the default Set of permissions for entries in the Zip file system.<br>
|
||||
* The value can be a {@link java.util.Set Set}<{@link java.nio.file.attribute.PosixFilePermission PosixFilePermission}> or<br>
|
||||
* a String that is parsed by {@link java.nio.file.attribute.PosixFilePermissions#fromString PosixFilePermissions::fromString}
|
||||
* </td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
|
725
test/jdk/jdk/nio/zipfs/TestPosix.java
Normal file
725
test/jdk/jdk/nio/zipfs/TestPosix.java
Normal file
@ -0,0 +1,725 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 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.
|
||||
*
|
||||
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.GroupPrincipal;
|
||||
import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.nio.file.attribute.UserPrincipal;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.spi.ToolProvider;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
|
||||
import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
|
||||
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertNull;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8213031
|
||||
* @modules jdk.zipfs
|
||||
* jdk.jartool
|
||||
* @run testng TestPosix
|
||||
* @run testng/othervm/java.security.policy=test.policy.posix TestPosix
|
||||
* @summary Test POSIX zip file operations.
|
||||
*/
|
||||
public class TestPosix {
|
||||
private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
|
||||
.orElseThrow(()->new RuntimeException("jar tool not found"));
|
||||
|
||||
// files and directories
|
||||
private static final Path ZIP_FILE = Paths.get("testPosix.zip");
|
||||
private static final Path JAR_FILE = Paths.get("testPosix.jar");
|
||||
private static final Path ZIP_FILE_COPY = Paths.get("testPosixCopy.zip");
|
||||
private static final Path UNZIP_DIR = Paths.get("unzip/");
|
||||
|
||||
// permission sets
|
||||
private static final Set<PosixFilePermission> ALLPERMS =
|
||||
PosixFilePermissions.fromString("rwxrwxrwx");
|
||||
private static final Set<PosixFilePermission> EMPTYPERMS =
|
||||
Collections.<PosixFilePermission>emptySet();
|
||||
private static final Set<PosixFilePermission> UR = Set.of(OWNER_READ);
|
||||
private static final Set<PosixFilePermission> UW = Set.of(OWNER_WRITE);
|
||||
private static final Set<PosixFilePermission> UE = Set.of(OWNER_EXECUTE);
|
||||
private static final Set<PosixFilePermission> GR = Set.of(GROUP_READ);
|
||||
private static final Set<PosixFilePermission> GW = Set.of(GROUP_WRITE);
|
||||
private static final Set<PosixFilePermission> GE = Set.of(GROUP_EXECUTE);
|
||||
private static final Set<PosixFilePermission> OR = Set.of(OTHERS_READ);
|
||||
private static final Set<PosixFilePermission> OW = Set.of(OTHERS_WRITE);
|
||||
private static final Set<PosixFilePermission> OE = Set.of(OTHERS_EXECUTE);
|
||||
|
||||
// principals
|
||||
private static final UserPrincipal DUMMY_USER = ()->"defusr";
|
||||
private static final GroupPrincipal DUMMY_GROUP = ()->"defgrp";
|
||||
|
||||
// FS open options
|
||||
private static final Map<String, Object> ENV_DEFAULT = Collections.<String, Object>emptyMap();
|
||||
private static final Map<String, Object> ENV_POSIX = Map.of("enablePosixFileAttributes", true);
|
||||
|
||||
// misc
|
||||
private static final CopyOption[] COPY_ATTRIBUTES = {StandardCopyOption.COPY_ATTRIBUTES};
|
||||
private static final Map<String, ZipFileEntryInfo> ENTRIES = new HashMap<>();
|
||||
|
||||
private int entriesCreated;
|
||||
|
||||
static enum checkExpects {
|
||||
contentOnly,
|
||||
noPermDataInZip,
|
||||
permsInZip,
|
||||
permsPosix
|
||||
}
|
||||
|
||||
static class ZipFileEntryInfo {
|
||||
// permissions to set initially
|
||||
private final Set<PosixFilePermission> intialPerms;
|
||||
// permissions to set in a later call
|
||||
private final Set<PosixFilePermission> laterPerms;
|
||||
// permissions that should be effective in the zip file
|
||||
private final Set<PosixFilePermission> permsInZip;
|
||||
// permissions that should be returned by zipfs w/Posix support
|
||||
private final Set<PosixFilePermission> permsPosix;
|
||||
// entry is a directory
|
||||
private final boolean isDir;
|
||||
// need additional read flag in copy test
|
||||
private final boolean setReadFlag;
|
||||
|
||||
private ZipFileEntryInfo(Set<PosixFilePermission> initialPerms, Set<PosixFilePermission> laterPerms,
|
||||
Set<PosixFilePermission> permsInZip, Set<PosixFilePermission> permsZipPosix, boolean isDir, boolean setReadFlag)
|
||||
{
|
||||
this.intialPerms = initialPerms;
|
||||
this.laterPerms = laterPerms;
|
||||
this.permsInZip = permsInZip;
|
||||
this.permsPosix = permsZipPosix;
|
||||
this.isDir = isDir;
|
||||
this.setReadFlag = setReadFlag;
|
||||
}
|
||||
}
|
||||
|
||||
static class CopyVisitor extends SimpleFileVisitor<Path> {
|
||||
private Path from, to;
|
||||
private boolean copyPerms;
|
||||
|
||||
CopyVisitor(Path from, Path to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
CopyVisitor(Path from, Path to, boolean copyPerms) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.copyPerms = copyPerms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
FileVisitResult rc = super.preVisitDirectory(dir, attrs);
|
||||
Path target = to.resolve(from.relativize(dir).toString());
|
||||
if (!Files.exists(target)) {
|
||||
Files.copy(dir, target, COPY_ATTRIBUTES);
|
||||
if (copyPerms) {
|
||||
Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(dir));
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
FileVisitResult rc = super.visitFile(file, attrs);
|
||||
Path target = to.resolve(from.relativize(file).toString());
|
||||
Files.copy(file, target, COPY_ATTRIBUTES);
|
||||
if (copyPerms) {
|
||||
Files.setPosixFilePermissions(target, Files.getPosixFilePermissions(file));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
static class DeleteVisitor extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
FileVisitResult rc = super.postVisitDirectory(dir, exc);
|
||||
Files.delete(dir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
FileVisitResult rc = super.visitFile(file, attrs);
|
||||
Files.delete(file);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
static interface Executor {
|
||||
void doIt() throws IOException;
|
||||
}
|
||||
|
||||
static {
|
||||
ENTRIES.put("dir", new ZipFileEntryInfo(ALLPERMS, null, ALLPERMS, ALLPERMS, true, false));
|
||||
ENTRIES.put("uread", new ZipFileEntryInfo(UR, null, UR, UR, false, false));
|
||||
ENTRIES.put("uwrite", new ZipFileEntryInfo(UW, null, UW, UW, false, true));
|
||||
ENTRIES.put("uexec", new ZipFileEntryInfo(UE, null, UE, UE, false, true));
|
||||
ENTRIES.put("gread", new ZipFileEntryInfo(GR, null, GR, GR, false, true));
|
||||
ENTRIES.put("gwrite", new ZipFileEntryInfo(GW, null, GW, GW, false, true));
|
||||
ENTRIES.put("gexec", new ZipFileEntryInfo(GE, null, GE, GE, false, true));
|
||||
ENTRIES.put("oread", new ZipFileEntryInfo(OR, null, OR, OR, false, true));
|
||||
ENTRIES.put("owrite", new ZipFileEntryInfo(OW, null, OW, OW, false, true));
|
||||
ENTRIES.put("oexec", new ZipFileEntryInfo(OE, null, OE, OE, false, true));
|
||||
ENTRIES.put("emptyperms", new ZipFileEntryInfo(EMPTYPERMS, null, EMPTYPERMS, EMPTYPERMS, false, true));
|
||||
ENTRIES.put("noperms", new ZipFileEntryInfo(null, null, null, ALLPERMS, false, false));
|
||||
ENTRIES.put("permslater", new ZipFileEntryInfo(null, UR, UR, UR, false, false));
|
||||
}
|
||||
|
||||
private static String expectedDefaultOwner(Path zf) {
|
||||
try {
|
||||
try {
|
||||
PrivilegedExceptionAction<String> pa = ()->Files.getOwner(zf).getName();
|
||||
return AccessController.doPrivileged(pa);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
// if we can't get the owner of the file, we fall back to system property user.name
|
||||
PrivilegedAction<String> pa = ()->System.getProperty("user.name");
|
||||
return AccessController.doPrivileged(pa);
|
||||
}
|
||||
} catch (PrivilegedActionException | SecurityException e) {
|
||||
System.out.println("Caught " + e.getClass().getName() + "(" + e.getMessage() +
|
||||
") when running a privileged operation to get the default owner.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String expectedDefaultGroup(Path zf, String defaultOwner) {
|
||||
try {
|
||||
try {
|
||||
PosixFileAttributeView zfpv = Files.getFileAttributeView(zf, PosixFileAttributeView.class);
|
||||
if (zfpv == null) {
|
||||
return defaultOwner;
|
||||
}
|
||||
PrivilegedExceptionAction<String> pa = ()->zfpv.readAttributes().group().getName();
|
||||
return AccessController.doPrivileged(pa);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
return defaultOwner;
|
||||
}
|
||||
} catch (PrivilegedActionException | SecurityException e) {
|
||||
System.out.println("Caught an exception when running a privileged operation to get the default group.");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void putEntry(FileSystem fs, String name, ZipFileEntryInfo entry) throws IOException {
|
||||
if (entry.isDir) {
|
||||
if (entry.intialPerms == null) {
|
||||
Files.createDirectory(fs.getPath(name));
|
||||
} else {
|
||||
Files.createDirectory(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (entry.intialPerms == null) {
|
||||
Files.createFile(fs.getPath(name));
|
||||
} else {
|
||||
Files.createFile(fs.getPath(name), PosixFilePermissions.asFileAttribute(entry.intialPerms));
|
||||
}
|
||||
}
|
||||
if (entry.laterPerms != null) {
|
||||
Files.setAttribute(fs.getPath(name), "zip:permissions", entry.laterPerms);
|
||||
}
|
||||
entriesCreated++;
|
||||
}
|
||||
|
||||
private FileSystem createTestZipFile(Path zpath, Map<String, Object> env) throws IOException {
|
||||
if (Files.exists(zpath)) {
|
||||
System.out.println("Deleting old " + zpath + "...");
|
||||
Files.delete(zpath);
|
||||
}
|
||||
System.out.println("Creating " + zpath + "...");
|
||||
entriesCreated = 0;
|
||||
var opts = new HashMap<String, Object>();
|
||||
opts.putAll(env);
|
||||
opts.put("create", true);
|
||||
FileSystem fs = FileSystems.newFileSystem(zpath, opts);
|
||||
for (String name : ENTRIES.keySet()) {
|
||||
putEntry(fs, name, ENTRIES.get(name));
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
private FileSystem createEmptyZipFile(Path zpath, Map<String, Object> env) throws IOException {
|
||||
if (Files.exists(zpath)) {
|
||||
System.out.println("Deleting old " + zpath + "...");
|
||||
Files.delete(zpath);
|
||||
}
|
||||
System.out.println("Creating " + zpath + "...");
|
||||
var opts = new HashMap<String, Object>();
|
||||
opts.putAll(env);
|
||||
opts.put("create", true);
|
||||
return FileSystems.newFileSystem(zpath, opts);
|
||||
}
|
||||
|
||||
private void delTree(Path p) throws IOException {
|
||||
if (Files.exists(p)) {
|
||||
Files.walkFileTree(p, new DeleteVisitor());
|
||||
}
|
||||
}
|
||||
|
||||
private void addOwnerRead(Path root) throws IOException {
|
||||
for (String name : ENTRIES.keySet()) {
|
||||
ZipFileEntryInfo ei = ENTRIES.get(name);
|
||||
if (!ei.setReadFlag) {
|
||||
continue;
|
||||
}
|
||||
Path setReadOn = root.resolve(name);
|
||||
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(setReadOn);
|
||||
perms.add(OWNER_READ);
|
||||
Files.setPosixFilePermissions(setReadOn, perms);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeOwnerRead(Path root) throws IOException {
|
||||
for (String name : ENTRIES.keySet()) {
|
||||
ZipFileEntryInfo ei = ENTRIES.get(name);
|
||||
if (!ei.setReadFlag) {
|
||||
continue;
|
||||
}
|
||||
Path removeReadFrom = root.resolve(name);
|
||||
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(removeReadFrom);
|
||||
perms.remove(OWNER_READ);
|
||||
Files.setPosixFilePermissions(removeReadFrom, perms);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void checkEntry(Path file, checkExpects expected) {
|
||||
System.out.println("Checking " + file + "...");
|
||||
String name = file.getFileName().toString();
|
||||
ZipFileEntryInfo ei = ENTRIES.get(name);
|
||||
assertNotNull(ei, "Found unknown entry " + name + ".");
|
||||
BasicFileAttributes attrs = null;
|
||||
if (expected == checkExpects.permsPosix) {
|
||||
try {
|
||||
attrs = Files.readAttributes(file, PosixFileAttributes.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail("Caught IOException reading file attributes (posix) for " + name + ": " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
attrs = Files.readAttributes(file, BasicFileAttributes.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail("Caught IOException reading file attributes (basic) " + name + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
assertEquals(Files.isDirectory(file), ei.isDir, "Unexpected directory attribute for:" + System.lineSeparator() + attrs);
|
||||
|
||||
if (expected == checkExpects.contentOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<PosixFilePermission> permissions;
|
||||
if (expected == checkExpects.permsPosix) {
|
||||
try {
|
||||
permissions = Files.getPosixFilePermissions(file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
|
||||
return;
|
||||
}
|
||||
comparePermissions(ei.permsPosix, permissions);
|
||||
} else if (expected == checkExpects.permsInZip || expected == checkExpects.noPermDataInZip) {
|
||||
try {
|
||||
permissions = (Set<PosixFilePermission>)Files.getAttribute(file, "zip:permissions");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
fail("Caught IOException getting permission attribute for:" + System.lineSeparator() + attrs);
|
||||
return;
|
||||
}
|
||||
comparePermissions(expected == checkExpects.noPermDataInZip ? null : ei.permsInZip, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
private void doCheckEntries(Path path, checkExpects expected) throws IOException {
|
||||
AtomicInteger entries = new AtomicInteger();
|
||||
|
||||
try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
|
||||
paths.forEach(file -> {
|
||||
entries.getAndIncrement();
|
||||
checkEntry(file, expected);
|
||||
});
|
||||
}
|
||||
System.out.println("Number of entries: " + entries.get() + ".");
|
||||
assertEquals(entries.get(), entriesCreated, "File contained wrong number of entries.");
|
||||
}
|
||||
|
||||
private void checkEntries(FileSystem fs, checkExpects expected) throws IOException {
|
||||
System.out.println("Checking permissions on file system " + fs + "...");
|
||||
doCheckEntries(fs.getPath("/"), expected);
|
||||
}
|
||||
|
||||
private void checkEntries(Path path, checkExpects expected) throws IOException {
|
||||
System.out.println("Checking permissions on path " + path + "...");
|
||||
doCheckEntries(path, expected);
|
||||
}
|
||||
|
||||
private boolean throwsUOE(Executor e) throws IOException {
|
||||
try {
|
||||
e.doIt();
|
||||
return false;
|
||||
} catch (UnsupportedOperationException exc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void comparePermissions(Set<PosixFilePermission> expected, Set<PosixFilePermission> actual) {
|
||||
if (expected == null) {
|
||||
assertNull(actual, "Permissions are not null");
|
||||
} else {
|
||||
assertNotNull(actual, "Permissions are null.");
|
||||
assertEquals(actual.size(), expected.size(), "Unexpected number of permissions (" +
|
||||
actual.size() + " received vs " + expected.size() + " expected).");
|
||||
for (PosixFilePermission p : expected) {
|
||||
assertTrue(actual.contains(p), "Posix permission " + p + " missing.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests whether the entries in a zip file created w/o
|
||||
* Posix support are correct.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testDefault() throws IOException {
|
||||
// create zip file using zipfs with default options
|
||||
createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
|
||||
// check entries on zipfs with default options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
|
||||
checkEntries(zip, checkExpects.permsInZip);
|
||||
}
|
||||
// check entries on zipfs with posix options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
|
||||
checkEntries(zip, checkExpects.permsPosix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests whether the entries in a zip file created w/
|
||||
* Posix support are correct.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testPosix() throws IOException {
|
||||
// create zip file using zipfs with posix option
|
||||
createTestZipFile(ZIP_FILE, ENV_POSIX).close();
|
||||
// check entries on zipfs with default options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_DEFAULT)) {
|
||||
checkEntries(zip, checkExpects.permsInZip);
|
||||
}
|
||||
// check entries on zipfs with posix options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
|
||||
checkEntries(zip, checkExpects.permsPosix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests whether the entries in a zip file copied from another
|
||||
* are correct.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testCopy() throws IOException {
|
||||
// copy zip to zip with default options
|
||||
try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT);
|
||||
FileSystem zipOut = createEmptyZipFile(ZIP_FILE_COPY, ENV_DEFAULT)) {
|
||||
Path from = zipIn.getPath("/");
|
||||
Files.walkFileTree(from, new CopyVisitor(from, zipOut.getPath("/")));
|
||||
}
|
||||
// check entries on copied zipfs with default options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
|
||||
checkEntries(zip, checkExpects.permsInZip);
|
||||
}
|
||||
// check entries on copied zipfs with posix options
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
|
||||
checkEntries(zip, checkExpects.permsPosix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests whether the entries of a zip file look correct after extraction
|
||||
* and re-packing. When not using zipfs with Posix support, we expect the
|
||||
* effective permissions in the resulting zip file to be empty.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testUnzipDefault() throws IOException {
|
||||
delTree(UNZIP_DIR);
|
||||
Files.createDirectory(UNZIP_DIR);
|
||||
|
||||
try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
|
||||
Path from = srcZip.getPath("/");
|
||||
Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR));
|
||||
}
|
||||
|
||||
// we just check that the entries got extracted to file system
|
||||
checkEntries(UNZIP_DIR, checkExpects.contentOnly);
|
||||
|
||||
// the target zip file is opened with Posix support
|
||||
// but we expect no permission data to be copied using the default copy method
|
||||
try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
|
||||
Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/")));
|
||||
}
|
||||
|
||||
// check entries on copied zipfs - no permission data should exist
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_DEFAULT)) {
|
||||
checkEntries(zip, checkExpects.noPermDataInZip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests whether the entries of a zip file look correct after extraction
|
||||
* and re-packing. If the default file system supports Posix, we test whether we
|
||||
* correctly carry the Posix permissions. Otherwise there's not much to test in
|
||||
* this method.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testUnzipPosix() throws IOException {
|
||||
delTree(UNZIP_DIR);
|
||||
Files.createDirectory(UNZIP_DIR);
|
||||
|
||||
try {
|
||||
Files.getPosixFilePermissions(UNZIP_DIR);
|
||||
} catch (Exception e) {
|
||||
// if we run into any exception here, be it because of the fact that the file system
|
||||
// is not Posix or if we have insufficient security permissions, we can't do this test.
|
||||
System.out.println("This can't be tested here because of " + e);
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileSystem srcZip = createTestZipFile(ZIP_FILE, ENV_POSIX)) {
|
||||
Path from = srcZip.getPath("/");
|
||||
// copy permissions as well
|
||||
Files.walkFileTree(from, new CopyVisitor(from, UNZIP_DIR, true));
|
||||
}
|
||||
|
||||
// permissions should have been propagated to file system
|
||||
checkEntries(UNZIP_DIR, checkExpects.permsPosix);
|
||||
|
||||
try (FileSystem tgtZip = createEmptyZipFile(ZIP_FILE_COPY, ENV_POSIX)) {
|
||||
// Make some files owner readable to be able to copy them into the zipfs
|
||||
addOwnerRead(UNZIP_DIR);
|
||||
|
||||
// copy permissions as well
|
||||
Files.walkFileTree(UNZIP_DIR, new CopyVisitor(UNZIP_DIR, tgtZip.getPath("/"), true));
|
||||
|
||||
// Fix back all the files in the target zip file which have been made readable before
|
||||
removeOwnerRead(tgtZip.getPath("/"));
|
||||
}
|
||||
|
||||
// check entries on copied zipfs - permission data should have been propagated
|
||||
try (FileSystem zip = FileSystems.newFileSystem(ZIP_FILE_COPY, ENV_POSIX)) {
|
||||
checkEntries(zip, checkExpects.permsPosix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests POSIX default behavior.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testPosixDefaults() throws IOException {
|
||||
// test with posix = false, expect UnsupportedOperationException
|
||||
try (FileSystem zipIn = createTestZipFile(ZIP_FILE, ENV_DEFAULT)) {
|
||||
var entry = zipIn.getPath("/dir");
|
||||
assertTrue(throwsUOE(()->Files.getPosixFilePermissions(entry)));
|
||||
assertTrue(throwsUOE(()->Files.setPosixFilePermissions(entry, UW)));
|
||||
assertTrue(throwsUOE(()->Files.getOwner(entry)));
|
||||
assertTrue(throwsUOE(()->Files.setOwner(entry, DUMMY_USER)));
|
||||
assertTrue(throwsUOE(()->Files.getFileAttributeView(entry, PosixFileAttributeView.class)));
|
||||
}
|
||||
|
||||
// test with posix = true -> default values
|
||||
try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, ENV_POSIX)) {
|
||||
String defaultOwner = expectedDefaultOwner(ZIP_FILE);
|
||||
String defaultGroup = expectedDefaultGroup(ZIP_FILE, defaultOwner);
|
||||
var entry = zipIn.getPath("/noperms");
|
||||
comparePermissions(ALLPERMS, Files.getPosixFilePermissions(entry));
|
||||
var owner = Files.getOwner(entry);
|
||||
assertNotNull(owner, "owner should not be null");
|
||||
if (defaultOwner != null) {
|
||||
assertEquals(owner.getName(), defaultOwner);
|
||||
}
|
||||
Files.setOwner(entry, DUMMY_USER);
|
||||
assertEquals(Files.getOwner(entry), DUMMY_USER);
|
||||
var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
|
||||
var group = view.readAttributes().group();
|
||||
assertNotNull(group, "group must not be null");
|
||||
if (defaultGroup != null) {
|
||||
assertEquals(group.getName(), defaultGroup);
|
||||
}
|
||||
view.setGroup(DUMMY_GROUP);
|
||||
assertEquals(view.readAttributes().group(), DUMMY_GROUP);
|
||||
entry = zipIn.getPath("/uexec");
|
||||
Files.setPosixFilePermissions(entry, GR); // will be persisted
|
||||
comparePermissions(GR, Files.getPosixFilePermissions(entry));
|
||||
}
|
||||
|
||||
// test with posix = true + custom defaults of type String
|
||||
try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
|
||||
"defaultOwner", "auser", "defaultGroup", "agroup", "defaultPermissions", "r--------")))
|
||||
{
|
||||
var entry = zipIn.getPath("/noperms");
|
||||
comparePermissions(UR, Files.getPosixFilePermissions(entry));
|
||||
assertEquals(Files.getOwner(entry).getName(), "auser");
|
||||
var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
|
||||
assertEquals(view.readAttributes().group().getName(), "agroup");
|
||||
// check if the change to permissions of /uexec was persisted
|
||||
comparePermissions(GR, Files.getPosixFilePermissions(zipIn.getPath("/uexec")));
|
||||
}
|
||||
|
||||
// test with posix = true + custom defaults as Objects
|
||||
try (FileSystem zipIn = FileSystems.newFileSystem(ZIP_FILE, Map.of("enablePosixFileAttributes", true,
|
||||
"defaultOwner", DUMMY_USER, "defaultGroup", DUMMY_GROUP, "defaultPermissions", UR)))
|
||||
{
|
||||
var entry = zipIn.getPath("/noperms");
|
||||
comparePermissions(UR, Files.getPosixFilePermissions(entry));
|
||||
assertEquals(Files.getOwner(entry), DUMMY_USER);
|
||||
var view = Files.getFileAttributeView(entry, PosixFileAttributeView.class);
|
||||
assertEquals(view.readAttributes().group(), DUMMY_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity check to test whether the zip file can be unzipped with the java.util.zip API.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testUnzipWithJavaUtilZip() throws IOException {
|
||||
createTestZipFile(ZIP_FILE, ENV_DEFAULT).close();
|
||||
delTree(UNZIP_DIR);
|
||||
Files.createDirectory(UNZIP_DIR);
|
||||
File targetDir = UNZIP_DIR.toFile();
|
||||
try (ZipFile zf = new ZipFile(ZIP_FILE.toFile())) {
|
||||
Enumeration<? extends ZipEntry> zenum = zf.entries();
|
||||
while (zenum.hasMoreElements()) {
|
||||
ZipEntry ze = zenum.nextElement();
|
||||
File target = new File(targetDir + File.separator + ze.getName());
|
||||
if (ze.isDirectory()) {
|
||||
target.mkdir();
|
||||
continue;
|
||||
}
|
||||
try (InputStream is = zf.getInputStream(ze);
|
||||
FileOutputStream fos = new FileOutputStream(target))
|
||||
{
|
||||
while (is.available() > 0) {
|
||||
fos.write(is.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity check to test whether a jar file created with zipfs can be
|
||||
* extracted with the java.util.jar API.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void testJarFile() throws IOException {
|
||||
// create jar file using zipfs with default options
|
||||
createTestZipFile(JAR_FILE, ENV_DEFAULT).close();
|
||||
|
||||
// extract it using java.util.jar.JarFile
|
||||
delTree(UNZIP_DIR);
|
||||
Files.createDirectory(UNZIP_DIR);
|
||||
File targetDir = UNZIP_DIR.toFile();
|
||||
try (JarFile jf = new JarFile(ZIP_FILE.toFile())) {
|
||||
Enumeration<? extends JarEntry> zenum = jf.entries();
|
||||
while (zenum.hasMoreElements()) {
|
||||
JarEntry ze = zenum.nextElement();
|
||||
File target = new File(targetDir + File.separator + ze.getName());
|
||||
if (ze.isDirectory()) {
|
||||
target.mkdir();
|
||||
continue;
|
||||
}
|
||||
try (InputStream is = jf.getInputStream(ze);
|
||||
FileOutputStream fos = new FileOutputStream(target))
|
||||
{
|
||||
while (is.available() > 0) {
|
||||
fos.write(is.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extract it using the jar tool
|
||||
delTree(UNZIP_DIR);
|
||||
System.out.println("jar xvf " + JAR_FILE);
|
||||
|
||||
// the run method catches IOExceptions, we need to expose them
|
||||
int rc = JAR_TOOL.run(System.out, System.err, "xvf", JAR_FILE.toString());
|
||||
assertEquals(rc, 0, "Return code of jar call is " + rc + " but expected 0");
|
||||
}
|
||||
}
|
8
test/jdk/jdk/nio/zipfs/test.policy.posix
Normal file
8
test/jdk/jdk/nio/zipfs/test.policy.posix
Normal file
@ -0,0 +1,8 @@
|
||||
grant {
|
||||
permission java.io.FilePermission "<<ALL FILES>>","read,write,delete";
|
||||
permission java.util.PropertyPermission "sun.tools.jar.useExtractionTime","read";
|
||||
permission java.util.PropertyPermission "test.jdk","read";
|
||||
permission java.util.PropertyPermission "test.src","read";
|
||||
permission java.util.PropertyPermission "user.dir","read";
|
||||
permission java.lang.RuntimePermission "accessClassInPackage.jdk.internal.module";
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user