1 /* 2 * Copyright (c) 2015, 2017, 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 package jdk.internal.jrtfs; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.net.URI; 32 import java.net.URISyntaxException; 33 import java.nio.channels.FileChannel; 34 import java.nio.channels.SeekableByteChannel; 35 import java.nio.file.*; 36 import java.nio.file.DirectoryStream.Filter; 37 import java.nio.file.attribute.BasicFileAttributes; 38 import java.nio.file.attribute.BasicFileAttributeView; 39 import java.nio.file.attribute.FileAttribute; 40 import java.nio.file.attribute.FileTime; 41 import java.util.Iterator; 42 import java.util.Map; 43 import java.util.NoSuchElementException; 44 import java.util.Objects; 45 import java.util.Set; 46 import static java.nio.file.StandardOpenOption.*; 47 import static java.nio.file.StandardCopyOption.*; 48 49 /** 50 * Base class for Path implementation of jrt file systems. 51 * 52 * @implNote This class needs to maintain JDK 8 source compatibility. 53 * 54 * It is used internally in the JDK to implement jimage/jrtfs access, 55 * but also compiled and delivered as part of the jrtfs.jar to support access 56 * to the jimage file provided by the shipped JDK by tools running on JDK 8. 57 */ 58 final class JrtPath implements Path { 59 60 final JrtFileSystem jrtfs; 61 private final String path; 62 private volatile int[] offsets; 63 64 JrtPath(JrtFileSystem jrtfs, String path) { 65 this.jrtfs = jrtfs; 66 this.path = normalize(path); 67 this.resolved = null; 68 } 69 70 JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) { 71 this.jrtfs = jrtfs; 72 this.path = normalized ? path : normalize(path); 73 this.resolved = null; 74 } 75 76 final String getName() { 77 return path; 78 } 79 80 @Override 81 public final JrtPath getRoot() { 82 if (this.isAbsolute()) { 83 return jrtfs.getRootPath(); 84 } else { 85 return null; 86 } 87 } 88 89 @Override 90 public final JrtPath getFileName() { 91 if (path.length() == 0) 92 return this; 93 if (path.length() == 1 && path.charAt(0) == '/') 94 return null; 95 int off = path.lastIndexOf('/'); 96 if (off == -1) 97 return this; 98 return new JrtPath(jrtfs, path.substring(off + 1), true); 99 } 100 101 @Override 102 public final JrtPath getParent() { 103 initOffsets(); 104 int count = offsets.length; 105 if (count == 0) { // no elements so no parent 106 return null; 107 } 108 int off = offsets[count - 1] - 1; 109 if (off <= 0) { // parent is root only (may be null) 110 return getRoot(); 111 } 112 return new JrtPath(jrtfs, path.substring(0, off)); 113 } 114 115 @Override 116 public final int getNameCount() { 117 initOffsets(); 118 return offsets.length; 119 } 120 121 @Override 122 public final JrtPath getName(int index) { 123 initOffsets(); 124 if (index < 0 || index >= offsets.length) { 125 throw new IllegalArgumentException("index: " + 126 index + ", offsets length: " + offsets.length); 127 } 128 int begin = offsets[index]; 129 int end; 130 if (index == (offsets.length - 1)) { 131 end = path.length(); 132 } else { 133 end = offsets[index + 1]; 134 } 135 return new JrtPath(jrtfs, path.substring(begin, end)); 136 } 137 138 @Override 139 public final JrtPath subpath(int beginIndex, int endIndex) { 140 initOffsets(); 141 if (beginIndex < 0 || endIndex > offsets.length || 142 beginIndex >= endIndex) { 143 throw new IllegalArgumentException( 144 "beginIndex: " + beginIndex + ", endIndex: " + endIndex + 145 ", offsets length: " + offsets.length); 146 } 147 // starting/ending offsets 148 int begin = offsets[beginIndex]; 149 int end; 150 if (endIndex == offsets.length) { 151 end = path.length(); 152 } else { 153 end = offsets[endIndex]; 154 } 155 return new JrtPath(jrtfs, path.substring(begin, end)); 156 } 157 158 @Override 159 public final JrtPath toRealPath(LinkOption... options) throws IOException { 160 return jrtfs.toRealPath(this, options); 161 } 162 163 @Override 164 public final JrtPath toAbsolutePath() { 165 if (isAbsolute()) 166 return this; 167 return new JrtPath(jrtfs, "/" + path, true); 168 } 169 170 @Override 171 public final URI toUri() { 172 try { 173 return new URI("jrt", toAbsolutePath().path, null); 174 } catch (URISyntaxException ex) { 175 throw new AssertionError(ex); 176 } 177 } 178 179 private boolean equalsNameAt(JrtPath other, int index) { 180 int mbegin = offsets[index]; 181 int mlen; 182 if (index == (offsets.length - 1)) { 183 mlen = path.length() - mbegin; 184 } else { 185 mlen = offsets[index + 1] - mbegin - 1; 186 } 187 int obegin = other.offsets[index]; 188 int olen; 189 if (index == (other.offsets.length - 1)) { 190 olen = other.path.length() - obegin; 191 } else { 192 olen = other.offsets[index + 1] - obegin - 1; 193 } 194 if (mlen != olen) { 195 return false; 196 } 197 int n = 0; 198 while (n < mlen) { 199 if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) { 200 return false; 201 } 202 n++; 203 } 204 return true; 205 } 206 207 @Override 208 public final JrtPath relativize(Path other) { 209 final JrtPath o = checkPath(other); 210 if (o.equals(this)) { 211 return new JrtPath(jrtfs, "", true); 212 } 213 if (path.length() == 0) { 214 return o; 215 } 216 if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) { 217 throw new IllegalArgumentException( 218 "Incorrect filesystem or path: " + other); 219 } 220 final String tp = this.path; 221 final String op = o.path; 222 if (op.startsWith(tp)) { // fast path 223 int off = tp.length(); 224 if (op.charAt(off - 1) == '/') 225 return new JrtPath(jrtfs, op.substring(off), true); 226 if (op.charAt(off) == '/') 227 return new JrtPath(jrtfs, op.substring(off + 1), true); 228 } 229 int mc = this.getNameCount(); 230 int oc = o.getNameCount(); 231 int n = Math.min(mc, oc); 232 int i = 0; 233 while (i < n) { 234 if (!equalsNameAt(o, i)) { 235 break; 236 } 237 i++; 238 } 239 int dotdots = mc - i; 240 int len = dotdots * 3 - 1; 241 if (i < oc) { 242 len += (o.path.length() - o.offsets[i] + 1); 243 } 244 StringBuilder sb = new StringBuilder(len); 245 while (dotdots > 0) { 246 sb.append(".."); 247 if (sb.length() < len) { // no tailing slash at the end 248 sb.append('/'); 249 } 250 dotdots--; 251 } 252 if (i < oc) { 253 sb.append(o.path, o.offsets[i], o.path.length()); 254 } 255 return new JrtPath(jrtfs, sb.toString(), true); 256 } 257 258 @Override 259 public JrtFileSystem getFileSystem() { 260 return jrtfs; 261 } 262 263 @Override 264 public final boolean isAbsolute() { 265 return path.length() > 0 && path.charAt(0) == '/'; 266 } 267 268 @Override 269 public final JrtPath resolve(Path other) { 270 final JrtPath o = checkPath(other); 271 if (this.path.length() == 0 || o.isAbsolute()) { 272 return o; 273 } 274 if (o.path.length() == 0) { 275 return this; 276 } 277 StringBuilder sb = new StringBuilder(path.length() + o.path.length() + 1); 278 sb.append(path); 279 if (path.charAt(path.length() - 1) != '/') 280 sb.append('/'); 281 sb.append(o.path); 282 return new JrtPath(jrtfs, sb.toString(), true); 283 } 284 285 @Override 286 public final Path resolveSibling(Path other) { 287 Objects.requireNonNull(other, "other"); 288 Path parent = getParent(); 289 return (parent == null) ? other : parent.resolve(other); 290 } 291 292 @Override 293 public final boolean startsWith(Path other) { 294 if (!(Objects.requireNonNull(other) instanceof JrtPath)) 295 return false; 296 final JrtPath o = (JrtPath)other; 297 final String tp = this.path; 298 final String op = o.path; 299 if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) { 300 return false; 301 } 302 int off = op.length(); 303 if (off == 0) { 304 return tp.length() == 0; 305 } 306 // check match is on name boundary 307 return tp.length() == off || tp.charAt(off) == '/' || 308 off == 0 || op.charAt(off - 1) == '/'; 309 } 310 311 @Override 312 public final boolean endsWith(Path other) { 313 if (!(Objects.requireNonNull(other) instanceof JrtPath)) 314 return false; 315 final JrtPath o = (JrtPath)other; 316 final JrtPath t = this; 317 int olast = o.path.length() - 1; 318 if (olast > 0 && o.path.charAt(olast) == '/') { 319 olast--; 320 } 321 int last = t.path.length() - 1; 322 if (last > 0 && t.path.charAt(last) == '/') { 323 last--; 324 } 325 if (olast == -1) { // o.path.length == 0 326 return last == -1; 327 } 328 if ((o.isAbsolute() && (!t.isAbsolute() || olast != last)) 329 || last < olast) { 330 return false; 331 } 332 for (; olast >= 0; olast--, last--) { 333 if (o.path.charAt(olast) != t.path.charAt(last)) { 334 return false; 335 } 336 } 337 return o.path.charAt(olast + 1) == '/' || 338 last == -1 || t.path.charAt(last) == '/'; 339 } 340 341 @Override 342 public final JrtPath resolve(String other) { 343 return resolve(getFileSystem().getPath(other)); 344 } 345 346 @Override 347 public final Path resolveSibling(String other) { 348 return resolveSibling(getFileSystem().getPath(other)); 349 } 350 351 @Override 352 public final boolean startsWith(String other) { 353 return startsWith(getFileSystem().getPath(other)); 354 } 355 356 @Override 357 public final boolean endsWith(String other) { 358 return endsWith(getFileSystem().getPath(other)); 359 } 360 361 @Override 362 public final JrtPath normalize() { 363 String res = getResolved(); 364 if (res == path) { // no change 365 return this; 366 } 367 return new JrtPath(jrtfs, res, true); 368 } 369 370 private JrtPath checkPath(Path path) { 371 Objects.requireNonNull(path); 372 if (!(path instanceof JrtPath)) 373 throw new ProviderMismatchException("path class: " + 374 path.getClass()); 375 return (JrtPath) path; 376 } 377 378 // create offset list if not already created 379 private void initOffsets() { 380 if (this.offsets == null) { 381 int len = path.length(); 382 // count names 383 int count = 0; 384 int off = 0; 385 while (off < len) { 386 char c = path.charAt(off++); 387 if (c != '/') { 388 count++; 389 off = path.indexOf('/', off); 390 if (off == -1) 391 break; 392 } 393 } 394 // populate offsets 395 int[] offsets = new int[count]; 396 count = 0; 397 off = 0; 398 while (off < len) { 399 char c = path.charAt(off); 400 if (c == '/') { 401 off++; 402 } else { 403 offsets[count++] = off++; 404 off = path.indexOf('/', off); 405 if (off == -1) 406 break; 407 } 408 } 409 this.offsets = offsets; 410 } 411 } 412 413 private volatile String resolved; 414 415 final String getResolvedPath() { 416 String r = resolved; 417 if (r == null) { 418 if (isAbsolute()) { 419 r = getResolved(); 420 } else { 421 r = toAbsolutePath().getResolvedPath(); 422 } 423 resolved = r; 424 } 425 return r; 426 } 427 428 // removes redundant slashs, replace "\" to separator "/" 429 // and check for invalid characters 430 private static String normalize(String path) { 431 int len = path.length(); 432 if (len == 0) { 433 return path; 434 } 435 char prevC = 0; 436 for (int i = 0; i < len; i++) { 437 char c = path.charAt(i); 438 if (c == '\\' || c == '\u0000') { 439 return normalize(path, i); 440 } 441 if (c == '/' && prevC == '/') { 442 return normalize(path, i - 1); 443 } 444 prevC = c; 445 } 446 if (prevC == '/' && len > 1) { 447 return path.substring(0, len - 1); 448 } 449 return path; 450 } 451 452 private static String normalize(String path, int off) { 453 int len = path.length(); 454 StringBuilder to = new StringBuilder(len); 455 to.append(path, 0, off); 456 char prevC = 0; 457 while (off < len) { 458 char c = path.charAt(off++); 459 if (c == '\\') { 460 c = '/'; 461 } 462 if (c == '/' && prevC == '/') { 463 continue; 464 } 465 if (c == '\u0000') { 466 throw new InvalidPathException(path, 467 "Path: NUL character not allowed"); 468 } 469 to.append(c); 470 prevC = c; 471 } 472 len = to.length(); 473 if (len > 1 && to.charAt(len - 1) == '/') { 474 to.deleteCharAt(len - 1); 475 } 476 return to.toString(); 477 } 478 479 // Remove DotSlash(./) and resolve DotDot (..) components 480 private String getResolved() { 481 int length = path.length(); 482 if (length == 0 || (path.indexOf("./") == -1 && path.charAt(length - 1) != '.')) { 483 return path; 484 } else { 485 return resolvePath(); 486 } 487 } 488 489 private String resolvePath() { 490 int length = path.length(); 491 char[] to = new char[length]; 492 int nc = getNameCount(); 493 int[] lastM = new int[nc]; 494 int lastMOff = -1; 495 int m = 0; 496 for (int i = 0; i < nc; i++) { 497 int n = offsets[i]; 498 int len = (i == offsets.length - 1) ? length - n 499 : offsets[i + 1] - n - 1; 500 if (len == 1 && path.charAt(n) == '.') { 501 if (m == 0 && path.charAt(0) == '/') // absolute path 502 to[m++] = '/'; 503 continue; 504 } 505 if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') { 506 if (lastMOff >= 0) { 507 m = lastM[lastMOff--]; // retreat 508 continue; 509 } 510 if (path.charAt(0) == '/') { // "/../xyz" skip 511 if (m == 0) 512 to[m++] = '/'; 513 } else { // "../xyz" -> "../xyz" 514 if (m != 0 && to[m-1] != '/') 515 to[m++] = '/'; 516 while (len-- > 0) 517 to[m++] = path.charAt(n++); 518 } 519 continue; 520 } 521 if (m == 0 && path.charAt(0) == '/' || // absolute path 522 m != 0 && to[m-1] != '/') { // not the first name 523 to[m++] = '/'; 524 } 525 lastM[++lastMOff] = m; 526 while (len-- > 0) 527 to[m++] = path.charAt(n++); 528 } 529 if (m > 1 && to[m - 1] == '/') 530 m--; 531 return (m == to.length) ? new String(to) : new String(to, 0, m); 532 } 533 534 @Override 535 public final String toString() { 536 return path; 537 } 538 539 @Override 540 public final int hashCode() { 541 return path.hashCode(); 542 } 543 544 @Override 545 public final boolean equals(Object obj) { 546 return obj instanceof JrtPath && 547 this.path.equals(((JrtPath) obj).path); 548 } 549 550 @Override 551 public final int compareTo(Path other) { 552 final JrtPath o = checkPath(other); 553 return path.compareTo(o.path); 554 } 555 556 @Override 557 public final WatchKey register( 558 WatchService watcher, 559 WatchEvent.Kind<?>[] events, 560 WatchEvent.Modifier... modifiers) { 561 Objects.requireNonNull(watcher, "watcher"); 562 Objects.requireNonNull(events, "events"); 563 Objects.requireNonNull(modifiers, "modifiers"); 564 throw new UnsupportedOperationException(); 565 } 566 567 @Override 568 public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) { 569 return register(watcher, events, new WatchEvent.Modifier[0]); 570 } 571 572 @Override 573 public final File toFile() { 574 throw new UnsupportedOperationException(); 575 } 576 577 @Override 578 public final Iterator<Path> iterator() { 579 return new Iterator<Path>() { 580 private int i = 0; 581 582 @Override 583 public boolean hasNext() { 584 return (i < getNameCount()); 585 } 586 587 @Override 588 public Path next() { 589 if (i < getNameCount()) { 590 Path result = getName(i); 591 i++; 592 return result; 593 } else { 594 throw new NoSuchElementException(); 595 } 596 } 597 598 @Override 599 public void remove() { 600 throw new ReadOnlyFileSystemException(); 601 } 602 }; 603 } 604 605 // Helpers for JrtFileSystemProvider and JrtFileSystem 606 607 final JrtPath readSymbolicLink() throws IOException { 608 if (!jrtfs.isLink(this)) { 609 throw new IOException("not a symbolic link"); 610 } 611 return jrtfs.resolveLink(this); 612 } 613 614 final boolean isHidden() { 615 return false; 616 } 617 618 final void createDirectory(FileAttribute<?>... attrs) 619 throws IOException { 620 jrtfs.createDirectory(this, attrs); 621 } 622 623 final InputStream newInputStream(OpenOption... options) throws IOException { 624 if (options.length > 0) { 625 for (OpenOption opt : options) { 626 if (opt != READ) { 627 throw new UnsupportedOperationException("'" + opt + "' not allowed"); 628 } 629 } 630 } 631 return jrtfs.newInputStream(this); 632 } 633 634 final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter) 635 throws IOException { 636 return new JrtDirectoryStream(this, filter); 637 } 638 639 final void delete() throws IOException { 640 jrtfs.deleteFile(this, true); 641 } 642 643 final void deleteIfExists() throws IOException { 644 jrtfs.deleteFile(this, false); 645 } 646 647 final JrtFileAttributes getAttributes(LinkOption... options) throws IOException { 648 JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options); 649 if (zfas == null) { 650 throw new NoSuchFileException(toString()); 651 } 652 return zfas; 653 } 654 655 final void setAttribute(String attribute, Object value, LinkOption... options) 656 throws IOException { 657 JrtFileAttributeView.setAttribute(this, attribute, value); 658 } 659 660 final Map<String, Object> readAttributes(String attributes, LinkOption... options) 661 throws IOException { 662 return JrtFileAttributeView.readAttributes(this, attributes, options); 663 } 664 665 final void setTimes(FileTime mtime, FileTime atime, FileTime ctime) 666 throws IOException { 667 jrtfs.setTimes(this, mtime, atime, ctime); 668 } 669 670 final FileStore getFileStore() throws IOException { 671 // each JrtFileSystem only has one root (as requested for now) 672 if (exists()) { 673 return jrtfs.getFileStore(this); 674 } 675 throw new NoSuchFileException(path); 676 } 677 678 final boolean isSameFile(Path other) throws IOException { 679 if (this == other || this.equals(other)) { 680 return true; 681 } 682 if (other == null || this.getFileSystem() != other.getFileSystem()) { 683 return false; 684 } 685 this.checkAccess(); 686 JrtPath o = (JrtPath) other; 687 o.checkAccess(); 688 return this.getResolvedPath().equals(o.getResolvedPath()) || 689 jrtfs.isSameFile(this, o); 690 } 691 692 final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options, 693 FileAttribute<?>... attrs) 694 throws IOException 695 { 696 return jrtfs.newByteChannel(this, options, attrs); 697 } 698 699 final FileChannel newFileChannel(Set<? extends OpenOption> options, 700 FileAttribute<?>... attrs) 701 throws IOException { 702 return jrtfs.newFileChannel(this, options, attrs); 703 } 704 705 final void checkAccess(AccessMode... modes) throws IOException { 706 if (modes.length == 0) { // check if the path exists 707 jrtfs.checkNode(this); // no need to follow link. the "link" node 708 // is built from real node under "/module" 709 } else { 710 boolean w = false; 711 for (AccessMode mode : modes) { 712 switch (mode) { 713 case READ: 714 break; 715 case WRITE: 716 w = true; 717 break; 718 case EXECUTE: 719 throw new AccessDeniedException(toString()); 720 default: 721 throw new UnsupportedOperationException(); 722 } 723 } 724 jrtfs.checkNode(this); 725 if (w && jrtfs.isReadOnly()) { 726 throw new AccessDeniedException(toString()); 727 } 728 } 729 } 730 731 final boolean exists() { 732 try { 733 return jrtfs.exists(this); 734 } catch (IOException x) {} 735 return false; 736 } 737 738 final OutputStream newOutputStream(OpenOption... options) throws IOException { 739 if (options.length == 0) { 740 return jrtfs.newOutputStream(this, CREATE_NEW, WRITE); 741 } 742 return jrtfs.newOutputStream(this, options); 743 } 744 745 final void move(JrtPath target, CopyOption... options) throws IOException { 746 if (this.jrtfs == target.jrtfs) { 747 jrtfs.copyFile(true, this, target, options); 748 } else { 749 copyToTarget(target, options); 750 delete(); 751 } 752 } 753 754 final void copy(JrtPath target, CopyOption... options) throws IOException { 755 if (this.jrtfs == target.jrtfs) { 756 jrtfs.copyFile(false, this, target, options); 757 } else { 758 copyToTarget(target, options); 759 } 760 } 761 762 private void copyToTarget(JrtPath target, CopyOption... options) 763 throws IOException { 764 boolean replaceExisting = false; 765 boolean copyAttrs = false; 766 for (CopyOption opt : options) { 767 if (opt == REPLACE_EXISTING) { 768 replaceExisting = true; 769 } else if (opt == COPY_ATTRIBUTES) { 770 copyAttrs = true; 771 } 772 } 773 // attributes of source file 774 BasicFileAttributes jrtfas = getAttributes(); 775 // check if target exists 776 boolean exists; 777 if (replaceExisting) { 778 try { 779 target.deleteIfExists(); 780 exists = false; 781 } catch (DirectoryNotEmptyException x) { 782 exists = true; 783 } 784 } else { 785 exists = target.exists(); 786 } 787 if (exists) { 788 throw new FileAlreadyExistsException(target.toString()); 789 } 790 if (jrtfas.isDirectory()) { 791 // create directory or file 792 target.createDirectory(); 793 } else { 794 try (InputStream is = jrtfs.newInputStream(this); 795 OutputStream os = target.newOutputStream()) { 796 byte[] buf = new byte[8192]; 797 int n; 798 while ((n = is.read(buf)) != -1) { 799 os.write(buf, 0, n); 800 } 801 } 802 } 803 if (copyAttrs) { 804 BasicFileAttributeView view = 805 Files.getFileAttributeView(target, BasicFileAttributeView.class); 806 try { 807 view.setTimes(jrtfas.lastModifiedTime(), 808 jrtfas.lastAccessTime(), 809 jrtfas.creationTime()); 810 } catch (IOException x) { 811 try { 812 target.delete(); // rollback? 813 } catch (IOException ignore) {} 814 throw x; 815 } 816 } 817 } 818 }