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 }