mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-24 06:40:05 +00:00
1182 lines
36 KiB
Java
1182 lines
36 KiB
Java
/*
|
|
* Copyright (c) 2008, 2009, 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 sun.nio.fs;
|
|
|
|
import java.nio.*;
|
|
import java.nio.file.*;
|
|
import java.nio.file.attribute.*;
|
|
import java.nio.charset.*;
|
|
import java.nio.channels.*;
|
|
import java.security.AccessController;
|
|
import java.io.*;
|
|
import java.net.URI;
|
|
import java.util.*;
|
|
import java.lang.ref.SoftReference;
|
|
import sun.security.util.SecurityConstants;
|
|
|
|
import static sun.nio.fs.UnixNativeDispatcher.*;
|
|
import static sun.nio.fs.UnixConstants.*;
|
|
|
|
/**
|
|
* Solaris/Linux implementation of java.nio.file.Path
|
|
*/
|
|
|
|
class UnixPath
|
|
extends AbstractPath
|
|
{
|
|
private static ThreadLocal<SoftReference<CharsetEncoder>> encoder =
|
|
new ThreadLocal<SoftReference<CharsetEncoder>>();
|
|
|
|
// FIXME - eliminate this reference to reduce space
|
|
private final UnixFileSystem fs;
|
|
|
|
// internal representation
|
|
private final byte[] path;
|
|
|
|
// String representation (created lazily)
|
|
private volatile String stringValue;
|
|
|
|
// cached hashcode (created lazily, no need to be volatile)
|
|
private int hash;
|
|
|
|
// array of offsets of elements in path (created lazily)
|
|
private volatile int[] offsets;
|
|
|
|
UnixPath(UnixFileSystem fs, byte[] path) {
|
|
this.fs = fs;
|
|
this.path = path;
|
|
}
|
|
|
|
UnixPath(UnixFileSystem fs, String input) {
|
|
// removes redundant slashes and checks for invalid characters
|
|
this(fs, encode(normalizeAndCheck(input)));
|
|
}
|
|
|
|
// package-private
|
|
// removes redundant slashes and check input for invalid characters
|
|
static String normalizeAndCheck(String input) {
|
|
int n = input.length();
|
|
if (n == 0)
|
|
throw new InvalidPathException(input, "Path is empty");
|
|
char prevChar = 0;
|
|
for (int i=0; i < n; i++) {
|
|
char c = input.charAt(i);
|
|
if ((c == '/') && (prevChar == '/'))
|
|
return normalize(input, n, i - 1);
|
|
checkNotNul(input, c);
|
|
prevChar = c;
|
|
}
|
|
if (prevChar == '/')
|
|
return normalize(input, n, n - 1);
|
|
return input;
|
|
}
|
|
|
|
private static void checkNotNul(String input, char c) {
|
|
if (c == '\u0000')
|
|
throw new InvalidPathException(input, "Nul character not allowed");
|
|
}
|
|
|
|
private static String normalize(String input, int len, int off) {
|
|
if (len == 0)
|
|
return input;
|
|
int n = len;
|
|
while ((n > 0) && (input.charAt(n - 1) == '/')) n--;
|
|
if (n == 0)
|
|
return "/";
|
|
StringBuilder sb = new StringBuilder(input.length());
|
|
if (off > 0)
|
|
sb.append(input.substring(0, off));
|
|
char prevChar = 0;
|
|
for (int i=off; i < n; i++) {
|
|
char c = input.charAt(i);
|
|
if ((c == '/') && (prevChar == '/'))
|
|
continue;
|
|
checkNotNul(input, c);
|
|
sb.append(c);
|
|
prevChar = c;
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
// encodes the given path-string into a sequence of bytes
|
|
private static byte[] encode(String input) {
|
|
SoftReference<CharsetEncoder> ref = encoder.get();
|
|
CharsetEncoder ce = (ref != null) ? ref.get() : null;
|
|
if (ce == null) {
|
|
ce = Charset.defaultCharset().newEncoder()
|
|
.onMalformedInput(CodingErrorAction.REPORT)
|
|
.onUnmappableCharacter(CodingErrorAction.REPORT);
|
|
encoder.set(new SoftReference<CharsetEncoder>(ce));
|
|
}
|
|
|
|
char[] ca = input.toCharArray();
|
|
|
|
// size output buffer for worse-case size
|
|
byte[] ba = new byte[(int)(ca.length * (double)ce.maxBytesPerChar())];
|
|
|
|
// encode
|
|
ByteBuffer bb = ByteBuffer.wrap(ba);
|
|
CharBuffer cb = CharBuffer.wrap(ca);
|
|
ce.reset();
|
|
CoderResult cr = ce.encode(cb, bb, true);
|
|
boolean error;
|
|
if (!cr.isUnderflow()) {
|
|
error = true;
|
|
} else {
|
|
cr = ce.flush(bb);
|
|
error = !cr.isUnderflow();
|
|
}
|
|
if (error) {
|
|
throw new InvalidPathException(input,
|
|
"Malformed input or input contains unmappable chacraters");
|
|
}
|
|
|
|
// trim result to actual length if required
|
|
int len = bb.position();
|
|
if (len != ba.length)
|
|
ba = Arrays.copyOf(ba, len);
|
|
|
|
return ba;
|
|
}
|
|
|
|
// package-private
|
|
byte[] asByteArray() {
|
|
return path;
|
|
}
|
|
|
|
// use this path when making system/library calls
|
|
byte[] getByteArrayForSysCalls() {
|
|
// resolve against default directory if required (chdir allowed or
|
|
// file system default directory is not working directory)
|
|
if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
|
|
return resolve(getFileSystem().defaultDirectory(), path);
|
|
} else {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
// use this message when throwing exceptions
|
|
String getPathForExecptionMessage() {
|
|
return toString();
|
|
}
|
|
|
|
// use this path for permission checks
|
|
String getPathForPermissionCheck() {
|
|
if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
|
|
return new String(getByteArrayForSysCalls());
|
|
} else {
|
|
return toString();
|
|
}
|
|
}
|
|
|
|
// Checks that the given file is a UnixPath
|
|
private UnixPath checkPath(FileRef obj) {
|
|
if (obj == null)
|
|
throw new NullPointerException();
|
|
if (!(obj instanceof UnixPath))
|
|
throw new ProviderMismatchException();
|
|
return (UnixPath)obj;
|
|
}
|
|
|
|
// create offset list if not already created
|
|
private void initOffsets() {
|
|
if (offsets == null) {
|
|
int count, index;
|
|
|
|
// count names
|
|
count = 0;
|
|
index = 0;
|
|
while (index < path.length) {
|
|
byte c = path[index++];
|
|
if (c != '/') {
|
|
count++;
|
|
while (index < path.length && path[index] != '/')
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// populate offsets
|
|
int[] result = new int[count];
|
|
count = 0;
|
|
index = 0;
|
|
while (index < path.length) {
|
|
byte c = path[index];
|
|
if (c == '/') {
|
|
index++;
|
|
} else {
|
|
result[count++] = index++;
|
|
while (index < path.length && path[index] != '/')
|
|
index++;
|
|
}
|
|
}
|
|
synchronized (this) {
|
|
if (offsets == null)
|
|
offsets = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public UnixFileSystem getFileSystem() {
|
|
return fs;
|
|
}
|
|
|
|
@Override
|
|
public UnixPath getRoot() {
|
|
if (path[0] == '/') {
|
|
return getFileSystem().rootDirectory();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public UnixPath getName() {
|
|
initOffsets();
|
|
|
|
int count = offsets.length;
|
|
if (count == 0)
|
|
return null; // no elements so no name
|
|
|
|
if (count == 1 && path[0] != '/')
|
|
return this;
|
|
|
|
int lastOffset = offsets[count-1];
|
|
int len = path.length - lastOffset;
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(path, lastOffset, result, 0, len);
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public UnixPath getParent() {
|
|
initOffsets();
|
|
|
|
int count = offsets.length;
|
|
if (count == 0) {
|
|
// no elements so no parent
|
|
return null;
|
|
}
|
|
int len = offsets[count-1] - 1;
|
|
if (len <= 0) {
|
|
// parent is root only (may be null)
|
|
return getRoot();
|
|
}
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(path, 0, result, 0, len);
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public int getNameCount() {
|
|
initOffsets();
|
|
return offsets.length;
|
|
}
|
|
|
|
@Override
|
|
public UnixPath getName(int index) {
|
|
initOffsets();
|
|
if (index < 0)
|
|
throw new IllegalArgumentException();
|
|
if (index >= offsets.length)
|
|
throw new IllegalArgumentException();
|
|
|
|
int begin = offsets[index];
|
|
int len;
|
|
if (index == (offsets.length-1)) {
|
|
len = path.length - begin;
|
|
} else {
|
|
len = offsets[index+1] - begin - 1;
|
|
}
|
|
|
|
// construct result
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(path, begin, result, 0, len);
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public UnixPath subpath(int beginIndex, int endIndex) {
|
|
initOffsets();
|
|
|
|
if (beginIndex < 0)
|
|
throw new IllegalArgumentException();
|
|
if (beginIndex >= offsets.length)
|
|
throw new IllegalArgumentException();
|
|
if (endIndex > offsets.length)
|
|
throw new IllegalArgumentException();
|
|
if (beginIndex >= endIndex) {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
|
|
// starting offset and length
|
|
int begin = offsets[beginIndex];
|
|
int len;
|
|
if (endIndex == offsets.length) {
|
|
len = path.length - begin;
|
|
} else {
|
|
len = offsets[endIndex] - begin - 1;
|
|
}
|
|
|
|
// construct result
|
|
byte[] result = new byte[len];
|
|
System.arraycopy(path, begin, result, 0, len);
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public boolean isAbsolute() {
|
|
return (path[0] == '/');
|
|
}
|
|
|
|
// Resolve child against given base
|
|
private static byte[] resolve(byte[] base, byte[] child) {
|
|
if (child[0] == '/')
|
|
return child;
|
|
byte[] result;
|
|
if (base.length == 1 && base[0] == '/') {
|
|
result = new byte[child.length + 1];
|
|
result[0] = '/';
|
|
System.arraycopy(child, 0, result, 1, child.length);
|
|
} else {
|
|
result = new byte[base.length + 1 + child.length];
|
|
System.arraycopy(base, 0, result, 0, base.length);
|
|
result[base.length] = '/';
|
|
System.arraycopy(child, 0, result, base.length+1, child.length);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public UnixPath resolve(Path obj) {
|
|
if (obj == null)
|
|
return this;
|
|
byte[] other = checkPath(obj).path;
|
|
if (other[0] == '/')
|
|
return ((UnixPath)obj);
|
|
byte[] result = resolve(path, other);
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public UnixPath resolve(String other) {
|
|
return resolve(new UnixPath(getFileSystem(), other));
|
|
}
|
|
|
|
UnixPath resolve(byte[] other) {
|
|
return resolve(new UnixPath(getFileSystem(), other));
|
|
}
|
|
|
|
@Override
|
|
public UnixPath relativize(Path obj) {
|
|
UnixPath other = checkPath(obj);
|
|
if (other.equals(this))
|
|
return null;
|
|
|
|
// can only relativize paths of the same type
|
|
if (this.isAbsolute() != other.isAbsolute())
|
|
throw new IllegalArgumentException("'other' is different type of Path");
|
|
|
|
int bn = this.getNameCount();
|
|
int cn = other.getNameCount();
|
|
|
|
// skip matching names
|
|
int n = (bn > cn) ? cn : bn;
|
|
int i = 0;
|
|
while (i < n) {
|
|
if (!this.getName(i).equals(other.getName(i)))
|
|
break;
|
|
i++;
|
|
}
|
|
|
|
int dotdots = bn - i;
|
|
if (i < cn) {
|
|
// remaining name components in other
|
|
UnixPath remainder = other.subpath(i, cn);
|
|
if (dotdots == 0)
|
|
return remainder;
|
|
|
|
// result is a "../" for each remaining name in base
|
|
// followed by the remaining names in other
|
|
byte[] result = new byte[dotdots*3 + remainder.path.length];
|
|
int pos = 0;
|
|
while (dotdots > 0) {
|
|
result[pos++] = (byte)'.';
|
|
result[pos++] = (byte)'.';
|
|
result[pos++] = (byte)'/';
|
|
dotdots--;
|
|
}
|
|
System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);
|
|
return new UnixPath(getFileSystem(), result);
|
|
} else {
|
|
// no remaining names in other so result is simply a sequence of ".."
|
|
byte[] result = new byte[dotdots*3 - 1];
|
|
int pos = 0;
|
|
while (dotdots > 0) {
|
|
result[pos++] = (byte)'.';
|
|
result[pos++] = (byte)'.';
|
|
// no tailing slash at the end
|
|
if (dotdots > 1)
|
|
result[pos++] = (byte)'/';
|
|
dotdots--;
|
|
}
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Path normalize() {
|
|
final int count = getNameCount();
|
|
if (count == 0)
|
|
return this;
|
|
|
|
boolean[] ignore = new boolean[count]; // true => ignore name
|
|
int[] size = new int[count]; // length of name
|
|
int remaining = count; // number of names remaining
|
|
boolean hasDotDot = false; // has at least one ..
|
|
boolean isAbsolute = path[0] == '/';
|
|
|
|
// first pass:
|
|
// 1. compute length of names
|
|
// 2. mark all occurences of "." to ignore
|
|
// 3. and look for any occurences of ".."
|
|
for (int i=0; i<count; i++) {
|
|
int begin = offsets[i];
|
|
int len;
|
|
if (i == (offsets.length-1)) {
|
|
len = path.length - begin;
|
|
} else {
|
|
len = offsets[i+1] - begin - 1;
|
|
}
|
|
size[i] = len;
|
|
|
|
if (path[begin] == '.') {
|
|
if (len == 1) {
|
|
ignore[i] = true; // ignore "."
|
|
remaining--;
|
|
}
|
|
else {
|
|
if (path[begin+1] == '.') // ".." found
|
|
hasDotDot = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// multiple passes to eliminate all occurences of name/..
|
|
if (hasDotDot) {
|
|
int prevRemaining;
|
|
do {
|
|
prevRemaining = remaining;
|
|
int prevName = -1;
|
|
for (int i=0; i<count; i++) {
|
|
if (ignore[i])
|
|
continue;
|
|
|
|
// not a ".."
|
|
if (size[i] != 2) {
|
|
prevName = i;
|
|
continue;
|
|
}
|
|
|
|
int begin = offsets[i];
|
|
if (path[begin] != '.' || path[begin+1] != '.') {
|
|
prevName = i;
|
|
continue;
|
|
}
|
|
|
|
// ".." found
|
|
if (prevName >= 0) {
|
|
// name/<ignored>/.. found so mark name and ".." to be
|
|
// ignored
|
|
ignore[prevName] = true;
|
|
ignore[i] = true;
|
|
remaining = remaining - 2;
|
|
prevName = -1;
|
|
} else {
|
|
// Case: /<ignored>/.. so mark ".." as ignored
|
|
if (isAbsolute) {
|
|
boolean hasPrevious = false;
|
|
for (int j=0; j<i; j++) {
|
|
if (!ignore[j]) {
|
|
hasPrevious = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!hasPrevious) {
|
|
// all proceeding names are ignored
|
|
ignore[i] = true;
|
|
remaining--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (prevRemaining > remaining);
|
|
}
|
|
|
|
// no redundant names
|
|
if (remaining == count)
|
|
return this;
|
|
|
|
// corner case - all names removed
|
|
if (remaining == 0) {
|
|
return isAbsolute ? getFileSystem().rootDirectory() : null;
|
|
}
|
|
|
|
// compute length of result
|
|
int len = remaining - 1;
|
|
if (isAbsolute)
|
|
len++;
|
|
|
|
for (int i=0; i<count; i++) {
|
|
if (!ignore[i])
|
|
len += size[i];
|
|
}
|
|
byte[] result = new byte[len];
|
|
|
|
// copy names into result
|
|
int pos = 0;
|
|
if (isAbsolute)
|
|
result[pos++] = '/';
|
|
for (int i=0; i<count; i++) {
|
|
if (!ignore[i]) {
|
|
System.arraycopy(path, offsets[i], result, pos, size[i]);
|
|
pos += size[i];
|
|
if (--remaining > 0) {
|
|
result[pos++] = '/';
|
|
}
|
|
}
|
|
}
|
|
return new UnixPath(getFileSystem(), result);
|
|
}
|
|
|
|
@Override
|
|
public boolean startsWith(Path other) {
|
|
UnixPath that = checkPath(other);
|
|
|
|
// other path is longer
|
|
if (that.path.length > path.length)
|
|
return false;
|
|
|
|
int thisOffsetCount = getNameCount();
|
|
int thatOffsetCount = that.getNameCount();
|
|
|
|
// other path has no name elements
|
|
if (thatOffsetCount == 0 && this.isAbsolute())
|
|
return true;
|
|
|
|
// given path has more elements that this path
|
|
if (thatOffsetCount > thisOffsetCount)
|
|
return false;
|
|
|
|
// same number of elements so must be exact match
|
|
if ((thatOffsetCount == thisOffsetCount) &&
|
|
(path.length != that.path.length)) {
|
|
return false;
|
|
}
|
|
|
|
// check offsets of elements match
|
|
for (int i=0; i<thatOffsetCount; i++) {
|
|
Integer o1 = offsets[i];
|
|
Integer o2 = that.offsets[i];
|
|
if (!o1.equals(o2))
|
|
return false;
|
|
}
|
|
|
|
// offsets match so need to compare bytes
|
|
int i=0;
|
|
while (i < that.path.length) {
|
|
if (this.path[i] != that.path[i])
|
|
return false;
|
|
i++;
|
|
}
|
|
|
|
// final check that match is on name boundary
|
|
if (i < path.length && this.path[i] != '/')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean endsWith(Path other) {
|
|
UnixPath that = checkPath(other);
|
|
|
|
int thisLen = path.length;
|
|
int thatLen = that.path.length;
|
|
|
|
// other path is longer
|
|
if (thatLen > thisLen)
|
|
return false;
|
|
|
|
// other path is absolute so this path must be absolute
|
|
if (that.isAbsolute() && !this.isAbsolute())
|
|
return false;
|
|
|
|
int thisOffsetCount = getNameCount();
|
|
int thatOffsetCount = that.getNameCount();
|
|
|
|
// given path has more elements that this path
|
|
if (thatOffsetCount > thisOffsetCount) {
|
|
return false;
|
|
} else {
|
|
// same number of elements
|
|
if (thatOffsetCount == thisOffsetCount) {
|
|
if (thisOffsetCount == 0)
|
|
return true;
|
|
int expectedLen = thisLen;
|
|
if (this.isAbsolute() && !that.isAbsolute())
|
|
expectedLen--;
|
|
if (thatLen != expectedLen)
|
|
return false;
|
|
} else {
|
|
// this path has more elements so given path must be relative
|
|
if (that.isAbsolute())
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// compare bytes
|
|
int thisPos = offsets[thisOffsetCount - thatOffsetCount];
|
|
int thatPos = that.offsets[0];
|
|
if ((thatLen - thatPos) != (thisLen - thisPos))
|
|
return false;
|
|
while (thatPos < thatLen) {
|
|
if (this.path[thisPos++] != that.path[thatPos++])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(Path other) {
|
|
int len1 = path.length;
|
|
int len2 = ((UnixPath) other).path.length;
|
|
|
|
int n = Math.min(len1, len2);
|
|
byte v1[] = path;
|
|
byte v2[] = ((UnixPath) other).path;
|
|
|
|
int k = 0;
|
|
while (k < n) {
|
|
int c1 = v1[k] & 0xff;
|
|
int c2 = v2[k] & 0xff;
|
|
if (c1 != c2) {
|
|
return c1 - c2;
|
|
}
|
|
k++;
|
|
}
|
|
return len1 - len2;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object ob) {
|
|
if ((ob != null) && (ob instanceof UnixPath)) {
|
|
return compareTo((Path)ob) == 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
// OK if two or more threads compute hash
|
|
int h = hash;
|
|
if (h == 0) {
|
|
for (int i = 0; i< path.length; i++) {
|
|
h = 31*h + (path[i] & 0xff);
|
|
}
|
|
hash = h;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
// OK if two or more threads create a String
|
|
if (stringValue == null)
|
|
stringValue = new String(path); // platform encoding
|
|
return stringValue;
|
|
}
|
|
|
|
@Override
|
|
public Iterator<Path> iterator() {
|
|
initOffsets();
|
|
return new Iterator<Path>() {
|
|
int i = 0;
|
|
@Override
|
|
public boolean hasNext() {
|
|
return (i < offsets.length);
|
|
}
|
|
@Override
|
|
public Path next() {
|
|
if (i < offsets.length) {
|
|
Path result = getName(i);
|
|
i++;
|
|
return result;
|
|
} else {
|
|
throw new NoSuchElementException();
|
|
}
|
|
}
|
|
@Override
|
|
public void remove() {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
}
|
|
|
|
// -- file operations --
|
|
|
|
// package-private
|
|
int openForAttributeAccess(boolean followLinks) throws IOException {
|
|
int flags = O_RDONLY;
|
|
if (!followLinks)
|
|
flags |= O_NOFOLLOW;
|
|
try {
|
|
return open(this, flags, 0);
|
|
} catch (UnixException x) {
|
|
// HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
|
|
if (getFileSystem().isSolaris() && x.errno() == EINVAL)
|
|
x.setError(ELOOP);
|
|
|
|
if (x.errno() == ELOOP)
|
|
throw new FileSystemException(getPathForExecptionMessage(), null,
|
|
x.getMessage() + " or unable to access attributes of symbolic link");
|
|
|
|
x.rethrowAsIOException(this);
|
|
return -1; // keep compile happy
|
|
}
|
|
}
|
|
|
|
|
|
void checkRead() {
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null)
|
|
sm.checkRead(getPathForPermissionCheck());
|
|
}
|
|
|
|
void checkWrite() {
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null)
|
|
sm.checkWrite(getPathForPermissionCheck());
|
|
}
|
|
|
|
void checkDelete() {
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null)
|
|
sm.checkDelete(getPathForPermissionCheck());
|
|
}
|
|
|
|
@Override
|
|
public FileStore getFileStore()
|
|
throws IOException
|
|
{
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
|
|
checkRead();
|
|
}
|
|
return getFileSystem().getFileStore(this);
|
|
}
|
|
|
|
@Override
|
|
public void checkAccess(AccessMode... modes) throws IOException {
|
|
boolean e = false;
|
|
boolean r = false;
|
|
boolean w = false;
|
|
boolean x = false;
|
|
|
|
if (modes.length == 0) {
|
|
e = true;
|
|
} else {
|
|
for (AccessMode mode: modes) {
|
|
switch (mode) {
|
|
case READ : r = true; break;
|
|
case WRITE : w = true; break;
|
|
case EXECUTE : x = true; break;
|
|
default: throw new AssertionError("Should not get here");
|
|
}
|
|
}
|
|
}
|
|
|
|
int mode = 0;
|
|
if (e || r) {
|
|
checkRead();
|
|
mode |= (r) ? R_OK : F_OK;
|
|
}
|
|
if (w) {
|
|
checkWrite();
|
|
mode |= W_OK;
|
|
}
|
|
if (x) {
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
// not cached
|
|
sm.checkExec(getPathForPermissionCheck());
|
|
}
|
|
mode |= X_OK;
|
|
}
|
|
try {
|
|
access(this, mode);
|
|
} catch (UnixException exc) {
|
|
exc.rethrowAsIOException(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
void implDelete(boolean failIfNotExists) throws IOException {
|
|
checkDelete();
|
|
|
|
// need file attributes to know if file is directory
|
|
UnixFileAttributes attrs = null;
|
|
try {
|
|
attrs = UnixFileAttributes.get(this, false);
|
|
if (attrs.isDirectory()) {
|
|
rmdir(this);
|
|
} else {
|
|
unlink(this);
|
|
}
|
|
} catch (UnixException x) {
|
|
// no-op if file does not exist
|
|
if (!failIfNotExists && x.errno() == ENOENT)
|
|
return;
|
|
|
|
// DirectoryNotEmptyException if not empty
|
|
if (attrs != null && attrs.isDirectory() &&
|
|
(x.errno() == EEXIST || x.errno() == ENOTEMPTY))
|
|
throw new DirectoryNotEmptyException(getPathForExecptionMessage());
|
|
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public DirectoryStream<Path> newDirectoryStream(DirectoryStream.Filter<? super Path> filter)
|
|
throws IOException
|
|
{
|
|
if (filter == null)
|
|
throw new NullPointerException();
|
|
checkRead();
|
|
|
|
// can't return SecureDirectoryStream on kernels that don't support
|
|
// openat, etc.
|
|
if (!supportsAtSysCalls()) {
|
|
try {
|
|
long ptr = opendir(this);
|
|
return new UnixDirectoryStream(this, ptr, filter);
|
|
} catch (UnixException x) {
|
|
if (x.errno() == ENOTDIR)
|
|
throw new NotDirectoryException(getPathForExecptionMessage());
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
}
|
|
|
|
// open directory and dup file descriptor for use by
|
|
// opendir/readdir/closedir
|
|
int dfd1 = -1;
|
|
int dfd2 = -1;
|
|
long dp = 0L;
|
|
try {
|
|
dfd1 = open(this, O_RDONLY, 0);
|
|
dfd2 = dup(dfd1);
|
|
dp = fdopendir(dfd1);
|
|
} catch (UnixException x) {
|
|
if (dfd1 != -1)
|
|
close(dfd1);
|
|
if (dfd2 != -1)
|
|
close(dfd2);
|
|
if (x.errno() == UnixConstants.ENOTDIR)
|
|
throw new NotDirectoryException(getPathForExecptionMessage());
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
return new UnixSecureDirectoryStream(this, dp, dfd2, filter);
|
|
}
|
|
|
|
// invoked by AbstractPath#copyTo
|
|
@Override
|
|
public void implCopyTo(Path obj, CopyOption... options)
|
|
throws IOException
|
|
{
|
|
UnixPath target = (UnixPath)obj;
|
|
UnixCopyFile.copy(this, target, options);
|
|
}
|
|
|
|
@Override
|
|
public void implMoveTo(Path obj, CopyOption... options)
|
|
throws IOException
|
|
{
|
|
UnixPath target = (UnixPath)obj;
|
|
UnixCopyFile.move(this, target, options);
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public <V extends FileAttributeView> V
|
|
getFileAttributeView(Class<V> type, LinkOption... options)
|
|
{
|
|
FileAttributeView view = getFileSystem()
|
|
.newFileAttributeView(type, this, options);
|
|
if (view == null)
|
|
return null;
|
|
return (V) view;
|
|
}
|
|
|
|
@Override
|
|
public DynamicFileAttributeView getFileAttributeView(String name,
|
|
LinkOption... options)
|
|
{
|
|
return getFileSystem().newFileAttributeView(name, this, options);
|
|
}
|
|
|
|
@Override
|
|
public Path createDirectory(FileAttribute<?>... attrs)
|
|
throws IOException
|
|
{
|
|
checkWrite();
|
|
|
|
int mode = UnixFileModeAttribute
|
|
.toUnixMode(UnixFileModeAttribute.ALL_PERMISSIONS, attrs);
|
|
try {
|
|
mkdir(this, mode);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
|
|
FileAttribute<?>... attrs)
|
|
throws IOException
|
|
{
|
|
int mode = UnixFileModeAttribute
|
|
.toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
|
|
try {
|
|
return UnixChannelFactory.newFileChannel(this, options, mode);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this);
|
|
return null; // keep compiler happy
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isSameFile(Path obj) throws IOException {
|
|
if (this.equals(obj))
|
|
return true;
|
|
if (!(obj instanceof UnixPath)) // includes null check
|
|
return false;
|
|
UnixPath other = (UnixPath)obj;
|
|
|
|
// check security manager access to both files
|
|
this.checkRead();
|
|
other.checkRead();
|
|
|
|
UnixFileAttributes thisAttrs;
|
|
UnixFileAttributes otherAttrs;
|
|
try {
|
|
thisAttrs = UnixFileAttributes.get(this, true);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this);
|
|
return false; // keep compiler happy
|
|
}
|
|
try {
|
|
otherAttrs = UnixFileAttributes.get(other, true);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(other);
|
|
return false; // keep compiler happy
|
|
}
|
|
return thisAttrs.isSameFile(otherAttrs);
|
|
}
|
|
|
|
@Override
|
|
public Path createSymbolicLink(Path obj, FileAttribute<?>... attrs)
|
|
throws IOException
|
|
{
|
|
UnixPath target = checkPath(obj);
|
|
|
|
// no attributes supported when creating links
|
|
if (attrs.length > 0) {
|
|
UnixFileModeAttribute.toUnixMode(0, attrs); // may throw NPE or UOE
|
|
throw new UnsupportedOperationException("Initial file attributes" +
|
|
"not supported when creating symbolic link");
|
|
}
|
|
|
|
// permission check
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
sm.checkPermission(new LinkPermission("symbolic"));
|
|
checkWrite();
|
|
}
|
|
|
|
// create link
|
|
try {
|
|
symlink(target.asByteArray(), this);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Path createLink(Path obj) throws IOException {
|
|
UnixPath existing = checkPath(obj);
|
|
|
|
// permission check
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
sm.checkPermission(new LinkPermission("hard"));
|
|
this.checkWrite();
|
|
existing.checkWrite();
|
|
}
|
|
try {
|
|
link(existing, this);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this, existing);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Path readSymbolicLink() throws IOException {
|
|
// permission check
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
FilePermission perm = new FilePermission(getPathForPermissionCheck(),
|
|
SecurityConstants.FILE_READLINK_ACTION);
|
|
AccessController.checkPermission(perm);
|
|
}
|
|
try {
|
|
byte[] target = readlink(this);
|
|
return new UnixPath(getFileSystem(), target);
|
|
} catch (UnixException x) {
|
|
if (x.errno() == UnixConstants.EINVAL)
|
|
throw new NotLinkException(getPathForExecptionMessage());
|
|
x.rethrowAsIOException(this);
|
|
return null; // keep compiler happy
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public UnixPath toAbsolutePath() {
|
|
if (isAbsolute()) {
|
|
return this;
|
|
}
|
|
// The path is relative so need to resolve against default directory,
|
|
// taking care not to reveal the user.dir
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
sm.checkPropertyAccess("user.dir");
|
|
}
|
|
return new UnixPath(getFileSystem(),
|
|
resolve(getFileSystem().defaultDirectory(), path));
|
|
}
|
|
|
|
@Override
|
|
public UnixPath toRealPath(boolean resolveLinks) throws IOException {
|
|
checkRead();
|
|
|
|
UnixPath absolute = toAbsolutePath();
|
|
|
|
// if resolveLinks is true then use realpath
|
|
if (resolveLinks) {
|
|
try {
|
|
byte[] rp = realpath(absolute);
|
|
return new UnixPath(getFileSystem(), rp);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(this);
|
|
}
|
|
}
|
|
|
|
// if resolveLinks is false then eliminate "." and also ".."
|
|
// where the previous element is not a link.
|
|
UnixPath root = getFileSystem().rootDirectory();
|
|
UnixPath result = root;
|
|
for (int i=0; i<absolute.getNameCount(); i++) {
|
|
UnixPath element = absolute.getName(i);
|
|
|
|
// eliminate "."
|
|
if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
|
|
continue;
|
|
|
|
// cannot eliminate ".." if previous element is a link
|
|
if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
|
|
(element.asByteArray()[1] == '.'))
|
|
{
|
|
UnixFileAttributes attrs = null;
|
|
try {
|
|
attrs = UnixFileAttributes.get(result, false);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(result);
|
|
}
|
|
if (!attrs.isSymbolicLink()) {
|
|
result = result.getParent();
|
|
if (result == null) {
|
|
result = root;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
result = result.resolve(element);
|
|
}
|
|
|
|
// check file exists (without following links)
|
|
try {
|
|
UnixFileAttributes.get(result, false);
|
|
} catch (UnixException x) {
|
|
x.rethrowAsIOException(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean isHidden() {
|
|
checkRead();
|
|
UnixPath name = getName();
|
|
if (name == null)
|
|
return false;
|
|
return (name.asByteArray()[0] == '.');
|
|
}
|
|
|
|
@Override
|
|
public URI toUri() {
|
|
return UnixUriUtils.toUri(this);
|
|
}
|
|
|
|
@Override
|
|
public WatchKey register(WatchService watcher,
|
|
WatchEvent.Kind<?>[] events,
|
|
WatchEvent.Modifier... modifiers)
|
|
throws IOException
|
|
{
|
|
if (watcher == null)
|
|
throw new NullPointerException();
|
|
if (!(watcher instanceof AbstractWatchService))
|
|
throw new ProviderMismatchException();
|
|
checkRead();
|
|
return ((AbstractWatchService)watcher).register(this, events, modifiers);
|
|
}
|
|
}
|