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.*;
  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         // other path is longer
 628         if (that.path.length > path.length)
 629             return false;
 630 
 631         // other path is absolute so this path must be absolute
 632         if (that.isAbsolute() && !this.isAbsolute())
 633             return false;
 634 
 635         int thisOffsetCount = getNameCount();
 636         int thatOffsetCount = that.getNameCount();
 637 
 638         // given path has more elements that this path
 639         if (thatOffsetCount > thisOffsetCount) {
 640             return false;
 641         } else {
 642             // same number of elements
 643             if (thatOffsetCount == thisOffsetCount) {
 644                 if (thisOffsetCount == 0)
 645                     return true;
 646                 int expectedLen = path.length;
 647                 if (this.isAbsolute() && !that.isAbsolute())
 648                     expectedLen--;
 649                 if (that.path.length != expectedLen)
 650                     return false;
 651             } else {
 652                 // this path has more elements so given path must be relative
 653                 if (that.isAbsolute())
 654                     return false;
 655             }
 656         }
 657 
 658         // compare bytes
 659         int thisPos = offsets[thisOffsetCount - thatOffsetCount];
 660         int thatPos = that.offsets[0];
 661         while (thatPos < that.path.length) {
 662             if (this.path[thisPos++] != that.path[thatPos++])
 663                 return false;
 664         }
 665 
 666         return true;
 667     }
 668 
 669     @Override
 670     public int compareTo(Path other) {
 671         int len1 = path.length;
 672         int len2 = ((UnixPath) other).path.length;
 673 
 674         int n = Math.min(len1, len2);
 675         byte v1[] = path;
 676         byte v2[] = ((UnixPath) other).path;
 677 
 678         int k = 0;
 679         while (k < n) {
 680             int c1 = v1[k] & 0xff;
 681             int c2 = v2[k] & 0xff;
 682             if (c1 != c2) {
 683                 return c1 - c2;
 684             }
 685             k++;
 686         }
 687         return len1 - len2;
 688     }
 689 
 690     @Override
 691     public boolean equals(Object ob) {
 692         if ((ob != null) && (ob instanceof UnixPath)) {
 693             return compareTo((Path)ob) == 0;
 694         }
 695         return false;
 696     }
 697 
 698     @Override
 699     public int hashCode() {
 700         // OK if two or more threads compute hash
 701         int h = hash;
 702         if (h == 0) {
 703             for (int i = 0; i< path.length; i++) {
 704                 h = 31*h + (path[i] & 0xff);
 705             }
 706             hash = h;
 707         }
 708         return h;
 709     }
 710 
 711     @Override
 712     public String toString() {
 713         // OK if two or more threads create a String
 714         if (stringValue == null)
 715             stringValue = new String(path);     // platform encoding
 716         return stringValue;
 717     }
 718 
 719     @Override
 720     public Iterator<Path> iterator() {
 721         initOffsets();
 722         return new Iterator<Path>() {
 723             int i = 0;
 724             @Override
 725             public boolean hasNext() {
 726                 return (i < offsets.length);
 727             }
 728             @Override
 729             public Path next() {
 730                 if (i < offsets.length) {
 731                     Path result = getName(i);
 732                     i++;
 733                     return result;
 734                 } else {
 735                     throw new NoSuchElementException();
 736                 }
 737             }
 738             @Override
 739             public void remove() {
 740                 throw new UnsupportedOperationException();
 741             }
 742         };
 743     }
 744 
 745     // -- file operations --
 746 
 747     // package-private
 748     int openForAttributeAccess(boolean followLinks) throws IOException {
 749         int flags = O_RDONLY;
 750         if (!followLinks)
 751             flags |= O_NOFOLLOW;
 752         try {
 753             return open(this, flags, 0);
 754         } catch (UnixException x) {
 755             // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
 756             if (getFileSystem().isSolaris() && x.errno() == EINVAL)
 757                 x.setError(ELOOP);
 758 
 759             if (x.errno() == ELOOP)
 760                 throw new FileSystemException(getPathForExecptionMessage(), null,
 761                     x.getMessage() + " or unable to access attributes of symbolic link");
 762 
 763             x.rethrowAsIOException(this);
 764             return -1; // keep compile happy
 765         }
 766     }
 767 
 768 
 769     void checkRead() {
 770         SecurityManager sm = System.getSecurityManager();
 771         if (sm != null)
 772             sm.checkRead(getPathForPermissionCheck());
 773     }
 774 
 775     void checkWrite() {
 776         SecurityManager sm = System.getSecurityManager();
 777         if (sm != null)
 778             sm.checkWrite(getPathForPermissionCheck());
 779     }
 780 
 781     void checkDelete() {
 782         SecurityManager sm = System.getSecurityManager();
 783         if (sm != null)
 784             sm.checkDelete(getPathForPermissionCheck());
 785     }
 786 
 787     @Override
 788     public FileStore getFileStore()
 789         throws IOException
 790     {
 791         SecurityManager sm = System.getSecurityManager();
 792         if (sm != null) {
 793             sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
 794             checkRead();
 795         }
 796         return getFileSystem().getFileStore(this);
 797     }
 798 
 799     @Override
 800     public void checkAccess(AccessMode... modes) throws IOException {
 801         boolean e = false;
 802         boolean r = false;
 803         boolean w = false;
 804         boolean x = false;
 805 
 806         if (modes.length == 0) {
 807             e = true;
 808         } else {
 809             for (AccessMode mode: modes) {
 810                 switch (mode) {
 811                     case READ : r = true; break;
 812                     case WRITE : w = true; break;
 813                     case EXECUTE : x = true; break;
 814                     default: throw new AssertionError("Should not get here");
 815                 }
 816             }
 817         }
 818 
 819         int mode = 0;
 820         if (e || r) {
 821             checkRead();
 822             mode |= (r) ? R_OK : F_OK;
 823         }
 824         if (w) {
 825             checkWrite();
 826             mode |= W_OK;
 827         }
 828         if (x) {
 829             SecurityManager sm = System.getSecurityManager();
 830             if (sm != null) {
 831                 // not cached
 832                 sm.checkExec(getPathForPermissionCheck());
 833             }
 834             mode |= X_OK;
 835         }
 836         try {
 837             access(this, mode);
 838         } catch (UnixException exc) {
 839             exc.rethrowAsIOException(this);
 840         }
 841     }
 842 
 843     @Override
 844     void implDelete(boolean failIfNotExists) throws IOException {
 845         checkDelete();
 846 
 847         // need file attributes to know if file is directory
 848         UnixFileAttributes attrs = null;
 849         try {
 850             attrs = UnixFileAttributes.get(this, false);
 851             if (attrs.isDirectory()) {
 852                 rmdir(this);
 853             } else {
 854                 unlink(this);
 855             }
 856         } catch (UnixException x) {
 857             // no-op if file does not exist
 858             if (!failIfNotExists && x.errno() == ENOENT)
 859                 return;
 860 
 861             // DirectoryNotEmptyException if not empty
 862             if (attrs != null && attrs.isDirectory() &&
 863                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 864                 throw new DirectoryNotEmptyException(getPathForExecptionMessage());
 865 
 866             x.rethrowAsIOException(this);
 867         }
 868     }
 869 
 870     @Override
 871     public DirectoryStream<Path> newDirectoryStream(DirectoryStream.Filter<? super Path> filter)
 872         throws IOException
 873     {
 874         if (filter == null)
 875             throw new NullPointerException();
 876         checkRead();
 877 
 878         // can't return SecureDirectoryStream on kernels that don't support
 879         // openat, etc.
 880         if (!supportsAtSysCalls()) {
 881             try {
 882                 long ptr = opendir(this);
 883                 return new UnixDirectoryStream(this, ptr, filter);
 884             } catch (UnixException x) {
 885                 if (x.errno() == ENOTDIR)
 886                     throw new NotDirectoryException(getPathForExecptionMessage());
 887                 x.rethrowAsIOException(this);
 888             }
 889         }
 890 
 891         // open directory and dup file descriptor for use by
 892         // opendir/readdir/closedir
 893         int dfd1 = -1;
 894         int dfd2 = -1;
 895         long dp = 0L;
 896         try {
 897             dfd1 = open(this, O_RDONLY, 0);
 898             dfd2 = dup(dfd1);
 899             dp = fdopendir(dfd1);
 900         } catch (UnixException x) {
 901             if (dfd1 != -1)
 902                 close(dfd1);
 903             if (dfd2 != -1)
 904                 close(dfd2);
 905             if (x.errno() == UnixConstants.ENOTDIR)
 906                 throw new NotDirectoryException(getPathForExecptionMessage());
 907             x.rethrowAsIOException(this);
 908         }
 909         return new UnixSecureDirectoryStream(this, dp, dfd2, filter);
 910     }
 911 
 912     // invoked by AbstractPath#copyTo
 913     @Override
 914     public void implCopyTo(Path obj, CopyOption... options)
 915         throws IOException
 916     {
 917         UnixPath target = (UnixPath)obj;
 918         UnixCopyFile.copy(this, target, options);
 919     }
 920 
 921     @Override
 922     public void implMoveTo(Path obj, CopyOption... options)
 923         throws IOException
 924     {
 925         UnixPath target = (UnixPath)obj;
 926         UnixCopyFile.move(this, target, options);
 927     }
 928 
 929     @Override
 930     @SuppressWarnings("unchecked")
 931     public <V extends FileAttributeView> V
 932         getFileAttributeView(Class<V> type, LinkOption... options)
 933     {
 934         FileAttributeView view = getFileSystem()
 935             .newFileAttributeView(type, this, options);
 936         if (view == null)
 937             return null;
 938         return (V) view;
 939     }
 940 
 941     @Override
 942     public DynamicFileAttributeView getFileAttributeView(String name,
 943                                                          LinkOption... options)
 944     {
 945         return getFileSystem().newFileAttributeView(name, this, options);
 946     }
 947 
 948     @Override
 949     public Path createDirectory(FileAttribute<?>... attrs)
 950         throws IOException
 951     {
 952         checkWrite();
 953 
 954         int mode = UnixFileModeAttribute
 955             .toUnixMode(UnixFileModeAttribute.ALL_PERMISSIONS, attrs);
 956         try {
 957             mkdir(this, mode);
 958         } catch (UnixException x) {
 959             x.rethrowAsIOException(this);
 960         }
 961         return this;
 962     }
 963 
 964     @Override
 965     public SeekableByteChannel newByteChannel(Set<? extends OpenOption> options,
 966                                               FileAttribute<?>... attrs)
 967          throws IOException
 968     {
 969         int mode = UnixFileModeAttribute
 970             .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
 971         try {
 972             return UnixChannelFactory.newFileChannel(this, options, mode);
 973         } catch (UnixException x) {
 974             x.rethrowAsIOException(this);
 975             return null;  // keep compiler happy
 976         }
 977     }
 978 
 979     @Override
 980     public boolean isSameFile(Path obj) throws IOException {
 981         if (this.equals(obj))
 982             return true;
 983         if (!(obj instanceof UnixPath))  // includes null check
 984             return false;
 985         UnixPath other = (UnixPath)obj;
 986 
 987         // check security manager access to both files
 988         this.checkRead();
 989         other.checkRead();
 990 
 991         UnixFileAttributes thisAttrs;
 992         UnixFileAttributes otherAttrs;
 993         try {
 994              thisAttrs = UnixFileAttributes.get(this, true);
 995         } catch (UnixException x) {
 996             x.rethrowAsIOException(this);
 997             return false;    // keep compiler happy
 998         }
 999         try {
1000             otherAttrs = UnixFileAttributes.get(other, true);
1001         } catch (UnixException x) {
1002             x.rethrowAsIOException(other);
1003             return false;    // keep compiler happy
1004         }
1005         return thisAttrs.isSameFile(otherAttrs);
1006     }
1007 
1008     @Override
1009     public Path createSymbolicLink(Path obj, FileAttribute<?>... attrs)
1010         throws IOException
1011     {
1012         UnixPath target = checkPath(obj);
1013 
1014         // no attributes supported when creating links
1015         if (attrs.length > 0) {
1016             UnixFileModeAttribute.toUnixMode(0, attrs);  // may throw NPE or UOE
1017             throw new UnsupportedOperationException("Initial file attributes" +
1018                 "not supported when creating symbolic link");
1019         }
1020 
1021         // permission check
1022         SecurityManager sm = System.getSecurityManager();
1023         if (sm != null) {
1024             sm.checkPermission(new LinkPermission("symbolic"));
1025             checkWrite();
1026         }
1027 
1028         // create link
1029         try {
1030             symlink(target.asByteArray(), this);
1031         } catch (UnixException x) {
1032             x.rethrowAsIOException(this);
1033         }
1034 
1035         return this;
1036     }
1037 
1038     @Override
1039     public Path createLink(Path obj) throws IOException {
1040         UnixPath existing = checkPath(obj);
1041 
1042         // permission check
1043         SecurityManager sm = System.getSecurityManager();
1044         if (sm != null) {
1045             sm.checkPermission(new LinkPermission("hard"));
1046             this.checkWrite();
1047             existing.checkWrite();
1048         }
1049         try {
1050             link(existing, this);
1051         } catch (UnixException x) {
1052             x.rethrowAsIOException(this, existing);
1053         }
1054         return this;
1055     }
1056 
1057     @Override
1058     public Path readSymbolicLink() throws IOException {
1059         // permission check
1060         SecurityManager sm = System.getSecurityManager();
1061         if (sm != null) {
1062             FilePermission perm = new FilePermission(getPathForPermissionCheck(),
1063                 SecurityConstants.FILE_READLINK_ACTION);
1064             AccessController.checkPermission(perm);
1065         }
1066         try {
1067             byte[] target = readlink(this);
1068             return new UnixPath(getFileSystem(), target);
1069         } catch (UnixException x) {
1070            if (x.errno() == UnixConstants.EINVAL)
1071                 throw new NotLinkException(getPathForExecptionMessage());
1072             x.rethrowAsIOException(this);
1073             return null;    // keep compiler happy
1074         }
1075     }
1076 
1077     @Override
1078     public UnixPath toAbsolutePath() {
1079         if (isAbsolute()) {
1080             return this;
1081         }
1082         // The path is relative so need to resolve against default directory,
1083         // taking care not to reveal the user.dir
1084         SecurityManager sm = System.getSecurityManager();
1085         if (sm != null) {
1086             sm.checkPropertyAccess("user.dir");
1087         }
1088         return new UnixPath(getFileSystem(),
1089             resolve(getFileSystem().defaultDirectory(), path));
1090     }
1091 
1092     @Override
1093     public UnixPath toRealPath(boolean resolveLinks) throws IOException {
1094         checkRead();
1095 
1096         UnixPath absolute = toAbsolutePath();
1097 
1098         // if resolveLinks is true then use realpath
1099         if (resolveLinks) {
1100             try {
1101                 byte[] rp = realpath(absolute);
1102                 return new UnixPath(getFileSystem(), rp);
1103             } catch (UnixException x) {
1104                 x.rethrowAsIOException(this);
1105             }
1106         }
1107 
1108         // if resolveLinks is false then eliminate "." and also ".."
1109         // where the previous element is not a link.
1110         UnixPath root = getFileSystem().rootDirectory();
1111         UnixPath result = root;
1112         for (int i=0; i<absolute.getNameCount(); i++) {
1113             UnixPath element = absolute.getName(i);
1114 
1115             // eliminate "."
1116             if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
1117                 continue;
1118 
1119             // cannot eliminate ".." if previous element is a link
1120             if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
1121                 (element.asByteArray()[1] == '.'))
1122             {
1123                 UnixFileAttributes attrs = null;
1124                 try {
1125                     attrs = UnixFileAttributes.get(result, false);
1126                 } catch (UnixException x) {
1127                     x.rethrowAsIOException(result);
1128                 }
1129                 if (!attrs.isSymbolicLink()) {
1130                     result = result.getParent();
1131                     if (result == null) {
1132                         result = root;
1133                     }
1134                     continue;
1135                 }
1136             }
1137             result = result.resolve(element);
1138         }
1139         return result;
1140     }
1141 
1142     @Override
1143     public boolean isHidden() {
1144         checkRead();
1145         UnixPath name = getName();
1146         if (name == null)
1147             return false;
1148         return (name.asByteArray()[0] == '.');
1149     }
1150 
1151     @Override
1152     public URI toUri() {
1153         return UnixUriUtils.toUri(this);
1154     }
1155 
1156     @Override
1157     public WatchKey register(WatchService watcher,
1158                              WatchEvent.Kind<?>[] events,
1159                              WatchEvent.Modifier... modifiers)
1160         throws IOException
1161     {
1162         if (watcher == null)
1163             throw new NullPointerException();
1164         if (!(watcher instanceof AbstractWatchService))
1165             throw new ProviderMismatchException();
1166         checkRead();
1167         return ((AbstractWatchService)watcher).register(this, events, modifiers);
1168     }
1169 }