1 /*
   2  * Copyright (c) 2008, 2013, 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.charset.*;
  31 import java.io.*;
  32 import java.net.URI;
  33 import java.util.*;
  34 import java.lang.ref.SoftReference;
  35 
  36 import static sun.nio.fs.UnixNativeDispatcher.*;
  37 import static sun.nio.fs.UnixConstants.*;
  38 
  39 /**
  40  * Solaris/Linux implementation of java.nio.file.Path
  41  */
  42 
  43 class UnixPath implements Path {
  44     private static ThreadLocal<SoftReference<CharsetEncoder>> encoder =
  45         new ThreadLocal<SoftReference<CharsetEncoder>>();
  46 
  47     // FIXME - eliminate this reference to reduce space
  48     private final UnixFileSystem fs;
  49 
  50     // internal representation
  51     private final byte[] path;
  52 
  53     // String representation (created lazily)
  54     private volatile String stringValue;
  55 
  56     // cached hashcode (created lazily, no need to be volatile)
  57     private int hash;
  58 
  59     // array of offsets of elements in path (created lazily)
  60     private volatile int[] offsets;
  61 
  62     UnixPath(UnixFileSystem fs, byte[] path) {
  63         this.fs = fs;
  64         this.path = path;
  65     }
  66 
  67     UnixPath(UnixFileSystem fs, String input) {
  68         // removes redundant slashes and checks for invalid characters
  69         this(fs, encode(fs, normalizeAndCheck(input)));
  70     }
  71 
  72     // package-private
  73     // removes redundant slashes and check input for invalid characters
  74     static String normalizeAndCheck(String input) {
  75         int n = input.length();
  76         char prevChar = 0;
  77         for (int i=0; i < n; i++) {
  78             char c = input.charAt(i);
  79             if ((c == '/') && (prevChar == '/'))
  80                 return normalize(input, n, i - 1);
  81             checkNotNul(input, c);
  82             prevChar = c;
  83         }
  84         if (prevChar == '/')
  85             return normalize(input, n, n - 1);
  86         return input;
  87     }
  88 
  89     private static void checkNotNul(String input, char c) {
  90         if (c == '\u0000')
  91             throw new InvalidPathException(input, "Nul character not allowed");
  92     }
  93 
  94     private static String normalize(String input, int len, int off) {
  95         if (len == 0)
  96             return input;
  97         int n = len;
  98         while ((n > 0) && (input.charAt(n - 1) == '/')) n--;
  99         if (n == 0)
 100             return "/";
 101         StringBuilder sb = new StringBuilder(input.length());
 102         if (off > 0)
 103             sb.append(input.substring(0, off));
 104         char prevChar = 0;
 105         for (int i=off; i < n; i++) {
 106             char c = input.charAt(i);
 107             if ((c == '/') && (prevChar == '/'))
 108                 continue;
 109             checkNotNul(input, c);
 110             sb.append(c);
 111             prevChar = c;
 112         }
 113         return sb.toString();
 114     }
 115 
 116     // encodes the given path-string into a sequence of bytes
 117     private static byte[] encode(UnixFileSystem fs, String input) {
 118         SoftReference<CharsetEncoder> ref = encoder.get();
 119         CharsetEncoder ce = (ref != null) ? ref.get() : null;
 120         if (ce == null) {
 121             ce = Util.jnuEncoding().newEncoder()
 122                 .onMalformedInput(CodingErrorAction.REPORT)
 123                 .onUnmappableCharacter(CodingErrorAction.REPORT);
 124             encoder.set(new SoftReference<CharsetEncoder>(ce));
 125         }
 126 
 127         char[] ca = fs.normalizeNativePath(input.toCharArray());
 128 
 129         // size output buffer for worse-case size
 130         byte[] ba = new byte[(int)(ca.length * (double)ce.maxBytesPerChar())];
 131 
 132         // encode
 133         ByteBuffer bb = ByteBuffer.wrap(ba);
 134         CharBuffer cb = CharBuffer.wrap(ca);
 135         ce.reset();
 136         CoderResult cr = ce.encode(cb, bb, true);
 137         boolean error;
 138         if (!cr.isUnderflow()) {
 139             error = true;
 140         } else {
 141             cr = ce.flush(bb);
 142             error = !cr.isUnderflow();
 143         }
 144         if (error) {
 145             throw new InvalidPathException(input,
 146                 "Malformed input or input contains unmappable characters");
 147         }
 148 
 149         // trim result to actual length if required
 150         int len = bb.position();
 151         if (len != ba.length)
 152             ba = Arrays.copyOf(ba, len);
 153 
 154         return ba;
 155     }
 156 
 157     // package-private
 158     byte[] asByteArray() {
 159         return path;
 160     }
 161 
 162     // use this path when making system/library calls
 163     byte[] getByteArrayForSysCalls() {
 164         // resolve against default directory if required (chdir allowed or
 165         // file system default directory is not working directory)
 166         if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
 167             return resolve(getFileSystem().defaultDirectory(), path);
 168         } else {
 169             if (!isEmpty()) {
 170                 return path;
 171             } else {
 172                 // empty path case will access current directory
 173                 byte[] here = { '.' };
 174                 return here;
 175             }
 176         }
 177     }
 178 
 179     // use this message when throwing exceptions
 180     String getPathForExceptionMessage() {
 181         return toString();
 182     }
 183 
 184     // use this path for permission checks
 185     String getPathForPermissionCheck() {
 186         if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
 187             return Util.toString(getByteArrayForSysCalls());
 188         } else {
 189             return toString();
 190         }
 191     }
 192 
 193     // Checks that the given file is a UnixPath
 194     static UnixPath toUnixPath(Path obj) {
 195         if (obj == null)
 196             throw new NullPointerException();
 197         if (!(obj instanceof UnixPath))
 198             throw new ProviderMismatchException();
 199         return (UnixPath)obj;
 200     }
 201 
 202     // create offset list if not already created
 203     private void initOffsets() {
 204         if (offsets == null) {
 205             int count, index;
 206 
 207             // count names
 208             count = 0;
 209             index = 0;
 210             if (isEmpty()) {
 211                 // empty path has one name
 212                 count = 1;
 213             } else {
 214                 while (index < path.length) {
 215                     byte c = path[index++];
 216                     if (c != '/') {
 217                         count++;
 218                         while (index < path.length && path[index] != '/')
 219                             index++;
 220                     }
 221                 }
 222             }
 223 
 224             // populate offsets
 225             int[] result = new int[count];
 226             count = 0;
 227             index = 0;
 228             while (index < path.length) {
 229                 byte c = path[index];
 230                 if (c == '/') {
 231                     index++;
 232                 } else {
 233                     result[count++] = index++;
 234                     while (index < path.length && path[index] != '/')
 235                         index++;
 236                 }
 237             }
 238             synchronized (this) {
 239                 if (offsets == null)
 240                     offsets = result;
 241             }
 242         }
 243     }
 244 
 245     // returns {@code true} if this path is an empty path
 246     private boolean isEmpty() {
 247         return path.length == 0;
 248     }
 249 
 250     // returns an empty path
 251     private UnixPath emptyPath() {
 252         return new UnixPath(getFileSystem(), new byte[0]);
 253     }
 254 
 255     @Override
 256     public UnixFileSystem getFileSystem() {
 257         return fs;
 258     }
 259 
 260     @Override
 261     public UnixPath getRoot() {
 262         if (path.length > 0 && path[0] == '/') {
 263             return getFileSystem().rootDirectory();
 264         } else {
 265             return null;
 266         }
 267     }
 268 
 269     @Override
 270     public UnixPath getFileName() {
 271         initOffsets();
 272 
 273         int count = offsets.length;
 274 
 275         // no elements so no name
 276         if (count == 0)
 277             return null;
 278 
 279         // one name element and no root component
 280         if (count == 1 && path.length > 0 && path[0] != '/')
 281             return this;
 282 
 283         int lastOffset = offsets[count-1];
 284         int len = path.length - lastOffset;
 285         byte[] result = new byte[len];
 286         System.arraycopy(path, lastOffset, result, 0, len);
 287         return new UnixPath(getFileSystem(), result);
 288     }
 289 
 290     @Override
 291     public UnixPath getParent() {
 292         initOffsets();
 293 
 294         int count = offsets.length;
 295         if (count == 0) {
 296             // no elements so no parent
 297             return null;
 298         }
 299         int len = offsets[count-1] - 1;
 300         if (len <= 0) {
 301             // parent is root only (may be null)
 302             return getRoot();
 303         }
 304         byte[] result = new byte[len];
 305         System.arraycopy(path, 0, result, 0, len);
 306         return new UnixPath(getFileSystem(), result);
 307     }
 308 
 309     @Override
 310     public int getNameCount() {
 311         initOffsets();
 312         return offsets.length;
 313     }
 314 
 315     @Override
 316     public UnixPath getName(int index) {
 317         initOffsets();
 318         if (index < 0)
 319             throw new IllegalArgumentException();
 320         if (index >= offsets.length)
 321             throw new IllegalArgumentException();
 322 
 323         int begin = offsets[index];
 324         int len;
 325         if (index == (offsets.length-1)) {
 326             len = path.length - begin;
 327         } else {
 328             len = offsets[index+1] - begin - 1;
 329         }
 330 
 331         // construct result
 332         byte[] result = new byte[len];
 333         System.arraycopy(path, begin, result, 0, len);
 334         return new UnixPath(getFileSystem(), result);
 335     }
 336 
 337     @Override
 338     public UnixPath subpath(int beginIndex, int endIndex) {
 339         initOffsets();
 340 
 341         if (beginIndex < 0)
 342             throw new IllegalArgumentException();
 343         if (beginIndex >= offsets.length)
 344             throw new IllegalArgumentException();
 345         if (endIndex > offsets.length)
 346             throw new IllegalArgumentException();
 347         if (beginIndex >= endIndex) {
 348             throw new IllegalArgumentException();
 349         }
 350 
 351         // starting offset and length
 352         int begin = offsets[beginIndex];
 353         int len;
 354         if (endIndex == offsets.length) {
 355             len = path.length - begin;
 356         } else {
 357             len = offsets[endIndex] - begin - 1;
 358         }
 359 
 360         // construct result
 361         byte[] result = new byte[len];
 362         System.arraycopy(path, begin, result, 0, len);
 363         return new UnixPath(getFileSystem(), result);
 364     }
 365 
 366     @Override
 367     public boolean isAbsolute() {
 368         return (path.length > 0 && path[0] == '/');
 369     }
 370 
 371     // Resolve child against given base
 372     private static byte[] resolve(byte[] base, byte[] child) {
 373         int baseLength = base.length;
 374         int childLength = child.length;
 375         if (childLength == 0)
 376             return base;
 377         if (baseLength == 0 || child[0] == '/')
 378             return child;
 379         byte[] result;
 380         if (baseLength == 1 && base[0] == '/') {
 381             result = new byte[childLength + 1];
 382             result[0] = '/';
 383             System.arraycopy(child, 0, result, 1, childLength);
 384         } else {
 385             result = new byte[baseLength + 1 + childLength];
 386             System.arraycopy(base, 0, result, 0, baseLength);
 387             result[base.length] = '/';
 388             System.arraycopy(child, 0, result, baseLength+1, childLength);
 389         }
 390         return result;
 391     }
 392 
 393     @Override
 394     public UnixPath resolve(Path obj) {
 395         byte[] other = toUnixPath(obj).path;
 396         if (other.length > 0 && other[0] == '/')
 397             return ((UnixPath)obj);
 398         byte[] result = resolve(path, other);
 399         return new UnixPath(getFileSystem(), result);
 400     }
 401 
 402     UnixPath resolve(byte[] other) {
 403         return resolve(new UnixPath(getFileSystem(), other));
 404     }
 405 
 406     @Override
 407     public UnixPath relativize(Path obj) {
 408         UnixPath other = toUnixPath(obj);
 409         if (other.equals(this))
 410             return emptyPath();
 411 
 412         // can only relativize paths of the same type
 413         if (this.isAbsolute() != other.isAbsolute())
 414             throw new IllegalArgumentException("'other' is different type of Path");
 415 
 416         // this path is the empty path
 417         if (this.isEmpty())
 418             return other;
 419 
 420         int bn = this.getNameCount();
 421         int cn = other.getNameCount();
 422 
 423         // skip matching names
 424         int n = (bn > cn) ? cn : bn;
 425         int i = 0;
 426         while (i < n) {
 427             if (!this.getName(i).equals(other.getName(i)))
 428                 break;
 429             i++;
 430         }
 431 
 432         int dotdots = bn - i;
 433         if (i < cn) {
 434             // remaining name components in other
 435             UnixPath remainder = other.subpath(i, cn);
 436             if (dotdots == 0)
 437                 return remainder;
 438 
 439             // other is the empty path
 440             boolean isOtherEmpty = other.isEmpty();
 441 
 442             // result is a  "../" for each remaining name in base
 443             // followed by the remaining names in other. If the remainder is
 444             // the empty path then we don't add the final trailing slash.
 445             int len = dotdots*3 + remainder.path.length;
 446             if (isOtherEmpty) {
 447                 assert remainder.isEmpty();
 448                 len--;
 449             }
 450             byte[] result = new byte[len];
 451             int pos = 0;
 452             while (dotdots > 0) {
 453                 result[pos++] = (byte)'.';
 454                 result[pos++] = (byte)'.';
 455                 if (isOtherEmpty) {
 456                     if (dotdots > 1) result[pos++] = (byte)'/';
 457                 } else {
 458                     result[pos++] = (byte)'/';
 459                 }
 460                 dotdots--;
 461             }
 462             System.arraycopy(remainder.path, 0, result, pos, remainder.path.length);
 463             return new UnixPath(getFileSystem(), result);
 464         } else {
 465             // no remaining names in other so result is simply a sequence of ".."
 466             byte[] result = new byte[dotdots*3 - 1];
 467             int pos = 0;
 468             while (dotdots > 0) {
 469                 result[pos++] = (byte)'.';
 470                 result[pos++] = (byte)'.';
 471                 // no tailing slash at the end
 472                 if (dotdots > 1)
 473                     result[pos++] = (byte)'/';
 474                 dotdots--;
 475             }
 476             return new UnixPath(getFileSystem(), result);
 477         }
 478     }
 479 
 480     @Override
 481     public Path normalize() {
 482         final int count = getNameCount();
 483         if (count == 0 || isEmpty())
 484             return this;
 485 
 486         boolean[] ignore = new boolean[count];      // true => ignore name
 487         int[] size = new int[count];                // length of name
 488         int remaining = count;                      // number of names remaining
 489         boolean hasDotDot = false;                  // has at least one ..
 490         boolean isAbsolute = isAbsolute();
 491 
 492         // first pass:
 493         //   1. compute length of names
 494         //   2. mark all occurrences of "." to ignore
 495         //   3. and look for any occurrences of ".."
 496         for (int i=0; i<count; i++) {
 497             int begin = offsets[i];
 498             int len;
 499             if (i == (offsets.length-1)) {
 500                 len = path.length - begin;
 501             } else {
 502                 len = offsets[i+1] - begin - 1;
 503             }
 504             size[i] = len;
 505 
 506             if (path[begin] == '.') {
 507                 if (len == 1) {
 508                     ignore[i] = true;  // ignore  "."
 509                     remaining--;
 510                 }
 511                 else {
 512                     if (path[begin+1] == '.')   // ".." found
 513                         hasDotDot = true;
 514                 }
 515             }
 516         }
 517 
 518         // multiple passes to eliminate all occurrences of name/..
 519         if (hasDotDot) {
 520             int prevRemaining;
 521             do {
 522                 prevRemaining = remaining;
 523                 int prevName = -1;
 524                 for (int i=0; i<count; i++) {
 525                     if (ignore[i])
 526                         continue;
 527 
 528                     // not a ".."
 529                     if (size[i] != 2) {
 530                         prevName = i;
 531                         continue;
 532                     }
 533 
 534                     int begin = offsets[i];
 535                     if (path[begin] != '.' || path[begin+1] != '.') {
 536                         prevName = i;
 537                         continue;
 538                     }
 539 
 540                     // ".." found
 541                     if (prevName >= 0) {
 542                         // name/<ignored>/.. found so mark name and ".." to be
 543                         // ignored
 544                         ignore[prevName] = true;
 545                         ignore[i] = true;
 546                         remaining = remaining - 2;
 547                         prevName = -1;
 548                     } else {
 549                         // Case: /<ignored>/.. so mark ".." as ignored
 550                         if (isAbsolute) {
 551                             boolean hasPrevious = false;
 552                             for (int j=0; j<i; j++) {
 553                                 if (!ignore[j]) {
 554                                     hasPrevious = true;
 555                                     break;
 556                                 }
 557                             }
 558                             if (!hasPrevious) {
 559                                 // all proceeding names are ignored
 560                                 ignore[i] = true;
 561                                 remaining--;
 562                             }
 563                         }
 564                     }
 565                 }
 566             } while (prevRemaining > remaining);
 567         }
 568 
 569         // no redundant names
 570         if (remaining == count)
 571             return this;
 572 
 573         // corner case - all names removed
 574         if (remaining == 0) {
 575             return isAbsolute ? getFileSystem().rootDirectory() : emptyPath();
 576         }
 577 
 578         // compute length of result
 579         int len = remaining - 1;
 580         if (isAbsolute)
 581             len++;
 582 
 583         for (int i=0; i<count; i++) {
 584             if (!ignore[i])
 585                 len += size[i];
 586         }
 587         byte[] result = new byte[len];
 588 
 589         // copy names into result
 590         int pos = 0;
 591         if (isAbsolute)
 592             result[pos++] = '/';
 593         for (int i=0; i<count; i++) {
 594             if (!ignore[i]) {
 595                 System.arraycopy(path, offsets[i], result, pos, size[i]);
 596                 pos += size[i];
 597                 if (--remaining > 0) {
 598                     result[pos++] = '/';
 599                 }
 600             }
 601         }
 602         return new UnixPath(getFileSystem(), result);
 603     }
 604 
 605     @Override
 606     public boolean startsWith(Path other) {
 607         if (!(Objects.requireNonNull(other) instanceof UnixPath))
 608             return false;
 609         UnixPath that = (UnixPath)other;
 610 
 611         // other path is longer
 612         if (that.path.length > path.length)
 613             return false;
 614 
 615         int thisOffsetCount = getNameCount();
 616         int thatOffsetCount = that.getNameCount();
 617 
 618         // other path has no name elements
 619         if (thatOffsetCount == 0 && this.isAbsolute()) {
 620             return that.isEmpty() ? false : true;
 621         }
 622 
 623         // given path has more elements that this path
 624         if (thatOffsetCount > thisOffsetCount)
 625             return false;
 626 
 627         // same number of elements so must be exact match
 628         if ((thatOffsetCount == thisOffsetCount) &&
 629             (path.length != that.path.length)) {
 630             return false;
 631         }
 632 
 633         // check offsets of elements match
 634         for (int i=0; i<thatOffsetCount; i++) {
 635             Integer o1 = offsets[i];
 636             Integer o2 = that.offsets[i];
 637             if (!o1.equals(o2))
 638                 return false;
 639         }
 640 
 641         // offsets match so need to compare bytes
 642         int i=0;
 643         while (i < that.path.length) {
 644             if (this.path[i] != that.path[i])
 645                 return false;
 646             i++;
 647         }
 648 
 649         // final check that match is on name boundary
 650         if (i < path.length && this.path[i] != '/')
 651             return false;
 652 
 653         return true;
 654     }
 655 
 656     @Override
 657     public boolean endsWith(Path other) {
 658         if (!(Objects.requireNonNull(other) instanceof UnixPath))
 659             return false;
 660         UnixPath that = (UnixPath)other;
 661 
 662         int thisLen = path.length;
 663         int thatLen = that.path.length;
 664 
 665         // other path is longer
 666         if (thatLen > thisLen)
 667             return false;
 668 
 669         // other path is the empty path
 670         if (thisLen > 0 && thatLen == 0)
 671             return false;
 672 
 673         // other path is absolute so this path must be absolute
 674         if (that.isAbsolute() && !this.isAbsolute())
 675             return false;
 676 
 677         int thisOffsetCount = getNameCount();
 678         int thatOffsetCount = that.getNameCount();
 679 
 680         // given path has more elements that this path
 681         if (thatOffsetCount > thisOffsetCount) {
 682             return false;
 683         } else {
 684             // same number of elements
 685             if (thatOffsetCount == thisOffsetCount) {
 686                 if (thisOffsetCount == 0)
 687                     return true;
 688                 int expectedLen = thisLen;
 689                 if (this.isAbsolute() && !that.isAbsolute())
 690                     expectedLen--;
 691                 if (thatLen != expectedLen)
 692                     return false;
 693             } else {
 694                 // this path has more elements so given path must be relative
 695                 if (that.isAbsolute())
 696                     return false;
 697             }
 698         }
 699 
 700         // compare bytes
 701         int thisPos = offsets[thisOffsetCount - thatOffsetCount];
 702         int thatPos = that.offsets[0];
 703         if ((thatLen - thatPos) != (thisLen - thisPos))
 704             return false;
 705         while (thatPos < thatLen) {
 706             if (this.path[thisPos++] != that.path[thatPos++])
 707                 return false;
 708         }
 709 
 710         return true;
 711     }
 712 
 713     @Override
 714     public int compareTo(Path other) {
 715         int len1 = path.length;
 716         int len2 = ((UnixPath) other).path.length;
 717 
 718         int n = Math.min(len1, len2);
 719         byte v1[] = path;
 720         byte v2[] = ((UnixPath) other).path;
 721 
 722         int k = 0;
 723         while (k < n) {
 724             int c1 = v1[k] & 0xff;
 725             int c2 = v2[k] & 0xff;
 726             if (c1 != c2) {
 727                 return c1 - c2;
 728             }
 729            k++;
 730         }
 731         return len1 - len2;
 732     }
 733 
 734     @Override
 735     public boolean equals(Object ob) {
 736         if ((ob != null) && (ob instanceof UnixPath)) {
 737             return compareTo((Path)ob) == 0;
 738         }
 739         return false;
 740     }
 741 
 742     @Override
 743     public int hashCode() {
 744         // OK if two or more threads compute hash
 745         int h = hash;
 746         if (h == 0) {
 747             for (int i = 0; i< path.length; i++) {
 748                 h = 31*h + (path[i] & 0xff);
 749             }
 750             hash = h;
 751         }
 752         return h;
 753     }
 754 
 755     @Override
 756     public String toString() {
 757         // OK if two or more threads create a String
 758         if (stringValue == null) {
 759             stringValue = fs.normalizeJavaPath(Util.toString(path));     // platform encoding
 760         }
 761         return stringValue;
 762     }
 763 
 764     // -- file operations --
 765 
 766     // package-private
 767     int openForAttributeAccess(boolean followLinks) throws IOException {
 768         int flags = O_RDONLY;
 769         if (!followLinks) {
 770             if (O_NOFOLLOW == 0)
 771                 throw new IOException("NOFOLLOW_LINKS is not supported on this platform");
 772             flags |= O_NOFOLLOW;
 773         }
 774         try {
 775             return open(this, flags, 0);
 776         } catch (UnixException x) {
 777             // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
 778             if (getFileSystem().isSolaris() && x.errno() == EINVAL)
 779                 x.setError(ELOOP);
 780 
 781             if (x.errno() == ELOOP)
 782                 throw new FileSystemException(getPathForExceptionMessage(), null,
 783                     x.getMessage() + " or unable to access attributes of symbolic link");
 784 
 785             x.rethrowAsIOException(this);
 786             return -1; // keep compile happy
 787         }
 788     }
 789 
 790     void checkRead() {
 791         SecurityManager sm = System.getSecurityManager();
 792         if (sm != null)
 793             sm.checkRead(getPathForPermissionCheck());
 794     }
 795 
 796     void checkWrite() {
 797         SecurityManager sm = System.getSecurityManager();
 798         if (sm != null)
 799             sm.checkWrite(getPathForPermissionCheck());
 800     }
 801 
 802     void checkDelete() {
 803         SecurityManager sm = System.getSecurityManager();
 804         if (sm != null)
 805             sm.checkDelete(getPathForPermissionCheck());
 806     }
 807 
 808     @Override
 809     public UnixPath toAbsolutePath() {
 810         if (isAbsolute()) {
 811             return this;
 812         }
 813         // The path is relative so need to resolve against default directory,
 814         // taking care not to reveal the user.dir
 815         SecurityManager sm = System.getSecurityManager();
 816         if (sm != null) {
 817             sm.checkPropertyAccess("user.dir");
 818         }
 819         return new UnixPath(getFileSystem(),
 820             resolve(getFileSystem().defaultDirectory(), path));
 821     }
 822 
 823     @Override
 824     public Path toRealPath(LinkOption... options) throws IOException {
 825         checkRead();
 826 
 827         UnixPath absolute = toAbsolutePath();
 828 
 829         // if resolving links then use realpath
 830         if (Util.followLinks(options)) {
 831             try {
 832                 byte[] rp = realpath(absolute);
 833                 return new UnixPath(getFileSystem(), rp);
 834             } catch (UnixException x) {
 835                 x.rethrowAsIOException(this);
 836             }
 837         }
 838 
 839         // if not resolving links then eliminate "." and also ".."
 840         // where the previous element is not a link.
 841         UnixPath result = fs.rootDirectory();
 842         for (int i=0; i<absolute.getNameCount(); i++) {
 843             UnixPath element = absolute.getName(i);
 844 
 845             // eliminate "."
 846             if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
 847                 continue;
 848 
 849             // cannot eliminate ".." if previous element is a link
 850             if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
 851                 (element.asByteArray()[1] == '.'))
 852             {
 853                 UnixFileAttributes attrs = null;
 854                 try {
 855                     attrs = UnixFileAttributes.get(result, false);
 856                 } catch (UnixException x) {
 857                     x.rethrowAsIOException(result);
 858                 }
 859                 if (!attrs.isSymbolicLink()) {
 860                     result = result.getParent();
 861                     if (result == null) {
 862                         result = fs.rootDirectory();
 863                     }
 864                     continue;
 865                 }
 866             }
 867             result = result.resolve(element);
 868         }
 869 
 870         // check file exists (without following links)
 871         try {
 872             UnixFileAttributes.get(result, false);
 873         } catch (UnixException x) {
 874             x.rethrowAsIOException(result);
 875         }
 876         return result;
 877     }
 878 
 879     @Override
 880     public URI toUri() {
 881         return UnixUriUtils.toUri(this);
 882     }
 883 
 884     @Override
 885     public WatchKey register(WatchService watcher,
 886                              WatchEvent.Kind<?>[] events,
 887                              WatchEvent.Modifier... modifiers)
 888         throws IOException
 889     {
 890         if (watcher == null)
 891             throw new NullPointerException();
 892         if (!(watcher instanceof AbstractWatchService))
 893             throw new ProviderMismatchException();
 894         checkRead();
 895         return ((AbstractWatchService)watcher).register(this, events, modifiers);
 896     }
 897 }