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