--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/solaris/classes/sun/nio/fs/UnixPath.java Sun Feb 15 12:25:54 2009 +0000
@@ -0,0 +1,1228 @@
+/*
+ * Copyright 2008-2009 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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.file.spi.AbstractPath;
+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;
+
+ // file permissions (created lazily)
+ private volatile FilePermission[] perms;
+
+ 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 == '\u0000')
+ throw new InvalidPathException(input, "Nul character not allowed");
+ if ((c == '/') && (prevChar == '/'))
+ return normalize(input, n, i - 1);
+ prevChar = c;
+ }
+ if (prevChar == '/')
+ return normalize(input, n, n - 1);
+ return input;
+ }
+
+ 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;
+ 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);
+
+ // other path is longer
+ if (that.path.length > path.length)
+ 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 = path.length;
+ if (this.isAbsolute() && !that.isAbsolute())
+ expectedLen--;
+ if (that.path.length != 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];
+ while (thatPos < that.path.length) {
+ 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
+ }
+ }
+
+ // create file permissions used for read and write checks
+ private void checkReadOrWrite(boolean checkRead) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm == null)
+ return;
+ if (perms == null) {
+ synchronized (this) {
+ if (perms == null) {
+ FilePermission[] p = new FilePermission[2];
+ String pathForPermCheck = getPathForPermissionCheck();
+ p[0] = new FilePermission(pathForPermCheck,
+ SecurityConstants.FILE_READ_ACTION);
+ p[1] = new FilePermission(pathForPermCheck,
+ SecurityConstants.FILE_WRITE_ACTION);
+ perms = p;
+ }
+ }
+ }
+ if (checkRead) {
+ sm.checkPermission(perms[0]);
+ } else {
+ sm.checkPermission(perms[1]);
+ }
+ }
+
+ void checkRead() {
+ checkReadOrWrite(true);
+ }
+
+ void checkWrite() {
+ checkReadOrWrite(false);
+ }
+
+ void checkDelete() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ // permission not cached
+ 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
+ public void delete(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 older kernels.
+ if (!getFileSystem().supportsSecureDirectoryStreams()) {
+ try {
+ long ptr = opendir(this);
+ return new UnixDirectoryStream(this, ptr, filter);
+ } catch (UnixException x) {
+ if (x.errno() == UnixConstants.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 FileAttributeView 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 InputStream newInputStream()throws IOException {
+ try {
+ Set<OpenOption> options = Collections.emptySet();
+ FileChannel fc = UnixChannelFactory.newFileChannel(this, options, 0);
+ return Channels.newInputStream(fc);
+ } catch (UnixException x) {
+ x.rethrowAsIOException(this);
+ return null; // keep compiler happy
+ }
+ }
+
+ @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 OutputStream newOutputStream(Set<? extends OpenOption> options,
+ FileAttribute<?>... attrs)
+ throws IOException
+ {
+ // need to copy options to add WRITE
+ Set<OpenOption> opts = new HashSet<OpenOption>(options);
+ if (opts.contains(StandardOpenOption.READ))
+ throw new IllegalArgumentException("READ not allowed");
+ opts.add(StandardOpenOption.WRITE);
+
+ int mode = UnixFileModeAttribute
+ .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
+ try {
+ FileChannel fc = UnixChannelFactory.newFileChannel(this, opts, mode);
+ return Channels.newOutputStream(fc);
+ } catch (UnixException x) {
+ x.rethrowAsIOException(this);
+ return null; // keep compiler happy
+ }
+ }
+
+ @Override
+ public boolean isSameFile(FileRef 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);
+ }
+
+ // finally check that file exists
+ try {
+ UnixFileAttributes.get(result, true);
+ } 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);
+ }
+}