Home | History | Annotate | Download | only in fs
      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
     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(fs, 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(UnixFileSystem fs, String input) {
    120         SoftReference<CharsetEncoder> ref = encoder.get();
    121         CharsetEncoder ce = (ref != null) ? ref.get() : null;
    122         if (ce == null) {
    123             ce = Util.jnuEncoding().newEncoder()
    124                 .onMalformedInput(CodingErrorAction.REPORT)
    125                 .onUnmappableCharacter(CodingErrorAction.REPORT);
    126             encoder.set(new SoftReference<CharsetEncoder>(ce));
    127         }
    128 
    129         char[] ca = fs.normalizeNativePath(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 characters");
    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 getPathForExceptionMessage() {
    183         return toString();
    184     }
    185 
    186     // use this path for permission checks
    187     String getPathForPermissionCheck() {
    188         if (getFileSystem().needToResolveAgainstDefaultDirectory()) {
    189             return Util.toString(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 || isEmpty())
    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 occurrences of "." to ignore
    497         //   3. and look for any occurrences 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 occurrences 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 = fs.normalizeJavaPath(Util.toString(path));     // platform encoding
    762         }
    763         return stringValue;
    764     }
    765 
    766     // -- file operations --
    767 
    768     // package-private
    769     int openForAttributeAccess(boolean followLinks) throws IOException {
    770         int flags = O_RDONLY;
    771         if (!followLinks) {
    772             if (O_NOFOLLOW == 0)
    773                 throw new IOException("NOFOLLOW_LINKS is not supported on this platform");
    774             flags |= O_NOFOLLOW;
    775         }
    776         try {
    777             return open(this, flags, 0);
    778         } catch (UnixException x) {
    779             // HACK: EINVAL instead of ELOOP on Solaris 10 prior to u4 (see 6460380)
    780             if (getFileSystem().isSolaris() && x.errno() == EINVAL)
    781                 x.setError(ELOOP);
    782 
    783             if (x.errno() == ELOOP)
    784                 throw new FileSystemException(getPathForExceptionMessage(), null,
    785                     x.getMessage() + " or unable to access attributes of symbolic link");
    786 
    787             x.rethrowAsIOException(this);
    788             return -1; // keep compile happy
    789         }
    790     }
    791 
    792     void checkRead() {
    793         SecurityManager sm = System.getSecurityManager();
    794         if (sm != null)
    795             sm.checkRead(getPathForPermissionCheck());
    796     }
    797 
    798     void checkWrite() {
    799         SecurityManager sm = System.getSecurityManager();
    800         if (sm != null)
    801             sm.checkWrite(getPathForPermissionCheck());
    802     }
    803 
    804     void checkDelete() {
    805         SecurityManager sm = System.getSecurityManager();
    806         if (sm != null)
    807             sm.checkDelete(getPathForPermissionCheck());
    808     }
    809 
    810     @Override
    811     public UnixPath toAbsolutePath() {
    812         if (isAbsolute()) {
    813             return this;
    814         }
    815         // The path is relative so need to resolve against default directory,
    816         // taking care not to reveal the user.dir
    817         SecurityManager sm = System.getSecurityManager();
    818         if (sm != null) {
    819             sm.checkPropertyAccess("user.dir");
    820         }
    821         return new UnixPath(getFileSystem(),
    822             resolve(getFileSystem().defaultDirectory(), path));
    823     }
    824 
    825     @Override
    826     public Path toRealPath(LinkOption... options) throws IOException {
    827         checkRead();
    828 
    829         UnixPath absolute = toAbsolutePath();
    830 
    831         // if resolving links then use realpath
    832         if (Util.followLinks(options)) {
    833             try {
    834                 byte[] rp = realpath(absolute);
    835                 return new UnixPath(getFileSystem(), rp);
    836             } catch (UnixException x) {
    837                 x.rethrowAsIOException(this);
    838             }
    839         }
    840 
    841         // if not resolving links then eliminate "." and also ".."
    842         // where the previous element is not a link.
    843         UnixPath result = fs.rootDirectory();
    844         for (int i=0; i<absolute.getNameCount(); i++) {
    845             UnixPath element = absolute.getName(i);
    846 
    847             // eliminate "."
    848             if ((element.asByteArray().length == 1) && (element.asByteArray()[0] == '.'))
    849                 continue;
    850 
    851             // cannot eliminate ".." if previous element is a link
    852             if ((element.asByteArray().length == 2) && (element.asByteArray()[0] == '.') &&
    853                 (element.asByteArray()[1] == '.'))
    854             {
    855                 UnixFileAttributes attrs = null;
    856                 try {
    857                     attrs = UnixFileAttributes.get(result, false);
    858                 } catch (UnixException x) {
    859                     x.rethrowAsIOException(result);
    860                 }
    861                 if (!attrs.isSymbolicLink()) {
    862                     result = result.getParent();
    863                     if (result == null) {
    864                         result = fs.rootDirectory();
    865                     }
    866                     continue;
    867                 }
    868             }
    869             result = result.resolve(element);
    870         }
    871 
    872         // check file exists (without following links)
    873         try {
    874             UnixFileAttributes.get(result, false);
    875         } catch (UnixException x) {
    876             x.rethrowAsIOException(result);
    877         }
    878         return result;
    879     }
    880 
    881     @Override
    882     public URI toUri() {
    883         return UnixUriUtils.toUri(this);
    884     }
    885 
    886     @Override
    887     public WatchKey register(WatchService watcher,
    888                              WatchEvent.Kind<?>[] events,
    889                              WatchEvent.Modifier... modifiers)
    890         throws IOException
    891     {
    892         if (watcher == null)
    893             throw new NullPointerException();
    894         if (!(watcher instanceof AbstractWatchService))
    895             throw new ProviderMismatchException();
    896         checkRead();
    897         return ((AbstractWatchService)watcher).register(this, events, modifiers);
    898     }
    899 }
    900