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