Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/solaris/classes/sun/nio/fs/UnixPath.java
32288 views
/*1* Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.nio.fs;2627import java.nio.*;28import java.nio.file.*;29import java.nio.charset.*;30import java.io.*;31import java.net.URI;32import java.util.*;33import java.lang.ref.SoftReference;3435import static sun.nio.fs.UnixNativeDispatcher.*;36import static sun.nio.fs.UnixConstants.*;3738/**39* Solaris/Linux implementation of java.nio.file.Path40*/4142class UnixPath43extends AbstractPath44{45private static ThreadLocal<SoftReference<CharsetEncoder>> encoder =46new ThreadLocal<SoftReference<CharsetEncoder>>();4748// FIXME - eliminate this reference to reduce space49private final UnixFileSystem fs;5051// internal representation52private final byte[] path;5354// String representation (created lazily)55private volatile String stringValue;5657// cached hashcode (created lazily, no need to be volatile)58private int hash;5960// array of offsets of elements in path (created lazily)61private volatile int[] offsets;6263UnixPath(UnixFileSystem fs, byte[] path) {64this.fs = fs;65this.path = path;66}6768UnixPath(UnixFileSystem fs, String input) {69// removes redundant slashes and checks for invalid characters70this(fs, encode(fs, normalizeAndCheck(input)));71}7273// package-private74// removes redundant slashes and check input for invalid characters75static String normalizeAndCheck(String input) {76int n = input.length();77char prevChar = 0;78for (int i=0; i < n; i++) {79char c = input.charAt(i);80if ((c == '/') && (prevChar == '/'))81return normalize(input, n, i - 1);82checkNotNul(input, c);83prevChar = c;84}85if (prevChar == '/')86return normalize(input, n, n - 1);87return input;88}8990private static void checkNotNul(String input, char c) {91if (c == '\u0000')92throw new InvalidPathException(input, "Nul character not allowed");93}9495private static String normalize(String input, int len, int off) {96if (len == 0)97return input;98int n = len;99while ((n > 0) && (input.charAt(n - 1) == '/')) n--;100if (n == 0)101return "/";102StringBuilder sb = new StringBuilder(input.length());103if (off > 0)104sb.append(input.substring(0, off));105char prevChar = 0;106for (int i=off; i < n; i++) {107char c = input.charAt(i);108if ((c == '/') && (prevChar == '/'))109continue;110checkNotNul(input, c);111sb.append(c);112prevChar = c;113}114return sb.toString();115}116117// encodes the given path-string into a sequence of bytes118private static byte[] encode(UnixFileSystem fs, String input) {119SoftReference<CharsetEncoder> ref = encoder.get();120CharsetEncoder ce = (ref != null) ? ref.get() : null;121if (ce == null) {122ce = Util.jnuEncoding().newEncoder()123.onMalformedInput(CodingErrorAction.REPORT)124.onUnmappableCharacter(CodingErrorAction.REPORT);125encoder.set(new SoftReference<CharsetEncoder>(ce));126}127128char[] ca = fs.normalizeNativePath(input.toCharArray());129130// size output buffer for worse-case size131byte[] ba = new byte[(int)(ca.length * (double)ce.maxBytesPerChar())];132133// encode134ByteBuffer bb = ByteBuffer.wrap(ba);135CharBuffer cb = CharBuffer.wrap(ca);136ce.reset();137CoderResult cr = ce.encode(cb, bb, true);138boolean error;139if (!cr.isUnderflow()) {140error = true;141} else {142cr = ce.flush(bb);143error = !cr.isUnderflow();144}145if (error) {146throw new InvalidPathException(input,147"Malformed input or input contains unmappable characters");148}149150// trim result to actual length if required151int len = bb.position();152if (len != ba.length)153ba = Arrays.copyOf(ba, len);154155return ba;156}157158// package-private159byte[] asByteArray() {160return path;161}162163// use this path when making system/library calls164byte[] getByteArrayForSysCalls() {165// resolve against default directory if required (chdir allowed or166// file system default directory is not working directory)167if (getFileSystem().needToResolveAgainstDefaultDirectory()) {168return resolve(getFileSystem().defaultDirectory(), path);169} else {170if (!isEmpty()) {171return path;172} else {173// empty path case will access current directory174byte[] here = { '.' };175return here;176}177}178}179180// use this message when throwing exceptions181String getPathForExceptionMessage() {182return toString();183}184185// use this path for permission checks186String getPathForPermissionCheck() {187if (getFileSystem().needToResolveAgainstDefaultDirectory()) {188return Util.toString(getByteArrayForSysCalls());189} else {190return toString();191}192}193194// Checks that the given file is a UnixPath195static UnixPath toUnixPath(Path obj) {196if (obj == null)197throw new NullPointerException();198if (!(obj instanceof UnixPath))199throw new ProviderMismatchException();200return (UnixPath)obj;201}202203// create offset list if not already created204private void initOffsets() {205if (offsets == null) {206int count, index;207208// count names209count = 0;210index = 0;211if (isEmpty()) {212// empty path has one name213count = 1;214} else {215while (index < path.length) {216byte c = path[index++];217if (c != '/') {218count++;219while (index < path.length && path[index] != '/')220index++;221}222}223}224225// populate offsets226int[] result = new int[count];227count = 0;228index = 0;229while (index < path.length) {230byte c = path[index];231if (c == '/') {232index++;233} else {234result[count++] = index++;235while (index < path.length && path[index] != '/')236index++;237}238}239synchronized (this) {240if (offsets == null)241offsets = result;242}243}244}245246// returns {@code true} if this path is an empty path247private boolean isEmpty() {248return path.length == 0;249}250251// returns an empty path252private UnixPath emptyPath() {253return new UnixPath(getFileSystem(), new byte[0]);254}255256@Override257public UnixFileSystem getFileSystem() {258return fs;259}260261@Override262public UnixPath getRoot() {263if (path.length > 0 && path[0] == '/') {264return getFileSystem().rootDirectory();265} else {266return null;267}268}269270@Override271public UnixPath getFileName() {272initOffsets();273274int count = offsets.length;275276// no elements so no name277if (count == 0)278return null;279280// one name element and no root component281if (count == 1 && path.length > 0 && path[0] != '/')282return this;283284int lastOffset = offsets[count-1];285int len = path.length - lastOffset;286byte[] result = new byte[len];287System.arraycopy(path, lastOffset, result, 0, len);288return new UnixPath(getFileSystem(), result);289}290291@Override292public UnixPath getParent() {293initOffsets();294295int count = offsets.length;296if (count == 0) {297// no elements so no parent298return null;299}300int len = offsets[count-1] - 1;301if (len <= 0) {302// parent is root only (may be null)303return getRoot();304}305byte[] result = new byte[len];306System.arraycopy(path, 0, result, 0, len);307return new UnixPath(getFileSystem(), result);308}309310@Override311public int getNameCount() {312initOffsets();313return offsets.length;314}315316@Override317public UnixPath getName(int index) {318initOffsets();319if (index < 0)320throw new IllegalArgumentException();321if (index >= offsets.length)322throw new IllegalArgumentException();323324int begin = offsets[index];325int len;326if (index == (offsets.length-1)) {327len = path.length - begin;328} else {329len = offsets[index+1] - begin - 1;330}331332// construct result333byte[] result = new byte[len];334System.arraycopy(path, begin, result, 0, len);335return new UnixPath(getFileSystem(), result);336}337338@Override339public UnixPath subpath(int beginIndex, int endIndex) {340initOffsets();341342if (beginIndex < 0)343throw new IllegalArgumentException();344if (beginIndex >= offsets.length)345throw new IllegalArgumentException();346if (endIndex > offsets.length)347throw new IllegalArgumentException();348if (beginIndex >= endIndex) {349throw new IllegalArgumentException();350}351352// starting offset and length353int begin = offsets[beginIndex];354int len;355if (endIndex == offsets.length) {356len = path.length - begin;357} else {358len = offsets[endIndex] - begin - 1;359}360361// construct result362byte[] result = new byte[len];363System.arraycopy(path, begin, result, 0, len);364return new UnixPath(getFileSystem(), result);365}366367@Override368public boolean isAbsolute() {369return (path.length > 0 && path[0] == '/');370}371372// Resolve child against given base373private static byte[] resolve(byte[] base, byte[] child) {374int baseLength = base.length;375int childLength = child.length;376if (childLength == 0)377return base;378if (baseLength == 0 || child[0] == '/')379return child;380byte[] result;381if (baseLength == 1 && base[0] == '/') {382result = new byte[childLength + 1];383result[0] = '/';384System.arraycopy(child, 0, result, 1, childLength);385} else {386result = new byte[baseLength + 1 + childLength];387System.arraycopy(base, 0, result, 0, baseLength);388result[base.length] = '/';389System.arraycopy(child, 0, result, baseLength+1, childLength);390}391return result;392}393394@Override395public UnixPath resolve(Path obj) {396byte[] other = toUnixPath(obj).path;397if (other.length > 0 && other[0] == '/')398return ((UnixPath)obj);399byte[] result = resolve(path, other);400return new UnixPath(getFileSystem(), result);401}402403UnixPath resolve(byte[] other) {404return resolve(new UnixPath(getFileSystem(), other));405}406407@Override408public UnixPath relativize(Path obj) {409UnixPath other = toUnixPath(obj);410if (other.equals(this))411return emptyPath();412413// can only relativize paths of the same type414if (this.isAbsolute() != other.isAbsolute())415throw new IllegalArgumentException("'other' is different type of Path");416417// this path is the empty path418if (this.isEmpty())419return other;420421int bn = this.getNameCount();422int cn = other.getNameCount();423424// skip matching names425int n = (bn > cn) ? cn : bn;426int i = 0;427while (i < n) {428if (!this.getName(i).equals(other.getName(i)))429break;430i++;431}432433int dotdots = bn - i;434if (i < cn) {435// remaining name components in other436UnixPath remainder = other.subpath(i, cn);437if (dotdots == 0)438return remainder;439440// other is the empty path441boolean isOtherEmpty = other.isEmpty();442443// result is a "../" for each remaining name in base444// followed by the remaining names in other. If the remainder is445// the empty path then we don't add the final trailing slash.446int len = dotdots*3 + remainder.path.length;447if (isOtherEmpty) {448assert remainder.isEmpty();449len--;450}451byte[] result = new byte[len];452int pos = 0;453while (dotdots > 0) {454result[pos++] = (byte)'.';455result[pos++] = (byte)'.';456if (isOtherEmpty) {457if (dotdots > 1) result[pos++] = (byte)'/';458} else {459result[pos++] = (byte)'/';460}461dotdots--;462}463System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);464return new UnixPath(getFileSystem(), result);465} else {466// no remaining names in other so result is simply a sequence of ".."467byte[] result = new byte[dotdots*3 - 1];468int pos = 0;469while (dotdots > 0) {470result[pos++] = (byte)'.';471result[pos++] = (byte)'.';472// no tailing slash at the end473if (dotdots > 1)474result[pos++] = (byte)'/';475dotdots--;476}477return new UnixPath(getFileSystem(), result);478}479}480481@Override482public Path normalize() {483final int count = getNameCount();484if (count == 0 || isEmpty())485return this;486487boolean[] ignore = new boolean[count]; // true => ignore name488int[] size = new int[count]; // length of name489int remaining = count; // number of names remaining490boolean hasDotDot = false; // has at least one ..491boolean isAbsolute = isAbsolute();492493// first pass:494// 1. compute length of names495// 2. mark all occurrences of "." to ignore496// 3. and look for any occurrences of ".."497for (int i=0; i<count; i++) {498int begin = offsets[i];499int len;500if (i == (offsets.length-1)) {501len = path.length - begin;502} else {503len = offsets[i+1] - begin - 1;504}505size[i] = len;506507if (path[begin] == '.') {508if (len == 1) {509ignore[i] = true; // ignore "."510remaining--;511}512else {513if (path[begin+1] == '.') // ".." found514hasDotDot = true;515}516}517}518519// multiple passes to eliminate all occurrences of name/..520if (hasDotDot) {521int prevRemaining;522do {523prevRemaining = remaining;524int prevName = -1;525for (int i=0; i<count; i++) {526if (ignore[i])527continue;528529// not a ".."530if (size[i] != 2) {531prevName = i;532continue;533}534535int begin = offsets[i];536if (path[begin] != '.' || path[begin+1] != '.') {537prevName = i;538continue;539}540541// ".." found542if (prevName >= 0) {543// name/<ignored>/.. found so mark name and ".." to be544// ignored545ignore[prevName] = true;546ignore[i] = true;547remaining = remaining - 2;548prevName = -1;549} else {550// Case: /<ignored>/.. so mark ".." as ignored551if (isAbsolute) {552boolean hasPrevious = false;553for (int j=0; j<i; j++) {554if (!ignore[j]) {555hasPrevious = true;556break;557}558}559if (!hasPrevious) {560// all proceeding names are ignored561ignore[i] = true;562remaining--;563}564}565}566}567} while (prevRemaining > remaining);568}569570// no redundant names571if (remaining == count)572return this;573574// corner case - all names removed575if (remaining == 0) {576return isAbsolute ? getFileSystem().rootDirectory() : emptyPath();577}578579// compute length of result580int len = remaining - 1;581if (isAbsolute)582len++;583584for (int i=0; i<count; i++) {585if (!ignore[i])586len += size[i];587}588byte[] result = new byte[len];589590// copy names into result591int pos = 0;592if (isAbsolute)593result[pos++] = '/';594for (int i=0; i<count; i++) {595if (!ignore[i]) {596System.arraycopy(path, offsets[i], result, pos, size[i]);597pos += size[i];598if (--remaining > 0) {599result[pos++] = '/';600}601}602}603return new UnixPath(getFileSystem(), result);604}605606@Override607public boolean startsWith(Path other) {608if (!(Objects.requireNonNull(other) instanceof UnixPath))609return false;610UnixPath that = (UnixPath)other;611612// other path is longer613if (that.path.length > path.length)614return false;615616int thisOffsetCount = getNameCount();617int thatOffsetCount = that.getNameCount();618619// other path has no name elements620if (thatOffsetCount == 0 && this.isAbsolute()) {621return that.isEmpty() ? false : true;622}623624// given path has more elements that this path625if (thatOffsetCount > thisOffsetCount)626return false;627628// same number of elements so must be exact match629if ((thatOffsetCount == thisOffsetCount) &&630(path.length != that.path.length)) {631return false;632}633634// check offsets of elements match635for (int i=0; i<thatOffsetCount; i++) {636Integer o1 = offsets[i];637Integer o2 = that.offsets[i];638if (!o1.equals(o2))639return false;640}641642// offsets match so need to compare bytes643int i=0;644while (i < that.path.length) {645if (this.path[i] != that.path[i])646return false;647i++;648}649650// final check that match is on name boundary651if (i < path.length && this.path[i] != '/')652return false;653654return true;655}656657@Override658public boolean endsWith(Path other) {659if (!(Objects.requireNonNull(other) instanceof UnixPath))660return false;661UnixPath that = (UnixPath)other;662663int thisLen = path.length;664int thatLen = that.path.length;665666// other path is longer667if (thatLen > thisLen)668return false;669670// other path is the empty path671if (thisLen > 0 && thatLen == 0)672return false;673674// other path is absolute so this path must be absolute675if (that.isAbsolute() && !this.isAbsolute())676return false;677678int thisOffsetCount = getNameCount();679int thatOffsetCount = that.getNameCount();680681// given path has more elements that this path682if (thatOffsetCount > thisOffsetCount) {683return false;684} else {685// same number of elements686if (thatOffsetCount == thisOffsetCount) {687if (thisOffsetCount == 0)688return true;689int expectedLen = thisLen;690if (this.isAbsolute() && !that.isAbsolute())691expectedLen--;692if (thatLen != expectedLen)693return false;694} else {695// this path has more elements so given path must be relative696if (that.isAbsolute())697return false;698}699}700701// compare bytes702int thisPos = offsets[thisOffsetCount - thatOffsetCount];703int thatPos = that.offsets[0];704if ((thatLen - thatPos) != (thisLen - thisPos))705return false;706while (thatPos < thatLen) {707if (this.path[thisPos++] != that.path[thatPos++])708return false;709}710711return true;712}713714@Override715public int compareTo(Path other) {716int len1 = path.length;717int len2 = ((UnixPath) other).path.length;718719int n = Math.min(len1, len2);720byte v1[] = path;721byte v2[] = ((UnixPath) other).path;722723int k = 0;724while (k < n) {725int c1 = v1[k] & 0xff;726int c2 = v2[k] & 0xff;727if (c1 != c2) {728return c1 - c2;729}730k++;731}732return len1 - len2;733}734735@Override736public boolean equals(Object ob) {737if ((ob != null) && (ob instanceof UnixPath)) {738return compareTo((Path)ob) == 0;739}740return false;741}742743@Override744public int hashCode() {745// OK if two or more threads compute hash746int h = hash;747if (h == 0) {748for (int i = 0; i< path.length; i++) {749h = 31*h + (path[i] & 0xff);750}751hash = h;752}753return h;754}755756@Override757public String toString() {758// OK if two or more threads create a String759if (stringValue == null) {760stringValue = fs.normalizeJavaPath(Util.toString(path)); // platform encoding761}762return stringValue;763}764765// -- file operations --766767// package-private768int openForAttributeAccess(boolean followLinks) throws IOException {769int flags = O_RDONLY;770if (!followLinks) {771if (O_NOFOLLOW == 0)772throw new IOException("NOFOLLOW_LINKS is not supported on this platform");773flags |= O_NOFOLLOW;774}775try {776return open(this, flags, 0);777} catch (UnixException x) {778// HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)779if (getFileSystem().isSolaris() && x.errno() == EINVAL)780x.setError(ELOOP);781782if (x.errno() == ELOOP)783throw new FileSystemException(getPathForExceptionMessage(), null,784x.getMessage() + " or unable to access attributes of symbolic link");785786x.rethrowAsIOException(this);787return -1; // keep compile happy788}789}790791void checkRead() {792SecurityManager sm = System.getSecurityManager();793if (sm != null)794sm.checkRead(getPathForPermissionCheck());795}796797void checkWrite() {798SecurityManager sm = System.getSecurityManager();799if (sm != null)800sm.checkWrite(getPathForPermissionCheck());801}802803void checkDelete() {804SecurityManager sm = System.getSecurityManager();805if (sm != null)806sm.checkDelete(getPathForPermissionCheck());807}808809@Override810public UnixPath toAbsolutePath() {811if (isAbsolute()) {812return this;813}814// The path is relative so need to resolve against default directory,815// taking care not to reveal the user.dir816SecurityManager sm = System.getSecurityManager();817if (sm != null) {818sm.checkPropertyAccess("user.dir");819}820return new UnixPath(getFileSystem(),821resolve(getFileSystem().defaultDirectory(), path));822}823824@Override825public Path toRealPath(LinkOption... options) throws IOException {826checkRead();827828UnixPath absolute = toAbsolutePath();829830// if resolving links then use realpath831if (Util.followLinks(options)) {832try {833byte[] rp = realpath(absolute);834return new UnixPath(getFileSystem(), rp);835} catch (UnixException x) {836x.rethrowAsIOException(this);837}838}839840// if not resolving links then eliminate "." and also ".."841// where the previous element is not a link.842UnixPath result = fs.rootDirectory();843for (int i=0; i<absolute.getNameCount(); i++) {844UnixPath element = absolute.getName(i);845846// eliminate "."847if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))848continue;849850// cannot eliminate ".." if previous element is a link851if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&852(element.asByteArray()[1] == '.'))853{854UnixFileAttributes attrs = null;855try {856attrs = UnixFileAttributes.get(result, false);857} catch (UnixException x) {858x.rethrowAsIOException(result);859}860if (!attrs.isSymbolicLink()) {861result = result.getParent();862if (result == null) {863result = fs.rootDirectory();864}865continue;866}867}868result = result.resolve(element);869}870871// check file exists (without following links)872try {873UnixFileAttributes.get(result, false);874} catch (UnixException x) {875x.rethrowAsIOException(result);876}877return result;878}879880@Override881public URI toUri() {882return UnixUriUtils.toUri(this);883}884885@Override886public WatchKey register(WatchService watcher,887WatchEvent.Kind<?>[] events,888WatchEvent.Modifier... modifiers)889throws IOException890{891if (watcher == null)892throw new NullPointerException();893if (!(watcher instanceof AbstractWatchService))894throw new ProviderMismatchException();895checkRead();896return ((AbstractWatchService)watcher).register(this, events, modifiers);897}898}899900901