Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/windows/classes/java/io/WinNTFileSystem.java
32287 views
/*1* Copyright (c) 2001, 2012, 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.security.AccessController;28import java.util.Locale;29import sun.security.action.GetPropertyAction;3031/**32* Unicode-aware FileSystem for Windows NT/2000.33*34* @author Konstantin Kladko35* @since 1.436*/37class WinNTFileSystem extends FileSystem {3839private final char slash;40private final char altSlash;41private final char semicolon;4243public WinNTFileSystem() {44slash = AccessController.doPrivileged(45new GetPropertyAction("file.separator")).charAt(0);46semicolon = AccessController.doPrivileged(47new GetPropertyAction("path.separator")).charAt(0);48altSlash = (this.slash == '\\') ? '/' : '\\';49}5051private boolean isSlash(char c) {52return (c == '\\') || (c == '/');53}5455private boolean isLetter(char c) {56return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));57}5859private String slashify(String p) {60if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;61else return p;62}6364/* -- Normalization and construction -- */6566@Override67public char getSeparator() {68return slash;69}7071@Override72public char getPathSeparator() {73return semicolon;74}7576/* Check that the given pathname is normal. If not, invoke the real77normalizer on the part of the pathname that requires normalization.78This way we iterate through the whole pathname string only once. */79@Override80public String normalize(String path) {81int n = path.length();82char slash = this.slash;83char altSlash = this.altSlash;84char prev = 0;85for (int i = 0; i < n; i++) {86char c = path.charAt(i);87if (c == altSlash)88return normalize(path, n, (prev == slash) ? i - 1 : i);89if ((c == slash) && (prev == slash) && (i > 1))90return normalize(path, n, i - 1);91if ((c == ':') && (i > 1))92return normalize(path, n, 0);93prev = c;94}95if (prev == slash) return normalize(path, n, n - 1);96return path;97}9899/* Normalize the given pathname, whose length is len, starting at the given100offset; everything before this offset is already normal. */101private String normalize(String path, int len, int off) {102if (len == 0) return path;103if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */104int src;105char slash = this.slash;106StringBuffer sb = new StringBuffer(len);107108if (off == 0) {109/* Complete normalization, including prefix */110src = normalizePrefix(path, len, sb);111} else {112/* Partial normalization */113src = off;114sb.append(path.substring(0, off));115}116117/* Remove redundant slashes from the remainder of the path, forcing all118slashes into the preferred slash */119while (src < len) {120char c = path.charAt(src++);121if (isSlash(c)) {122while ((src < len) && isSlash(path.charAt(src))) src++;123if (src == len) {124/* Check for trailing separator */125int sn = sb.length();126if ((sn == 2) && (sb.charAt(1) == ':')) {127/* "z:\\" */128sb.append(slash);129break;130}131if (sn == 0) {132/* "\\" */133sb.append(slash);134break;135}136if ((sn == 1) && (isSlash(sb.charAt(0)))) {137/* "\\\\" is not collapsed to "\\" because "\\\\" marks138the beginning of a UNC pathname. Even though it is139not, by itself, a valid UNC pathname, we leave it as140is in order to be consistent with the win32 APIs,141which treat this case as an invalid UNC pathname142rather than as an alias for the root directory of143the current drive. */144sb.append(slash);145break;146}147/* Path does not denote a root directory, so do not append148trailing slash */149break;150} else {151sb.append(slash);152}153} else {154sb.append(c);155}156}157158String rv = sb.toString();159return rv;160}161162/* A normal Win32 pathname contains no duplicate slashes, except possibly163for a UNC prefix, and does not end with a slash. It may be the empty164string. Normalized Win32 pathnames have the convenient property that165the length of the prefix almost uniquely identifies the type of the path166and whether it is absolute or relative:1671680 relative to both drive and directory1691 drive-relative (begins with '\\')1702 absolute UNC (if first char is '\\'),171else directory-relative (has form "z:foo")1723 absolute local pathname (begins with "z:\\")173*/174private int normalizePrefix(String path, int len, StringBuffer sb) {175int src = 0;176while ((src < len) && isSlash(path.charAt(src))) src++;177char c;178if ((len - src >= 2)179&& isLetter(c = path.charAt(src))180&& path.charAt(src + 1) == ':') {181/* Remove leading slashes if followed by drive specifier.182This hack is necessary to support file URLs containing drive183specifiers (e.g., "file://c:/path"). As a side effect,184"/c:/path" can be used as an alternative to "c:/path". */185sb.append(c);186sb.append(':');187src += 2;188} else {189src = 0;190if ((len >= 2)191&& isSlash(path.charAt(0))192&& isSlash(path.charAt(1))) {193/* UNC pathname: Retain first slash; leave src pointed at194second slash so that further slashes will be collapsed195into the second slash. The result will be a pathname196beginning with "\\\\" followed (most likely) by a host197name. */198src = 1;199sb.append(slash);200}201}202return src;203}204205@Override206public int prefixLength(String path) {207char slash = this.slash;208int n = path.length();209if (n == 0) return 0;210char c0 = path.charAt(0);211char c1 = (n > 1) ? path.charAt(1) : 0;212if (c0 == slash) {213if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */214return 1; /* Drive-relative "\\foo" */215}216if (isLetter(c0) && (c1 == ':')) {217if ((n > 2) && (path.charAt(2) == slash))218return 3; /* Absolute local pathname "z:\\foo" */219return 2; /* Directory-relative "z:foo" */220}221return 0; /* Completely relative */222}223224@Override225public String resolve(String parent, String child) {226int pn = parent.length();227if (pn == 0) return child;228int cn = child.length();229if (cn == 0) return parent;230231String c = child;232int childStart = 0;233int parentEnd = pn;234235if ((cn > 1) && (c.charAt(0) == slash)) {236if (c.charAt(1) == slash) {237/* Drop prefix when child is a UNC pathname */238childStart = 2;239} else {240/* Drop prefix when child is drive-relative */241childStart = 1;242243}244if (cn == childStart) { // Child is double slash245if (parent.charAt(pn - 1) == slash)246return parent.substring(0, pn - 1);247return parent;248}249}250251if (parent.charAt(pn - 1) == slash)252parentEnd--;253254int strlen = parentEnd + cn - childStart;255char[] theChars = null;256if (child.charAt(childStart) == slash) {257theChars = new char[strlen];258parent.getChars(0, parentEnd, theChars, 0);259child.getChars(childStart, cn, theChars, parentEnd);260} else {261theChars = new char[strlen + 1];262parent.getChars(0, parentEnd, theChars, 0);263theChars[parentEnd] = slash;264child.getChars(childStart, cn, theChars, parentEnd + 1);265}266return new String(theChars);267}268269@Override270public String getDefaultParent() {271return ("" + slash);272}273274@Override275public String fromURIPath(String path) {276String p = path;277if ((p.length() > 2) && (p.charAt(2) == ':')) {278// "/c:/foo" --> "c:/foo"279p = p.substring(1);280// "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"281if ((p.length() > 3) && p.endsWith("/"))282p = p.substring(0, p.length() - 1);283} else if ((p.length() > 1) && p.endsWith("/")) {284// "/foo/" --> "/foo"285p = p.substring(0, p.length() - 1);286}287return p;288}289290/* -- Path operations -- */291292@Override293public boolean isAbsolute(File f) {294int pl = f.getPrefixLength();295return (((pl == 2) && (f.getPath().charAt(0) == slash))296|| (pl == 3));297}298299@Override300public String resolve(File f) {301String path = f.getPath();302int pl = f.getPrefixLength();303if ((pl == 2) && (path.charAt(0) == slash))304return path; /* UNC */305if (pl == 3)306return path; /* Absolute local */307if (pl == 0)308return getUserPath() + slashify(path); /* Completely relative */309if (pl == 1) { /* Drive-relative */310String up = getUserPath();311String ud = getDrive(up);312if (ud != null) return ud + path;313return up + path; /* User dir is a UNC path */314}315if (pl == 2) { /* Directory-relative */316String up = getUserPath();317String ud = getDrive(up);318if ((ud != null) && path.startsWith(ud))319return up + slashify(path.substring(2));320char drive = path.charAt(0);321String dir = getDriveDirectory(drive);322String np;323if (dir != null) {324/* When resolving a directory-relative path that refers to a325drive other than the current drive, insist that the caller326have read permission on the result */327String p = drive + (':' + dir + slashify(path.substring(2)));328SecurityManager security = System.getSecurityManager();329try {330if (security != null) security.checkRead(p);331} catch (SecurityException x) {332/* Don't disclose the drive's directory in the exception */333throw new SecurityException("Cannot resolve path " + path);334}335return p;336}337return drive + ":" + slashify(path.substring(2)); /* fake it */338}339throw new InternalError("Unresolvable path: " + path);340}341342private String getUserPath() {343/* For both compatibility and security,344we must look this up every time */345return normalize(System.getProperty("user.dir"));346}347348private String getDrive(String path) {349int pl = prefixLength(path);350return (pl == 3) ? path.substring(0, 2) : null;351}352353private static String[] driveDirCache = new String[26];354355private static int driveIndex(char d) {356if ((d >= 'a') && (d <= 'z')) return d - 'a';357if ((d >= 'A') && (d <= 'Z')) return d - 'A';358return -1;359}360361private native String getDriveDirectory(int drive);362363private String getDriveDirectory(char drive) {364int i = driveIndex(drive);365if (i < 0) return null;366String s = driveDirCache[i];367if (s != null) return s;368s = getDriveDirectory(i + 1);369driveDirCache[i] = s;370return s;371}372373// Caches for canonicalization results to improve startup performance.374// The first cache handles repeated canonicalizations of the same path375// name. The prefix cache handles repeated canonicalizations within the376// same directory, and must not create results differing from the true377// canonicalization algorithm in canonicalize_md.c. For this reason the378// prefix cache is conservative and is not used for complex path names.379private ExpiringCache cache = new ExpiringCache();380private ExpiringCache prefixCache = new ExpiringCache();381382@Override383public String canonicalize(String path) throws IOException {384// If path is a drive letter only then skip canonicalization385int len = path.length();386if ((len == 2) &&387(isLetter(path.charAt(0))) &&388(path.charAt(1) == ':')) {389char c = path.charAt(0);390if ((c >= 'A') && (c <= 'Z'))391return path;392return "" + ((char) (c-32)) + ':';393} else if ((len == 3) &&394(isLetter(path.charAt(0))) &&395(path.charAt(1) == ':') &&396(path.charAt(2) == '\\')) {397char c = path.charAt(0);398if ((c >= 'A') && (c <= 'Z'))399return path;400return "" + ((char) (c-32)) + ':' + '\\';401}402if (!useCanonCaches) {403return canonicalize0(path);404} else {405String res = cache.get(path);406if (res == null) {407String dir = null;408String resDir = null;409if (useCanonPrefixCache) {410dir = parentOrNull(path);411if (dir != null) {412resDir = prefixCache.get(dir);413if (resDir != null) {414/*415* Hit only in prefix cache; full path is canonical,416* but we need to get the canonical name of the file417* in this directory to get the appropriate418* capitalization419*/420String filename = path.substring(1 + dir.length());421res = canonicalizeWithPrefix(resDir, filename);422cache.put(dir + File.separatorChar + filename, res);423}424}425}426if (res == null) {427res = canonicalize0(path);428cache.put(path, res);429if (useCanonPrefixCache && dir != null) {430resDir = parentOrNull(res);431if (resDir != null) {432File f = new File(res);433if (f.exists() && !f.isDirectory()) {434prefixCache.put(dir, resDir);435}436}437}438}439}440return res;441}442}443444private native String canonicalize0(String path)445throws IOException;446447private String canonicalizeWithPrefix(String canonicalPrefix,448String filename) throws IOException449{450return canonicalizeWithPrefix0(canonicalPrefix,451canonicalPrefix + File.separatorChar + filename);452}453454// Run the canonicalization operation assuming that the prefix455// (everything up to the last filename) is canonical; just gets456// the canonical name of the last element of the path457private native String canonicalizeWithPrefix0(String canonicalPrefix,458String pathWithCanonicalPrefix)459throws IOException;460461// Best-effort attempt to get parent of this path; used for462// optimization of filename canonicalization. This must return null for463// any cases where the code in canonicalize_md.c would throw an464// exception or otherwise deal with non-simple pathnames like handling465// of "." and "..". It may conservatively return null in other466// situations as well. Returning null will cause the underlying467// (expensive) canonicalization routine to be called.468private static String parentOrNull(String path) {469if (path == null) return null;470char sep = File.separatorChar;471char altSep = '/';472int last = path.length() - 1;473int idx = last;474int adjacentDots = 0;475int nonDotCount = 0;476while (idx > 0) {477char c = path.charAt(idx);478if (c == '.') {479if (++adjacentDots >= 2) {480// Punt on pathnames containing . and ..481return null;482}483if (nonDotCount == 0) {484// Punt on pathnames ending in a .485return null;486}487} else if (c == sep) {488if (adjacentDots == 1 && nonDotCount == 0) {489// Punt on pathnames containing . and ..490return null;491}492if (idx == 0 ||493idx >= last - 1 ||494path.charAt(idx - 1) == sep ||495path.charAt(idx - 1) == altSep) {496// Punt on pathnames containing adjacent slashes497// toward the end498return null;499}500return path.substring(0, idx);501} else if (c == altSep) {502// Punt on pathnames containing both backward and503// forward slashes504return null;505} else if (c == '*' || c == '?') {506// Punt on pathnames containing wildcards507return null;508} else {509++nonDotCount;510adjacentDots = 0;511}512--idx;513}514return null;515}516517/* -- Attribute accessors -- */518519@Override520public native int getBooleanAttributes(File f);521522@Override523public native boolean checkAccess(File f, int access);524525@Override526public native long getLastModifiedTime(File f);527528@Override529public native long getLength(File f);530531@Override532public native boolean setPermission(File f, int access, boolean enable,533boolean owneronly);534535/* -- File operations -- */536537@Override538public native boolean createFileExclusively(String path)539throws IOException;540541@Override542public native String[] list(File f);543544@Override545public native boolean createDirectory(File f);546547@Override548public native boolean setLastModifiedTime(File f, long time);549550@Override551public native boolean setReadOnly(File f);552553@Override554public boolean delete(File f) {555// Keep canonicalization caches in sync after file deletion556// and renaming operations. Could be more clever than this557// (i.e., only remove/update affected entries) but probably558// not worth it since these entries expire after 30 seconds559// anyway.560cache.clear();561prefixCache.clear();562return delete0(f);563}564565private native boolean delete0(File f);566567@Override568public boolean rename(File f1, File f2) {569// Keep canonicalization caches in sync after file deletion570// and renaming operations. Could be more clever than this571// (i.e., only remove/update affected entries) but probably572// not worth it since these entries expire after 30 seconds573// anyway.574cache.clear();575prefixCache.clear();576return rename0(f1, f2);577}578579private native boolean rename0(File f1, File f2);580581/* -- Filesystem interface -- */582583@Override584public File[] listRoots() {585int ds = listRoots0();586int n = 0;587for (int i = 0; i < 26; i++) {588if (((ds >> i) & 1) != 0) {589if (!access((char)('A' + i) + ":" + slash))590ds &= ~(1 << i);591else592n++;593}594}595File[] fs = new File[n];596int j = 0;597char slash = this.slash;598for (int i = 0; i < 26; i++) {599if (((ds >> i) & 1) != 0)600fs[j++] = new File((char)('A' + i) + ":" + slash);601}602return fs;603}604605private static native int listRoots0();606607private boolean access(String path) {608try {609SecurityManager security = System.getSecurityManager();610if (security != null) security.checkRead(path);611return true;612} catch (SecurityException x) {613return false;614}615}616617/* -- Disk usage -- */618619@Override620public long getSpace(File f, int t) {621if (f.exists()) {622return getSpace0(f, t);623}624return 0;625}626627private native long getSpace0(File f, int t);628629/* -- Basic infrastructure -- */630631@Override632public int compare(File f1, File f2) {633return f1.getPath().compareToIgnoreCase(f2.getPath());634}635636@Override637public int hashCode(File f) {638/* Could make this more efficient: String.hashCodeIgnoreCase */639return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;640}641642private static native void initIDs();643644static {645initIDs();646}647}648649650