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