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