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