1 /*
   2  * Copyright (c) 2008, 2015, 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<>(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 
 256     // return true if this path has "." or ".."
 257     private boolean hasDotOrDotDot() {
 258         int n = getNameCount();
 259         for (int i=0; i<n; i++) {
 260             byte[] bytes = getName(i).path;
 261             if ((bytes.length == 1 && bytes[0] == '.'))
 262                 return true;
 263             if ((bytes.length == 2 && bytes[0] == '.') && bytes[1] == '.') {
 264                 return true;
 265             }
 266         }
 267         return false;
 268     }
 269 
 270     @Override
 271     public UnixFileSystem getFileSystem() {
 272         return fs;
 273     }
 274 
 275     @Override
 276     public UnixPath getRoot() {
 277         if (path.length > 0 && path[0] == '/') {
 278             return getFileSystem().rootDirectory();
 279         } else {
 280             return null;
 281         }
 282     }
 283 
 284     @Override
 285     public UnixPath getFileName() {
 286         initOffsets();
 287 
 288         int count = offsets.length;
 289 
 290         // no elements so no name
 291         if (count == 0)
 292             return null;
 293 
 294         // one name element and no root component
 295         if (count == 1 && path.length > 0 && path[0] != '/')
 296             return this;
 297 
 298         int lastOffset = offsets[count-1];
 299         int len = path.length - lastOffset;
 300         byte[] result = new byte[len];
 301         System.arraycopy(path, lastOffset, result, 0, len);
 302         return new UnixPath(getFileSystem(), result);
 303     }
 304 
 305     @Override
 306     public UnixPath getParent() {
 307         initOffsets();
 308 
 309         int count = offsets.length;
 310         if (count == 0) {
 311             // no elements so no parent
 312             return null;
 313         }
 314         int len = offsets[count-1] - 1;
 315         if (len <= 0) {
 316             // parent is root only (may be null)
 317             return getRoot();
 318         }
 319         byte[] result = new byte[len];
 320         System.arraycopy(path, 0, result, 0, len);
 321         return new UnixPath(getFileSystem(), result);
 322     }
 323 
 324     @Override
 325     public int getNameCount() {
 326         initOffsets();
 327         return offsets.length;
 328     }
 329 
 330     @Override
 331     public UnixPath getName(int index) {
 332         initOffsets();
 333         if (index < 0)
 334             throw new IllegalArgumentException();
 335         if (index >= offsets.length)
 336             throw new IllegalArgumentException();
 337 
 338         int begin = offsets[index];
 339         int len;
 340         if (index == (offsets.length-1)) {
 341             len = path.length - begin;
 342         } else {
 343             len = offsets[index+1] - begin - 1;
 344         }
 345 
 346         // construct result
 347         byte[] result = new byte[len];
 348         System.arraycopy(path, begin, result, 0, len);
 349         return new UnixPath(getFileSystem(), result);
 350     }
 351 
 352     @Override
 353     public UnixPath subpath(int beginIndex, int endIndex) {
 354         initOffsets();
 355 
 356         if (beginIndex < 0)
 357             throw new IllegalArgumentException();
 358         if (beginIndex >= offsets.length)
 359             throw new IllegalArgumentException();
 360         if (endIndex > offsets.length)
 361             throw new IllegalArgumentException();
 362         if (beginIndex >= endIndex) {
 363             throw new IllegalArgumentException();
 364         }
 365 
 366         // starting offset and length
 367         int begin = offsets[beginIndex];
 368         int len;
 369         if (endIndex == offsets.length) {
 370             len = path.length - begin;
 371         } else {
 372             len = offsets[endIndex] - begin - 1;
 373         }
 374 
 375         // construct result
 376         byte[] result = new byte[len];
 377         System.arraycopy(path, begin, result, 0, len);
 378         return new UnixPath(getFileSystem(), result);
 379     }
 380 
 381     @Override
 382     public boolean isAbsolute() {
 383         return (path.length > 0 && path[0] == '/');
 384     }
 385 
 386     // Resolve child against given base
 387     private static byte[] resolve(byte[] base, byte[] child) {
 388         int baseLength = base.length;
 389         int childLength = child.length;
 390         if (childLength == 0)
 391             return base;
 392         if (baseLength == 0 || child[0] == '/')
 393             return child;
 394         byte[] result;
 395         if (baseLength == 1 && base[0] == '/') {
 396             result = new byte[childLength + 1];
 397             result[0] = '/';
 398             System.arraycopy(child, 0, result, 1, childLength);
 399         } else {
 400             result = new byte[baseLength + 1 + childLength];
 401             System.arraycopy(base, 0, result, 0, baseLength);
 402             result[base.length] = '/';
 403             System.arraycopy(child, 0, result, baseLength+1, childLength);
 404         }
 405         return result;
 406     }
 407 
 408     @Override
 409     public UnixPath resolve(Path obj) {
 410         byte[] other = toUnixPath(obj).path;
 411         if (other.length > 0 && other[0] == '/')
 412             return ((UnixPath)obj);
 413         byte[] result = resolve(path, other);
 414         return new UnixPath(getFileSystem(), result);
 415     }
 416 
 417     UnixPath resolve(byte[] other) {
 418         return resolve(new UnixPath(getFileSystem(), other));
 419     }
 420 
 421     @Override
 422     public UnixPath relativize(Path obj) {
 423         UnixPath child = toUnixPath(obj);
 424         if (child.equals(this))
 425             return emptyPath();
 426 
 427         // can only relativize paths of the same type
 428         if (this.isAbsolute() != child.isAbsolute())
 429             throw new IllegalArgumentException("'other' is different type of Path");
 430 
 431         // this path is the empty path
 432         if (this.isEmpty())
 433             return child;
 434 
 435         UnixPath base = this;
 436         if (base.hasDotOrDotDot() || child.hasDotOrDotDot()) {
 437             base = base.normalize();
 438             child = child.normalize();
 439         }
 440 
 441         int baseCount = base.getNameCount();
 442         int childCount = child.getNameCount();
 443 
 444         // skip matching names
 445         int n = Math.min(baseCount, childCount);
 446         int i = 0;
 447         while (i < n) {
 448             if (!base.getName(i).equals(child.getName(i)))
 449                 break;
 450             i++;
 451         }
 452 
 453         // remaining elements in child
 454         UnixPath childRemaining;
 455         boolean isChildEmpty;
 456         if (i == childCount) {
 457             childRemaining = emptyPath();
 458             isChildEmpty = true;
 459         } else {
 460             childRemaining = child.subpath(i, childCount);
 461             isChildEmpty = childRemaining.isEmpty();
 462         }
 463 
 464         // matched all of base
 465         if (i == baseCount) {
 466             return childRemaining;
 467         }
 468 
 469         // the remainder of base cannot contain ".."
 470         UnixPath baseRemaining = base.subpath(i, baseCount);
 471         if (baseRemaining.hasDotOrDotDot()) {
 472             throw new IllegalArgumentException("Unable to compute relative "
 473                     + " path from " + this + " to " + obj);
 474         }
 475         if (baseRemaining.isEmpty())
 476             return childRemaining;
 477 
 478         // number of ".." needed
 479         int dotdots = baseRemaining.getNameCount();
 480         if (dotdots == 0) {
 481             return childRemaining;
 482         }
 483 
 484         // result is a  "../" for each remaining name in base followed by the
 485         // remaining names in child. If the remainder is the empty path
 486         // then we don't add the final trailing slash.
 487         int len = dotdots*3 + childRemaining.path.length;
 488         if (isChildEmpty) {
 489             assert childRemaining.isEmpty();
 490             len--;
 491         }
 492         byte[] result = new byte[len];
 493         int pos = 0;
 494         while (dotdots > 0) {
 495             result[pos++] = (byte)'.';
 496             result[pos++] = (byte)'.';
 497             if (isChildEmpty) {
 498                 if (dotdots > 1) result[pos++] = (byte)'/';
 499             } else {
 500                 result[pos++] = (byte)'/';
 501             }
 502             dotdots--;
 503         }
 504         System.arraycopy(childRemaining.path,0, result, pos,
 505                              childRemaining.path.length);
 506         return new UnixPath(getFileSystem(), result);
 507     }
 508 
 509     @Override
 510     public UnixPath normalize() {
 511         final int count = getNameCount();
 512         if (count == 0 || isEmpty())
 513             return this;
 514 
 515         boolean[] ignore = new boolean[count];      // true => ignore name
 516         int[] size = new int[count];                // length of name
 517         int remaining = count;                      // number of names remaining
 518         boolean hasDotDot = false;                  // has at least one ..
 519         boolean isAbsolute = isAbsolute();
 520 
 521         // first pass:
 522         //   1. compute length of names
 523         //   2. mark all occurrences of "." to ignore
 524         //   3. and look for any occurrences of ".."
 525         for (int i=0; i<count; i++) {
 526             int begin = offsets[i];
 527             int len;
 528             if (i == (offsets.length-1)) {
 529                 len = path.length - begin;
 530             } else {
 531                 len = offsets[i+1] - begin - 1;
 532             }
 533             size[i] = len;
 534 
 535             if (path[begin] == '.') {
 536                 if (len == 1) {
 537                     ignore[i] = true;  // ignore  "."
 538                     remaining--;
 539                 }
 540                 else {
 541                     if (path[begin+1] == '.')   // ".." found
 542                         hasDotDot = true;
 543                 }
 544             }
 545         }
 546 
 547         // multiple passes to eliminate all occurrences of name/..
 548         if (hasDotDot) {
 549             int prevRemaining;
 550             do {
 551                 prevRemaining = remaining;
 552                 int prevName = -1;
 553                 for (int i=0; i<count; i++) {
 554                     if (ignore[i])
 555                         continue;
 556 
 557                     // not a ".."
 558                     if (size[i] != 2) {
 559                         prevName = i;
 560                         continue;
 561                     }
 562 
 563                     int begin = offsets[i];
 564                     if (path[begin] != '.' || path[begin+1] != '.') {
 565                         prevName = i;
 566                         continue;
 567                     }
 568 
 569                     // ".." found
 570                     if (prevName >= 0) {
 571                         // name/<ignored>/.. found so mark name and ".." to be
 572                         // ignored
 573                         ignore[prevName] = true;
 574                         ignore[i] = true;
 575                         remaining = remaining - 2;
 576                         prevName = -1;
 577                     } else {
 578                         // Case: /<ignored>/.. so mark ".." as ignored
 579                         if (isAbsolute) {
 580                             boolean hasPrevious = false;
 581                             for (int j=0; j<i; j++) {
 582                                 if (!ignore[j]) {
 583                                     hasPrevious = true;
 584                                     break;
 585                                 }
 586                             }
 587                             if (!hasPrevious) {
 588                                 // all proceeding names are ignored
 589                                 ignore[i] = true;
 590                                 remaining--;
 591                             }
 592                         }
 593                     }
 594                 }
 595             } while (prevRemaining > remaining);
 596         }
 597 
 598         // no redundant names
 599         if (remaining == count)
 600             return this;
 601 
 602         // corner case - all names removed
 603         if (remaining == 0) {
 604             return isAbsolute ? getFileSystem().rootDirectory() : emptyPath();
 605         }
 606 
 607         // compute length of result
 608         int len = remaining - 1;
 609         if (isAbsolute)
 610             len++;
 611 
 612         for (int i=0; i<count; i++) {
 613             if (!ignore[i])
 614                 len += size[i];
 615         }
 616         byte[] result = new byte[len];
 617 
 618         // copy names into result
 619         int pos = 0;
 620         if (isAbsolute)
 621             result[pos++] = '/';
 622         for (int i=0; i<count; i++) {
 623             if (!ignore[i]) {
 624                 System.arraycopy(path, offsets[i], result, pos, size[i]);
 625                 pos += size[i];
 626                 if (--remaining > 0) {
 627                     result[pos++] = '/';
 628                 }
 629             }
 630         }
 631         return new UnixPath(getFileSystem(), result);
 632     }
 633 
 634     @Override
 635     public boolean startsWith(Path other) {
 636         if (!(Objects.requireNonNull(other) instanceof UnixPath))
 637             return false;
 638         UnixPath that = (UnixPath)other;
 639 
 640         // other path is longer
 641         if (that.path.length > path.length)
 642             return false;
 643 
 644         int thisOffsetCount = getNameCount();
 645         int thatOffsetCount = that.getNameCount();
 646 
 647         // other path has no name elements
 648         if (thatOffsetCount == 0 && this.isAbsolute()) {
 649             return that.isEmpty() ? false : true;
 650         }
 651 
 652         // given path has more elements that this path
 653         if (thatOffsetCount > thisOffsetCount)
 654             return false;
 655 
 656         // same number of elements so must be exact match
 657         if ((thatOffsetCount == thisOffsetCount) &&
 658             (path.length != that.path.length)) {
 659             return false;
 660         }
 661 
 662         // check offsets of elements match
 663         for (int i=0; i<thatOffsetCount; i++) {
 664             Integer o1 = offsets[i];
 665             Integer o2 = that.offsets[i];
 666             if (!o1.equals(o2))
 667                 return false;
 668         }
 669 
 670         // offsets match so need to compare bytes
 671         int i=0;
 672         while (i < that.path.length) {
 673             if (this.path[i] != that.path[i])
 674                 return false;
 675             i++;
 676         }
 677 
 678         // final check that match is on name boundary
 679         if (i < path.length && this.path[i] != '/')
 680             return false;
 681 
 682         return true;
 683     }
 684 
 685     @Override
 686     public boolean endsWith(Path other) {
 687         if (!(Objects.requireNonNull(other) instanceof UnixPath))
 688             return false;
 689         UnixPath that = (UnixPath)other;
 690 
 691         int thisLen = path.length;
 692         int thatLen = that.path.length;
 693 
 694         // other path is longer
 695         if (thatLen > thisLen)
 696             return false;
 697 
 698         // other path is the empty path
 699         if (thisLen > 0 && thatLen == 0)
 700             return false;
 701 
 702         // other path is absolute so this path must be absolute
 703         if (that.isAbsolute() && !this.isAbsolute())
 704             return false;
 705 
 706         int thisOffsetCount = getNameCount();
 707         int thatOffsetCount = that.getNameCount();
 708 
 709         // given path has more elements that this path
 710         if (thatOffsetCount > thisOffsetCount) {
 711             return false;
 712         } else {
 713             // same number of elements
 714             if (thatOffsetCount == thisOffsetCount) {
 715                 if (thisOffsetCount == 0)
 716                     return true;
 717                 int expectedLen = thisLen;
 718                 if (this.isAbsolute() && !that.isAbsolute())
 719                     expectedLen--;
 720                 if (thatLen != expectedLen)
 721                     return false;
 722             } else {
 723                 // this path has more elements so given path must be relative
 724                 if (that.isAbsolute())
 725                     return false;
 726             }
 727         }
 728 
 729         // compare bytes
 730         int thisPos = offsets[thisOffsetCount - thatOffsetCount];
 731         int thatPos = that.offsets[0];
 732         if ((thatLen - thatPos) != (thisLen - thisPos))
 733             return false;
 734         while (thatPos < thatLen) {
 735             if (this.path[thisPos++] != that.path[thatPos++])
 736                 return false;
 737         }
 738 
 739         return true;
 740     }
 741 
 742     @Override
 743     public int compareTo(Path other) {
 744         int len1 = path.length;
 745         int len2 = ((UnixPath) other).path.length;
 746 
 747         int n = Math.min(len1, len2);
 748         byte v1[] = path;
 749         byte v2[] = ((UnixPath) other).path;
 750 
 751         int k = 0;
 752         while (k < n) {
 753             int c1 = v1[k] & 0xff;
 754             int c2 = v2[k] & 0xff;
 755             if (c1 != c2) {
 756                 return c1 - c2;
 757             }
 758            k++;
 759         }
 760         return len1 - len2;
 761     }
 762 
 763     @Override
 764     public boolean equals(Object ob) {
 765         if ((ob != null) && (ob instanceof UnixPath)) {
 766             return compareTo((Path)ob) == 0;
 767         }
 768         return false;
 769     }
 770 
 771     @Override
 772     public int hashCode() {
 773         // OK if two or more threads compute hash
 774         int h = hash;
 775         if (h == 0) {
 776             for (int i = 0; i< path.length; i++) {
 777                 h = 31*h + (path[i] & 0xff);
 778             }
 779             hash = h;
 780         }
 781         return h;
 782     }
 783 
 784     @Override
 785     public String toString() {
 786         // OK if two or more threads create a String
 787         if (stringValue == null) {
 788             stringValue = fs.normalizeJavaPath(Util.toString(path));     // platform encoding
 789         }
 790         return stringValue;
 791     }
 792 
 793     // -- file operations --
 794 
 795     // package-private
 796     int openForAttributeAccess(boolean followLinks) throws UnixException {
 797         int flags = O_RDONLY;
 798         if (!followLinks) {
 799             if (O_NOFOLLOW == 0)
 800                 throw new UnixException
 801                     ("NOFOLLOW_LINKS is not supported on this platform");
 802             flags |= O_NOFOLLOW;
 803         }
 804         try {
 805             return open(this, flags, 0);
 806         } catch (UnixException x) {
 807             // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
 808             if (getFileSystem().isSolaris() && x.errno() == EINVAL)
 809                 x.setError(ELOOP);
 810 
 811             throw x;
 812         }
 813     }
 814 
 815     void checkRead() {
 816         SecurityManager sm = System.getSecurityManager();
 817         if (sm != null)
 818             sm.checkRead(getPathForPermissionCheck());
 819     }
 820 
 821     void checkWrite() {
 822         SecurityManager sm = System.getSecurityManager();
 823         if (sm != null)
 824             sm.checkWrite(getPathForPermissionCheck());
 825     }
 826 
 827     void checkDelete() {
 828         SecurityManager sm = System.getSecurityManager();
 829         if (sm != null)
 830             sm.checkDelete(getPathForPermissionCheck());
 831     }
 832 
 833     @Override
 834     public UnixPath toAbsolutePath() {
 835         if (isAbsolute()) {
 836             return this;
 837         }
 838         // The path is relative so need to resolve against default directory,
 839         // taking care not to reveal the user.dir
 840         SecurityManager sm = System.getSecurityManager();
 841         if (sm != null) {
 842             sm.checkPropertyAccess("user.dir");
 843         }
 844         return new UnixPath(getFileSystem(),
 845             resolve(getFileSystem().defaultDirectory(), path));
 846     }
 847 
 848     @Override
 849     public Path toRealPath(LinkOption... options) throws IOException {
 850         checkRead();
 851 
 852         UnixPath absolute = toAbsolutePath();
 853 
 854         // if resolving links then use realpath
 855         if (Util.followLinks(options)) {
 856             try {
 857                 byte[] rp = realpath(absolute);
 858                 return new UnixPath(getFileSystem(), rp);
 859             } catch (UnixException x) {
 860                 x.rethrowAsIOException(this);
 861             }
 862         }
 863 
 864         // if not resolving links then eliminate "." and also ".."
 865         // where the previous element is not a link.
 866         UnixPath result = fs.rootDirectory();
 867         for (int i=0; i<absolute.getNameCount(); i++) {
 868             UnixPath element = absolute.getName(i);
 869 
 870             // eliminate "."
 871             if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
 872                 continue;
 873 
 874             // cannot eliminate ".." if previous element is a link
 875             if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
 876                 (element.asByteArray()[1] == '.'))
 877             {
 878                 UnixFileAttributes attrs = null;
 879                 try {
 880                     attrs = UnixFileAttributes.get(result, false);
 881                 } catch (UnixException x) {
 882                     x.rethrowAsIOException(result);
 883                 }
 884                 if (!attrs.isSymbolicLink()) {
 885                     result = result.getParent();
 886                     if (result == null) {
 887                         result = fs.rootDirectory();
 888                     }
 889                     continue;
 890                 }
 891             }
 892             result = result.resolve(element);
 893         }
 894 
 895         // check file exists (without following links)
 896         try {
 897             UnixFileAttributes.get(result, false);
 898         } catch (UnixException x) {
 899             x.rethrowAsIOException(result);
 900         }
 901         return result;
 902     }
 903 
 904     @Override
 905     public URI toUri() {
 906         return UnixUriUtils.toUri(this);
 907     }
 908 
 909     @Override
 910     public WatchKey register(WatchService watcher,
 911                              WatchEvent.Kind<?>[] events,
 912                              WatchEvent.Modifier... modifiers)
 913         throws IOException
 914     {
 915         if (watcher == null)
 916             throw new NullPointerException();
 917         if (!(watcher instanceof AbstractWatchService))
 918             throw new ProviderMismatchException();
 919         checkRead();
 920         return ((AbstractWatchService)watcher).register(this, events, modifiers);
 921     }
 922 }