• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
UnixPath(UnixFileSystem fs, byte[] path)64     UnixPath(UnixFileSystem fs, byte[] path) {
65         this.fs = fs;
66         this.path = path;
67     }
68 
UnixPath(UnixFileSystem fs, String input)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
normalizeAndCheck(String input)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 
checkNotNul(String input, char c)91     private static void checkNotNul(String input, char c) {
92         if (c == '\u0000')
93             throw new InvalidPathException(input, "Nul character not allowed");
94     }
95 
normalize(String input, int len, int off)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
encode(UnixFileSystem fs, String input)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
asByteArray()160     byte[] asByteArray() {
161         return path;
162     }
163 
164     // use this path when making system/library calls
getByteArrayForSysCalls()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
getPathForExceptionMessage()182     String getPathForExceptionMessage() {
183         return toString();
184     }
185 
186     // use this path for permission checks
getPathForPermissionCheck()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
toUnixPath(Path obj)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
initOffsets()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
isEmpty()248     private boolean isEmpty() {
249         return path.length == 0;
250     }
251 
252     // returns an empty path
emptyPath()253     private UnixPath emptyPath() {
254         return new UnixPath(getFileSystem(), new byte[0]);
255     }
256 
257     @Override
getFileSystem()258     public UnixFileSystem getFileSystem() {
259         return fs;
260     }
261 
262     @Override
getRoot()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
getFileName()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
getParent()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
getNameCount()312     public int getNameCount() {
313         initOffsets();
314         return offsets.length;
315     }
316 
317     @Override
getName(int index)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
subpath(int beginIndex, int endIndex)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
isAbsolute()369     public boolean isAbsolute() {
370         return (path.length > 0 && path[0] == '/');
371     }
372 
373     // Resolve child against given base
resolve(byte[] base, byte[] child)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
resolve(Path obj)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 
resolve(byte[] other)404     UnixPath resolve(byte[] other) {
405         return resolve(new UnixPath(getFileSystem(), other));
406     }
407 
408     @Override
relativize(Path obj)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
normalize()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
startsWith(Path other)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
endsWith(Path other)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
compareTo(Path other)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
equals(Object ob)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
hashCode()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
toString()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
openForAttributeAccess(boolean followLinks)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 
checkRead()792     void checkRead() {
793         SecurityManager sm = System.getSecurityManager();
794         if (sm != null)
795             sm.checkRead(getPathForPermissionCheck());
796     }
797 
checkWrite()798     void checkWrite() {
799         SecurityManager sm = System.getSecurityManager();
800         if (sm != null)
801             sm.checkWrite(getPathForPermissionCheck());
802     }
803 
checkDelete()804     void checkDelete() {
805         SecurityManager sm = System.getSecurityManager();
806         if (sm != null)
807             sm.checkDelete(getPathForPermissionCheck());
808     }
809 
810     @Override
toAbsolutePath()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
toRealPath(LinkOption... options)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
toUri()882     public URI toUri() {
883         return UnixUriUtils.toUri(this);
884     }
885 
886     @Override
register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)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