Path: blob/master/src/java.base/windows/classes/sun/nio/fs/WindowsPath.java
41139 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.file.attribute.*;29import java.io.*;30import java.net.URI;31import java.util.*;32import java.lang.ref.WeakReference;3334import static sun.nio.fs.WindowsNativeDispatcher.*;35import static sun.nio.fs.WindowsConstants.*;3637/**38* Windows implementation of Path39*/4041class WindowsPath implements Path {4243// The maximum path that does not require long path prefix. On Windows44// the maximum path is 260 minus 1 (NUL) but for directories it is 26045// minus 12 minus 1 (to allow for the creation of a 8.3 file in the46// directory).47private static final int MAX_PATH = 247;4849// Maximum extended-length path50private static final int MAX_LONG_PATH = 32000;5152// FIXME - eliminate this reference to reduce space53private final WindowsFileSystem fs;5455// path type56private final WindowsPathType type;57// root component (may be empty)58private final String root;59// normalized path60private final String path;6162// the path to use in Win32 calls. This differs from path for relative63// paths and has a long path prefix for all paths longer than MAX_PATH.64private volatile WeakReference<String> pathForWin32Calls;6566// offsets into name components (computed lazily)67private volatile Integer[] offsets;6869// computed hash code (computed lazily, no need to be volatile)70private int hash;717273/**74* Initializes a new instance of this class.75*/76private WindowsPath(WindowsFileSystem fs,77WindowsPathType type,78String root,79String path)80{81this.fs = fs;82this.type = type;83this.root = root;84this.path = path;85}8687/**88* Creates a Path by parsing the given path.89*/90static WindowsPath parse(WindowsFileSystem fs, String path) {91WindowsPathParser.Result result = WindowsPathParser.parse(path);92return new WindowsPath(fs, result.type(), result.root(), result.path());93}9495/**96* Creates a Path from a given path that is known to be normalized.97*/98static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,99String path,100BasicFileAttributes attrs)101{102try {103WindowsPathParser.Result result =104WindowsPathParser.parseNormalizedPath(path);105if (attrs == null) {106return new WindowsPath(fs,107result.type(),108result.root(),109result.path());110} else {111return new WindowsPathWithAttributes(fs,112result.type(),113result.root(),114result.path(),115attrs);116}117} catch (InvalidPathException x) {118throw new AssertionError(x.getMessage());119}120}121122/**123* Creates a WindowsPath from a given path that is known to be normalized.124*/125static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,126String path)127{128return createFromNormalizedPath(fs, path, null);129}130131/**132* Special implementation with attached/cached attributes (used to quicken133* file tree traversal)134*/135private static class WindowsPathWithAttributes136extends WindowsPath implements BasicFileAttributesHolder137{138final WeakReference<BasicFileAttributes> ref;139140WindowsPathWithAttributes(WindowsFileSystem fs,141WindowsPathType type,142String root,143String path,144BasicFileAttributes attrs)145{146super(fs, type, root, path);147ref = new WeakReference<BasicFileAttributes>(attrs);148}149150@Override151public BasicFileAttributes get() {152return ref.get();153}154155@Override156public void invalidate() {157ref.clear();158}159160// no need to override equals/hashCode.161}162163// use this message when throwing exceptions164String getPathForExceptionMessage() {165return path;166}167168// use this path for permission checks169String getPathForPermissionCheck() {170return path;171}172173// use this path for Win32 calls174// This method will prefix long paths with \\?\ or \\?\UNC as required.175String getPathForWin32Calls() throws WindowsException {176// short absolute paths can be used directly177if (isAbsolute() && path.length() <= MAX_PATH)178return path;179180// return cached values if available181WeakReference<String> ref = pathForWin32Calls;182String resolved = (ref != null) ? ref.get() : null;183if (resolved != null) {184// Win32 path already available185return resolved;186}187188// resolve against default directory189resolved = getAbsolutePath();190191// Long paths need to have "." and ".." removed and be prefixed with192// "\\?\". Note that it is okay to remove ".." even when it follows193// a link - for example, it is okay for foo/link/../bar to be changed194// to foo/bar. The reason is that Win32 APIs to access foo/link/../bar195// will access foo/bar anyway (which differs to Unix systems)196if (resolved.length() > MAX_PATH) {197if (resolved.length() > MAX_LONG_PATH) {198throw new WindowsException("Cannot access file with path exceeding "199+ MAX_LONG_PATH + " characters");200}201resolved = addPrefixIfNeeded(GetFullPathName(resolved));202}203204// cache the resolved path (except drive relative paths as the working205// directory on removal media devices can change during the lifetime206// of the VM)207if (type != WindowsPathType.DRIVE_RELATIVE) {208synchronized (this) {209pathForWin32Calls = new WeakReference<String>(resolved);210}211}212return resolved;213}214215// return this path resolved against the file system's default directory216private String getAbsolutePath() throws WindowsException {217if (isAbsolute())218return path;219220// Relative path ("foo" for example)221if (type == WindowsPathType.RELATIVE) {222String defaultDirectory = getFileSystem().defaultDirectory();223if (isEmpty())224return defaultDirectory;225if (defaultDirectory.endsWith("\\")) {226return defaultDirectory + path;227} else {228StringBuilder sb =229new StringBuilder(defaultDirectory.length() + path.length() + 1);230return sb.append(defaultDirectory).append('\\').append(path).toString();231}232}233234// Directory relative path ("\foo" for example)235if (type == WindowsPathType.DIRECTORY_RELATIVE) {236String defaultRoot = getFileSystem().defaultRoot();237return defaultRoot + path.substring(1);238}239240// Drive relative path ("C:foo" for example).241if (isSameDrive(root, getFileSystem().defaultRoot())) {242// relative to default directory243String remaining = path.substring(root.length());244String defaultDirectory = getFileSystem().defaultDirectory();245if (remaining.isEmpty()) {246return defaultDirectory;247} else if (defaultDirectory.endsWith("\\")) {248return defaultDirectory + remaining;249} else {250return defaultDirectory + "\\" + remaining;251}252} else {253// relative to some other drive254String wd;255try {256int dt = GetDriveType(root + "\\");257if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR)258throw new WindowsException("");259wd = GetFullPathName(root + ".");260} catch (WindowsException x) {261throw new WindowsException("Unable to get working directory of drive '" +262Character.toUpperCase(root.charAt(0)) + "'");263}264String result = wd;265if (wd.endsWith("\\")) {266result += path.substring(root.length());267} else {268if (path.length() > root.length())269result += "\\" + path.substring(root.length());270}271return result;272}273}274275// returns true if same drive letter276private static boolean isSameDrive(String root1, String root2) {277return Character.toUpperCase(root1.charAt(0)) ==278Character.toUpperCase(root2.charAt(0));279}280281// Add long path prefix to path if required282static String addPrefixIfNeeded(String path) {283if (path.length() > MAX_PATH) {284if (path.startsWith("\\\\")) {285path = "\\\\?\\UNC" + path.substring(1, path.length());286} else {287path = "\\\\?\\" + path;288}289}290return path;291}292293@Override294public WindowsFileSystem getFileSystem() {295return fs;296}297298// -- Path operations --299300private boolean isEmpty() {301return path.isEmpty();302}303304private WindowsPath emptyPath() {305return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", "");306}307308@Override309public Path getFileName() {310int len = path.length();311// represents empty path312if (len == 0)313return this;314// represents root component only315if (root.length() == len)316return null;317int off = path.lastIndexOf('\\');318if (off < root.length())319off = root.length();320else321off++;322return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", path.substring(off));323}324325@Override326public WindowsPath getParent() {327// represents root component only328if (root.length() == path.length())329return null;330int off = path.lastIndexOf('\\');331if (off < root.length())332return getRoot();333else334return new WindowsPath(getFileSystem(),335type,336root,337path.substring(0, off));338}339340@Override341public WindowsPath getRoot() {342if (root.isEmpty())343return null;344return new WindowsPath(getFileSystem(), type, root, root);345}346347// package-private348WindowsPathType type() {349return type;350}351352// package-private353boolean isUnc() {354return type == WindowsPathType.UNC;355}356357boolean needsSlashWhenResolving() {358if (path.endsWith("\\"))359return false;360return path.length() > root.length();361}362363@Override364public boolean isAbsolute() {365return type == WindowsPathType.ABSOLUTE || type == WindowsPathType.UNC;366}367368static WindowsPath toWindowsPath(Path path) {369if (path == null)370throw new NullPointerException();371if (!(path instanceof WindowsPath)) {372throw new ProviderMismatchException();373}374return (WindowsPath)path;375}376377// return true if this path has "." or ".."378private boolean hasDotOrDotDot() {379int n = getNameCount();380for (int i=0; i<n; i++) {381String name = elementAsString(i);382if (name.length() == 1 && name.charAt(0) == '.')383return true;384if (name.length() == 2385&& name.charAt(0) == '.' && name.charAt(1) == '.')386return true;387}388return false;389}390391@Override392public WindowsPath relativize(Path obj) {393WindowsPath child = toWindowsPath(obj);394if (this.equals(child))395return emptyPath();396397// can only relativize paths of the same type398if (this.type != child.type)399throw new IllegalArgumentException("'other' is different type of Path");400401// can only relativize paths if root component matches402if (!this.root.equalsIgnoreCase(child.root))403throw new IllegalArgumentException("'other' has different root");404405// this path is the empty path406if (this.isEmpty())407return child;408409410WindowsPath base = this;411if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {412base = base.normalize();413child = child.normalize();414}415416int baseCount = base.getNameCount();417int childCount = child.getNameCount();418419// skip matching names420int n = Math.min(baseCount, childCount);421int i = 0;422while (i < n) {423if (!base.getName(i).equals(child.getName(i)))424break;425i++;426}427428// remaining elements in child429WindowsPath childRemaining;430boolean isChildEmpty;431if (i == childCount) {432childRemaining = emptyPath();433isChildEmpty = true;434} else {435childRemaining = child.subpath(i, childCount);436isChildEmpty = childRemaining.isEmpty();437}438439// matched all of base440if (i == baseCount) {441return childRemaining;442}443444// the remainder of base cannot contain ".."445WindowsPath baseRemaining = base.subpath(i, baseCount);446if (baseRemaining.hasDotOrDotDot()) {447throw new IllegalArgumentException("Unable to compute relative "448+ " path from " + this + " to " + obj);449}450if (baseRemaining.isEmpty())451return childRemaining;452453// number of ".." needed454int dotdots = baseRemaining.getNameCount();455if (dotdots == 0) {456return childRemaining;457}458459StringBuilder result = new StringBuilder();460for (int j=0; j<dotdots; j++) {461result.append("..\\");462}463464// append remaining names in child465if (!isChildEmpty) {466for (int j=0; j<childRemaining.getNameCount(); j++) {467result.append(childRemaining.getName(j).toString());468result.append("\\");469}470}471472// drop trailing slash473result.setLength(result.length()-1);474return createFromNormalizedPath(getFileSystem(), result.toString());475}476477@Override478public WindowsPath normalize() {479final int count = getNameCount();480if (count == 0 || isEmpty())481return this;482483boolean[] ignore = new boolean[count]; // true => ignore name484int remaining = count; // number of names remaining485486// multiple passes to eliminate all occurrences of "." and "name/.."487int prevRemaining;488do {489prevRemaining = remaining;490int prevName = -1;491for (int i=0; i<count; i++) {492if (ignore[i])493continue;494495String name = elementAsString(i);496497// not "." or ".."498if (name.length() > 2) {499prevName = i;500continue;501}502503// "." or something else504if (name.length() == 1) {505// ignore "."506if (name.charAt(0) == '.') {507ignore[i] = true;508remaining--;509} else {510prevName = i;511}512continue;513}514515// not ".."516if (name.charAt(0) != '.' || name.charAt(1) != '.') {517prevName = i;518continue;519}520521// ".." found522if (prevName >= 0) {523// name/<ignored>/.. found so mark name and ".." to be524// ignored525ignore[prevName] = true;526ignore[i] = true;527remaining = remaining - 2;528prevName = -1;529} else {530// Cases:531// C:\<ignored>\..532// \\server\\share\<ignored>\..533// \<ignored>..534if (isAbsolute() || type == WindowsPathType.DIRECTORY_RELATIVE) {535boolean hasPrevious = false;536for (int j=0; j<i; j++) {537if (!ignore[j]) {538hasPrevious = true;539break;540}541}542if (!hasPrevious) {543// all proceeding names are ignored544ignore[i] = true;545remaining--;546}547}548}549}550} while (prevRemaining > remaining);551552// no redundant names553if (remaining == count)554return this;555556// corner case - all names removed557if (remaining == 0) {558return root.isEmpty() ? emptyPath() : getRoot();559}560561// re-constitute the path from the remaining names.562StringBuilder result = new StringBuilder();563if (root != null)564result.append(root);565for (int i=0; i<count; i++) {566if (!ignore[i]) {567result.append(getName(i));568result.append("\\");569}570}571572// drop trailing slash in result573result.setLength(result.length()-1);574return createFromNormalizedPath(getFileSystem(), result.toString());575}576577@Override578public WindowsPath resolve(Path obj) {579WindowsPath other = toWindowsPath(obj);580if (other.isEmpty())581return this;582if (other.isAbsolute())583return other;584585switch (other.type) {586case RELATIVE: {587String result;588if (path.endsWith("\\") || (root.length() == path.length())) {589result = path + other.path;590} else {591result = path + "\\" + other.path;592}593return new WindowsPath(getFileSystem(), type, root, result);594}595596case DIRECTORY_RELATIVE: {597String result;598if (root.endsWith("\\")) {599result = root + other.path.substring(1);600} else {601result = root + other.path;602}603return createFromNormalizedPath(getFileSystem(), result);604}605606case DRIVE_RELATIVE: {607if (!root.endsWith("\\"))608return other;609// if different roots then return other610String thisRoot = root.substring(0, root.length()-1);611if (!thisRoot.equalsIgnoreCase(other.root))612return other;613// same roots614String remaining = other.path.substring(other.root.length());615String result;616if (path.endsWith("\\")) {617result = path + remaining;618} else {619result = path + "\\" + remaining;620}621return createFromNormalizedPath(getFileSystem(), result);622}623624default:625throw new AssertionError();626}627}628629// generate offset array630private void initOffsets() {631if (offsets == null) {632ArrayList<Integer> list = new ArrayList<>();633if (isEmpty()) {634// empty path considered to have one name element635list.add(0);636} else {637int start = root.length();638int off = root.length();639while (off < path.length()) {640if (path.charAt(off) != '\\') {641off++;642} else {643list.add(start);644start = ++off;645}646}647if (start != off)648list.add(start);649}650synchronized (this) {651if (offsets == null)652offsets = list.toArray(new Integer[list.size()]);653}654}655}656657@Override658public int getNameCount() {659initOffsets();660return offsets.length;661}662663private String elementAsString(int i) {664initOffsets();665if (i == (offsets.length-1))666return path.substring(offsets[i]);667return path.substring(offsets[i], offsets[i+1]-1);668}669670@Override671public WindowsPath getName(int index) {672initOffsets();673if (index < 0 || index >= offsets.length)674throw new IllegalArgumentException();675return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", elementAsString(index));676}677678@Override679public WindowsPath subpath(int beginIndex, int endIndex) {680initOffsets();681if (beginIndex < 0)682throw new IllegalArgumentException();683if (beginIndex >= offsets.length)684throw new IllegalArgumentException();685if (endIndex > offsets.length)686throw new IllegalArgumentException();687if (beginIndex >= endIndex)688throw new IllegalArgumentException();689690StringBuilder sb = new StringBuilder();691for (int i = beginIndex; i < endIndex; i++) {692sb.append(elementAsString(i));693if (i != (endIndex-1))694sb.append("\\");695}696return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", sb.toString());697}698699@Override700public boolean startsWith(Path obj) {701if (!(Objects.requireNonNull(obj) instanceof WindowsPath))702return false;703WindowsPath other = (WindowsPath)obj;704705// if this path has a root component the given path's root must match706if (!this.root.equalsIgnoreCase(other.root)) {707return false;708}709710// empty path starts with itself711if (other.isEmpty())712return this.isEmpty();713714// roots match so compare elements715int thisCount = getNameCount();716int otherCount = other.getNameCount();717if (otherCount <= thisCount) {718while (--otherCount >= 0) {719String thisElement = this.elementAsString(otherCount);720String otherElement = other.elementAsString(otherCount);721// FIXME: should compare in uppercase722if (!thisElement.equalsIgnoreCase(otherElement))723return false;724}725return true;726}727return false;728}729730@Override731public boolean endsWith(Path obj) {732if (!(Objects.requireNonNull(obj) instanceof WindowsPath))733return false;734WindowsPath other = (WindowsPath)obj;735736// other path is longer737if (other.path.length() > this.path.length()) {738return false;739}740741// empty path ends in itself742if (other.isEmpty()) {743return this.isEmpty();744}745746int thisCount = this.getNameCount();747int otherCount = other.getNameCount();748749// given path has more elements that this path750if (otherCount > thisCount) {751return false;752}753754// compare roots755if (other.root.length() > 0) {756if (otherCount < thisCount)757return false;758// FIXME: should compare in uppercase759if (!this.root.equalsIgnoreCase(other.root))760return false;761}762763// match last 'otherCount' elements764int off = thisCount - otherCount;765while (--otherCount >= 0) {766String thisElement = this.elementAsString(off + otherCount);767String otherElement = other.elementAsString(otherCount);768// FIXME: should compare in uppercase769if (!thisElement.equalsIgnoreCase(otherElement))770return false;771}772return true;773}774775@Override776public int compareTo(Path obj) {777if (obj == null)778throw new NullPointerException();779String s1 = path;780String s2 = ((WindowsPath)obj).path;781int n1 = s1.length();782int n2 = s2.length();783int min = Math.min(n1, n2);784for (int i = 0; i < min; i++) {785char c1 = s1.charAt(i);786char c2 = s2.charAt(i);787if (c1 != c2) {788c1 = Character.toUpperCase(c1);789c2 = Character.toUpperCase(c2);790if (c1 != c2) {791return c1 - c2;792}793}794}795return n1 - n2;796}797798@Override799public boolean equals(Object obj) {800if (obj instanceof WindowsPath path) {801return compareTo(path) == 0;802}803return false;804}805806@Override807public int hashCode() {808// OK if two or more threads compute hash809int h = hash;810if (h == 0) {811for (int i = 0; i< path.length(); i++) {812h = 31*h + Character.toUpperCase(path.charAt(i));813}814hash = h;815}816return h;817}818819@Override820public String toString() {821return path;822}823824// -- file operations --825826// package-private827long openForReadAttributeAccess(boolean followLinks)828throws WindowsException829{830int flags = FILE_FLAG_BACKUP_SEMANTICS;831if (!followLinks)832flags |= FILE_FLAG_OPEN_REPARSE_POINT;833try {834return openFileForReadAttributeAccess(flags);835} catch (WindowsException e) {836if (followLinks && e.lastError() == ERROR_CANT_ACCESS_FILE) {837// Object could be a Unix domain socket838try {839return openSocketForReadAttributeAccess();840} catch (WindowsException ignore) {}841}842throw e;843}844}845846private long openFileForReadAttributeAccess(int flags)847throws WindowsException848{849return CreateFile(getPathForWin32Calls(),850FILE_READ_ATTRIBUTES,851(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),8520L,853OPEN_EXISTING,854flags);855}856857/**858* Returns a handle to the file if it is a socket.859* Throws WindowsException if file is not a socket860*/861private long openSocketForReadAttributeAccess()862throws WindowsException863{864// needs to specify FILE_FLAG_OPEN_REPARSE_POINT if the file is a socket865int flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT;866867long handle = openFileForReadAttributeAccess(flags);868869try {870WindowsFileAttributes attrs = WindowsFileAttributes.readAttributes(handle);871if (!attrs.isUnixDomainSocket()) {872throw new WindowsException("not a socket");873}874return handle;875} catch (WindowsException e) {876CloseHandle(handle);877throw e;878}879}880881void checkRead() {882@SuppressWarnings("removal")883SecurityManager sm = System.getSecurityManager();884if (sm != null) {885sm.checkRead(getPathForPermissionCheck());886}887}888889void checkWrite() {890@SuppressWarnings("removal")891SecurityManager sm = System.getSecurityManager();892if (sm != null) {893sm.checkWrite(getPathForPermissionCheck());894}895}896897void checkDelete() {898@SuppressWarnings("removal")899SecurityManager sm = System.getSecurityManager();900if (sm != null) {901sm.checkDelete(getPathForPermissionCheck());902}903}904905@Override906public URI toUri() {907return WindowsUriSupport.toUri(this);908}909910@Override911public WindowsPath toAbsolutePath() {912if (isAbsolute())913return this;914915// permission check as per spec916@SuppressWarnings("removal")917SecurityManager sm = System.getSecurityManager();918if (sm != null) {919sm.checkPropertyAccess("user.dir");920}921922try {923return createFromNormalizedPath(getFileSystem(), getAbsolutePath());924} catch (WindowsException x) {925throw new IOError(new IOException(x.getMessage()));926}927}928929@Override930public WindowsPath toRealPath(LinkOption... options) throws IOException {931checkRead();932String rp = WindowsLinkSupport.getRealPath(this, Util.followLinks(options));933return createFromNormalizedPath(getFileSystem(), rp);934}935936@Override937public WatchKey register(WatchService watcher,938WatchEvent.Kind<?>[] events,939WatchEvent.Modifier... modifiers)940throws IOException941{942if (watcher == null)943throw new NullPointerException();944if (!(watcher instanceof WindowsWatchService))945throw new ProviderMismatchException();946947// When a security manager is set then we need to make a defensive948// copy of the modifiers and check for the Windows specific FILE_TREE949// modifier. When the modifier is present then check that permission950// has been granted recursively.951@SuppressWarnings("removal")952SecurityManager sm = System.getSecurityManager();953if (sm != null) {954boolean watchSubtree = false;955final int ml = modifiers.length;956if (ml > 0) {957modifiers = Arrays.copyOf(modifiers, ml);958int i=0;959while (i < ml) {960if (ExtendedOptions.FILE_TREE.matches(modifiers[i++])) {961watchSubtree = true;962break;963}964}965}966String s = getPathForPermissionCheck();967sm.checkRead(s);968if (watchSubtree)969sm.checkRead(s + "\\-");970}971972return ((WindowsWatchService)watcher).register(this, events, modifiers);973}974}975976977