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