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