1 /* 2 * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.io; 27 28 import java.security.AccessController; 29 import java.util.Locale; 30 import sun.security.action.GetPropertyAction; 31 32 33 class Win32FileSystem extends FileSystem { 34 35 private final char slash; 36 private final char altSlash; 37 private final char semicolon; 38 39 public Win32FileSystem() { 40 slash = AccessController.doPrivileged( 41 new GetPropertyAction("file.separator")).charAt(0); 42 semicolon = AccessController.doPrivileged( 43 new GetPropertyAction("path.separator")).charAt(0); 44 altSlash = (this.slash == '\\') ? '/' : '\\'; 45 } 46 47 private boolean isSlash(char c) { 48 return (c == '\\') || (c == '/'); 49 } 50 51 private boolean isLetter(char c) { 52 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 53 } 54 55 private String slashify(String p) { 56 if ((p.length() > 0) && (p.charAt(0) != slash)) return slash + p; 57 else return p; 58 } 59 60 61 /* -- Normalization and construction -- */ 62 63 public char getSeparator() { 64 return slash; 65 } 66 67 public char getPathSeparator() { 68 return semicolon; 69 } 70 71 /* A normal Win32 pathname contains no duplicate slashes, except possibly 72 for a UNC prefix, and does not end with a slash. It may be the empty 73 string. Normalized Win32 pathnames have the convenient property that 74 the length of the prefix almost uniquely identifies the type of the path 75 and whether it is absolute or relative: 76 77 0 relative to both drive and directory 78 1 drive-relative (begins with '\\') 79 2 absolute UNC (if first char is '\\'), 80 else directory-relative (has form "z:foo") 81 3 absolute local pathname (begins with "z:\\") 82 */ 83 84 private int normalizePrefix(String path, int len, StringBuffer sb) { 85 int src = 0; 86 while ((src < len) && isSlash(path.charAt(src))) src++; 87 char c; 88 if ((len - src >= 2) 89 && isLetter(c = path.charAt(src)) 90 && path.charAt(src + 1) == ':') { 91 /* Remove leading slashes if followed by drive specifier. 92 This hack is necessary to support file URLs containing drive 93 specifiers (e.g., "file://c:/path"). As a side effect, 94 "/c:/path" can be used as an alternative to "c:/path". */ 95 sb.append(c); 96 sb.append(':'); 97 src += 2; 98 } else { 99 src = 0; 100 if ((len >= 2) 101 && isSlash(path.charAt(0)) 102 && isSlash(path.charAt(1))) { 103 /* UNC pathname: Retain first slash; leave src pointed at 104 second slash so that further slashes will be collapsed 105 into the second slash. The result will be a pathname 106 beginning with "\\\\" followed (most likely) by a host 107 name. */ 108 src = 1; 109 sb.append(slash); 110 } 111 } 112 return src; 113 } 114 115 /* Normalize the given pathname, whose length is len, starting at the given 116 offset; everything before this offset is already normal. */ 117 private String normalize(String path, int len, int off) { 118 if (len == 0) return path; 119 if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */ 120 int src; 121 char slash = this.slash; 122 StringBuffer sb = new StringBuffer(len); 123 124 if (off == 0) { 125 /* Complete normalization, including prefix */ 126 src = normalizePrefix(path, len, sb); 127 } else { 128 /* Partial normalization */ 129 src = off; 130 sb.append(path.substring(0, off)); 131 } 132 133 /* Remove redundant slashes from the remainder of the path, forcing all 134 slashes into the preferred slash */ 135 while (src < len) { 136 char c = path.charAt(src++); 137 if (isSlash(c)) { 138 while ((src < len) && isSlash(path.charAt(src))) src++; 139 if (src == len) { 140 /* Check for trailing separator */ 141 int sn = sb.length(); 142 if ((sn == 2) && (sb.charAt(1) == ':')) { 143 /* "z:\\" */ 144 sb.append(slash); 145 break; 146 } 147 if (sn == 0) { 148 /* "\\" */ 149 sb.append(slash); 150 break; 151 } 152 if ((sn == 1) && (isSlash(sb.charAt(0)))) { 153 /* "\\\\" is not collapsed to "\\" because "\\\\" marks 154 the beginning of a UNC pathname. Even though it is 155 not, by itself, a valid UNC pathname, we leave it as 156 is in order to be consistent with the win32 APIs, 157 which treat this case as an invalid UNC pathname 158 rather than as an alias for the root directory of 159 the current drive. */ 160 sb.append(slash); 161 break; 162 } 163 /* Path does not denote a root directory, so do not append 164 trailing slash */ 165 break; 166 } else { 167 sb.append(slash); 168 } 169 } else { 170 sb.append(c); 171 } 172 } 173 174 String rv = sb.toString(); 175 return rv; 176 } 177 178 /* Check that the given pathname is normal. If not, invoke the real 179 normalizer on the part of the pathname that requires normalization. 180 This way we iterate through the whole pathname string only once. */ 181 public String normalize(String path) { 182 int n = path.length(); 183 char slash = this.slash; 184 char altSlash = this.altSlash; 185 char prev = 0; 186 for (int i = 0; i < n; i++) { 187 char c = path.charAt(i); 188 if (c == altSlash) 189 return normalize(path, n, (prev == slash) ? i - 1 : i); 190 if ((c == slash) && (prev == slash) && (i > 1)) 191 return normalize(path, n, i - 1); 192 if ((c == ':') && (i > 1)) 193 return normalize(path, n, 0); 194 prev = c; 195 } 196 if (prev == slash) return normalize(path, n, n - 1); 197 return path; 198 } 199 200 public int prefixLength(String path) { 201 char slash = this.slash; 202 int n = path.length(); 203 if (n == 0) return 0; 204 char c0 = path.charAt(0); 205 char c1 = (n > 1) ? path.charAt(1) : 0; 206 if (c0 == slash) { 207 if (c1 == slash) return 2; /* Absolute UNC pathname "\\\\foo" */ 208 return 1; /* Drive-relative "\\foo" */ 209 } 210 if (isLetter(c0) && (c1 == ':')) { 211 if ((n > 2) && (path.charAt(2) == slash)) 212 return 3; /* Absolute local pathname "z:\\foo" */ 213 return 2; /* Directory-relative "z:foo" */ 214 } 215 return 0; /* Completely relative */ 216 } 217 218 public String resolve(String parent, String child) { 219 int pn = parent.length(); 220 if (pn == 0) return child; 221 int cn = child.length(); 222 if (cn == 0) return parent; 223 224 String c = child; 225 int childStart = 0; 226 int parentEnd = pn; 227 228 if ((cn > 1) && (c.charAt(0) == slash)) { 229 if (c.charAt(1) == slash) { 230 /* Drop prefix when child is a UNC pathname */ 231 childStart = 2; 232 } else { 233 /* Drop prefix when child is drive-relative */ 234 childStart = 1; 235 236 } 237 if (cn == childStart) { // Child is double slash 238 if (parent.charAt(pn - 1) == slash) 239 return parent.substring(0, pn - 1); 240 return parent; 241 } 242 } 243 244 if (parent.charAt(pn - 1) == slash) 245 parentEnd--; 246 247 int strlen = parentEnd + cn - childStart; 248 char[] theChars = null; 249 if (child.charAt(childStart) == slash) { 250 theChars = new char[strlen]; 251 parent.getChars(0, parentEnd, theChars, 0); 252 child.getChars(childStart, cn, theChars, parentEnd); 253 } else { 254 theChars = new char[strlen + 1]; 255 parent.getChars(0, parentEnd, theChars, 0); 256 theChars[parentEnd] = slash; 257 child.getChars(childStart, cn, theChars, parentEnd + 1); 258 } 259 return new String(theChars); 260 } 261 262 public String getDefaultParent() { 263 return ("" + slash); 264 } 265 266 public String fromURIPath(String path) { 267 String p = path; 268 if ((p.length() > 2) && (p.charAt(2) == ':')) { 269 // "/c:/foo" --> "c:/foo" 270 p = p.substring(1); 271 // "c:/foo/" --> "c:/foo", but "c:/" --> "c:/" 272 if ((p.length() > 3) && p.endsWith("/")) 273 p = p.substring(0, p.length() - 1); 274 } else if ((p.length() > 1) && p.endsWith("/")) { 275 // "/foo/" --> "/foo" 276 p = p.substring(0, p.length() - 1); 277 } 278 return p; 279 } 280 281 282 283 /* -- Path operations -- */ 284 285 public boolean isAbsolute(File f) { 286 int pl = f.getPrefixLength(); 287 return (((pl == 2) && (f.getPath().charAt(0) == slash)) 288 || (pl == 3)); 289 } 290 291 protected native String getDriveDirectory(int drive); 292 293 private static String[] driveDirCache = new String[26]; 294 295 private static int driveIndex(char d) { 296 if ((d >= 'a') && (d <= 'z')) return d - 'a'; 297 if ((d >= 'A') && (d <= 'Z')) return d - 'A'; 298 return -1; 299 } 300 301 private String getDriveDirectory(char drive) { 302 int i = driveIndex(drive); 303 if (i < 0) return null; 304 String s = driveDirCache[i]; 305 if (s != null) return s; 306 s = getDriveDirectory(i + 1); 307 driveDirCache[i] = s; 308 return s; 309 } 310 311 private String getUserPath() { 312 /* For both compatibility and security, 313 we must look this up every time */ 314 return normalize(System.getProperty("user.dir")); 315 } 316 317 private String getDrive(String path) { 318 int pl = prefixLength(path); 319 return (pl == 3) ? path.substring(0, 2) : null; 320 } 321 322 public String resolve(File f) { 323 String path = f.getPath(); 324 int pl = f.getPrefixLength(); 325 if ((pl == 2) && (path.charAt(0) == slash)) 326 return path; /* UNC */ 327 if (pl == 3) 328 return path; /* Absolute local */ 329 if (pl == 0) 330 return getUserPath() + slashify(path); /* Completely relative */ 331 if (pl == 1) { /* Drive-relative */ 332 String up = getUserPath(); 333 String ud = getDrive(up); 334 if (ud != null) return ud + path; 335 return up + path; /* User dir is a UNC path */ 336 } 337 if (pl == 2) { /* Directory-relative */ 338 String up = getUserPath(); 339 String ud = getDrive(up); 340 if ((ud != null) && path.startsWith(ud)) 341 return up + slashify(path.substring(2)); 342 char drive = path.charAt(0); 343 String dir = getDriveDirectory(drive); 344 String np; 345 if (dir != null) { 346 /* When resolving a directory-relative path that refers to a 347 drive other than the current drive, insist that the caller 348 have read permission on the result */ 349 String p = drive + (':' + dir + slashify(path.substring(2))); 350 SecurityManager security = System.getSecurityManager(); 351 try { 352 if (security != null) security.checkRead(p); 353 } catch (SecurityException x) { 354 /* Don't disclose the drive's directory in the exception */ 355 throw new SecurityException("Cannot resolve path " + path); 356 } 357 return p; 358 } 359 return drive + ":" + slashify(path.substring(2)); /* fake it */ 360 } 361 throw new InternalError("Unresolvable path: " + path); 362 } 363 364 // Caches for canonicalization results to improve startup performance. 365 // The first cache handles repeated canonicalizations of the same path 366 // name. The prefix cache handles repeated canonicalizations within the 367 // same directory, and must not create results differing from the true 368 // canonicalization algorithm in canonicalize_md.c. For this reason the 369 // prefix cache is conservative and is not used for complex path names. 370 private ExpiringCache cache = new ExpiringCache(); 371 private ExpiringCache prefixCache = new ExpiringCache(); 372 373 public String canonicalize(String path) throws IOException { 374 // If path is a drive letter only then skip canonicalization 375 int len = path.length(); 376 if ((len == 2) && 377 (isLetter(path.charAt(0))) && 378 (path.charAt(1) == ':')) { 379 char c = path.charAt(0); 380 if ((c >= 'A') && (c <= 'Z')) 381 return path; 382 return "" + ((char) (c-32)) + ':'; 383 } else if ((len == 3) && 384 (isLetter(path.charAt(0))) && 385 (path.charAt(1) == ':') && 386 (path.charAt(2) == '\\')) { 387 char c = path.charAt(0); 388 if ((c >= 'A') && (c <= 'Z')) 389 return path; 390 return "" + ((char) (c-32)) + ':' + '\\'; 391 } 392 if (!useCanonCaches) { 393 return canonicalize0(path); 394 } else { 395 String res = cache.get(path); 396 if (res == null) { 397 String dir = null; 398 String resDir = null; 399 if (useCanonPrefixCache) { 400 dir = parentOrNull(path); 401 if (dir != null) { 402 resDir = prefixCache.get(dir); 403 if (resDir != null) { 404 // Hit only in prefix cache; full path is canonical, 405 // but we need to get the canonical name of the file 406 // in this directory to get the appropriate capitalization 407 String filename = path.substring(1 + dir.length()); 408 res = canonicalizeWithPrefix(resDir, filename); 409 cache.put(dir + File.separatorChar + filename, res); 410 } 411 } 412 } 413 if (res == null) { 414 res = canonicalize0(path); 415 cache.put(path, res); 416 if (useCanonPrefixCache && dir != null) { 417 resDir = parentOrNull(res); 418 if (resDir != null) { 419 File f = new File(res); 420 if (f.exists() && !f.isDirectory()) { 421 prefixCache.put(dir, resDir); 422 } 423 } 424 } 425 } 426 } 427 return res; 428 } 429 } 430 431 protected native String canonicalize0(String path) 432 throws IOException; 433 protected String canonicalizeWithPrefix(String canonicalPrefix, 434 String filename) throws IOException 435 { 436 return canonicalizeWithPrefix0(canonicalPrefix, 437 canonicalPrefix + File.separatorChar + filename); 438 } 439 // Run the canonicalization operation assuming that the prefix 440 // (everything up to the last filename) is canonical; just gets 441 // the canonical name of the last element of the path 442 protected native String canonicalizeWithPrefix0(String canonicalPrefix, 443 String pathWithCanonicalPrefix) 444 throws IOException; 445 // Best-effort attempt to get parent of this path; used for 446 // optimization of filename canonicalization. This must return null for 447 // any cases where the code in canonicalize_md.c would throw an 448 // exception or otherwise deal with non-simple pathnames like handling 449 // of "." and "..". It may conservatively return null in other 450 // situations as well. Returning null will cause the underlying 451 // (expensive) canonicalization routine to be called. 452 static String parentOrNull(String path) { 453 if (path == null) return null; 454 char sep = File.separatorChar; 455 char altSep = '/'; 456 int last = path.length() - 1; 457 int idx = last; 458 int adjacentDots = 0; 459 int nonDotCount = 0; 460 while (idx > 0) { 461 char c = path.charAt(idx); 462 if (c == '.') { 463 if (++adjacentDots >= 2) { 464 // Punt on pathnames containing . and .. 465 return null; 466 } 467 if (nonDotCount == 0) { 468 // Punt on pathnames ending in a . 469 return null; 470 } 471 } else if (c == sep) { 472 if (adjacentDots == 1 && nonDotCount == 0) { 473 // Punt on pathnames containing . and .. 474 return null; 475 } 476 if (idx == 0 || 477 idx >= last - 1 || 478 path.charAt(idx - 1) == sep || 479 path.charAt(idx - 1) == altSep) { 480 // Punt on pathnames containing adjacent slashes 481 // toward the end 482 return null; 483 } 484 return path.substring(0, idx); 485 } else if (c == altSep) { 486 // Punt on pathnames containing both backward and 487 // forward slashes 488 return null; 489 } else if (c == '*' || c == '?') { 490 // Punt on pathnames containing wildcards 491 return null; 492 } else { 493 ++nonDotCount; 494 adjacentDots = 0; 495 } 496 --idx; 497 } 498 return null; 499 } 500 501 502 /* -- Attribute accessors -- */ 503 504 public native int getBooleanAttributes(File f); 505 public native boolean checkAccess(File f, int access); 506 public native long getLastModifiedTime(File f); 507 public native long getLength(File f); 508 public native boolean setPermission(File f, int access, boolean enable, boolean owneronly); 509 510 /* -- File operations -- */ 511 512 public native boolean createFileExclusively(String path) 513 throws IOException; 514 public boolean delete(File f) { 515 // Keep canonicalization caches in sync after file deletion 516 // and renaming operations. Could be more clever than this 517 // (i.e., only remove/update affected entries) but probably 518 // not worth it since these entries expire after 30 seconds 519 // anyway. 520 cache.clear(); 521 prefixCache.clear(); 522 return delete0(f); 523 } 524 protected native boolean delete0(File f); 525 public native String[] list(File f); 526 public native boolean createDirectory(File f); 527 public boolean rename(File f1, File f2) { 528 // Keep canonicalization caches in sync after file deletion 529 // and renaming operations. Could be more clever than this 530 // (i.e., only remove/update affected entries) but probably 531 // not worth it since these entries expire after 30 seconds 532 // anyway. 533 cache.clear(); 534 prefixCache.clear(); 535 return rename0(f1, f2); 536 } 537 protected native boolean rename0(File f1, File f2); 538 public native boolean setLastModifiedTime(File f, long time); 539 public native boolean setReadOnly(File f); 540 541 542 /* -- Filesystem interface -- */ 543 544 private boolean access(String path) { 545 try { 546 SecurityManager security = System.getSecurityManager(); 547 if (security != null) security.checkRead(path); 548 return true; 549 } catch (SecurityException x) { 550 return false; 551 } 552 } 553 554 private static native int listRoots0(); 555 556 public File[] listRoots() { 557 int ds = listRoots0(); 558 int n = 0; 559 for (int i = 0; i < 26; i++) { 560 if (((ds >> i) & 1) != 0) { 561 if (!access((char)('A' + i) + ":" + slash)) 562 ds &= ~(1 << i); 563 else 564 n++; 565 } 566 } 567 File[] fs = new File[n]; 568 int j = 0; 569 char slash = this.slash; 570 for (int i = 0; i < 26; i++) { 571 if (((ds >> i) & 1) != 0) 572 fs[j++] = new File((char)('A' + i) + ":" + slash); 573 } 574 return fs; 575 } 576 577 578 /* -- Disk usage -- */ 579 public long getSpace(File f, int t) { 580 if (f.exists()) { 581 File file = (f.isDirectory() ? f : f.getParentFile()); 582 return getSpace0(file, t); 583 } 584 return 0; 585 } 586 587 private native long getSpace0(File f, int t); 588 589 590 /* -- Basic infrastructure -- */ 591 592 public int compare(File f1, File f2) { 593 return f1.getPath().compareToIgnoreCase(f2.getPath()); 594 } 595 596 public int hashCode(File f) { 597 /* Could make this more efficient: String.hashCodeIgnoreCase */ 598 return f.getPath().toLowerCase(Locale.ENGLISH).hashCode() ^ 1234321; 599 } 600 601 602 private static native void initIDs(); 603 604 static { 605 initIDs(); 606 } 607 608 }