1 /*
   2  * Copyright 2008-2009 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import java.nio.file.attribute.*;
  30 import java.nio.channels.*;
  31 import java.io.*;
  32 import java.net.URI;
  33 import java.security.AccessController;
  34 import java.util.*;
  35 import java.lang.ref.WeakReference;
  36 
  37 import com.sun.nio.file.ExtendedWatchEventModifier;
  38 
  39 import sun.security.util.SecurityConstants;
  40 import sun.misc.Unsafe;
  41 
  42 import static sun.nio.fs.WindowsNativeDispatcher.*;
  43 import static sun.nio.fs.WindowsConstants.*;
  44 
  45 /**
  46  * Windows implementation of Path
  47  */
  48 
  49 class WindowsPath extends AbstractPath {
  50     private static final Unsafe unsafe = Unsafe.getUnsafe();
  51 
  52     // The maximum path that does not require long path prefix. On Windows
  53     // the maximum path is 260 minus 1 (NUL) but for directories it is 260
  54     // minus 12 minus 1 (to allow for the creation of a 8.3 file in the
  55     // directory).
  56     private static final int MAX_PATH = 247;
  57 
  58     // Maximum extended-length path
  59     private static final int MAX_LONG_PATH = 32000;
  60 
  61     // FIXME - eliminate this reference to reduce space
  62     private final WindowsFileSystem fs;
  63 
  64     // path type
  65     private final WindowsPathType type;
  66     // root component (may be empty)
  67     private final String root;
  68     // normalized path
  69     private final String path;
  70 
  71     // the path to use in Win32 calls. This differs from path for relative
  72     // paths and has a long path prefix for all paths longer than MAX_PATH.
  73     private volatile WeakReference<String> pathForWin32Calls;
  74 
  75     // offsets into name components (computed lazily)
  76     private volatile Integer[] offsets;
  77 
  78     // computed hash code (computed lazily, no need to be volatile)
  79     private int hash;
  80 
  81 
  82     /**
  83      * Initializes a new instance of this class.
  84      */
  85     private WindowsPath(WindowsFileSystem fs,
  86                         WindowsPathType type,
  87                         String root,
  88                         String path)
  89     {
  90         this.fs = fs;
  91         this.type = type;
  92         this.root = root;
  93         this.path = path;
  94     }
  95 
  96     /**
  97      * Creates a Path by parsing the given path.
  98      */
  99     static WindowsPath parse(WindowsFileSystem fs, String path) {
 100         WindowsPathParser.Result result = WindowsPathParser.parse(path);
 101         return new WindowsPath(fs, result.type(), result.root(), result.path());
 102     }
 103 
 104     /**
 105      * Creates a Path from a given path that is known to be normalized.
 106      */
 107     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 108                                                 String path,
 109                                                 BasicFileAttributes attrs)
 110     {
 111         try {
 112             WindowsPathParser.Result result =
 113                 WindowsPathParser.parseNormalizedPath(path);
 114             if (attrs == null) {
 115                 return new WindowsPath(fs,
 116                                        result.type(),
 117                                        result.root(),
 118                                        result.path());
 119             } else {
 120                 return new WindowsPathWithAttributes(fs,
 121                                                      result.type(),
 122                                                      result.root(),
 123                                                      result.path(),
 124                                                      attrs);
 125             }
 126         } catch (InvalidPathException x) {
 127             throw new AssertionError(x.getMessage());
 128         }
 129     }
 130 
 131     /**
 132      * Creates a WindowsPath from a given path that is known to be normalized.
 133      */
 134     static WindowsPath createFromNormalizedPath(WindowsFileSystem fs,
 135                                                 String path)
 136     {
 137         return createFromNormalizedPath(fs, path, null);
 138     }
 139 
 140     /**
 141      * Special implementation with attached/cached attributes (used to quicken
 142      * file tree traveral)
 143      */
 144     private static class WindowsPathWithAttributes
 145         extends WindowsPath implements BasicFileAttributesHolder
 146     {
 147         final WeakReference<BasicFileAttributes> ref;
 148 
 149         WindowsPathWithAttributes(WindowsFileSystem fs,
 150                                   WindowsPathType type,
 151                                   String root,
 152                                   String path,
 153                                   BasicFileAttributes attrs)
 154         {
 155             super(fs, type, root, path);
 156             ref = new WeakReference<BasicFileAttributes>(attrs);
 157         }
 158 
 159         @Override
 160         public BasicFileAttributes get() {
 161             return ref.get();
 162         }
 163 
 164         @Override
 165         public void invalidate() {
 166             ref.clear();
 167         }
 168 
 169         // no need to override equals/hashCode.
 170     }
 171 
 172     // use this message when throwing exceptions
 173     String getPathForExceptionMessage() {
 174         return path;
 175     }
 176 
 177     // use this path for permission checks
 178     String getPathForPermissionCheck() {
 179         return path;
 180     }
 181 
 182     // use this path for Win32 calls
 183     // This method will prefix long paths with \\?\ or \\?\UNC as required.
 184     String getPathForWin32Calls() throws WindowsException {
 185         // short absolute paths can be used directly
 186         if (isAbsolute() && path.length() <= MAX_PATH)
 187             return path;
 188 
 189         // return cached values if available
 190         WeakReference<String> ref = pathForWin32Calls;
 191         String resolved = (ref != null) ? ref.get() : null;
 192         if (resolved != null) {
 193             // Win32 path already available
 194             return resolved;
 195         }
 196 
 197         // resolve against default directory
 198         resolved = getAbsolutePath();
 199 
 200         // Long paths need to have "." and ".." removed and be prefixed with
 201         // "\\?\". Note that it is okay to remove ".." even when it follows
 202         // a link - for example, it is okay for foo/link/../bar to be changed
 203         // to foo/bar. The reason is that Win32 APIs to access foo/link/../bar
 204         // will access foo/bar anyway (which differs to Unix systems)
 205         if (resolved.length() > MAX_PATH) {
 206             if (resolved.length() > MAX_LONG_PATH) {
 207                 throw new WindowsException("Cannot access file with path exceeding "
 208                     + MAX_LONG_PATH + " characters");
 209             }
 210             resolved = addPrefixIfNeeded(GetFullPathName(resolved));
 211         }
 212 
 213         // cache the resolved path (except drive relative paths as the working
 214         // directory on removal media devices can change during the lifetime
 215         // of the VM)
 216         if (type != WindowsPathType.DRIVE_RELATIVE) {
 217             synchronized (path) {
 218                 pathForWin32Calls = new WeakReference<String>(resolved);
 219             }
 220         }
 221         return resolved;
 222     }
 223 
 224     // return this path resolved against the file system's default directory
 225     private String getAbsolutePath() throws WindowsException {
 226         if (isAbsolute())
 227             return path;
 228 
 229         // Relative path ("foo" for example)
 230         if (type == WindowsPathType.RELATIVE) {
 231             String defaultDirectory = getFileSystem().defaultDirectory();
 232             if (defaultDirectory.endsWith("\\")) {
 233                 return defaultDirectory + path;
 234             } else {
 235                 StringBuilder sb =
 236                     new StringBuilder(defaultDirectory.length() + path.length() + 1);
 237                 return sb.append(defaultDirectory).append('\\').append(path).toString();
 238             }
 239         }
 240 
 241         // Directory relative path ("\foo" for example)
 242         if (type == WindowsPathType.DIRECTORY_RELATIVE) {
 243             String defaultRoot = getFileSystem().defaultRoot();
 244             return defaultRoot + path.substring(1);
 245         }
 246 
 247         // Drive relative path ("C:foo" for example).
 248         if (isSameDrive(root, getFileSystem().defaultRoot())) {
 249             // relative to default directory
 250             String remaining = path.substring(root.length());
 251             String defaultDirectory = getFileSystem().defaultDirectory();
 252             String result;
 253             if (defaultDirectory.endsWith("\\")) {
 254                 result = defaultDirectory + remaining;
 255             } else {
 256                 result = defaultDirectory + "\\" + remaining;
 257             }
 258             return result;
 259         } else {
 260             // relative to some other drive
 261             String wd;
 262             try {
 263                 int dt = GetDriveType(root + "\\");
 264                 if (dt == DRIVE_UNKNOWN || dt == DRIVE_NO_ROOT_DIR)
 265                     throw new WindowsException("");
 266                 wd = GetFullPathName(root + ".");
 267             } catch (WindowsException x) {
 268                 throw new WindowsException("Unable to get working directory of drive '" +
 269                     Character.toUpperCase(root.charAt(0)) + "'");
 270             }
 271             String result = wd;
 272             if (wd.endsWith("\\")) {
 273                 result += path.substring(root.length());
 274             } else {
 275                 if (path.length() > root.length())
 276                     result += "\\" + path.substring(root.length());
 277             }
 278             return result;
 279         }
 280     }
 281 
 282     // returns true if same drive letter
 283     private static boolean isSameDrive(String root1, String root2) {
 284         return Character.toUpperCase(root1.charAt(0)) ==
 285                Character.toUpperCase(root2.charAt(0));
 286     }
 287 
 288     // Add long path prefix to path if required
 289     private static String addPrefixIfNeeded(String path) {
 290         if (path.length() > 248) {
 291             if (path.startsWith("\\\\")) {
 292                 path = "\\\\?\\UNC" + path.substring(1, path.length());
 293             } else {
 294                 path = "\\\\?\\" + path;
 295             }
 296         }
 297         return path;
 298     }
 299 
 300     @Override
 301     public WindowsFileSystem getFileSystem() {
 302         return fs;
 303     }
 304 
 305     // -- Path operations --
 306 
 307     @Override
 308     public Path getName() {
 309         // represents root component only
 310         if (root.length() == path.length())
 311             return null;
 312         int off = path.lastIndexOf('\\');
 313         if (off < root.length())
 314             off = root.length();
 315         else
 316             off++;
 317         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", path.substring(off));
 318     }
 319 
 320     @Override
 321     public WindowsPath getParent() {
 322         // represents root component only
 323         if (root.length() == path.length())
 324             return null;
 325         int off = path.lastIndexOf('\\');
 326         if (off < root.length())
 327             return getRoot();
 328         else
 329             return new WindowsPath(getFileSystem(),
 330                                    type,
 331                                    root,
 332                                    path.substring(0, off));
 333     }
 334 
 335     @Override
 336     public WindowsPath getRoot() {
 337         if (root.length() == 0)
 338             return null;
 339         return new WindowsPath(getFileSystem(), type, root, root);
 340     }
 341 
 342     // package-private
 343     boolean isUnc() {
 344         return type == WindowsPathType.UNC;
 345     }
 346 
 347     boolean needsSlashWhenResolving() {
 348         if (path.endsWith("\\"))
 349             return false;
 350         return path.length() > root.length();
 351     }
 352 
 353     @Override
 354     public boolean isAbsolute() {
 355         return type == WindowsPathType.ABSOLUTE || type == WindowsPathType.UNC;
 356     }
 357 
 358     private WindowsPath checkPath(FileRef path) {
 359         if (path == null)
 360             throw new NullPointerException();
 361         if (!(path instanceof WindowsPath)) {
 362             throw new ProviderMismatchException();
 363         }
 364         return (WindowsPath)path;
 365     }
 366 
 367     @Override
 368     public WindowsPath relativize(Path obj) {
 369         WindowsPath other = checkPath(obj);
 370         if (this.equals(other))
 371             return null;
 372 
 373         // can only relativize paths of the same type
 374         if (this.type != other.type)
 375             throw new IllegalArgumentException("'other' is different type of Path");
 376 
 377         // can only relativize paths if root component matches
 378         if (!this.root.equalsIgnoreCase(other.root))
 379             throw new IllegalArgumentException("'other' has different root");
 380 
 381         int bn = this.getNameCount();
 382         int cn = other.getNameCount();
 383 
 384         // skip matching names
 385         int n = (bn > cn) ? cn : bn;
 386         int i = 0;
 387         while (i < n) {
 388             if (!this.getName(i).equals(other.getName(i)))
 389                 break;
 390             i++;
 391         }
 392 
 393         // append ..\ for remaining names in the base
 394         StringBuilder result = new StringBuilder();
 395         for (int j=i; j<bn; j++) {
 396             result.append("..\\");
 397         }
 398 
 399         // append remaining names in child
 400         for (int j=i; j<cn; j++) {
 401             result.append(other.getName(j).toString());
 402             result.append("\\");
 403         }
 404 
 405         // drop trailing slash in result
 406         result.setLength(result.length()-1);
 407         return createFromNormalizedPath(getFileSystem(), result.toString());
 408     }
 409 
 410     @Override
 411     public Path normalize() {
 412         final int count = getNameCount();
 413         if (count == 0)
 414             return this;
 415 
 416         boolean[] ignore = new boolean[count];      // true => ignore name
 417         int remaining = count;                      // number of names remaining
 418 
 419         // multiple passes to eliminate all occurences of "." and "name/.."
 420         int prevRemaining;
 421         do {
 422             prevRemaining = remaining;
 423             int prevName = -1;
 424             for (int i=0; i<count; i++) {
 425                 if (ignore[i])
 426                     continue;
 427 
 428                 String name = elementAsString(i);
 429 
 430                 // not "." or ".."
 431                 if (name.length() > 2) {
 432                     prevName = i;
 433                     continue;
 434                 }
 435 
 436                 // "." or something else
 437                 if (name.length() == 1) {
 438                     // ignore "."
 439                     if (name.charAt(0) == '.') {
 440                         ignore[i] = true;
 441                         remaining--;
 442                     } else {
 443                         prevName = i;
 444                     }
 445                     continue;
 446                 }
 447 
 448                 // not ".."
 449                 if (name.charAt(0) != '.' || name.charAt(1) != '.') {
 450                     prevName = i;
 451                     continue;
 452                 }
 453 
 454                 // ".." found
 455                 if (prevName >= 0) {
 456                     // name/<ignored>/.. found so mark name and ".." to be
 457                     // ignored
 458                     ignore[prevName] = true;
 459                     ignore[i] = true;
 460                     remaining = remaining - 2;
 461                     prevName = -1;
 462                 } else {
 463                     // Cases:
 464                     //    C:\<ignored>\..
 465                     //    \\server\\share\<ignored>\..
 466                     //    \<ignored>..
 467                     if (isAbsolute() || type == WindowsPathType.DIRECTORY_RELATIVE) {
 468                         boolean hasPrevious = false;
 469                         for (int j=0; j<i; j++) {
 470                             if (!ignore[j]) {
 471                                 hasPrevious = true;
 472                                 break;
 473                             }
 474                         }
 475                         if (!hasPrevious) {
 476                             // all proceeding names are ignored
 477                             ignore[i] = true;
 478                             remaining--;
 479                         }
 480                     }
 481                 }
 482             }
 483         } while (prevRemaining > remaining);
 484 
 485         // no redundant names
 486         if (remaining == count)
 487             return this;
 488 
 489         // corner case - all names removed
 490         if (remaining == 0) {
 491             return getRoot();
 492         }
 493 
 494         // re-constitute the path from the remaining names.
 495         StringBuilder result = new StringBuilder();
 496         if (root != null)
 497             result.append(root);
 498         for (int i=0; i<count; i++) {
 499             if (!ignore[i]) {
 500                 result.append(getName(i).toString());
 501                 result.append("\\");
 502             }
 503         }
 504 
 505         // drop trailing slash in result
 506         result.setLength(result.length()-1);
 507         return createFromNormalizedPath(getFileSystem(), result.toString());
 508     }
 509 
 510     @Override
 511     public WindowsPath resolve(Path obj) {
 512         if (obj == null)
 513             return this;
 514         WindowsPath other = checkPath(obj);
 515         if (other.isAbsolute())
 516             return other;
 517 
 518         switch (other.type) {
 519             case RELATIVE: {
 520                 String result;
 521                 if (path.endsWith("\\") || (root.length() == path.length())) {
 522                     result = path + other.path;
 523                 } else {
 524                     result = path + "\\" + other.path;
 525                 }
 526                 return new WindowsPath(getFileSystem(), type, root, result);
 527             }
 528 
 529             case DIRECTORY_RELATIVE: {
 530                 String result;
 531                 if (root.endsWith("\\")) {
 532                     result = root + other.path.substring(1);
 533                 } else {
 534                     result = root + other.path;
 535                 }
 536                 return createFromNormalizedPath(getFileSystem(), result);
 537             }
 538 
 539             case DRIVE_RELATIVE: {
 540                 if (!root.endsWith("\\"))
 541                     return other;
 542                 // if different roots then return other
 543                 String thisRoot = root.substring(0, root.length()-1);
 544                 if (!thisRoot.equalsIgnoreCase(other.root))
 545                     return other;
 546                 // same roots
 547                 String remaining = other.path.substring(other.root.length());
 548                 String result;
 549                 if (path.endsWith("\\")) {
 550                     result = path + remaining;
 551                 } else {
 552                     result = path + "\\" + remaining;
 553                 }
 554                 return createFromNormalizedPath(getFileSystem(), result);
 555             }
 556 
 557             default:
 558                 throw new AssertionError();
 559         }
 560     }
 561 
 562     @Override
 563     public WindowsPath resolve(String other) {
 564         return resolve(getFileSystem().getPath(other));
 565     }
 566 
 567     // generate offset array
 568     private void initOffsets() {
 569         if (offsets == null) {
 570             ArrayList<Integer> list = new ArrayList<Integer>();
 571             int start = root.length();
 572             int off = root.length();
 573             while (off < path.length()) {
 574                 if (path.charAt(off) != '\\') {
 575                     off++;
 576                 } else {
 577                     list.add(start);
 578                     start = ++off;
 579                 }
 580             }
 581             if (start != off)
 582                 list.add(start);
 583             synchronized (this) {
 584                 if (offsets == null)
 585                     offsets = list.toArray(new Integer[list.size()]);
 586             }
 587         }
 588     }
 589 
 590     @Override
 591     public int getNameCount() {
 592         initOffsets();
 593         return offsets.length;
 594     }
 595 
 596     private String elementAsString(int i) {
 597         initOffsets();
 598         if (i == (offsets.length-1))
 599             return path.substring(offsets[i]);
 600         return path.substring(offsets[i], offsets[i+1]-1);
 601     }
 602 
 603     @Override
 604     public WindowsPath getName(int index) {
 605         initOffsets();
 606         if (index < 0 || index >= offsets.length)
 607             throw new IllegalArgumentException();
 608         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", elementAsString(index));
 609     }
 610 
 611     @Override
 612     public WindowsPath subpath(int beginIndex, int endIndex) {
 613         initOffsets();
 614         if (beginIndex < 0)
 615             throw new IllegalArgumentException();
 616         if (beginIndex >= offsets.length)
 617             throw new IllegalArgumentException();
 618         if (endIndex > offsets.length)
 619             throw new IllegalArgumentException();
 620         if (beginIndex >= endIndex)
 621             throw new IllegalArgumentException();
 622 
 623         StringBuilder sb = new StringBuilder();
 624         Integer[] nelems = new Integer[endIndex - beginIndex];
 625         for (int i = beginIndex; i < endIndex; i++) {
 626             nelems[i-beginIndex] = sb.length();
 627             sb.append(elementAsString(i));
 628             if (i != (endIndex-1))
 629                 sb.append("\\");
 630         }
 631         return new WindowsPath(getFileSystem(), WindowsPathType.RELATIVE, "", sb.toString());
 632     }
 633 
 634     @Override
 635     public boolean startsWith(Path obj) {
 636         WindowsPath other = checkPath(obj);
 637 
 638         // if this path has a root component the given path's root must match
 639         if (!this.root.equalsIgnoreCase(other.root))
 640             return false;
 641 
 642         // roots match so compare elements
 643         int thisCount = getNameCount();
 644         int otherCount = other.getNameCount();
 645         if (otherCount <= thisCount) {
 646             while (--otherCount >= 0) {
 647                 String thisElement = this.elementAsString(otherCount);
 648                 String otherElement = other.elementAsString(otherCount);
 649                 // FIXME: should compare in uppercase
 650                 if (!thisElement.equalsIgnoreCase(otherElement))
 651                     return false;
 652             }
 653             return true;
 654         }
 655         return false;
 656     }
 657 
 658     @Override
 659     public boolean endsWith(Path obj) {
 660         WindowsPath other = checkPath(obj);
 661 
 662         // other path is longer
 663         if (other.path.length() > path.length()) {
 664             return false;
 665         }
 666 
 667         int thisCount = this.getNameCount();
 668         int otherCount = other.getNameCount();
 669 
 670         // given path has more elements that this path
 671         if (otherCount > thisCount) {
 672             return false;
 673         }
 674 
 675         // compare roots
 676         if (other.root.length() > 0) {
 677             if (otherCount < thisCount)
 678                 return false;
 679             // FIXME: should compare in uppercase
 680             if (!this.root.equalsIgnoreCase(other.root))
 681                 return false;
 682         }
 683 
 684         // match last 'otherCount' elements
 685         int off = thisCount - otherCount;
 686         while (--otherCount >= 0) {
 687             String thisElement = this.elementAsString(off + otherCount);
 688             String otherElement = other.elementAsString(otherCount);
 689             // FIXME: should compare in uppercase
 690             if (!thisElement.equalsIgnoreCase(otherElement))
 691                 return false;
 692         }
 693         return true;
 694     }
 695 
 696     @Override
 697     public int compareTo(Path obj) {
 698         if (obj == null)
 699             throw new NullPointerException();
 700         String s1 = path;
 701         String s2 = ((WindowsPath)obj).path;
 702         int n1 = s1.length();
 703         int n2 = s2.length();
 704         int min = Math.min(n1, n2);
 705         for (int i = 0; i < min; i++) {
 706             char c1 = s1.charAt(i);
 707             char c2 = s2.charAt(i);
 708              if (c1 != c2) {
 709                  c1 = Character.toUpperCase(c1);
 710                  c2 = Character.toUpperCase(c2);
 711                  if (c1 != c2) {
 712                      return c1 - c2;
 713                  }
 714              }
 715         }
 716         return n1 - n2;
 717     }
 718 
 719     @Override
 720     public boolean equals(Object obj) {
 721         if ((obj != null) && (obj instanceof WindowsPath)) {
 722             return compareTo((Path)obj) == 0;
 723         }
 724         return false;
 725     }
 726 
 727     @Override
 728     public int hashCode() {
 729         // OK if two or more threads compute hash
 730         int h = hash;
 731         if (h == 0) {
 732             for (int i = 0; i< path.length(); i++) {
 733                 h = 31*h + Character.toUpperCase(path.charAt(i));
 734             }
 735             hash = h;
 736         }
 737         return h;
 738     }
 739 
 740     @Override
 741     public String toString() {
 742         return path;
 743     }
 744 
 745     @Override
 746     public Iterator<Path> iterator() {
 747         return new Iterator<Path>() {
 748             private int i = 0;
 749             @Override
 750             public boolean hasNext() {
 751                 return (i < getNameCount());
 752             }
 753             @Override
 754             public Path next() {
 755                 if (i < getNameCount()) {
 756                     Path result = getName(i);
 757                     i++;
 758                     return result;
 759                 } else {
 760                     throw new NoSuchElementException();
 761                 }
 762             }
 763             @Override
 764             public void remove() {
 765                 throw new UnsupportedOperationException();
 766             }
 767         };
 768     }
 769 
 770     // -- file operations --
 771 
 772     // package-private
 773     long openForReadAttributeAccess(boolean followLinks)
 774         throws WindowsException
 775     {
 776         int flags = FILE_FLAG_BACKUP_SEMANTICS;
 777         if (!followLinks && getFileSystem().supportsLinks())
 778             flags |= FILE_FLAG_OPEN_REPARSE_POINT;
 779         return CreateFile(getPathForWin32Calls(),
 780                           FILE_READ_ATTRIBUTES,
 781                           (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
 782                           0L,
 783                           OPEN_EXISTING,
 784                           flags);
 785     }
 786 
 787     void checkRead() {
 788         SecurityManager sm = System.getSecurityManager();
 789         if (sm != null) {
 790             sm.checkRead(getPathForPermissionCheck());
 791         }
 792     }
 793 
 794     void checkWrite() {
 795         SecurityManager sm = System.getSecurityManager();
 796         if (sm != null) {
 797             sm.checkWrite(getPathForPermissionCheck());
 798         }
 799     }
 800 
 801     void checkDelete() {
 802         SecurityManager sm = System.getSecurityManager();
 803         if (sm != null) {
 804             sm.checkDelete(getPathForPermissionCheck());
 805         }
 806     }
 807 
 808     @Override
 809     public FileStore getFileStore()
 810         throws IOException
 811     {
 812         SecurityManager sm = System.getSecurityManager();
 813         if (sm != null) {
 814             sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
 815             checkRead();
 816         }
 817         return WindowsFileStore.create(this);
 818     }
 819 
 820     /**
 821      * Returns buffer with SID_AND_ATTRIBUTES structure representing the user
 822      * associated with the current thread access token.
 823      * FIXME - this should be cached.
 824      */
 825     private NativeBuffer getUserInfo() throws IOException {
 826         try {
 827             long hToken = WindowsSecurity.processTokenWithQueryAccess;
 828             int size = GetTokenInformation(hToken, TokenUser, 0L, 0);
 829             assert size > 0;
 830 
 831             NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
 832             try {
 833                 int newsize = GetTokenInformation(hToken, TokenUser,
 834                                                   buffer.address(), size);
 835                 if (newsize != size)
 836                     throw new AssertionError();
 837                 return buffer;
 838             } catch (WindowsException x) {
 839                 buffer.release();
 840                 throw x;
 841             }
 842         } catch (WindowsException x) {
 843             throw new IOException(x.getMessage());
 844         }
 845     }
 846 
 847     /**
 848      * Reads the file ACL and return the effective access as ACCESS_MASK
 849      */
 850     private int getEffectiveAccess() throws IOException {
 851         // read security descriptor continaing ACL (symlinks are followed)
 852         String target = WindowsLinkSupport.getFinalPath(this, true);
 853         NativeBuffer aclBuffer = WindowsAclFileAttributeView
 854             .getFileSecurity(target, DACL_SECURITY_INFORMATION);
 855 
 856         // retrieves DACL from security descriptor
 857         long pAcl = GetSecurityDescriptorDacl(aclBuffer.address());
 858 
 859         // Use GetEffectiveRightsFromAcl to get effective access to file
 860         try {
 861             NativeBuffer userBuffer = getUserInfo();
 862             try {
 863                 try {
 864                     // SID_AND_ATTRIBUTES->pSid
 865                     long pSid = unsafe.getAddress(userBuffer.address());
 866                     long pTrustee = BuildTrusteeWithSid(pSid);
 867                     try {
 868                         return GetEffectiveRightsFromAcl(pAcl, pTrustee);
 869                     } finally {
 870                         LocalFree(pTrustee);
 871                     }
 872                 } catch (WindowsException x) {
 873                     throw new IOException("Unable to get effective rights from ACL: " +
 874                         x.getMessage());
 875                 }
 876             } finally {
 877                 userBuffer.release();
 878             }
 879         } finally {
 880             aclBuffer.release();
 881         }
 882     }
 883 
 884     @Override
 885     public void checkAccess(AccessMode... modes) throws IOException {
 886         // if no access modes then simply file attributes
 887         if (modes.length == 0) {
 888             checkRead();
 889             try {
 890                 WindowsFileAttributes.get(this, true);
 891             } catch (WindowsException exc) {
 892                 exc.rethrowAsIOException(this);
 893             }
 894             return;
 895         }
 896 
 897         boolean r = false;
 898         boolean w = false;
 899         boolean x = false;
 900         for (AccessMode mode: modes) {
 901             switch (mode) {
 902                 case READ : r = true; break;
 903                 case WRITE : w = true; break;
 904                 case EXECUTE : x = true; break;
 905                 default: throw new AssertionError("Should not get here");
 906             }
 907         }
 908 
 909         int mask = 0;
 910         if (r) {
 911             checkRead();
 912             mask |= FILE_READ_DATA;
 913         }
 914         if (w) {
 915             checkWrite();
 916             mask |= FILE_WRITE_DATA;
 917         }
 918         if (x) {
 919             SecurityManager sm = System.getSecurityManager();
 920             if (sm != null)
 921                 sm.checkExec(getPathForPermissionCheck());
 922             mask |= FILE_EXECUTE;
 923         }
 924 
 925         if ((getEffectiveAccess() & mask) == 0)
 926             throw new AccessDeniedException(
 927                 this.getPathForExceptionMessage(), null,
 928                 "Effective permissions does not allow requested access");
 929 
 930         // for write access we neeed to check if the DOS readonly attribute
 931         // and if the volume is read-only
 932         if (w) {
 933             try {
 934                 WindowsFileAttributes attrs = WindowsFileAttributes.get(this, true);
 935                 if (!attrs.isDirectory() && attrs.isReadOnly())
 936                     throw new AccessDeniedException(
 937                         this.getPathForExceptionMessage(), null,
 938                         "DOS readonly attribute is set");
 939             } catch (WindowsException exc) {
 940                 exc.rethrowAsIOException(this);
 941             }
 942 
 943             if (WindowsFileStore.create(this).isReadOnly()) {
 944                 throw new AccessDeniedException(
 945                     this.getPathForExceptionMessage(), null, "Read-only file system");
 946             }
 947             return;
 948         }
 949     }
 950 
 951     @Override
 952     void implDelete(boolean failIfNotExists) throws IOException {
 953         checkDelete();
 954 
 955         WindowsFileAttributes attrs = null;
 956         try {
 957              // need to know if file is a directory or junction
 958              attrs = WindowsFileAttributes.get(this, false);
 959              if (attrs.isDirectory() || attrs.isDirectoryLink()) {
 960                 RemoveDirectory(getPathForWin32Calls());
 961              } else {
 962                 DeleteFile(getPathForWin32Calls());
 963              }
 964         } catch (WindowsException x) {
 965 
 966             // no-op if file does not exist
 967             if (!failIfNotExists &&
 968                 (x.lastError() == ERROR_FILE_NOT_FOUND ||
 969                  x.lastError() == ERROR_PATH_NOT_FOUND)) return;
 970 
 971             if (attrs != null && attrs.isDirectory()) {
 972                 // ERROR_ALREADY_EXISTS is returned when attempting to delete
 973                 // non-empty directory on SAMBA servers.
 974                 if (x.lastError() == ERROR_DIR_NOT_EMPTY ||
 975                     x.lastError() == ERROR_ALREADY_EXISTS)
 976                 {
 977                     throw new DirectoryNotEmptyException(
 978                         getPathForExceptionMessage());
 979                 }
 980             }
 981             x.rethrowAsIOException(this);
 982         }
 983     }
 984 
 985     @Override
 986     public DirectoryStream<Path> newDirectoryStream(DirectoryStream.Filter<? super Path> filter)
 987         throws IOException
 988     {
 989         checkRead();
 990         if (filter == null)
 991             throw new NullPointerException();
 992         return new WindowsDirectoryStream(this, filter);
 993     }
 994 
 995     @Override
 996     public void implCopyTo(Path obj, CopyOption... options) throws IOException {
 997         WindowsPath target = (WindowsPath)obj;
 998         WindowsFileCopy.copy(this, target, options);
 999     }
1000 
1001     @Override
1002     public void implMoveTo(Path obj, CopyOption... options) throws IOException {
1003         WindowsPath target = (WindowsPath)obj;
1004         WindowsFileCopy.move(this, target, options);
1005     }
1006 
1007     private boolean followLinks(LinkOption... options) {
1008         boolean followLinks = true;
1009         for (LinkOption option: options) {
1010             if (option == LinkOption.NOFOLLOW_LINKS) {
1011                 followLinks = false;
1012                 continue;
1013             }
1014             if (option == null)
1015                 throw new NullPointerException();
1016             throw new AssertionError("Should not get here");
1017         }
1018         return followLinks;
1019     }
1020 
1021     @Override
1022     @SuppressWarnings("unchecked")
1023     public <V extends FileAttributeView> V
1024         getFileAttributeView(Class<V> view, LinkOption... options)
1025     {
1026         if (view == null)
1027             throw new NullPointerException();
1028         boolean followLinks = followLinks(options);
1029         if (view == BasicFileAttributeView.class)
1030             return (V) WindowsFileAttributeViews.createBasicView(this, followLinks);
1031         if (view == DosFileAttributeView.class)
1032             return (V) WindowsFileAttributeViews.createDosView(this, followLinks);
1033         if (view == AclFileAttributeView.class)
1034             return (V) new WindowsAclFileAttributeView(this, followLinks);
1035         if (view == FileOwnerAttributeView.class)
1036             return (V) new FileOwnerAttributeViewImpl(
1037                 new WindowsAclFileAttributeView(this, followLinks));
1038         if (view == UserDefinedFileAttributeView.class)
1039             return (V) new WindowsUserDefinedFileAttributeView(this, followLinks);
1040         return (V) null;
1041     }
1042 
1043     @Override
1044     public DynamicFileAttributeView getFileAttributeView(String name, LinkOption... options) {
1045         boolean followLinks = followLinks(options);
1046         if (name.equals("basic"))
1047             return WindowsFileAttributeViews.createBasicView(this, followLinks);
1048         if (name.equals("dos"))
1049             return WindowsFileAttributeViews.createDosView(this, followLinks);
1050         if (name.equals("acl"))
1051             return new WindowsAclFileAttributeView(this, followLinks);
1052         if (name.equals("owner"))
1053             return new FileOwnerAttributeViewImpl(
1054                 new WindowsAclFileAttributeView(this, followLinks));
1055         if (name.equals("user"))
1056             return new WindowsUserDefinedFileAttributeView(this, followLinks);
1057         return null;
1058     }
1059 
1060     @Override
1061     public WindowsPath createDirectory(FileAttribute<?>... attrs)
1062         throws IOException
1063     {
1064         checkWrite();
1065         WindowsSecurityDescriptor sd = WindowsSecurityDescriptor.fromAttribute(attrs);
1066         try {
1067             CreateDirectory(getPathForWin32Calls(), sd.address());
1068         } catch (WindowsException x) {
1069             x.rethrowAsIOException(this);
1070         } finally {
1071             sd.release();
1072         }
1073         return this;
1074     }
1075 
1076     @Override
1077     public SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
1078                                               FileAttribute<?>... attrs)
1079          throws IOException
1080     {
1081         WindowsSecurityDescriptor sd =
1082             WindowsSecurityDescriptor.fromAttribute(attrs);
1083         try {
1084             return WindowsChannelFactory
1085                 .newFileChannel(getPathForWin32Calls(),
1086                                 getPathForPermissionCheck(),
1087                                 options,
1088                                 sd.address());
1089         } catch (WindowsException x) {
1090             x.rethrowAsIOException(this);
1091             return null;  // keep compiler happy
1092         } finally {
1093             sd.release();
1094         }
1095     }
1096 
1097     @Override
1098     public boolean isSameFile(Path obj) throws IOException {
1099         if (this.equals(obj))
1100             return true;
1101         if (!(obj instanceof WindowsPath))  // includes null check
1102             return false;
1103         WindowsPath other = (WindowsPath)obj;
1104 
1105         // check security manager access to both files
1106         this.checkRead();
1107         other.checkRead();
1108 
1109         // open both files and see if they are the same
1110         long h1 = 0L;
1111         try {
1112             h1 = this.openForReadAttributeAccess(true);
1113         } catch (WindowsException x) {
1114             x.rethrowAsIOException(this);
1115         }
1116         try {
1117             WindowsFileAttributes attrs1 = null;
1118             try {
1119                 attrs1 = WindowsFileAttributes.readAttributes(h1);
1120             } catch (WindowsException x) {
1121                 x.rethrowAsIOException(this);
1122             }
1123             long h2 = 0L;
1124             try {
1125                 h2 = other.openForReadAttributeAccess(true);
1126             } catch (WindowsException x) {
1127                 x.rethrowAsIOException(other);
1128             }
1129             try {
1130                 WindowsFileAttributes attrs2 = null;
1131                 try {
1132                     attrs2 = WindowsFileAttributes.readAttributes(h2);
1133                 } catch (WindowsException x) {
1134                     x.rethrowAsIOException(other);
1135                 }
1136                 return WindowsFileAttributes.isSameFile(attrs1, attrs2);
1137             } finally {
1138                 CloseHandle(h2);
1139             }
1140         } finally {
1141             CloseHandle(h1);
1142         }
1143     }
1144 
1145     @Override
1146     public WindowsPath createSymbolicLink(Path obj, FileAttribute<?>... attrs)
1147         throws IOException
1148     {
1149         if (!getFileSystem().supportsLinks()) {
1150             throw new UnsupportedOperationException("Symbolic links not supported "
1151                 + "on this operating system");
1152         }
1153 
1154         WindowsPath target = checkPath(obj);
1155 
1156         // no attributes allowed
1157         if (attrs.length > 0) {
1158             WindowsSecurityDescriptor.fromAttribute(attrs);  // may throw NPE or UOE
1159             throw new UnsupportedOperationException("Initial file attributes" +
1160                 "not supported when creating symbolic link");
1161         }
1162 
1163         // permission check
1164         SecurityManager sm = System.getSecurityManager();
1165         if (sm != null) {
1166             sm.checkPermission(new LinkPermission("symbolic"));
1167             this.checkWrite();
1168         }
1169 
1170         /**
1171          * Throw I/O exception for the drive-relative case because Windows
1172          * creates a link with the resolved target for this case.
1173          */
1174         if (target.type == WindowsPathType.DRIVE_RELATIVE) {
1175             throw new IOException("Cannot create symbolic link to working directory relative target");
1176         }
1177 
1178         /*
1179          * Windows treates symbolic links to directories differently than it
1180          * does to other file types. For that reason we check if the exists and
1181          * is a directory.
1182          */
1183         int flags = 0;
1184         WindowsPath resolvedTarget =
1185             WindowsPath.createFromNormalizedPath(getFileSystem(), resolve(target).path);
1186         try {
1187             if (WindowsFileAttributes.get(resolvedTarget, true).isDirectory())
1188                 flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
1189         } catch (WindowsException x) {
1190             // unable to access target so assume target is not a directory
1191         }
1192 
1193         // create the link
1194         try {
1195             CreateSymbolicLink(getPathForWin32Calls(),
1196                                addPrefixIfNeeded(target.toString()),
1197                                flags);
1198         } catch (WindowsException x) {
1199             if (x.lastError() == ERROR_INVALID_REPARSE_DATA) {
1200                 x.rethrowAsIOException(this, target);
1201             } else {
1202                 x.rethrowAsIOException(this);
1203             }
1204         }
1205         return this;
1206     }
1207 
1208     @Override
1209     public Path createLink(Path obj) throws IOException {
1210         WindowsPath existing = checkPath(obj);
1211 
1212         // permission check
1213         SecurityManager sm = System.getSecurityManager();
1214         if (sm != null) {
1215             sm.checkPermission(new LinkPermission("hard"));
1216             this.checkWrite();
1217             existing.checkWrite();
1218         }
1219 
1220         // create hard link
1221         try {
1222             CreateHardLink(this.getPathForWin32Calls(),
1223                            existing.getPathForWin32Calls());
1224         } catch (WindowsException x) {
1225             x.rethrowAsIOException(this, existing);
1226         }
1227 
1228         return this;
1229     }
1230 
1231     @Override
1232     public WindowsPath readSymbolicLink() throws IOException {
1233         if (!getFileSystem().supportsLinks()) {
1234             throw new UnsupportedOperationException("symbolic links not supported");
1235         }
1236 
1237         // permission check
1238         SecurityManager sm = System.getSecurityManager();
1239         if (sm != null) {
1240             FilePermission perm = new FilePermission(getPathForPermissionCheck(),
1241                 SecurityConstants.FILE_READLINK_ACTION);
1242             AccessController.checkPermission(perm);
1243         }
1244 
1245         String target = WindowsLinkSupport.readLink(this);
1246         return createFromNormalizedPath(getFileSystem(), target);
1247     }
1248 
1249     @Override
1250     public URI toUri() {
1251         return WindowsUriSupport.toUri(this);
1252     }
1253 
1254     @Override
1255     public WindowsPath toAbsolutePath() {
1256         if (isAbsolute())
1257             return this;
1258 
1259         // permission check as per spec
1260         SecurityManager sm = System.getSecurityManager();
1261         if (sm != null) {
1262             sm.checkPropertyAccess("user.dir");
1263         }
1264 
1265         try {
1266             return createFromNormalizedPath(getFileSystem(), getAbsolutePath());
1267         } catch (WindowsException x) {
1268             throw new IOError(new IOException(x.getMessage()));
1269         }
1270     }
1271 
1272     @Override
1273     public WindowsPath toRealPath(boolean resolveLinks) throws IOException {
1274         checkRead();
1275         String rp = WindowsLinkSupport.getRealPath(this, resolveLinks);
1276         return createFromNormalizedPath(getFileSystem(), rp);
1277     }
1278 
1279     @Override
1280     public boolean isHidden() throws IOException {
1281         checkRead();
1282         WindowsFileAttributes attrs = null;
1283         try {
1284             attrs = WindowsFileAttributes.get(this, true);
1285         } catch (WindowsException x) {
1286             x.rethrowAsIOException(this);
1287         }
1288         // DOS hidden attribute not meaningful when set on directories
1289         if (attrs.isDirectory())
1290             return false;
1291         return attrs.isHidden();
1292     }
1293 
1294     @Override
1295     public WatchKey register(WatchService watcher,
1296                              WatchEvent.Kind<?>[] events,
1297                              WatchEvent.Modifier... modifiers)
1298         throws IOException
1299     {
1300         if (watcher == null)
1301             throw new NullPointerException();
1302         if (!(watcher instanceof WindowsWatchService))
1303             throw new ProviderMismatchException();
1304 
1305         // When a security manager is set then we need to make a defensive
1306         // copy of the modifiers and check for the Windows specific FILE_TREE
1307         // modifier. When the modifier is present then check that permission
1308         // has been granted recursively.
1309         SecurityManager sm = System.getSecurityManager();
1310         if (sm != null) {
1311             boolean watchSubtree = false;
1312             final int ml = modifiers.length;
1313             if (ml > 0) {
1314                 modifiers = Arrays.copyOf(modifiers, ml);
1315                 int i=0;
1316                 while (i < ml) {
1317                     if (modifiers[i++] == ExtendedWatchEventModifier.FILE_TREE) {
1318                         watchSubtree = true;
1319                         break;
1320                     }
1321                 }
1322             }
1323             String s = getPathForPermissionCheck();
1324             sm.checkRead(s);
1325             if (watchSubtree)
1326                 sm.checkRead(s + "\\-");
1327         }
1328 
1329         return ((WindowsWatchService)watcher).register(this, events, modifiers);
1330     }
1331 }