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