Path: blob/master/src/java.base/windows/classes/java/io/WinNTFileSystem.java
67794 views
/*1* Copyright (c) 2001, 2022, 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 java.io;2627import java.io.File;28import java.nio.file.InvalidPathException;29import java.nio.file.Path;30import java.util.BitSet;31import java.util.Locale;32import java.util.Properties;33import sun.security.action.GetPropertyAction;3435/**36* Unicode-aware FileSystem for Windows NT/2000.37*38* @author Konstantin Kladko39* @since 1.440*/41class WinNTFileSystem extends FileSystem {4243private final char slash;44private final char altSlash;45private final char semicolon;46private final String userDir;4748// Whether to enable alternative data streams (ADS) by suppressing49// checking the path for invalid characters, in particular ":".50// By default, ADS support is enabled and will be disabled if and51// only if the property is set, ignoring case, to the string "false".52private static final boolean ENABLE_ADS;53static {54String enableADS = GetPropertyAction.privilegedGetProperty("jdk.io.File.enableADS");55if (enableADS != null) {56ENABLE_ADS = !enableADS.equalsIgnoreCase(Boolean.FALSE.toString());57} else {58ENABLE_ADS = true;59}60}6162public WinNTFileSystem() {63Properties props = GetPropertyAction.privilegedGetProperties();64slash = props.getProperty("file.separator").charAt(0);65semicolon = props.getProperty("path.separator").charAt(0);66altSlash = (this.slash == '\\') ? '/' : '\\';67userDir = normalize(props.getProperty("user.dir"));68cache = useCanonCaches ? new ExpiringCache() : null;69prefixCache = useCanonPrefixCache ? new ExpiringCache() : null;70}7172private boolean isSlash(char c) {73return (c == '\\') || (c == '/');74}7576private boolean isLetter(char c) {77return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));78}7980private String slashify(String p) {81if (!p.isEmpty() && p.charAt(0) != slash) return slash + p;82else return p;83}8485/* -- Normalization and construction -- */8687@Override88public char getSeparator() {89return slash;90}9192@Override93public char getPathSeparator() {94return semicolon;95}9697/* Check that the given pathname is normal. If not, invoke the real98normalizer on the part of the pathname that requires normalization.99This way we iterate through the whole pathname string only once. */100@Override101public String normalize(String path) {102int n = path.length();103char slash = this.slash;104char altSlash = this.altSlash;105char prev = 0;106for (int i = 0; i < n; i++) {107char c = path.charAt(i);108if (c == altSlash)109return normalize(path, n, (prev == slash) ? i - 1 : i);110if ((c == slash) && (prev == slash) && (i > 1))111return normalize(path, n, i - 1);112if ((c == ':') && (i > 1))113return normalize(path, n, 0);114prev = c;115}116if (prev == slash) return normalize(path, n, n - 1);117return path;118}119120/* Normalize the given pathname, whose length is len, starting at the given121offset; everything before this offset is already normal. */122private String normalize(String path, int len, int off) {123if (len == 0) return path;124if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */125int src;126char slash = this.slash;127StringBuilder sb = new StringBuilder(len);128129if (off == 0) {130/* Complete normalization, including prefix */131src = normalizePrefix(path, len, sb);132} else {133/* Partial normalization */134src = off;135sb.append(path, 0, off);136}137138/* Remove redundant slashes from the remainder of the path, forcing all139slashes into the preferred slash */140while (src < len) {141char c = path.charAt(src++);142if (isSlash(c)) {143while ((src < len) && isSlash(path.charAt(src))) src++;144if (src == len) {145/* Check for trailing separator */146int sn = sb.length();147if ((sn == 2) && (sb.charAt(1) == ':')) {148/* "z:\\" */149sb.append(slash);150break;151}152if (sn == 0) {153/* "\\" */154sb.append(slash);155break;156}157if ((sn == 1) && (isSlash(sb.charAt(0)))) {158/* "\\\\" is not collapsed to "\\" because "\\\\" marks159the beginning of a UNC pathname. Even though it is160not, by itself, a valid UNC pathname, we leave it as161is in order to be consistent with the win32 APIs,162which treat this case as an invalid UNC pathname163rather than as an alias for the root directory of164the current drive. */165sb.append(slash);166break;167}168/* Path does not denote a root directory, so do not append169trailing slash */170break;171} else {172sb.append(slash);173}174} else {175sb.append(c);176}177}178179return sb.toString();180}181182/* A normal Win32 pathname contains no duplicate slashes, except possibly183for a UNC prefix, and does not end with a slash. It may be the empty184string. Normalized Win32 pathnames have the convenient property that185the length of the prefix almost uniquely identifies the type of the path186and whether it is absolute or relative:1871880 relative to both drive and directory1891 drive-relative (begins with '\\')1902 absolute UNC (if first char is '\\'),191else directory-relative (has form "z:foo")1923 absolute local pathname (begins with "z:\\")193*/194private int normalizePrefix(String path, int len, StringBuilder sb) {195int src = 0;196while ((src < len) && isSlash(path.charAt(src))) src++;197char c;198if ((len - src >= 2)199&& isLetter(c = path.charAt(src))200&& path.charAt(src + 1) == ':') {201/* Remove leading slashes if followed by drive specifier.202This hack is necessary to support file URLs containing drive203specifiers (e.g., "file://c:/path"). As a side effect,204"/c:/path" can be used as an alternative to "c:/path". */205sb.append(c);206sb.append(':');207src += 2;208} else {209src = 0;210if ((len >= 2)211&& isSlash(path.charAt(0))212&& isSlash(path.charAt(1))) {213/* UNC pathname: Retain first slash; leave src pointed at214second slash so that further slashes will be collapsed215into the second slash. The result will be a pathname216beginning with "\\\\" followed (most likely) by a host217name. */218src = 1;219sb.append(slash);220}221}222return src;223}224225@Override226public int prefixLength(String path) {227char slash = this.slash;228int n = path.length();229if (n == 0) return 0;230char c0 = path.charAt(0);231char c1 = (n > 1) ? path.charAt(1) : 0;232if (c0 == slash) {233if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */234return 1; /* Drive-relative "\\foo" */235}236if (isLetter(c0) && (c1 == ':')) {237if ((n > 2) && (path.charAt(2) == slash))238return 3; /* Absolute local pathname "z:\\foo" */239return 2; /* Directory-relative "z:foo" */240}241return 0; /* Completely relative */242}243244@Override245public String resolve(String parent, String child) {246int pn = parent.length();247if (pn == 0) return child;248int cn = child.length();249if (cn == 0) return parent;250251String c = child;252int childStart = 0;253int parentEnd = pn;254255boolean isDirectoryRelative =256pn == 2 && isLetter(parent.charAt(0)) && parent.charAt(1) == ':';257258if ((cn > 1) && (c.charAt(0) == slash)) {259if (c.charAt(1) == slash) {260/* Drop prefix when child is a UNC pathname */261childStart = 2;262} else if (!isDirectoryRelative) {263/* Drop prefix when child is drive-relative */264childStart = 1;265266}267if (cn == childStart) { // Child is double slash268if (parent.charAt(pn - 1) == slash)269return parent.substring(0, pn - 1);270return parent;271}272}273274if (parent.charAt(pn - 1) == slash)275parentEnd--;276277int strlen = parentEnd + cn - childStart;278char[] theChars = null;279if (child.charAt(childStart) == slash || isDirectoryRelative) {280theChars = new char[strlen];281parent.getChars(0, parentEnd, theChars, 0);282child.getChars(childStart, cn, theChars, parentEnd);283} else {284theChars = new char[strlen + 1];285parent.getChars(0, parentEnd, theChars, 0);286theChars[parentEnd] = slash;287child.getChars(childStart, cn, theChars, parentEnd + 1);288}289return new String(theChars);290}291292@Override293public String getDefaultParent() {294return ("" + slash);295}296297@Override298public String fromURIPath(String path) {299String p = path;300if ((p.length() > 2) && (p.charAt(2) == ':')) {301// "/c:/foo" --> "c:/foo"302p = p.substring(1);303// "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"304if ((p.length() > 3) && p.endsWith("/"))305p = p.substring(0, p.length() - 1);306} else if ((p.length() > 1) && p.endsWith("/")) {307// "/foo/" --> "/foo"308p = p.substring(0, p.length() - 1);309}310return p;311}312313/* -- Path operations -- */314315@Override316public boolean isAbsolute(File f) {317int pl = f.getPrefixLength();318return (((pl == 2) && (f.getPath().charAt(0) == slash))319|| (pl == 3));320}321322@Override323public boolean isInvalid(File f) {324if (f.getPath().indexOf('\u0000') >= 0)325return true;326327if (ENABLE_ADS)328return false;329330// Invalid if there is a ":" at a position greater than 1, or if there331// is a ":" at position 1 and the first character is not a letter332String pathname = f.getPath();333int lastColon = pathname.lastIndexOf(":");334335// Valid if there is no ":" present or if the last ":" present is336// at index 1 and the first character is a latter337if (lastColon < 0 ||338(lastColon == 1 && isLetter(pathname.charAt(0))))339return false;340341// Invalid if path creation fails342Path path = null;343try {344path = sun.nio.fs.DefaultFileSystemProvider.theFileSystem().getPath(pathname);345return false;346} catch (InvalidPathException ignored) {347}348349return true;350}351352@Override353public String resolve(File f) {354String path = f.getPath();355int pl = f.getPrefixLength();356if ((pl == 2) && (path.charAt(0) == slash))357return path; /* UNC */358if (pl == 3)359return path; /* Absolute local */360if (pl == 0)361return getUserPath() + slashify(path); /* Completely relative */362if (pl == 1) { /* Drive-relative */363String up = getUserPath();364String ud = getDrive(up);365if (ud != null) return ud + path;366return up + path; /* User dir is a UNC path */367}368if (pl == 2) { /* Directory-relative */369String up = getUserPath();370String ud = getDrive(up);371if ((ud != null) && path.startsWith(ud))372return up + slashify(path.substring(2));373char drive = path.charAt(0);374String dir = getDriveDirectory(drive);375if (dir != null) {376/* When resolving a directory-relative path that refers to a377drive other than the current drive, insist that the caller378have read permission on the result */379String p = drive + (':' + dir + slashify(path.substring(2)));380@SuppressWarnings("removal")381SecurityManager security = System.getSecurityManager();382try {383if (security != null) security.checkRead(p);384} catch (SecurityException x) {385/* Don't disclose the drive's directory in the exception */386throw new SecurityException("Cannot resolve path " + path);387}388return p;389}390return drive + ":" + slashify(path.substring(2)); /* fake it */391}392throw new InternalError("Unresolvable path: " + path);393}394395private String getUserPath() {396/* For both compatibility and security,397we must look this up every time */398@SuppressWarnings("removal")399SecurityManager sm = System.getSecurityManager();400if (sm != null) {401sm.checkPropertyAccess("user.dir");402}403return normalize(userDir);404}405406private String getDrive(String path) {407int pl = prefixLength(path);408return (pl == 3) ? path.substring(0, 2) : null;409}410411private static String[] driveDirCache = new String[26];412413private static int driveIndex(char d) {414if ((d >= 'a') && (d <= 'z')) return d - 'a';415if ((d >= 'A') && (d <= 'Z')) return d - 'A';416return -1;417}418419private native String getDriveDirectory(int drive);420421private String getDriveDirectory(char drive) {422int i = driveIndex(drive);423if (i < 0) return null;424String s = driveDirCache[i];425if (s != null) return s;426s = getDriveDirectory(i + 1);427driveDirCache[i] = s;428return s;429}430431// Caches for canonicalization results to improve startup performance.432// The first cache handles repeated canonicalizations of the same path433// name. The prefix cache handles repeated canonicalizations within the434// same directory, and must not create results differing from the true435// canonicalization algorithm in canonicalize_md.c. For this reason the436// prefix cache is conservative and is not used for complex path names.437private final ExpiringCache cache;438private final ExpiringCache prefixCache;439440@Override441public String canonicalize(String path) throws IOException {442// If path is a drive letter only then skip canonicalization443int len = path.length();444if ((len == 2) &&445(isLetter(path.charAt(0))) &&446(path.charAt(1) == ':')) {447char c = path.charAt(0);448if ((c >= 'A') && (c <= 'Z'))449return path;450return "" + ((char) (c-32)) + ':';451} else if ((len == 3) &&452(isLetter(path.charAt(0))) &&453(path.charAt(1) == ':') &&454(path.charAt(2) == '\\')) {455char c = path.charAt(0);456if ((c >= 'A') && (c <= 'Z'))457return path;458return "" + ((char) (c-32)) + ':' + '\\';459}460if (!useCanonCaches) {461return canonicalize0(path);462} else {463String res = cache.get(path);464if (res == null) {465String dir = null;466String resDir = null;467if (useCanonPrefixCache) {468dir = parentOrNull(path);469if (dir != null) {470resDir = prefixCache.get(dir);471if (resDir != null) {472/*473* Hit only in prefix cache; full path is canonical,474* but we need to get the canonical name of the file475* in this directory to get the appropriate476* capitalization477*/478String filename = path.substring(1 + dir.length());479res = canonicalizeWithPrefix(resDir, filename);480cache.put(dir + File.separatorChar + filename, res);481}482}483}484if (res == null) {485res = canonicalize0(path);486cache.put(path, res);487if (useCanonPrefixCache && dir != null) {488resDir = parentOrNull(res);489if (resDir != null) {490File f = new File(res);491if (f.exists() && !f.isDirectory()) {492prefixCache.put(dir, resDir);493}494}495}496}497}498return res;499}500}501502private native String canonicalize0(String path)503throws IOException;504505private String canonicalizeWithPrefix(String canonicalPrefix,506String filename) throws IOException507{508return canonicalizeWithPrefix0(canonicalPrefix,509canonicalPrefix + File.separatorChar + filename);510}511512// Run the canonicalization operation assuming that the prefix513// (everything up to the last filename) is canonical; just gets514// the canonical name of the last element of the path515private native String canonicalizeWithPrefix0(String canonicalPrefix,516String pathWithCanonicalPrefix)517throws IOException;518519// Best-effort attempt to get parent of this path; used for520// optimization of filename canonicalization. This must return null for521// any cases where the code in canonicalize_md.c would throw an522// exception or otherwise deal with non-simple pathnames like handling523// of "." and "..". It may conservatively return null in other524// situations as well. Returning null will cause the underlying525// (expensive) canonicalization routine to be called.526private static String parentOrNull(String path) {527if (path == null) return null;528char sep = File.separatorChar;529char altSep = '/';530int last = path.length() - 1;531int idx = last;532int adjacentDots = 0;533int nonDotCount = 0;534while (idx > 0) {535char c = path.charAt(idx);536if (c == '.') {537if (++adjacentDots >= 2) {538// Punt on pathnames containing . and ..539return null;540}541if (nonDotCount == 0) {542// Punt on pathnames ending in a .543return null;544}545} else if (c == sep) {546if (adjacentDots == 1 && nonDotCount == 0) {547// Punt on pathnames containing . and ..548return null;549}550if (idx == 0 ||551idx >= last - 1 ||552path.charAt(idx - 1) == sep ||553path.charAt(idx - 1) == altSep) {554// Punt on pathnames containing adjacent slashes555// toward the end556return null;557}558return path.substring(0, idx);559} else if (c == altSep) {560// Punt on pathnames containing both backward and561// forward slashes562return null;563} else if (c == '*' || c == '?') {564// Punt on pathnames containing wildcards565return null;566} else {567++nonDotCount;568adjacentDots = 0;569}570--idx;571}572return null;573}574575/* -- Attribute accessors -- */576577@Override578public native int getBooleanAttributes(File f);579580@Override581public native boolean checkAccess(File f, int access);582583@Override584public native long getLastModifiedTime(File f);585586@Override587public native long getLength(File f);588589@Override590public native boolean setPermission(File f, int access, boolean enable,591boolean owneronly);592593/* -- File operations -- */594595@Override596public native boolean createFileExclusively(String path)597throws IOException;598599@Override600public native String[] list(File f);601602@Override603public native boolean createDirectory(File f);604605@Override606public native boolean setLastModifiedTime(File f, long time);607608@Override609public native boolean setReadOnly(File f);610611@Override612public boolean delete(File f) {613// Keep canonicalization caches in sync after file deletion614// and renaming operations. Could be more clever than this615// (i.e., only remove/update affected entries) but probably616// not worth it since these entries expire after 30 seconds617// anyway.618if (useCanonCaches) {619cache.clear();620}621if (useCanonPrefixCache) {622prefixCache.clear();623}624return delete0(f);625}626627private native boolean delete0(File f);628629@Override630public boolean rename(File f1, File f2) {631// Keep canonicalization caches in sync after file deletion632// and renaming operations. Could be more clever than this633// (i.e., only remove/update affected entries) but probably634// not worth it since these entries expire after 30 seconds635// anyway.636if (useCanonCaches) {637cache.clear();638}639if (useCanonPrefixCache) {640prefixCache.clear();641}642return rename0(f1, f2);643}644645private native boolean rename0(File f1, File f2);646647/* -- Filesystem interface -- */648649@Override650public File[] listRoots() {651return BitSet652.valueOf(new long[] {listRoots0()})653.stream()654.mapToObj(i -> new File((char)('A' + i) + ":" + slash))655.filter(f -> access(f.getPath()) && f.exists())656.toArray(File[]::new);657}658659private static native int listRoots0();660661private boolean access(String path) {662try {663@SuppressWarnings("removal")664SecurityManager security = System.getSecurityManager();665if (security != null) security.checkRead(path);666return true;667} catch (SecurityException x) {668return false;669}670}671672/* -- Disk usage -- */673674@Override675public long getSpace(File f, int t) {676if (f.exists()) {677return getSpace0(f, t);678}679return 0;680}681682private native long getSpace0(File f, int t);683684/* -- Basic infrastructure -- */685686// Obtain maximum file component length from GetVolumeInformation which687// expects the path to be null or a root component ending in a backslash688private native int getNameMax0(String path);689690@Override691public int getNameMax(String path) {692String s = null;693if (path != null) {694File f = new File(path);695if (f.isAbsolute()) {696Path root = f.toPath().getRoot();697if (root != null) {698s = root.toString();699if (!s.endsWith("\\")) {700s = s + "\\";701}702}703}704}705return getNameMax0(s);706}707708@Override709public int compare(File f1, File f2) {710return f1.getPath().compareToIgnoreCase(f2.getPath());711}712713@Override714public int hashCode(File f) {715/* Could make this more efficient: String.hashCodeIgnoreCase */716return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;717}718719private static native void initIDs();720721static {722initIDs();723}724}725726727