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