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