Path: blob/master/src/java.base/windows/classes/java/io/WinNTFileSystem.java
41133 views
/*1* Copyright (c) 2001, 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 java.io;2627import java.io.File;28import java.nio.file.Path;29import java.util.BitSet;30import java.util.Locale;31import java.util.Properties;32import sun.security.action.GetPropertyAction;3334/**35* Unicode-aware FileSystem for Windows NT/2000.36*37* @author Konstantin Kladko38* @since 1.439*/40class WinNTFileSystem extends FileSystem {4142private final char slash;43private final char altSlash;44private final char semicolon;45private final String userDir;4647public WinNTFileSystem() {48Properties props = GetPropertyAction.privilegedGetProperties();49slash = props.getProperty("file.separator").charAt(0);50semicolon = props.getProperty("path.separator").charAt(0);51altSlash = (this.slash == '\\') ? '/' : '\\';52userDir = normalize(props.getProperty("user.dir"));53cache = useCanonCaches ? new ExpiringCache() : null;54prefixCache = useCanonPrefixCache ? new ExpiringCache() : null;55}5657private boolean isSlash(char c) {58return (c == '\\') || (c == '/');59}6061private boolean isLetter(char c) {62return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));63}6465private String slashify(String p) {66if (!p.isEmpty() && p.charAt(0) != slash) return slash + p;67else return p;68}6970/* -- Normalization and construction -- */7172@Override73public char getSeparator() {74return slash;75}7677@Override78public char getPathSeparator() {79return semicolon;80}8182/* Check that the given pathname is normal. If not, invoke the real83normalizer on the part of the pathname that requires normalization.84This way we iterate through the whole pathname string only once. */85@Override86public String normalize(String path) {87int n = path.length();88char slash = this.slash;89char altSlash = this.altSlash;90char prev = 0;91for (int i = 0; i < n; i++) {92char c = path.charAt(i);93if (c == altSlash)94return normalize(path, n, (prev == slash) ? i - 1 : i);95if ((c == slash) && (prev == slash) && (i > 1))96return normalize(path, n, i - 1);97if ((c == ':') && (i > 1))98return normalize(path, n, 0);99prev = c;100}101if (prev == slash) return normalize(path, n, n - 1);102return path;103}104105/* Normalize the given pathname, whose length is len, starting at the given106offset; everything before this offset is already normal. */107private String normalize(String path, int len, int off) {108if (len == 0) return path;109if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */110int src;111char slash = this.slash;112StringBuilder sb = new StringBuilder(len);113114if (off == 0) {115/* Complete normalization, including prefix */116src = normalizePrefix(path, len, sb);117} else {118/* Partial normalization */119src = off;120sb.append(path, 0, off);121}122123/* Remove redundant slashes from the remainder of the path, forcing all124slashes into the preferred slash */125while (src < len) {126char c = path.charAt(src++);127if (isSlash(c)) {128while ((src < len) && isSlash(path.charAt(src))) src++;129if (src == len) {130/* Check for trailing separator */131int sn = sb.length();132if ((sn == 2) && (sb.charAt(1) == ':')) {133/* "z:\\" */134sb.append(slash);135break;136}137if (sn == 0) {138/* "\\" */139sb.append(slash);140break;141}142if ((sn == 1) && (isSlash(sb.charAt(0)))) {143/* "\\\\" is not collapsed to "\\" because "\\\\" marks144the beginning of a UNC pathname. Even though it is145not, by itself, a valid UNC pathname, we leave it as146is in order to be consistent with the win32 APIs,147which treat this case as an invalid UNC pathname148rather than as an alias for the root directory of149the current drive. */150sb.append(slash);151break;152}153/* Path does not denote a root directory, so do not append154trailing slash */155break;156} else {157sb.append(slash);158}159} else {160sb.append(c);161}162}163164return sb.toString();165}166167/* A normal Win32 pathname contains no duplicate slashes, except possibly168for a UNC prefix, and does not end with a slash. It may be the empty169string. Normalized Win32 pathnames have the convenient property that170the length of the prefix almost uniquely identifies the type of the path171and whether it is absolute or relative:1721730 relative to both drive and directory1741 drive-relative (begins with '\\')1752 absolute UNC (if first char is '\\'),176else directory-relative (has form "z:foo")1773 absolute local pathname (begins with "z:\\")178*/179private int normalizePrefix(String path, int len, StringBuilder sb) {180int src = 0;181while ((src < len) && isSlash(path.charAt(src))) src++;182char c;183if ((len - src >= 2)184&& isLetter(c = path.charAt(src))185&& path.charAt(src + 1) == ':') {186/* Remove leading slashes if followed by drive specifier.187This hack is necessary to support file URLs containing drive188specifiers (e.g., "file://c:/path"). As a side effect,189"/c:/path" can be used as an alternative to "c:/path". */190sb.append(c);191sb.append(':');192src += 2;193} else {194src = 0;195if ((len >= 2)196&& isSlash(path.charAt(0))197&& isSlash(path.charAt(1))) {198/* UNC pathname: Retain first slash; leave src pointed at199second slash so that further slashes will be collapsed200into the second slash. The result will be a pathname201beginning with "\\\\" followed (most likely) by a host202name. */203src = 1;204sb.append(slash);205}206}207return src;208}209210@Override211public int prefixLength(String path) {212char slash = this.slash;213int n = path.length();214if (n == 0) return 0;215char c0 = path.charAt(0);216char c1 = (n > 1) ? path.charAt(1) : 0;217if (c0 == slash) {218if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */219return 1; /* Drive-relative "\\foo" */220}221if (isLetter(c0) && (c1 == ':')) {222if ((n > 2) && (path.charAt(2) == slash))223return 3; /* Absolute local pathname "z:\\foo" */224return 2; /* Directory-relative "z:foo" */225}226return 0; /* Completely relative */227}228229@Override230public String resolve(String parent, String child) {231int pn = parent.length();232if (pn == 0) return child;233int cn = child.length();234if (cn == 0) return parent;235236String c = child;237int childStart = 0;238int parentEnd = pn;239240boolean isDirectoryRelative =241pn == 2 && isLetter(parent.charAt(0)) && parent.charAt(1) == ':';242243if ((cn > 1) && (c.charAt(0) == slash)) {244if (c.charAt(1) == slash) {245/* Drop prefix when child is a UNC pathname */246childStart = 2;247} else if (!isDirectoryRelative) {248/* Drop prefix when child is drive-relative */249childStart = 1;250251}252if (cn == childStart) { // Child is double slash253if (parent.charAt(pn - 1) == slash)254return parent.substring(0, pn - 1);255return parent;256}257}258259if (parent.charAt(pn - 1) == slash)260parentEnd--;261262int strlen = parentEnd + cn - childStart;263char[] theChars = null;264if (child.charAt(childStart) == slash || isDirectoryRelative) {265theChars = new char[strlen];266parent.getChars(0, parentEnd, theChars, 0);267child.getChars(childStart, cn, theChars, parentEnd);268} else {269theChars = new char[strlen + 1];270parent.getChars(0, parentEnd, theChars, 0);271theChars[parentEnd] = slash;272child.getChars(childStart, cn, theChars, parentEnd + 1);273}274return new String(theChars);275}276277@Override278public String getDefaultParent() {279return ("" + slash);280}281282@Override283public String fromURIPath(String path) {284String p = path;285if ((p.length() > 2) && (p.charAt(2) == ':')) {286// "/c:/foo" --> "c:/foo"287p = p.substring(1);288// "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"289if ((p.length() > 3) && p.endsWith("/"))290p = p.substring(0, p.length() - 1);291} else if ((p.length() > 1) && p.endsWith("/")) {292// "/foo/" --> "/foo"293p = p.substring(0, p.length() - 1);294}295return p;296}297298/* -- Path operations -- */299300@Override301public boolean isAbsolute(File f) {302int pl = f.getPrefixLength();303return (((pl == 2) && (f.getPath().charAt(0) == slash))304|| (pl == 3));305}306307@Override308public String resolve(File f) {309String path = f.getPath();310int pl = f.getPrefixLength();311if ((pl == 2) && (path.charAt(0) == slash))312return path; /* UNC */313if (pl == 3)314return path; /* Absolute local */315if (pl == 0)316return getUserPath() + slashify(path); /* Completely relative */317if (pl == 1) { /* Drive-relative */318String up = getUserPath();319String ud = getDrive(up);320if (ud != null) return ud + path;321return up + path; /* User dir is a UNC path */322}323if (pl == 2) { /* Directory-relative */324String up = getUserPath();325String ud = getDrive(up);326if ((ud != null) && path.startsWith(ud))327return up + slashify(path.substring(2));328char drive = path.charAt(0);329String dir = getDriveDirectory(drive);330if (dir != null) {331/* When resolving a directory-relative path that refers to a332drive other than the current drive, insist that the caller333have read permission on the result */334String p = drive + (':' + dir + slashify(path.substring(2)));335@SuppressWarnings("removal")336SecurityManager security = System.getSecurityManager();337try {338if (security != null) security.checkRead(p);339} catch (SecurityException x) {340/* Don't disclose the drive's directory in the exception */341throw new SecurityException("Cannot resolve path " + path);342}343return p;344}345return drive + ":" + slashify(path.substring(2)); /* fake it */346}347throw new InternalError("Unresolvable path: " + path);348}349350private String getUserPath() {351/* For both compatibility and security,352we must look this up every time */353@SuppressWarnings("removal")354SecurityManager sm = System.getSecurityManager();355if (sm != null) {356sm.checkPropertyAccess("user.dir");357}358return normalize(userDir);359}360361private String getDrive(String path) {362int pl = prefixLength(path);363return (pl == 3) ? path.substring(0, 2) : null;364}365366private static String[] driveDirCache = new String[26];367368private static int driveIndex(char d) {369if ((d >= 'a') && (d <= 'z')) return d - 'a';370if ((d >= 'A') && (d <= 'Z')) return d - 'A';371return -1;372}373374private native String getDriveDirectory(int drive);375376private String getDriveDirectory(char drive) {377int i = driveIndex(drive);378if (i < 0) return null;379String s = driveDirCache[i];380if (s != null) return s;381s = getDriveDirectory(i + 1);382driveDirCache[i] = s;383return s;384}385386// Caches for canonicalization results to improve startup performance.387// The first cache handles repeated canonicalizations of the same path388// name. The prefix cache handles repeated canonicalizations within the389// same directory, and must not create results differing from the true390// canonicalization algorithm in canonicalize_md.c. For this reason the391// prefix cache is conservative and is not used for complex path names.392private final ExpiringCache cache;393private final ExpiringCache prefixCache;394395@Override396public String canonicalize(String path) throws IOException {397// If path is a drive letter only then skip canonicalization398int len = path.length();399if ((len == 2) &&400(isLetter(path.charAt(0))) &&401(path.charAt(1) == ':')) {402char c = path.charAt(0);403if ((c >= 'A') && (c <= 'Z'))404return path;405return "" + ((char) (c-32)) + ':';406} else if ((len == 3) &&407(isLetter(path.charAt(0))) &&408(path.charAt(1) == ':') &&409(path.charAt(2) == '\\')) {410char c = path.charAt(0);411if ((c >= 'A') && (c <= 'Z'))412return path;413return "" + ((char) (c-32)) + ':' + '\\';414}415if (!useCanonCaches) {416return canonicalize0(path);417} else {418String res = cache.get(path);419if (res == null) {420String dir = null;421String resDir = null;422if (useCanonPrefixCache) {423dir = parentOrNull(path);424if (dir != null) {425resDir = prefixCache.get(dir);426if (resDir != null) {427/*428* Hit only in prefix cache; full path is canonical,429* but we need to get the canonical name of the file430* in this directory to get the appropriate431* capitalization432*/433String filename = path.substring(1 + dir.length());434res = canonicalizeWithPrefix(resDir, filename);435cache.put(dir + File.separatorChar + filename, res);436}437}438}439if (res == null) {440res = canonicalize0(path);441cache.put(path, res);442if (useCanonPrefixCache && dir != null) {443resDir = parentOrNull(res);444if (resDir != null) {445File f = new File(res);446if (f.exists() && !f.isDirectory()) {447prefixCache.put(dir, resDir);448}449}450}451}452}453return res;454}455}456457private native String canonicalize0(String path)458throws IOException;459460private String canonicalizeWithPrefix(String canonicalPrefix,461String filename) throws IOException462{463return canonicalizeWithPrefix0(canonicalPrefix,464canonicalPrefix + File.separatorChar + filename);465}466467// Run the canonicalization operation assuming that the prefix468// (everything up to the last filename) is canonical; just gets469// the canonical name of the last element of the path470private native String canonicalizeWithPrefix0(String canonicalPrefix,471String pathWithCanonicalPrefix)472throws IOException;473474// Best-effort attempt to get parent of this path; used for475// optimization of filename canonicalization. This must return null for476// any cases where the code in canonicalize_md.c would throw an477// exception or otherwise deal with non-simple pathnames like handling478// of "." and "..". It may conservatively return null in other479// situations as well. Returning null will cause the underlying480// (expensive) canonicalization routine to be called.481private static String parentOrNull(String path) {482if (path == null) return null;483char sep = File.separatorChar;484char altSep = '/';485int last = path.length() - 1;486int idx = last;487int adjacentDots = 0;488int nonDotCount = 0;489while (idx > 0) {490char c = path.charAt(idx);491if (c == '.') {492if (++adjacentDots >= 2) {493// Punt on pathnames containing . and ..494return null;495}496if (nonDotCount == 0) {497// Punt on pathnames ending in a .498return null;499}500} else if (c == sep) {501if (adjacentDots == 1 && nonDotCount == 0) {502// Punt on pathnames containing . and ..503return null;504}505if (idx == 0 ||506idx >= last - 1 ||507path.charAt(idx - 1) == sep ||508path.charAt(idx - 1) == altSep) {509// Punt on pathnames containing adjacent slashes510// toward the end511return null;512}513return path.substring(0, idx);514} else if (c == altSep) {515// Punt on pathnames containing both backward and516// forward slashes517return null;518} else if (c == '*' || c == '?') {519// Punt on pathnames containing wildcards520return null;521} else {522++nonDotCount;523adjacentDots = 0;524}525--idx;526}527return null;528}529530/* -- Attribute accessors -- */531532@Override533public native int getBooleanAttributes(File f);534535@Override536public native boolean checkAccess(File f, int access);537538@Override539public native long getLastModifiedTime(File f);540541@Override542public native long getLength(File f);543544@Override545public native boolean setPermission(File f, int access, boolean enable,546boolean owneronly);547548/* -- File operations -- */549550@Override551public native boolean createFileExclusively(String path)552throws IOException;553554@Override555public native String[] list(File f);556557@Override558public native boolean createDirectory(File f);559560@Override561public native boolean setLastModifiedTime(File f, long time);562563@Override564public native boolean setReadOnly(File f);565566@Override567public boolean delete(File f) {568// Keep canonicalization caches in sync after file deletion569// and renaming operations. Could be more clever than this570// (i.e., only remove/update affected entries) but probably571// not worth it since these entries expire after 30 seconds572// anyway.573if (useCanonCaches) {574cache.clear();575}576if (useCanonPrefixCache) {577prefixCache.clear();578}579return delete0(f);580}581582private native boolean delete0(File f);583584@Override585public boolean rename(File f1, File f2) {586// Keep canonicalization caches in sync after file deletion587// and renaming operations. Could be more clever than this588// (i.e., only remove/update affected entries) but probably589// not worth it since these entries expire after 30 seconds590// anyway.591if (useCanonCaches) {592cache.clear();593}594if (useCanonPrefixCache) {595prefixCache.clear();596}597return rename0(f1, f2);598}599600private native boolean rename0(File f1, File f2);601602/* -- Filesystem interface -- */603604@Override605public File[] listRoots() {606return BitSet607.valueOf(new long[] {listRoots0()})608.stream()609.mapToObj(i -> new File((char)('A' + i) + ":" + slash))610.filter(f -> access(f.getPath()) && f.exists())611.toArray(File[]::new);612}613614private static native int listRoots0();615616private boolean access(String path) {617try {618@SuppressWarnings("removal")619SecurityManager security = System.getSecurityManager();620if (security != null) security.checkRead(path);621return true;622} catch (SecurityException x) {623return false;624}625}626627/* -- Disk usage -- */628629@Override630public long getSpace(File f, int t) {631if (f.exists()) {632return getSpace0(f, t);633}634return 0;635}636637private native long getSpace0(File f, int t);638639/* -- Basic infrastructure -- */640641// Obtain maximum file component length from GetVolumeInformation which642// expects the path to be null or a root component ending in a backslash643private native int getNameMax0(String path);644645@Override646public int getNameMax(String path) {647String s = null;648if (path != null) {649File f = new File(path);650if (f.isAbsolute()) {651Path root = f.toPath().getRoot();652if (root != null) {653s = root.toString();654if (!s.endsWith("\\")) {655s = s + "\\";656}657}658}659}660return getNameMax0(s);661}662663@Override664public int compare(File f1, File f2) {665return f1.getPath().compareToIgnoreCase(f2.getPath());666}667668@Override669public int hashCode(File f) {670/* Could make this more efficient: String.hashCodeIgnoreCase */671return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;672}673674private static native void initIDs();675676static {677initIDs();678}679}680681682