Path: blob/master/src/java.base/unix/classes/sun/nio/fs/UnixPath.java
41137 views
/*1* Copyright (c) 2008, 2021, 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.file.*;28import java.nio.charset.*;29import java.io.*;30import java.net.URI;31import java.util.*;3233import jdk.internal.access.JavaLangAccess;34import jdk.internal.access.SharedSecrets;3536import static sun.nio.fs.UnixNativeDispatcher.*;37import static sun.nio.fs.UnixConstants.*;3839/**40* Linux/Mac implementation of java.nio.file.Path41*/42class UnixPath implements Path {4344private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();4546private final UnixFileSystem fs;4748// internal representation49private final byte[] path;5051// String representation (created lazily)52private volatile String stringValue;5354// cached hashcode (created lazily, no need to be volatile)55private int hash;5657// array of offsets of elements in path (created lazily)58private volatile int[] offsets;5960UnixPath(UnixFileSystem fs, byte[] path) {61this.fs = fs;62this.path = path;63}6465UnixPath(UnixFileSystem fs, String input) {66// removes redundant slashes and checks for invalid characters67this(fs, encode(fs, normalizeAndCheck(input)));68}6970// package-private71// removes redundant slashes and check input for invalid characters72static String normalizeAndCheck(String input) {73int n = input.length();74char prevChar = 0;75for (int i=0; i < n; i++) {76char c = input.charAt(i);77if ((c == '/') && (prevChar == '/'))78return normalize(input, n, i - 1);79checkNotNul(input, c);80prevChar = c;81}82if (prevChar == '/')83return normalize(input, n, n - 1);84return input;85}8687private static void checkNotNul(String input, char c) {88if (c == '\u0000')89throw new InvalidPathException(input, "Nul character not allowed");90}9192private static String normalize(String input, int len, int off) {93if (len == 0)94return input;95int n = len;96while ((n > 0) && (input.charAt(n - 1) == '/')) n--;97if (n == 0)98return "/";99StringBuilder sb = new StringBuilder(input.length());100if (off > 0)101sb.append(input.substring(0, off));102char prevChar = 0;103for (int i=off; i < n; i++) {104char c = input.charAt(i);105if ((c == '/') && (prevChar == '/'))106continue;107checkNotNul(input, c);108sb.append(c);109prevChar = c;110}111return sb.toString();112}113114// encodes the given path-string into a sequence of bytes115private static byte[] encode(UnixFileSystem fs, String input) {116input = fs.normalizeNativePath(input);117try {118return JLA.getBytesNoRepl(input, Util.jnuEncoding());119} catch (CharacterCodingException cce) {120throw new InvalidPathException(input,121"Malformed input or input contains unmappable characters");122}123}124125// package-private126byte[] asByteArray() {127return path;128}129130// use this path when making system/library calls131byte[] getByteArrayForSysCalls() {132// resolve against default directory if required (chdir allowed or133// file system default directory is not working directory)134if (getFileSystem().needToResolveAgainstDefaultDirectory()) {135return resolve(getFileSystem().defaultDirectory(), path);136} else {137if (!isEmpty()) {138return path;139} else {140// empty path case will access current directory141byte[] here = { '.' };142return here;143}144}145}146147// use this message when throwing exceptions148String getPathForExceptionMessage() {149return toString();150}151152// use this path for permission checks153String getPathForPermissionCheck() {154if (getFileSystem().needToResolveAgainstDefaultDirectory()) {155return Util.toString(getByteArrayForSysCalls());156} else {157return toString();158}159}160161// Checks that the given file is a UnixPath162static UnixPath toUnixPath(Path obj) {163if (obj == null)164throw new NullPointerException();165if (!(obj instanceof UnixPath))166throw new ProviderMismatchException();167return (UnixPath)obj;168}169170// create offset list if not already created171private void initOffsets() {172if (offsets == null) {173int count, index;174175// count names176count = 0;177index = 0;178if (isEmpty()) {179// empty path has one name180count = 1;181} else {182while (index < path.length) {183byte c = path[index++];184if (c != '/') {185count++;186while (index < path.length && path[index] != '/')187index++;188}189}190}191192// populate offsets193int[] result = new int[count];194count = 0;195index = 0;196while (index < path.length) {197byte c = path[index];198if (c == '/') {199index++;200} else {201result[count++] = index++;202while (index < path.length && path[index] != '/')203index++;204}205}206synchronized (this) {207if (offsets == null)208offsets = result;209}210}211}212213// returns {@code true} if this path is an empty path214boolean isEmpty() {215return path.length == 0;216}217218// returns an empty path219private UnixPath emptyPath() {220return new UnixPath(getFileSystem(), new byte[0]);221}222223224// return true if this path has "." or ".."225private boolean hasDotOrDotDot() {226int n = getNameCount();227for (int i=0; i<n; i++) {228byte[] bytes = getName(i).path;229if ((bytes.length == 1 && bytes[0] == '.'))230return true;231if ((bytes.length == 2 && bytes[0] == '.') && bytes[1] == '.') {232return true;233}234}235return false;236}237238@Override239public UnixFileSystem getFileSystem() {240return fs;241}242243@Override244public UnixPath getRoot() {245if (path.length > 0 && path[0] == '/') {246return getFileSystem().rootDirectory();247} else {248return null;249}250}251252@Override253public UnixPath getFileName() {254initOffsets();255256int count = offsets.length;257258// no elements so no name259if (count == 0)260return null;261262// one name element and no root component263if (count == 1 && path.length > 0 && path[0] != '/')264return this;265266int lastOffset = offsets[count-1];267int len = path.length - lastOffset;268byte[] result = new byte[len];269System.arraycopy(path, lastOffset, result, 0, len);270return new UnixPath(getFileSystem(), result);271}272273@Override274public UnixPath getParent() {275initOffsets();276277int count = offsets.length;278if (count == 0) {279// no elements so no parent280return null;281}282int len = offsets[count-1] - 1;283if (len <= 0) {284// parent is root only (may be null)285return getRoot();286}287byte[] result = new byte[len];288System.arraycopy(path, 0, result, 0, len);289return new UnixPath(getFileSystem(), result);290}291292@Override293public int getNameCount() {294initOffsets();295return offsets.length;296}297298@Override299public UnixPath getName(int index) {300initOffsets();301if (index < 0)302throw new IllegalArgumentException();303if (index >= offsets.length)304throw new IllegalArgumentException();305306int begin = offsets[index];307int len;308if (index == (offsets.length-1)) {309len = path.length - begin;310} else {311len = offsets[index+1] - begin - 1;312}313314// construct result315byte[] result = new byte[len];316System.arraycopy(path, begin, result, 0, len);317return new UnixPath(getFileSystem(), result);318}319320@Override321public UnixPath subpath(int beginIndex, int endIndex) {322initOffsets();323324if (beginIndex < 0)325throw new IllegalArgumentException();326if (beginIndex >= offsets.length)327throw new IllegalArgumentException();328if (endIndex > offsets.length)329throw new IllegalArgumentException();330if (beginIndex >= endIndex) {331throw new IllegalArgumentException();332}333334// starting offset and length335int begin = offsets[beginIndex];336int len;337if (endIndex == offsets.length) {338len = path.length - begin;339} else {340len = offsets[endIndex] - begin - 1;341}342343// construct result344byte[] result = new byte[len];345System.arraycopy(path, begin, result, 0, len);346return new UnixPath(getFileSystem(), result);347}348349@Override350public boolean isAbsolute() {351return (path.length > 0 && path[0] == '/');352}353354// Resolve child against given base355private static byte[] resolve(byte[] base, byte[] child) {356int baseLength = base.length;357int childLength = child.length;358if (childLength == 0)359return base;360if (baseLength == 0 || child[0] == '/')361return child;362byte[] result;363if (baseLength == 1 && base[0] == '/') {364result = new byte[childLength + 1];365result[0] = '/';366System.arraycopy(child, 0, result, 1, childLength);367} else {368result = new byte[baseLength + 1 + childLength];369System.arraycopy(base, 0, result, 0, baseLength);370result[base.length] = '/';371System.arraycopy(child, 0, result, baseLength+1, childLength);372}373return result;374}375376@Override377public UnixPath resolve(Path obj) {378byte[] other = toUnixPath(obj).path;379if (other.length > 0 && other[0] == '/')380return ((UnixPath)obj);381byte[] result = resolve(path, other);382return new UnixPath(getFileSystem(), result);383}384385UnixPath resolve(byte[] other) {386return resolve(new UnixPath(getFileSystem(), other));387}388389@Override390public UnixPath relativize(Path obj) {391UnixPath child = toUnixPath(obj);392if (child.equals(this))393return emptyPath();394395// can only relativize paths of the same type396if (this.isAbsolute() != child.isAbsolute())397throw new IllegalArgumentException("'other' is different type of Path");398399// this path is the empty path400if (this.isEmpty())401return child;402403UnixPath base = this;404if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {405base = base.normalize();406child = child.normalize();407}408409int baseCount = base.getNameCount();410int childCount = child.getNameCount();411412// skip matching names413int n = Math.min(baseCount, childCount);414int i = 0;415while (i < n) {416if (!base.getName(i).equals(child.getName(i)))417break;418i++;419}420421// remaining elements in child422UnixPath childRemaining;423boolean isChildEmpty;424if (i == childCount) {425childRemaining = emptyPath();426isChildEmpty = true;427} else {428childRemaining = child.subpath(i, childCount);429isChildEmpty = childRemaining.isEmpty();430}431432// matched all of base433if (i == baseCount) {434return childRemaining;435}436437// the remainder of base cannot contain ".."438UnixPath baseRemaining = base.subpath(i, baseCount);439if (baseRemaining.hasDotOrDotDot()) {440throw new IllegalArgumentException("Unable to compute relative "441+ " path from " + this + " to " + obj);442}443if (baseRemaining.isEmpty())444return childRemaining;445446// number of ".." needed447int dotdots = baseRemaining.getNameCount();448if (dotdots == 0) {449return childRemaining;450}451452// result is a "../" for each remaining name in base followed by the453// remaining names in child. If the remainder is the empty path454// then we don't add the final trailing slash.455int len = dotdots*3 + childRemaining.path.length;456if (isChildEmpty) {457assert childRemaining.isEmpty();458len--;459}460byte[] result = new byte[len];461int pos = 0;462while (dotdots > 0) {463result[pos++] = (byte)'.';464result[pos++] = (byte)'.';465if (isChildEmpty) {466if (dotdots > 1) result[pos++] = (byte)'/';467} else {468result[pos++] = (byte)'/';469}470dotdots--;471}472System.arraycopy(childRemaining.path,0, result, pos,473childRemaining.path.length);474return new UnixPath(getFileSystem(), result);475}476477@Override478public UnixPath normalize() {479final int count = getNameCount();480if (count == 0 || isEmpty())481return this;482483boolean[] ignore = new boolean[count]; // true => ignore name484int[] size = new int[count]; // length of name485int remaining = count; // number of names remaining486boolean hasDotDot = false; // has at least one ..487boolean isAbsolute = isAbsolute();488489// first pass:490// 1. compute length of names491// 2. mark all occurrences of "." to ignore492// 3. and look for any occurrences of ".."493for (int i=0; i<count; i++) {494int begin = offsets[i];495int len;496if (i == (offsets.length-1)) {497len = path.length - begin;498} else {499len = offsets[i+1] - begin - 1;500}501size[i] = len;502503if (path[begin] == '.') {504if (len == 1) {505ignore[i] = true; // ignore "."506remaining--;507}508else {509if (path[begin+1] == '.') // ".." found510hasDotDot = true;511}512}513}514515// multiple passes to eliminate all occurrences of name/..516if (hasDotDot) {517int prevRemaining;518do {519prevRemaining = remaining;520int prevName = -1;521for (int i=0; i<count; i++) {522if (ignore[i])523continue;524525// not a ".."526if (size[i] != 2) {527prevName = i;528continue;529}530531int begin = offsets[i];532if (path[begin] != '.' || path[begin+1] != '.') {533prevName = i;534continue;535}536537// ".." found538if (prevName >= 0) {539// name/<ignored>/.. found so mark name and ".." to be540// ignored541ignore[prevName] = true;542ignore[i] = true;543remaining = remaining - 2;544prevName = -1;545} else {546// Case: /<ignored>/.. so mark ".." as ignored547if (isAbsolute) {548boolean hasPrevious = false;549for (int j=0; j<i; j++) {550if (!ignore[j]) {551hasPrevious = true;552break;553}554}555if (!hasPrevious) {556// all proceeding names are ignored557ignore[i] = true;558remaining--;559}560}561}562}563} while (prevRemaining > remaining);564}565566// no redundant names567if (remaining == count)568return this;569570// corner case - all names removed571if (remaining == 0) {572return isAbsolute ? getFileSystem().rootDirectory() : emptyPath();573}574575// compute length of result576int len = remaining - 1;577if (isAbsolute)578len++;579580for (int i=0; i<count; i++) {581if (!ignore[i])582len += size[i];583}584byte[] result = new byte[len];585586// copy names into result587int pos = 0;588if (isAbsolute)589result[pos++] = '/';590for (int i=0; i<count; i++) {591if (!ignore[i]) {592System.arraycopy(path, offsets[i], result, pos, size[i]);593pos += size[i];594if (--remaining > 0) {595result[pos++] = '/';596}597}598}599return new UnixPath(getFileSystem(), result);600}601602@Override603public boolean startsWith(Path other) {604if (!(Objects.requireNonNull(other) instanceof UnixPath))605return false;606UnixPath that = (UnixPath)other;607608// other path is longer609if (that.path.length > path.length)610return false;611612int thisOffsetCount = getNameCount();613int thatOffsetCount = that.getNameCount();614615// other path has no name elements616if (thatOffsetCount == 0 && this.isAbsolute()) {617return that.isEmpty() ? false : true;618}619620// given path has more elements that this path621if (thatOffsetCount > thisOffsetCount)622return false;623624// same number of elements so must be exact match625if ((thatOffsetCount == thisOffsetCount) &&626(path.length != that.path.length)) {627return false;628}629630// check offsets of elements match631for (int i=0; i<thatOffsetCount; i++) {632Integer o1 = offsets[i];633Integer o2 = that.offsets[i];634if (!o1.equals(o2))635return false;636}637638// offsets match so need to compare bytes639int i=0;640while (i < that.path.length) {641if (this.path[i] != that.path[i])642return false;643i++;644}645646// final check that match is on name boundary647if (i < path.length && this.path[i] != '/')648return false;649650return true;651}652653@Override654public boolean endsWith(Path other) {655if (!(Objects.requireNonNull(other) instanceof UnixPath))656return false;657UnixPath that = (UnixPath)other;658659int thisLen = path.length;660int thatLen = that.path.length;661662// other path is longer663if (thatLen > thisLen)664return false;665666// other path is the empty path667if (thisLen > 0 && thatLen == 0)668return false;669670// other path is absolute so this path must be absolute671if (that.isAbsolute() && !this.isAbsolute())672return false;673674int thisOffsetCount = getNameCount();675int thatOffsetCount = that.getNameCount();676677// given path has more elements that this path678if (thatOffsetCount > thisOffsetCount) {679return false;680} else {681// same number of elements682if (thatOffsetCount == thisOffsetCount) {683if (thisOffsetCount == 0)684return true;685int expectedLen = thisLen;686if (this.isAbsolute() && !that.isAbsolute())687expectedLen--;688if (thatLen != expectedLen)689return false;690} else {691// this path has more elements so given path must be relative692if (that.isAbsolute())693return false;694}695}696697// compare bytes698int thisPos = offsets[thisOffsetCount - thatOffsetCount];699int thatPos = that.offsets[0];700if ((thatLen - thatPos) != (thisLen - thisPos))701return false;702while (thatPos < thatLen) {703if (this.path[thisPos++] != that.path[thatPos++])704return false;705}706707return true;708}709710@Override711public int compareTo(Path other) {712int len1 = path.length;713int len2 = ((UnixPath) other).path.length;714715int n = Math.min(len1, len2);716byte v1[] = path;717byte v2[] = ((UnixPath) other).path;718719int k = 0;720while (k < n) {721int c1 = v1[k] & 0xff;722int c2 = v2[k] & 0xff;723if (c1 != c2) {724return c1 - c2;725}726k++;727}728return len1 - len2;729}730731@Override732public boolean equals(Object ob) {733if (ob instanceof UnixPath path) {734return compareTo(path) == 0;735}736return false;737}738739@Override740public int hashCode() {741// OK if two or more threads compute hash742int h = hash;743if (h == 0) {744for (int i = 0; i< path.length; i++) {745h = 31*h + (path[i] & 0xff);746}747hash = h;748}749return h;750}751752@Override753public String toString() {754// OK if two or more threads create a String755if (stringValue == null) {756stringValue = fs.normalizeJavaPath(Util.toString(path)); // platform encoding757}758return stringValue;759}760761// -- file operations --762763// package-private764int openForAttributeAccess(boolean followLinks) throws UnixException {765int flags = O_RDONLY;766if (!followLinks) {767if (O_NOFOLLOW == 0)768throw new UnixException769("NOFOLLOW_LINKS is not supported on this platform");770flags |= O_NOFOLLOW;771}772return open(this, flags, 0);773}774775void checkRead() {776@SuppressWarnings("removal")777SecurityManager sm = System.getSecurityManager();778if (sm != null)779sm.checkRead(getPathForPermissionCheck());780}781782void checkWrite() {783@SuppressWarnings("removal")784SecurityManager sm = System.getSecurityManager();785if (sm != null)786sm.checkWrite(getPathForPermissionCheck());787}788789void checkDelete() {790@SuppressWarnings("removal")791SecurityManager sm = System.getSecurityManager();792if (sm != null)793sm.checkDelete(getPathForPermissionCheck());794}795796@Override797public UnixPath toAbsolutePath() {798if (isAbsolute()) {799return this;800}801// The path is relative so need to resolve against default directory,802// taking care not to reveal the user.dir803@SuppressWarnings("removal")804SecurityManager sm = System.getSecurityManager();805if (sm != null) {806sm.checkPropertyAccess("user.dir");807}808return new UnixPath(getFileSystem(),809resolve(getFileSystem().defaultDirectory(), path));810}811812@Override813public Path toRealPath(LinkOption... options) throws IOException {814checkRead();815816UnixPath absolute = toAbsolutePath();817818// if resolving links then use realpath819if (Util.followLinks(options)) {820try {821byte[] rp = realpath(absolute);822return new UnixPath(getFileSystem(), rp);823} catch (UnixException x) {824x.rethrowAsIOException(this);825}826}827828// if not resolving links then eliminate "." and also ".."829// where the previous element is not a link.830UnixPath result = fs.rootDirectory();831for (int i=0; i<absolute.getNameCount(); i++) {832UnixPath element = absolute.getName(i);833834// eliminate "."835if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))836continue;837838// cannot eliminate ".." if previous element is a link839if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&840(element.asByteArray()[1] == '.'))841{842UnixFileAttributes attrs = null;843try {844attrs = UnixFileAttributes.get(result, false);845} catch (UnixException x) {846x.rethrowAsIOException(result);847}848if (!attrs.isSymbolicLink()) {849result = result.getParent();850if (result == null) {851result = fs.rootDirectory();852}853continue;854}855}856result = result.resolve(element);857}858859// check file exists (without following links)860try {861UnixFileAttributes.get(result, false);862} catch (UnixException x) {863x.rethrowAsIOException(result);864}865return result;866}867868@Override869public URI toUri() {870return UnixUriUtils.toUri(this);871}872873@Override874public WatchKey register(WatchService watcher,875WatchEvent.Kind<?>[] events,876WatchEvent.Modifier... modifiers)877throws IOException878{879if (watcher == null)880throw new NullPointerException();881if (!(watcher instanceof AbstractWatchService))882throw new ProviderMismatchException();883checkRead();884return ((AbstractWatchService)watcher).register(this, events, modifiers);885}886}887888889