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