0N/A/*
3261N/A * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage java.io;
0N/A
0N/Aimport java.security.AccessController;
0N/Aimport java.util.Locale;
0N/Aimport sun.security.action.GetPropertyAction;
0N/A
0N/A
0N/Aclass Win32FileSystem extends FileSystem {
0N/A
0N/A private final char slash;
0N/A private final char altSlash;
0N/A private final char semicolon;
0N/A
0N/A public Win32FileSystem() {
0N/A slash = AccessController.doPrivileged(
0N/A new GetPropertyAction("file.separator")).charAt(0);
0N/A semicolon = AccessController.doPrivileged(
0N/A new GetPropertyAction("path.separator")).charAt(0);
0N/A altSlash = (this.slash == '\\') ? '/' : '\\';
0N/A }
0N/A
0N/A private boolean isSlash(char c) {
0N/A return (c == '\\') || (c == '/');
0N/A }
0N/A
0N/A private boolean isLetter(char c) {
0N/A return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
0N/A }
0N/A
0N/A private String slashify(String p) {
0N/A if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p;
0N/A else return p;
0N/A }
0N/A
0N/A
0N/A /* -- Normalization and construction -- */
0N/A
0N/A public char getSeparator() {
0N/A return slash;
0N/A }
0N/A
0N/A public char getPathSeparator() {
0N/A return semicolon;
0N/A }
0N/A
0N/A /* A normal Win32 pathname contains no duplicate slashes, except possibly
0N/A for a UNC prefix, and does not end with a slash. It may be the empty
0N/A string. Normalized Win32 pathnames have the convenient property that
0N/A the length of the prefix almost uniquely identifies the type of the path
0N/A and whether it is absolute or relative:
0N/A
0N/A 0 relative to both drive and directory
0N/A 1 drive-relative (begins with '\\')
0N/A 2 absolute UNC (if first char is '\\'),
0N/A else directory-relative (has form "z:foo")
0N/A 3 absolute local pathname (begins with "z:\\")
0N/A */
0N/A
0N/A private int normalizePrefix(String path, int len, StringBuffer sb) {
0N/A int src = 0;
0N/A while ((src < len) && isSlash(path.charAt(src))) src++;
0N/A char c;
0N/A if ((len - src >= 2)
0N/A && isLetter(c = path.charAt(src))
0N/A && path.charAt(src + 1) == ':') {
0N/A /* Remove leading slashes if followed by drive specifier.
0N/A This hack is necessary to support file URLs containing drive
0N/A specifiers (e.g., "file://c:/path"). As a side effect,
0N/A "/c:/path" can be used as an alternative to "c:/path". */
0N/A sb.append(c);
0N/A sb.append(':');
0N/A src += 2;
0N/A } else {
0N/A src = 0;
0N/A if ((len >= 2)
0N/A && isSlash(path.charAt(0))
0N/A && isSlash(path.charAt(1))) {
0N/A /* UNC pathname: Retain first slash; leave src pointed at
0N/A second slash so that further slashes will be collapsed
0N/A into the second slash. The result will be a pathname
0N/A beginning with "\\\\" followed (most likely) by a host
0N/A name. */
0N/A src = 1;
0N/A sb.append(slash);
0N/A }
0N/A }
0N/A return src;
0N/A }
0N/A
0N/A /* Normalize the given pathname, whose length is len, starting at the given
0N/A offset; everything before this offset is already normal. */
0N/A private String normalize(String path, int len, int off) {
0N/A if (len == 0) return path;
0N/A if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */
0N/A int src;
0N/A char slash = this.slash;
0N/A StringBuffer sb = new StringBuffer(len);
0N/A
0N/A if (off == 0) {
0N/A /* Complete normalization, including prefix */
0N/A src = normalizePrefix(path, len, sb);
0N/A } else {
0N/A /* Partial normalization */
0N/A src = off;
0N/A sb.append(path.substring(0, off));
0N/A }
0N/A
0N/A /* Remove redundant slashes from the remainder of the path, forcing all
0N/A slashes into the preferred slash */
0N/A while (src < len) {
0N/A char c = path.charAt(src++);
0N/A if (isSlash(c)) {
0N/A while ((src < len) && isSlash(path.charAt(src))) src++;
0N/A if (src == len) {
0N/A /* Check for trailing separator */
0N/A int sn = sb.length();
0N/A if ((sn == 2) && (sb.charAt(1) == ':')) {
0N/A /* "z:\\" */
0N/A sb.append(slash);
0N/A break;
0N/A }
0N/A if (sn == 0) {
0N/A /* "\\" */
0N/A sb.append(slash);
0N/A break;
0N/A }
0N/A if ((sn == 1) && (isSlash(sb.charAt(0)))) {
0N/A /* "\\\\" is not collapsed to "\\" because "\\\\" marks
0N/A the beginning of a UNC pathname. Even though it is
0N/A not, by itself, a valid UNC pathname, we leave it as
0N/A is in order to be consistent with the win32 APIs,
0N/A which treat this case as an invalid UNC pathname
0N/A rather than as an alias for the root directory of
0N/A the current drive. */
0N/A sb.append(slash);
0N/A break;
0N/A }
0N/A /* Path does not denote a root directory, so do not append
0N/A trailing slash */
0N/A break;
0N/A } else {
0N/A sb.append(slash);
0N/A }
0N/A } else {
0N/A sb.append(c);
0N/A }
0N/A }
0N/A
0N/A String rv = sb.toString();
0N/A return rv;
0N/A }
0N/A
0N/A /* Check that the given pathname is normal. If not, invoke the real
0N/A normalizer on the part of the pathname that requires normalization.
0N/A This way we iterate through the whole pathname string only once. */
0N/A public String normalize(String path) {
0N/A int n = path.length();
0N/A char slash = this.slash;
0N/A char altSlash = this.altSlash;
0N/A char prev = 0;
0N/A for (int i = 0; i < n; i++) {
0N/A char c = path.charAt(i);
0N/A if (c == altSlash)
0N/A return normalize(path, n, (prev == slash) ? i - 1 : i);
0N/A if ((c == slash) && (prev == slash) && (i > 1))
0N/A return normalize(path, n, i - 1);
0N/A if ((c == ':') && (i > 1))
0N/A return normalize(path, n, 0);
0N/A prev = c;
0N/A }
0N/A if (prev == slash) return normalize(path, n, n - 1);
0N/A return path;
0N/A }
0N/A
0N/A public int prefixLength(String path) {
0N/A char slash = this.slash;
0N/A int n = path.length();
0N/A if (n == 0) return 0;
0N/A char c0 = path.charAt(0);
0N/A char c1 = (n > 1) ? path.charAt(1) : 0;
0N/A if (c0 == slash) {
0N/A if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */
0N/A return 1; /* Drive-relative "\\foo" */
0N/A }
0N/A if (isLetter(c0) && (c1 == ':')) {
0N/A if ((n > 2) && (path.charAt(2) == slash))
0N/A return 3; /* Absolute local pathname "z:\\foo" */
0N/A return 2; /* Directory-relative "z:foo" */
0N/A }
0N/A return 0; /* Completely relative */
0N/A }
0N/A
0N/A public String resolve(String parent, String child) {
0N/A int pn = parent.length();
0N/A if (pn == 0) return child;
0N/A int cn = child.length();
0N/A if (cn == 0) return parent;
0N/A
0N/A String c = child;
0N/A int childStart = 0;
0N/A int parentEnd = pn;
0N/A
0N/A if ((cn > 1) && (c.charAt(0) == slash)) {
0N/A if (c.charAt(1) == slash) {
0N/A /* Drop prefix when child is a UNC pathname */
0N/A childStart = 2;
0N/A } else {
0N/A /* Drop prefix when child is drive-relative */
0N/A childStart = 1;
0N/A
0N/A }
0N/A if (cn == childStart) { // Child is double slash
0N/A if (parent.charAt(pn - 1) == slash)
0N/A return parent.substring(0, pn - 1);
0N/A return parent;
0N/A }
0N/A }
0N/A
0N/A if (parent.charAt(pn - 1) == slash)
0N/A parentEnd--;
0N/A
0N/A int strlen = parentEnd + cn - childStart;
0N/A char[] theChars = null;
0N/A if (child.charAt(childStart) == slash) {
0N/A theChars = new char[strlen];
0N/A parent.getChars(0, parentEnd, theChars, 0);
0N/A child.getChars(childStart, cn, theChars, parentEnd);
0N/A } else {
0N/A theChars = new char[strlen + 1];
0N/A parent.getChars(0, parentEnd, theChars, 0);
0N/A theChars[parentEnd] = slash;
0N/A child.getChars(childStart, cn, theChars, parentEnd + 1);
0N/A }
0N/A return new String(theChars);
0N/A }
0N/A
0N/A public String getDefaultParent() {
0N/A return ("" + slash);
0N/A }
0N/A
0N/A public String fromURIPath(String path) {
0N/A String p = path;
0N/A if ((p.length() > 2) && (p.charAt(2) == ':')) {
0N/A // "/c:/foo" --> "c:/foo"
0N/A p = p.substring(1);
0N/A // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/"
0N/A if ((p.length() > 3) && p.endsWith("/"))
0N/A p = p.substring(0, p.length() - 1);
0N/A } else if ((p.length() > 1) && p.endsWith("/")) {
0N/A // "/foo/" --> "/foo"
0N/A p = p.substring(0, p.length() - 1);
0N/A }
0N/A return p;
0N/A }
0N/A
0N/A
0N/A
0N/A /* -- Path operations -- */
0N/A
0N/A public boolean isAbsolute(File f) {
0N/A int pl = f.getPrefixLength();
0N/A return (((pl == 2) && (f.getPath().charAt(0) == slash))
0N/A || (pl == 3));
0N/A }
0N/A
0N/A protected native String getDriveDirectory(int drive);
0N/A
0N/A private static String[] driveDirCache = new String[26];
0N/A
0N/A private static int driveIndex(char d) {
0N/A if ((d >= 'a') && (d <= 'z')) return d - 'a';
0N/A if ((d >= 'A') && (d <= 'Z')) return d - 'A';
0N/A return -1;
0N/A }
0N/A
0N/A private String getDriveDirectory(char drive) {
0N/A int i = driveIndex(drive);
0N/A if (i < 0) return null;
0N/A String s = driveDirCache[i];
0N/A if (s != null) return s;
0N/A s = getDriveDirectory(i + 1);
0N/A driveDirCache[i] = s;
0N/A return s;
0N/A }
0N/A
0N/A private String getUserPath() {
0N/A /* For both compatibility and security,
0N/A we must look this up every time */
0N/A return normalize(System.getProperty("user.dir"));
0N/A }
0N/A
0N/A private String getDrive(String path) {
0N/A int pl = prefixLength(path);
0N/A return (pl == 3) ? path.substring(0, 2) : null;
0N/A }
0N/A
0N/A public String resolve(File f) {
0N/A String path = f.getPath();
0N/A int pl = f.getPrefixLength();
0N/A if ((pl == 2) && (path.charAt(0) == slash))
0N/A return path; /* UNC */
0N/A if (pl == 3)
0N/A return path; /* Absolute local */
0N/A if (pl == 0)
0N/A return getUserPath() + slashify(path); /* Completely relative */
0N/A if (pl == 1) { /* Drive-relative */
0N/A String up = getUserPath();
0N/A String ud = getDrive(up);
0N/A if (ud != null) return ud + path;
0N/A return up + path; /* User dir is a UNC path */
0N/A }
0N/A if (pl == 2) { /* Directory-relative */
0N/A String up = getUserPath();
0N/A String ud = getDrive(up);
0N/A if ((ud != null) && path.startsWith(ud))
0N/A return up + slashify(path.substring(2));
0N/A char drive = path.charAt(0);
0N/A String dir = getDriveDirectory(drive);
0N/A String np;
0N/A if (dir != null) {
0N/A /* When resolving a directory-relative path that refers to a
0N/A drive other than the current drive, insist that the caller
0N/A have read permission on the result */
0N/A String p = drive + (':' + dir + slashify(path.substring(2)));
0N/A SecurityManager security = System.getSecurityManager();
0N/A try {
0N/A if (security != null) security.checkRead(p);
0N/A } catch (SecurityException x) {
0N/A /* Don't disclose the drive's directory in the exception */
0N/A throw new SecurityException("Cannot resolve path " + path);
0N/A }
0N/A return p;
0N/A }
0N/A return drive + ":" + slashify(path.substring(2)); /* fake it */
0N/A }
0N/A throw new InternalError("Unresolvable path: " + path);
0N/A }
0N/A
0N/A // Caches for canonicalization results to improve startup performance.
0N/A // The first cache handles repeated canonicalizations of the same path
0N/A // name. The prefix cache handles repeated canonicalizations within the
0N/A // same directory, and must not create results differing from the true
0N/A // canonicalization algorithm in canonicalize_md.c. For this reason the
0N/A // prefix cache is conservative and is not used for complex path names.
0N/A private ExpiringCache cache = new ExpiringCache();
0N/A private ExpiringCache prefixCache = new ExpiringCache();
0N/A
0N/A public String canonicalize(String path) throws IOException {
0N/A // If path is a drive letter only then skip canonicalization
0N/A int len = path.length();
0N/A if ((len == 2) &&
0N/A (isLetter(path.charAt(0))) &&
0N/A (path.charAt(1) == ':')) {
0N/A char c = path.charAt(0);
0N/A if ((c >= 'A') && (c <= 'Z'))
0N/A return path;
0N/A return "" + ((char) (c-32)) + ':';
0N/A } else if ((len == 3) &&
0N/A (isLetter(path.charAt(0))) &&
0N/A (path.charAt(1) == ':') &&
0N/A (path.charAt(2) == '\\')) {
0N/A char c = path.charAt(0);
0N/A if ((c >= 'A') && (c <= 'Z'))
0N/A return path;
0N/A return "" + ((char) (c-32)) + ':' + '\\';
0N/A }
0N/A if (!useCanonCaches) {
0N/A return canonicalize0(path);
0N/A } else {
0N/A String res = cache.get(path);
0N/A if (res == null) {
0N/A String dir = null;
0N/A String resDir = null;
0N/A if (useCanonPrefixCache) {
0N/A dir = parentOrNull(path);
0N/A if (dir != null) {
0N/A resDir = prefixCache.get(dir);
0N/A if (resDir != null) {
0N/A // Hit only in prefix cache; full path is canonical,
0N/A // but we need to get the canonical name of the file
0N/A // in this directory to get the appropriate capitalization
0N/A String filename = path.substring(1 + dir.length());
0N/A res = canonicalizeWithPrefix(resDir, filename);
0N/A cache.put(dir + File.separatorChar + filename, res);
0N/A }
0N/A }
0N/A }
0N/A if (res == null) {
0N/A res = canonicalize0(path);
0N/A cache.put(path, res);
0N/A if (useCanonPrefixCache && dir != null) {
0N/A resDir = parentOrNull(res);
0N/A if (resDir != null) {
0N/A File f = new File(res);
0N/A if (f.exists() && !f.isDirectory()) {
0N/A prefixCache.put(dir, resDir);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A }
0N/A return res;
0N/A }
0N/A }
0N/A
0N/A protected native String canonicalize0(String path)
0N/A throws IOException;
0N/A protected String canonicalizeWithPrefix(String canonicalPrefix,
0N/A String filename) throws IOException
0N/A {
0N/A return canonicalizeWithPrefix0(canonicalPrefix,
0N/A canonicalPrefix + File.separatorChar + filename);
0N/A }
0N/A // Run the canonicalization operation assuming that the prefix
0N/A // (everything up to the last filename) is canonical; just gets
0N/A // the canonical name of the last element of the path
0N/A protected native String canonicalizeWithPrefix0(String canonicalPrefix,
0N/A String pathWithCanonicalPrefix)
0N/A throws IOException;
0N/A // Best-effort attempt to get parent of this path; used for
0N/A // optimization of filename canonicalization. This must return null for
0N/A // any cases where the code in canonicalize_md.c would throw an
0N/A // exception or otherwise deal with non-simple pathnames like handling
0N/A // of "." and "..". It may conservatively return null in other
0N/A // situations as well. Returning null will cause the underlying
0N/A // (expensive) canonicalization routine to be called.
0N/A static String parentOrNull(String path) {
0N/A if (path == null) return null;
0N/A char sep = File.separatorChar;
0N/A char altSep = '/';
0N/A int last = path.length() - 1;
0N/A int idx = last;
0N/A int adjacentDots = 0;
0N/A int nonDotCount = 0;
0N/A while (idx > 0) {
0N/A char c = path.charAt(idx);
0N/A if (c == '.') {
0N/A if (++adjacentDots >= 2) {
0N/A // Punt on pathnames containing . and ..
0N/A return null;
0N/A }
0N/A if (nonDotCount == 0) {
0N/A // Punt on pathnames ending in a .
0N/A return null;
0N/A }
0N/A } else if (c == sep) {
0N/A if (adjacentDots == 1 && nonDotCount == 0) {
0N/A // Punt on pathnames containing . and ..
0N/A return null;
0N/A }
0N/A if (idx == 0 ||
0N/A idx >= last - 1 ||
0N/A path.charAt(idx - 1) == sep ||
0N/A path.charAt(idx - 1) == altSep) {
0N/A // Punt on pathnames containing adjacent slashes
0N/A // toward the end
0N/A return null;
0N/A }
0N/A return path.substring(0, idx);
0N/A } else if (c == altSep) {
0N/A // Punt on pathnames containing both backward and
0N/A // forward slashes
0N/A return null;
0N/A } else if (c == '*' || c == '?') {
0N/A // Punt on pathnames containing wildcards
0N/A return null;
0N/A } else {
0N/A ++nonDotCount;
0N/A adjacentDots = 0;
0N/A }
0N/A --idx;
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A
0N/A /* -- Attribute accessors -- */
0N/A
0N/A public native int getBooleanAttributes(File f);
0N/A public native boolean checkAccess(File f, int access);
0N/A public native long getLastModifiedTime(File f);
0N/A public native long getLength(File f);
0N/A public native boolean setPermission(File f, int access, boolean enable, boolean owneronly);
0N/A
0N/A /* -- File operations -- */
0N/A
0N/A public native boolean createFileExclusively(String path)
0N/A throws IOException;
0N/A public boolean delete(File f) {
0N/A // Keep canonicalization caches in sync after file deletion
0N/A // and renaming operations. Could be more clever than this
0N/A // (i.e., only remove/update affected entries) but probably
0N/A // not worth it since these entries expire after 30 seconds
0N/A // anyway.
0N/A cache.clear();
0N/A prefixCache.clear();
0N/A return delete0(f);
0N/A }
0N/A protected native boolean delete0(File f);
0N/A public native String[] list(File f);
0N/A public native boolean createDirectory(File f);
0N/A public boolean rename(File f1, File f2) {
0N/A // Keep canonicalization caches in sync after file deletion
0N/A // and renaming operations. Could be more clever than this
0N/A // (i.e., only remove/update affected entries) but probably
0N/A // not worth it since these entries expire after 30 seconds
0N/A // anyway.
0N/A cache.clear();
0N/A prefixCache.clear();
0N/A return rename0(f1, f2);
0N/A }
0N/A protected native boolean rename0(File f1, File f2);
0N/A public native boolean setLastModifiedTime(File f, long time);
0N/A public native boolean setReadOnly(File f);
0N/A
0N/A
0N/A /* -- Filesystem interface -- */
0N/A
0N/A private boolean access(String path) {
0N/A try {
0N/A SecurityManager security = System.getSecurityManager();
0N/A if (security != null) security.checkRead(path);
0N/A return true;
0N/A } catch (SecurityException x) {
0N/A return false;
0N/A }
0N/A }
0N/A
0N/A private static native int listRoots0();
0N/A
0N/A public File[] listRoots() {
0N/A int ds = listRoots0();
0N/A int n = 0;
0N/A for (int i = 0; i < 26; i++) {
0N/A if (((ds >> i) & 1) != 0) {
0N/A if (!access((char)('A' + i) + ":" + slash))
0N/A ds &= ~(1 << i);
0N/A else
0N/A n++;
0N/A }
0N/A }
0N/A File[] fs = new File[n];
0N/A int j = 0;
0N/A char slash = this.slash;
0N/A for (int i = 0; i < 26; i++) {
0N/A if (((ds >> i) & 1) != 0)
0N/A fs[j++] = new File((char)('A' + i) + ":" + slash);
0N/A }
0N/A return fs;
0N/A }
0N/A
0N/A
0N/A /* -- Disk usage -- */
0N/A public long getSpace(File f, int t) {
0N/A if (f.exists()) {
0N/A File file = (f.isDirectory() ? f : f.getParentFile());
0N/A return getSpace0(file, t);
0N/A }
0N/A return 0;
0N/A }
0N/A
0N/A private native long getSpace0(File f, int t);
0N/A
0N/A
0N/A /* -- Basic infrastructure -- */
0N/A
0N/A public int compare(File f1, File f2) {
0N/A return f1.getPath().compareToIgnoreCase(f2.getPath());
0N/A }
0N/A
0N/A public int hashCode(File f) {
0N/A /* Could make this more efficient: String.hashCodeIgnoreCase */
0N/A return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321;
0N/A }
0N/A
0N/A
0N/A private static native void initIDs();
0N/A
0N/A static {
0N/A initIDs();
0N/A }
0N/A
0N/A}