1 /*
   2  * Copyright (c) 2008, 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 sun.nio.fs;
  27 
  28 import java.nio.*;
  29 import java.nio.file.*;
  30 import java.nio.file.attribute.*;
  31 import java.nio.charset.*;
  32 import java.nio.channels.*;
  33 import java.security.AccessController;
  34 import java.io.*;
  35 import java.net.URI;
  36 import java.util.*;
  37 import java.lang.ref.SoftReference;
  38 import sun.security.util.SecurityConstants;
  39 
  40 import static sun.nio.fs.UnixNativeDispatcher.*;
  41 import static sun.nio.fs.UnixConstants.*;
  42 
  43 /**
  44  * Solaris/Linux implementation of java.nio.file.Path
  45  */
  46 
  47 class UnixPath
  48     extends AbstractPath
  49 {
  50     private static ThreadLocal<SoftReference<CharsetEncoder>> encoder =
  51         new ThreadLocal<SoftReference<CharsetEncoder>>();
  52 
  53     // FIXME - eliminate this reference to reduce space
  54     private final UnixFileSystem fs;
  55 
  56     // internal representation
  57     private final byte[] path;
  58 
  59     // String representation (created lazily)
  60     private volatile String stringValue;
  61 
  62     // cached hashcode (created lazily, no need to be volatile)
  63     private int hash;
  64 
  65     // array of offsets of elements in path (created lazily)
  66     private volatile int[] offsets;
  67 
  68     UnixPath(UnixFileSystem fs, byte[] path) {
  69         this.fs = fs;
  70         this.path = path;
  71     }
  72 
  73     UnixPath(UnixFileSystem fs, String input) {
  74         // removes redundant slashes and checks for invalid characters
  75         this(fs, encode(normalizeAndCheck(input)));
  76     }
  77 
  78     // package-private
  79     // removes redundant slashes and check input for invalid characters
  80     static String normalizeAndCheck(String input) {
  81         int n = input.length();
  82         if (n == 0)
  83             throw new InvalidPathException(input, "Path is empty");
  84         char prevChar = 0;
  85         for (int i=0; i < n; i++) {
  86             char c = input.charAt(i);
  87             if ((c == '/') && (prevChar == '/'))
  88                 return normalize(input, n, i - 1);
  89             checkNotNul(input, c);
  90             prevChar = c;
  91         }
  92         if (prevChar == '/')
  93             return normalize(input, n, n - 1);
  94         return input;
  95     }
  96 
  97     private static void checkNotNul(String input, char c) {
  98         if (c == '\u0000')
  99             throw new InvalidPathException(input, "Nul character not allowed");
 100     }
 101 
 102     private static String normalize(String input, int len, int off) {
 103         if (len == 0)
 104             return input;
 105         int n = len;
 106         while ((n > 0) && (input.charAt(n - 1) == '/')) n--;
 107         if (n == 0)
 108             return "/";
 109         StringBuilder sb = new StringBuilder(input.length());
 110         if (off > 0)
 111             sb.append(input.substring(0, off));
 112         char prevChar = 0;
 113         for (int i=off; i < n; i++) {
 114             char c = input.charAt(i);
 115             if ((c == '/') && (prevChar == '/'))
 116                 continue;
 117             checkNotNul(input, c);
 118             sb.append(c);
 119             prevChar = c;
 120         }
 121         return sb.toString();
 122     }
 123 
 124     // encodes the given path-string into a sequence of bytes
 125     private static byte[] encode(String input) {
 126         SoftReference<CharsetEncoder> ref = encoder.get();
 127         CharsetEncoder ce = (ref != null) ? ref.get() : null;
 128         if (ce == null) {
 129             ce = Charset.defaultCharset().newEncoder()
 130                 .onMalformedInput(CodingErrorAction.REPORT)
 131                 .onUnmappableCharacter(CodingErrorAction.REPORT);
 132             encoder.set(new SoftReference<CharsetEncoder>(ce));
 133         }
 134 
 135         char[] ca = input.toCharArray();
 136 
 137         // size output buffer for worse-case size
 138         byte[] ba = new byte[(int)(ca.length * (double)ce.maxBytesPerChar())];
 139 
 140         // encode
 141         ByteBuffer bb = ByteBuffer.wrap(ba);
 142         CharBuffer cb = CharBuffer.wrap(ca);
 143         ce.reset();
 144         CoderResult cr = ce.encode(cb, bb, true);
 145         boolean error;
 146         if (!cr.isUnderflow()) {
 147             error = true;
 148         } else {
 149             cr = ce.flush(bb);
 150             error = !cr.isUnderflow();
 151         }
 152         if (error) {
 153             throw new InvalidPathException(input,
 154                 "Malformed input or input contains unmappable chacraters");
 155         }
 156 
 157         // trim result to actual length if required
 158         int len = bb.position();
 159         if (len != ba.length)
 160             ba = Arrays.copyOf(ba, len);
 161 
 162         return ba;
 163     }
 164 
 165     // package-private
 166     byte[] asByteArray() {
 167         return path;
 168     }
 169 
 170     // use this path when making system/library calls
 171     byte[] getByteArrayForSysCalls() {
 172         // resolve against default directory if required (chdir allowed or
 173         // file system default directory is not working directory)
 174         if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
 175             return resolve(getFileSystem().defaultDirectory(), path);
 176         } else {
 177             return path;
 178         }
 179     }
 180 
 181     // use this message when throwing exceptions
 182     String getPathForExecptionMessage() {
 183         return toString();
 184     }
 185 
 186     // use this path for permission checks
 187     String getPathForPermissionCheck() {
 188         if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
 189             return new String(getByteArrayForSysCalls());
 190         } else {
 191             return toString();
 192         }
 193     }
 194 
 195     // Checks that the given file is a UnixPath
 196     private UnixPath checkPath(FileRef obj) {
 197         if (obj == null)
 198             throw new NullPointerException();
 199         if (!(obj instanceof UnixPath))
 200             throw new ProviderMismatchException();
 201         return (UnixPath)obj;
 202     }
 203 
 204     // create offset list if not already created
 205     private void initOffsets() {
 206         if (offsets == null) {
 207             int count, index;
 208 
 209             // count names
 210             count = 0;
 211             index = 0;
 212             while (index < path.length) {
 213                 byte c = path[index++];
 214                 if (c != '/') {
 215                     count++;
 216                     while (index < path.length && path[index] != '/')
 217                         index++;
 218                 }
 219             }
 220 
 221             // populate offsets
 222             int[] result = new int[count];
 223             count = 0;
 224             index = 0;
 225             while (index < path.length) {
 226                 byte c = path[index];
 227                 if (c == '/') {
 228                     index++;
 229                 } else {
 230                     result[count++] = index++;
 231                     while (index < path.length && path[index] != '/')
 232                         index++;
 233                 }
 234             }
 235             synchronized (this) {
 236                 if (offsets == null)
 237                     offsets = result;
 238             }
 239         }
 240     }
 241 
 242     @Override
 243     public UnixFileSystem getFileSystem() {
 244         return fs;
 245     }
 246 
 247     @Override
 248     public UnixPath getRoot() {
 249         if (path[0] == '/') {
 250             return getFileSystem().rootDirectory();
 251         } else {
 252             return null;
 253         }
 254     }
 255 
 256     @Override
 257     public UnixPath getName() {
 258         initOffsets();
 259 
 260         int count = offsets.length;
 261         if (count == 0)
 262             return null;  // no elements so no name
 263 
 264         if (count == 1 && path[0] != '/')
 265             return this;
 266 
 267         int lastOffset = offsets[count-1];
 268         int len = path.length - lastOffset;
 269         byte[] result = new byte[len];
 270         System.arraycopy(path, lastOffset, result, 0, len);
 271         return new UnixPath(getFileSystem(), result);
 272     }
 273 
 274     @Override
 275     public UnixPath getParent() {
 276         initOffsets();
 277 
 278         int count = offsets.length;
 279         if (count == 0) {
 280             // no elements so no parent
 281             return null;
 282         }
 283         int len = offsets[count-1] - 1;
 284         if (len <= 0) {
 285             // parent is root only (may be null)
 286             return getRoot();
 287         }
 288         byte[] result = new byte[len];
 289         System.arraycopy(path, 0, result, 0, len);
 290         return new UnixPath(getFileSystem(), result);
 291     }
 292 
 293     @Override
 294     public int getNameCount() {
 295         initOffsets();
 296         return offsets.length;
 297     }
 298 
 299     @Override
 300     public UnixPath getName(int index) {
 301         initOffsets();
 302         if (index < 0)
 303             throw new IllegalArgumentException();
 304         if (index >= offsets.length)
 305             throw new IllegalArgumentException();
 306 
 307         int begin = offsets[index];
 308         int len;
 309         if (index == (offsets.length-1)) {
 310             len = path.length - begin;
 311         } else {
 312             len = offsets[index+1] - begin - 1;
 313         }
 314 
 315         // construct result
 316         byte[] result = new byte[len];
 317         System.arraycopy(path, begin, result, 0, len);
 318         return new UnixPath(getFileSystem(), result);
 319     }
 320 
 321     @Override
 322     public UnixPath subpath(int beginIndex, int endIndex) {
 323         initOffsets();
 324 
 325         if (beginIndex < 0)
 326             throw new IllegalArgumentException();
 327         if (beginIndex >= offsets.length)
 328             throw new IllegalArgumentException();
 329         if (endIndex > offsets.length)
 330             throw new IllegalArgumentException();
 331         if (beginIndex >= endIndex) {
 332             throw new IllegalArgumentException();
 333         }
 334 
 335         // starting offset and length
 336         int begin = offsets[beginIndex];
 337         int len;
 338         if (endIndex == offsets.length) {
 339             len = path.length - begin;
 340         } else {
 341             len = offsets[endIndex] - begin - 1;
 342         }
 343 
 344         // construct result
 345         byte[] result = new byte[len];
 346         System.arraycopy(path, begin, result, 0, len);
 347         return new UnixPath(getFileSystem(), result);
 348     }
 349 
 350     @Override
 351     public boolean isAbsolute() {
 352         return (path[0] == '/');
 353     }
 354 
 355     // Resolve child against given base
 356     private static byte[] resolve(byte[] base, byte[] child) {
 357         if (child[0] == '/')
 358             return child;
 359         byte[] result;
 360         if (base.length == 1 && base[0] == '/') {
 361             result = new byte[child.length + 1];
 362             result[0] = '/';
 363             System.arraycopy(child, 0, result, 1, child.length);
 364         } else {
 365             result = new byte[base.length + 1 + child.length];
 366             System.arraycopy(base, 0, result, 0, base.length);
 367             result[base.length] = '/';
 368             System.arraycopy(child, 0, result,  base.length+1, child.length);
 369         }
 370         return result;
 371     }
 372 
 373     @Override
 374     public UnixPath resolve(Path obj) {
 375         if (obj == null)
 376             return this;
 377         byte[] other = checkPath(obj).path;
 378         if (other[0] == '/')
 379             return ((UnixPath)obj);
 380         byte[] result = resolve(path, other);
 381         return new UnixPath(getFileSystem(), result);
 382     }
 383 
 384     @Override
 385     public UnixPath resolve(String other) {
 386         return resolve(new UnixPath(getFileSystem(), other));
 387     }
 388 
 389     UnixPath resolve(byte[] other) {
 390         return resolve(new UnixPath(getFileSystem(), other));
 391     }
 392 
 393     @Override
 394     public UnixPath relativize(Path obj) {
 395         UnixPath other = checkPath(obj);
 396         if (other.equals(this))
 397             return null;
 398 
 399         // can only relativize paths of the same type
 400         if (this.isAbsolute() != other.isAbsolute())
 401             throw new IllegalArgumentException("'other' is different type of Path");
 402 
 403         int bn = this.getNameCount();
 404         int cn = other.getNameCount();
 405 
 406         // skip matching names
 407         int n = (bn > cn) ? cn : bn;
 408         int i = 0;
 409         while (i < n) {
 410             if (!this.getName(i).equals(other.getName(i)))
 411                 break;
 412             i++;
 413         }
 414 
 415         int dotdots = bn - i;
 416         if (i < cn) {
 417             // remaining name components in other
 418             UnixPath remainder = other.subpath(i, cn);
 419             if (dotdots == 0)
 420                 return remainder;
 421 
 422             // result is a  "../" for each remaining name in base
 423             // followed by the remaining names in other
 424             byte[] result = new byte[dotdots*3 + remainder.path.length];
 425             int pos = 0;
 426             while (dotdots > 0) {
 427                 result[pos++] = (byte)'.';
 428                 result[pos++] = (byte)'.';
 429                 result[pos++] = (byte)'/';
 430                 dotdots--;
 431             }
 432             System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);
 433             return new UnixPath(getFileSystem(), result);
 434         } else {
 435             // no remaining names in other so result is simply a sequence of ".."
 436             byte[] result = new byte[dotdots*3 - 1];
 437             int pos = 0;
 438             while (dotdots > 0) {
 439                 result[pos++] = (byte)'.';
 440                 result[pos++] = (byte)'.';
 441                 // no tailing slash at the end
 442                 if (dotdots > 1)
 443                     result[pos++] = (byte)'/';
 444                 dotdots--;
 445             }
 446             return new UnixPath(getFileSystem(), result);
 447         }
 448     }
 449 
 450     @Override
 451     public Path normalize() {
 452         final int count = getNameCount();
 453         if (count == 0)
 454             return this;
 455 
 456         boolean[] ignore = new boolean[count];      // true => ignore name
 457         int[] size = new int[count];                // length of name
 458         int remaining = count;                      // number of names remaining
 459         boolean hasDotDot = false;                  // has at least one ..
 460         boolean isAbsolute = path[0] == '/';
 461 
 462         // first pass:
 463         //   1. compute length of names
 464         //   2. mark all occurences of "." to ignore
 465         //   3. and look for any occurences of ".."
 466         for (int i=0; i<count; i++) {
 467             int begin = offsets[i];
 468             int len;
 469             if (i == (offsets.length-1)) {
 470                 len = path.length - begin;
 471             } else {
 472                 len = offsets[i+1] - begin - 1;
 473             }
 474             size[i] = len;
 475 
 476             if (path[begin] == '.') {
 477                 if (len == 1) {
 478                     ignore[i] = true;  // ignore  "."
 479                     remaining--;
 480                 }
 481                 else {
 482                     if (path[begin+1] == '.')   // ".." found
 483                         hasDotDot = true;
 484                 }
 485             }
 486         }
 487 
 488         // multiple passes to eliminate all occurences of name/..
 489         if (hasDotDot) {
 490             int prevRemaining;
 491             do {
 492                 prevRemaining = remaining;
 493                 int prevName = -1;
 494                 for (int i=0; i<count; i++) {
 495                     if (ignore[i])
 496                         continue;
 497 
 498                     // not a ".."
 499                     if (size[i] != 2) {
 500                         prevName = i;
 501                         continue;
 502                     }
 503 
 504                     int begin = offsets[i];
 505                     if (path[begin] != '.' || path[begin+1] != '.') {
 506                         prevName = i;
 507                         continue;
 508                     }
 509 
 510                     // ".." found
 511                     if (prevName >= 0) {
 512                         // name/<ignored>/.. found so mark name and ".." to be
 513                         // ignored
 514                         ignore[prevName] = true;
 515                         ignore[i] = true;
 516                         remaining = remaining - 2;
 517                         prevName = -1;
 518                     } else {
 519                         // Case: /<ignored>/.. so mark ".." as ignored
 520                         if (isAbsolute) {
 521                             boolean hasPrevious = false;
 522                             for (int j=0; j<i; j++) {
 523                                 if (!ignore[j]) {
 524                                     hasPrevious = true;
 525                                     break;
 526                                 }
 527                             }
 528                             if (!hasPrevious) {
 529                                 // all proceeding names are ignored
 530                                 ignore[i] = true;
 531                                 remaining--;
 532                             }
 533                         }
 534                     }
 535                 }
 536             } while (prevRemaining > remaining);
 537         }
 538 
 539         // no redundant names
 540         if (remaining == count)
 541             return this;
 542 
 543         // corner case - all names removed
 544         if (remaining == 0) {
 545             return isAbsolute ? getFileSystem().rootDirectory() : null;
 546         }
 547 
 548         // compute length of result
 549         int len = remaining - 1;
 550         if (isAbsolute)
 551             len++;
 552 
 553         for (int i=0; i<count; i++) {
 554             if (!ignore[i])
 555                 len += size[i];
 556         }
 557         byte[] result = new byte[len];
 558 
 559         // copy names into result
 560         int pos = 0;
 561         if (isAbsolute)
 562             result[pos++] = '/';
 563         for (int i=0; i<count; i++) {
 564             if (!ignore[i]) {
 565                 System.arraycopy(path, offsets[i], result, pos, size[i]);
 566                 pos += size[i];
 567                 if (--remaining > 0) {
 568                     result[pos++] = '/';
 569                 }
 570             }
 571         }
 572         return new UnixPath(getFileSystem(), result);
 573     }
 574 
 575     @Override
 576     public boolean startsWith(Path other) {
 577         UnixPath that = checkPath(other);
 578 
 579         // other path is longer
 580         if (that.path.length > path.length)
 581             return false;
 582 
 583         int thisOffsetCount = getNameCount();
 584         int thatOffsetCount = that.getNameCount();
 585 
 586         // other path has no name elements
 587         if (thatOffsetCount == 0 && this.isAbsolute())
 588             return true;
 589 
 590         // given path has more elements that this path
 591         if (thatOffsetCount > thisOffsetCount)
 592             return false;
 593 
 594         // same number of elements so must be exact match
 595         if ((thatOffsetCount == thisOffsetCount) &&
 596             (path.length != that.path.length)) {
 597             return false;
 598         }
 599 
 600         // check offsets of elements match
 601         for (int i=0; i<thatOffsetCount; i++) {
 602             Integer o1 = offsets[i];
 603             Integer o2 = that.offsets[i];
 604             if (!o1.equals(o2))
 605                 return false;
 606         }
 607 
 608         // offsets match so need to compare bytes
 609         int i=0;
 610         while (i < that.path.length) {
 611             if (this.path[i] != that.path[i])
 612                 return false;
 613             i++;
 614         }
 615 
 616         // final check that match is on name boundary
 617         if (i < path.length && this.path[i] != '/')
 618             return false;
 619 
 620         return true;
 621     }
 622 
 623     @Override
 624     public boolean endsWith(Path other) {
 625         UnixPath that = checkPath(other);
 626 
 627         int thisLen = path.length;
 628         int thatLen = that.path.length;
 629 
 630         // other path is longer
 631         if (thatLen > thisLen)
 632             return false;
 633 
 634         // other path is absolute so this path must be absolute
 635         if (that.isAbsolute() && !this.isAbsolute())
 636             return false;
 637 
 638         int thisOffsetCount = getNameCount();
 639         int thatOffsetCount = that.getNameCount();
 640 
 641         // given path has more elements that this path
 642         if (thatOffsetCount > thisOffsetCount) {
 643             return false;
 644         } else {
 645             // same number of elements
 646             if (thatOffsetCount == thisOffsetCount) {
 647                 if (thisOffsetCount == 0)
 648                     return true;
 649                 int expectedLen = thisLen;
 650                 if (this.isAbsolute() && !that.isAbsolute())
 651                     expectedLen--;
 652                 if (thatLen != expectedLen)
 653                     return false;
 654             } else {
 655                 // this path has more elements so given path must be relative
 656                 if (that.isAbsolute())
 657                     return false;
 658             }
 659         }
 660 
 661         // compare bytes
 662         int thisPos = offsets[thisOffsetCount - thatOffsetCount];
 663         int thatPos = that.offsets[0];
 664         if ((thatLen - thatPos) != (thisLen - thisPos))
 665             return false;
 666         while (thatPos < thatLen) {
 667             if (this.path[thisPos++] != that.path[thatPos++])
 668                 return false;
 669         }
 670 
 671         return true;
 672     }
 673 
 674     @Override
 675     public int compareTo(Path other) {
 676         int len1 = path.length;
 677         int len2 = ((UnixPath) other).path.length;
 678 
 679         int n = Math.min(len1, len2);
 680         byte v1[] = path;
 681         byte v2[] = ((UnixPath) other).path;
 682 
 683         int k = 0;
 684         while (k < n) {
 685             int c1 = v1[k] & 0xff;
 686             int c2 = v2[k] & 0xff;
 687             if (c1 != c2) {
 688                 return c1 - c2;
 689             }
 690             k++;
 691         }
 692         return len1 - len2;
 693     }
 694 
 695     @Override
 696     public boolean equals(Object ob) {
 697         if ((ob != null) && (ob instanceof UnixPath)) {
 698             return compareTo((Path)ob) == 0;
 699         }
 700         return false;
 701     }
 702 
 703     @Override
 704     public int hashCode() {
 705         // OK if two or more threads compute hash
 706         int h = hash;
 707         if (h == 0) {
 708             for (int i = 0; i< path.length; i++) {
 709                 h = 31*h + (path[i] & 0xff);
 710             }
 711             hash = h;
 712         }
 713         return h;
 714     }
 715 
 716     @Override
 717     public String toString() {
 718         // OK if two or more threads create a String
 719         if (stringValue == null)
 720             stringValue = new String(path);     // platform encoding
 721         return stringValue;
 722     }
 723 
 724     @Override
 725     public Iterator<Path> iterator() {
 726         initOffsets();
 727         return new Iterator<Path>() {
 728             int i = 0;
 729             @Override
 730             public boolean hasNext() {
 731                 return (i < offsets.length);
 732             }
 733             @Override
 734             public Path next() {
 735                 if (i < offsets.length) {
 736                     Path result = getName(i);
 737                     i++;
 738                     return result;
 739                 } else {
 740                     throw new NoSuchElementException();
 741                 }
 742             }
 743             @Override
 744             public void remove() {
 745                 throw new UnsupportedOperationException();
 746             }
 747         };
 748     }
 749 
 750     // -- file operations --
 751 
 752     // package-private
 753     int openForAttributeAccess(boolean followLinks) throws IOException {
 754         int flags = O_RDONLY;
 755         if (!followLinks)
 756             flags |= O_NOFOLLOW;
 757         try {
 758             return open(this, flags, 0);
 759         } catch (UnixException x) {
 760             // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
 761             if (getFileSystem().isSolaris() && x.errno() == EINVAL)
 762                 x.setError(ELOOP);
 763 
 764             if (x.errno() == ELOOP)
 765                 throw new FileSystemException(getPathForExecptionMessage(), null,
 766                     x.getMessage() + " or unable to access attributes of symbolic link");
 767 
 768             x.rethrowAsIOException(this);
 769             return -1; // keep compile happy
 770         }
 771     }
 772 
 773 
 774     void checkRead() {
 775         SecurityManager sm = System.getSecurityManager();
 776         if (sm != null)
 777             sm.checkRead(getPathForPermissionCheck());
 778     }
 779 
 780     void checkWrite() {
 781         SecurityManager sm = System.getSecurityManager();
 782         if (sm != null)
 783             sm.checkWrite(getPathForPermissionCheck());
 784     }
 785 
 786     void checkDelete() {
 787         SecurityManager sm = System.getSecurityManager();
 788         if (sm != null)
 789             sm.checkDelete(getPathForPermissionCheck());
 790     }
 791 
 792     @Override
 793     public FileStore getFileStore()
 794         throws IOException
 795     {
 796         SecurityManager sm = System.getSecurityManager();
 797         if (sm != null) {
 798             sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
 799             checkRead();
 800         }
 801         return getFileSystem().getFileStore(this);
 802     }
 803 
 804     @Override
 805     public void checkAccess(AccessMode... modes) throws IOException {
 806         boolean e = false;
 807         boolean r = false;
 808         boolean w = false;
 809         boolean x = false;
 810 
 811         if (modes.length == 0) {
 812             e = true;
 813         } else {
 814             for (AccessMode mode: modes) {
 815                 switch (mode) {
 816                     case READ : r = true; break;
 817                     case WRITE : w = true; break;
 818                     case EXECUTE : x = true; break;
 819                     default: throw new AssertionError("Should not get here");
 820                 }
 821             }
 822         }
 823 
 824         int mode = 0;
 825         if (e || r) {
 826             checkRead();
 827             mode |= (r) ? R_OK : F_OK;
 828         }
 829         if (w) {
 830             checkWrite();
 831             mode |= W_OK;
 832         }
 833         if (x) {
 834             SecurityManager sm = System.getSecurityManager();
 835             if (sm != null) {
 836                 // not cached
 837                 sm.checkExec(getPathForPermissionCheck());
 838             }
 839             mode |= X_OK;
 840         }
 841         try {
 842             access(this, mode);
 843         } catch (UnixException exc) {
 844             exc.rethrowAsIOException(this);
 845         }
 846     }
 847 
 848     @Override
 849     void implDelete(boolean failIfNotExists) throws IOException {
 850         checkDelete();
 851 
 852         // need file attributes to know if file is directory
 853         UnixFileAttributes attrs = null;
 854         try {
 855             attrs = UnixFileAttributes.get(this, false);
 856             if (attrs.isDirectory()) {
 857                 rmdir(this);
 858             } else {
 859                 unlink(this);
 860             }
 861         } catch (UnixException x) {
 862             // no-op if file does not exist
 863             if (!failIfNotExists && x.errno() == ENOENT)
 864                 return;
 865 
 866             // DirectoryNotEmptyException if not empty
 867             if (attrs != null && attrs.isDirectory() &&
 868                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 869                 throw new DirectoryNotEmptyException(getPathForExecptionMessage());
 870 
 871             x.rethrowAsIOException(this);
 872         }
 873     }
 874 
 875     @Override
 876     public DirectoryStream<Path> newDirectoryStream(DirectoryStream.Filter<? super Path> filter)
 877         throws IOException
 878     {
 879         if (filter == null)
 880             throw new NullPointerException();
 881         checkRead();
 882 
 883         // can't return SecureDirectoryStream on kernels that don't support
 884         // openat, etc.
 885         if (!supportsAtSysCalls()) {
 886             try {
 887                 long ptr = opendir(this);
 888                 return new UnixDirectoryStream(this, ptr, filter);
 889             } catch (UnixException x) {
 890                 if (x.errno() == ENOTDIR)
 891                     throw new NotDirectoryException(getPathForExecptionMessage());
 892                 x.rethrowAsIOException(this);
 893             }
 894         }
 895 
 896         // open directory and dup file descriptor for use by
 897         // opendir/readdir/closedir
 898         int dfd1 = -1;
 899         int dfd2 = -1;
 900         long dp = 0L;
 901         try {
 902             dfd1 = open(this, O_RDONLY, 0);
 903             dfd2 = dup(dfd1);
 904             dp = fdopendir(dfd1);
 905         } catch (UnixException x) {
 906             if (dfd1 != -1)
 907                 close(dfd1);
 908             if (dfd2 != -1)
 909                 close(dfd2);
 910             if (x.errno() == UnixConstants.ENOTDIR)
 911                 throw new NotDirectoryException(getPathForExecptionMessage());
 912             x.rethrowAsIOException(this);
 913         }
 914         return new UnixSecureDirectoryStream(this, dp, dfd2, filter);
 915     }
 916 
 917     // invoked by AbstractPath#copyTo
 918     @Override
 919     public void implCopyTo(Path obj, CopyOption... options)
 920         throws IOException
 921     {
 922         UnixPath target = (UnixPath)obj;
 923         UnixCopyFile.copy(this, target, options);
 924     }
 925 
 926     @Override
 927     public void implMoveTo(Path obj, CopyOption... options)
 928         throws IOException
 929     {
 930         UnixPath target = (UnixPath)obj;
 931         UnixCopyFile.move(this, target, options);
 932     }
 933 
 934     @Override
 935     @SuppressWarnings("unchecked")
 936     public <V extends FileAttributeView> V
 937         getFileAttributeView(Class<V> type, LinkOption... options)
 938     {
 939         FileAttributeView view = getFileSystem()
 940             .newFileAttributeView(type, this, options);
 941         if (view == null)
 942             return null;
 943         return (V) view;
 944     }
 945 
 946     @Override
 947     public DynamicFileAttributeView getFileAttributeView(String name,
 948                                                          LinkOption... options)
 949     {
 950         return getFileSystem().newFileAttributeView(name, this, options);
 951     }
 952 
 953     @Override
 954     public Path createDirectory(FileAttribute<?>... attrs)
 955         throws IOException
 956     {
 957         checkWrite();
 958 
 959         int mode = UnixFileModeAttribute
 960             .toUnixMode(UnixFileModeAttribute.ALL_PERMISSIONS, attrs);
 961         try {
 962             mkdir(this, mode);
 963         } catch (UnixException x) {
 964             x.rethrowAsIOException(this);
 965         }
 966         return this;
 967     }
 968 
 969     @Override
 970     public SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
 971                                               FileAttribute<?>... attrs)
 972          throws IOException
 973     {
 974         int mode = UnixFileModeAttribute
 975             .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
 976         try {
 977             return UnixChannelFactory.newFileChannel(this, options, mode);
 978         } catch (UnixException x) {
 979             x.rethrowAsIOException(this);
 980             return null;  // keep compiler happy
 981         }
 982     }
 983 
 984     @Override
 985     public boolean isSameFile(Path obj) throws IOException {
 986         if (this.equals(obj))
 987             return true;
 988         if (!(obj instanceof UnixPath))  // includes null check
 989             return false;
 990         UnixPath other = (UnixPath)obj;
 991 
 992         // check security manager access to both files
 993         this.checkRead();
 994         other.checkRead();
 995 
 996         UnixFileAttributes thisAttrs;
 997         UnixFileAttributes otherAttrs;
 998         try {
 999              thisAttrs = UnixFileAttributes.get(this, true);
1000         } catch (UnixException x) {
1001             x.rethrowAsIOException(this);
1002             return false;    // keep compiler happy
1003         }
1004         try {
1005             otherAttrs = UnixFileAttributes.get(other, true);
1006         } catch (UnixException x) {
1007             x.rethrowAsIOException(other);
1008             return false;    // keep compiler happy
1009         }
1010         return thisAttrs.isSameFile(otherAttrs);
1011     }
1012 
1013     @Override
1014     public Path createSymbolicLink(Path obj, FileAttribute<?>... attrs)
1015         throws IOException
1016     {
1017         UnixPath target = checkPath(obj);
1018 
1019         // no attributes supported when creating links
1020         if (attrs.length > 0) {
1021             UnixFileModeAttribute.toUnixMode(0, attrs);  // may throw NPE or UOE
1022             throw new UnsupportedOperationException("Initial file attributes" +
1023                 "not supported when creating symbolic link");
1024         }
1025 
1026         // permission check
1027         SecurityManager sm = System.getSecurityManager();
1028         if (sm != null) {
1029             sm.checkPermission(new LinkPermission("symbolic"));
1030             checkWrite();
1031         }
1032 
1033         // create link
1034         try {
1035             symlink(target.asByteArray(), this);
1036         } catch (UnixException x) {
1037             x.rethrowAsIOException(this);
1038         }
1039 
1040         return this;
1041     }
1042 
1043     @Override
1044     public Path createLink(Path obj) throws IOException {
1045         UnixPath existing = checkPath(obj);
1046 
1047         // permission check
1048         SecurityManager sm = System.getSecurityManager();
1049         if (sm != null) {
1050             sm.checkPermission(new LinkPermission("hard"));
1051             this.checkWrite();
1052             existing.checkWrite();
1053         }
1054         try {
1055             link(existing, this);
1056         } catch (UnixException x) {
1057             x.rethrowAsIOException(this, existing);
1058         }
1059         return this;
1060     }
1061 
1062     @Override
1063     public Path readSymbolicLink() throws IOException {
1064         // permission check
1065         SecurityManager sm = System.getSecurityManager();
1066         if (sm != null) {
1067             FilePermission perm = new FilePermission(getPathForPermissionCheck(),
1068                 SecurityConstants.FILE_READLINK_ACTION);
1069             AccessController.checkPermission(perm);
1070         }
1071         try {
1072             byte[] target = readlink(this);
1073             return new UnixPath(getFileSystem(), target);
1074         } catch (UnixException x) {
1075            if (x.errno() == UnixConstants.EINVAL)
1076                 throw new NotLinkException(getPathForExecptionMessage());
1077             x.rethrowAsIOException(this);
1078             return null;    // keep compiler happy
1079         }
1080     }
1081 
1082     @Override
1083     public UnixPath toAbsolutePath() {
1084         if (isAbsolute()) {
1085             return this;
1086         }
1087         // The path is relative so need to resolve against default directory,
1088         // taking care not to reveal the user.dir
1089         SecurityManager sm = System.getSecurityManager();
1090         if (sm != null) {
1091             sm.checkPropertyAccess("user.dir");
1092         }
1093         return new UnixPath(getFileSystem(),
1094             resolve(getFileSystem().defaultDirectory(), path));
1095     }
1096 
1097     @Override
1098     public UnixPath toRealPath(boolean resolveLinks) throws IOException {
1099         checkRead();
1100 
1101         UnixPath absolute = toAbsolutePath();
1102 
1103         // if resolveLinks is true then use realpath
1104         if (resolveLinks) {
1105             try {
1106                 byte[] rp = realpath(absolute);
1107                 return new UnixPath(getFileSystem(), rp);
1108             } catch (UnixException x) {
1109                 x.rethrowAsIOException(this);
1110             }
1111         }
1112 
1113         // if resolveLinks is false then eliminate "." and also ".."
1114         // where the previous element is not a link.
1115         UnixPath root = getFileSystem().rootDirectory();
1116         UnixPath result = root;
1117         for (int i=0; i<absolute.getNameCount(); i++) {
1118             UnixPath element = absolute.getName(i);
1119 
1120             // eliminate "."
1121             if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
1122                 continue;
1123 
1124             // cannot eliminate ".." if previous element is a link
1125             if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
1126                 (element.asByteArray()[1] == '.'))
1127             {
1128                 UnixFileAttributes attrs = null;
1129                 try {
1130                     attrs = UnixFileAttributes.get(result, false);
1131                 } catch (UnixException x) {
1132                     x.rethrowAsIOException(result);
1133                 }
1134                 if (!attrs.isSymbolicLink()) {
1135                     result = result.getParent();
1136                     if (result == null) {
1137                         result = root;
1138                     }
1139                     continue;
1140                 }
1141             }
1142             result = result.resolve(element);
1143         }
1144 
1145         // check file exists (without following links)
1146         try {
1147             UnixFileAttributes.get(result, false);
1148         } catch (UnixException x) {
1149             x.rethrowAsIOException(result);
1150         }
1151         return result;
1152     }
1153 
1154     @Override
1155     public boolean isHidden() {
1156         checkRead();
1157         UnixPath name = getName();
1158         if (name == null)
1159             return false;
1160         return (name.asByteArray()[0] == '.');
1161     }
1162 
1163     @Override
1164     public URI toUri() {
1165         return UnixUriUtils.toUri(this);
1166     }
1167 
1168     @Override
1169     public WatchKey register(WatchService watcher,
1170                              WatchEvent.Kind<?>[] events,
1171                              WatchEvent.Modifier... modifiers)
1172         throws IOException
1173     {
1174         if (watcher == null)
1175             throw new NullPointerException();
1176         if (!(watcher instanceof AbstractWatchService))
1177             throw new ProviderMismatchException();
1178         checkRead();
1179         return ((AbstractWatchService)watcher).register(this, events, modifiers);
1180     }
1181 }