/* * 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> encoder = new ThreadLocal>(); // 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 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(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= 0) { // name//.. found so mark name and ".." to be // ignored ignore[prevName] = true; ignore[i] = true; remaining = remaining - 2; prevName = -1; } else { // Case: //.. so mark ".." as ignored if (isAbsolute) { boolean hasPrevious = false; for (int j=0; j 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 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 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 iterator() { initOffsets(); return new Iterator() { 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 newDirectoryStream(DirectoryStream.Filter 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 getFileAttributeView(Class 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 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 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 options, FileAttribute... attrs) throws IOException { // need to copy options to add WRITE Set opts = new HashSet(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[] 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); } }