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
26package sun.nio.fs;
27
28import java.nio.*;
29import java.nio.file.*;
30import java.nio.charset.*;
31import java.io.*;
32import java.net.URI;
33import java.util.*;
34import java.lang.ref.SoftReference;
35
36import static sun.nio.fs.UnixNativeDispatcher.*;
37import static sun.nio.fs.UnixConstants.*;
38
39/**
40 * Solaris/Linux implementation of java.nio.file.Path
41 */
42
43class 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}
923