1/* 2 * Copyright (c) 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 */ 25package jdk.internal.jrtfs; 26 27import java.io.File; 28import java.io.IOException; 29import java.io.InputStream; 30import java.io.OutputStream; 31import java.net.URI; 32import java.net.URISyntaxException; 33import java.nio.channels.FileChannel; 34import java.nio.channels.SeekableByteChannel; 35import java.nio.file.*; 36import java.nio.file.DirectoryStream.Filter;; 37import java.nio.file.attribute.BasicFileAttributes; 38import java.nio.file.attribute.BasicFileAttributeView; 39import java.nio.file.attribute.FileAttribute; 40import java.nio.file.attribute.FileTime; 41import java.util.Iterator; 42import java.util.Map; 43import java.util.NoSuchElementException; 44import java.util.Objects; 45import java.util.Set; 46import static java.nio.file.StandardOpenOption.*; 47import 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 */ 58final 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()); 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 if (path.length() == 0) { 482 return path; 483 } 484 if (path.indexOf('.') == -1) { 485 return path; 486 } 487 int length = path.length(); 488 char[] to = new char[length]; 489 int nc = getNameCount(); 490 int[] lastM = new int[nc]; 491 int lastMOff = -1; 492 int m = 0; 493 for (int i = 0; i < nc; i++) { 494 int n = offsets[i]; 495 int len = (i == offsets.length - 1) ? length - n 496 : offsets[i + 1] - n - 1; 497 if (len == 1 && path.charAt(n) == '.') { 498 if (m == 0 && path.charAt(0) == '/') // absolute path 499 to[m++] = '/'; 500 continue; 501 } 502 if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') { 503 if (lastMOff >= 0) { 504 m = lastM[lastMOff--]; // retreat 505 continue; 506 } 507 if (path.charAt(0) == '/') { // "/../xyz" skip 508 if (m == 0) 509 to[m++] = '/'; 510 } else { // "../xyz" -> "../xyz" 511 if (m != 0 && to[m-1] != '/') 512 to[m++] = '/'; 513 while (len-- > 0) 514 to[m++] = path.charAt(n++); 515 } 516 continue; 517 } 518 if (m == 0 && path.charAt(0) == '/' || // absolute path 519 m != 0 && to[m-1] != '/') { // not the first name 520 to[m++] = '/'; 521 } 522 lastM[++lastMOff] = m; 523 while (len-- > 0) 524 to[m++] = path.charAt(n++); 525 } 526 if (m > 1 && to[m - 1] == '/') 527 m--; 528 return (m == to.length) ? new String(to) : new String(to, 0, m); 529 } 530 531 @Override 532 public final String toString() { 533 return path; 534 } 535 536 @Override 537 public final int hashCode() { 538 return path.hashCode(); 539 } 540 541 @Override 542 public final boolean equals(Object obj) { 543 return obj instanceof JrtPath && 544 this.path.equals(((JrtPath) obj).path); 545 } 546 547 @Override 548 public final int compareTo(Path other) { 549 final JrtPath o = checkPath(other); 550 return path.compareTo(o.path); 551 } 552 553 @Override 554 public final WatchKey register( 555 WatchService watcher, 556 WatchEvent.Kind<?>[] events, 557 WatchEvent.Modifier... modifiers) { 558 Objects.requireNonNull(watcher, "watcher"); 559 Objects.requireNonNull(events, "events"); 560 Objects.requireNonNull(modifiers, "modifiers"); 561 throw new UnsupportedOperationException(); 562 } 563 564 @Override 565 public final WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) { 566 return register(watcher, events, new WatchEvent.Modifier[0]); 567 } 568 569 @Override 570 public final File toFile() { 571 throw new UnsupportedOperationException(); 572 } 573 574 @Override 575 public final Iterator<Path> iterator() { 576 return new Iterator<Path>() { 577 private int i = 0; 578 579 @Override 580 public boolean hasNext() { 581 return (i < getNameCount()); 582 } 583 584 @Override 585 public Path next() { 586 if (i < getNameCount()) { 587 Path result = getName(i); 588 i++; 589 return result; 590 } else { 591 throw new NoSuchElementException(); 592 } 593 } 594 595 @Override 596 public void remove() { 597 throw new ReadOnlyFileSystemException(); 598 } 599 }; 600 } 601 602 // Helpers for JrtFileSystemProvider and JrtFileSystem 603 604 final JrtPath readSymbolicLink() throws IOException { 605 if (!jrtfs.isLink(this)) { 606 throw new IOException("not a symbolic link"); 607 } 608 return jrtfs.resolveLink(this); 609 } 610 611 final boolean isHidden() { 612 return false; 613 } 614 615 final void createDirectory(FileAttribute<?>... attrs) 616 throws IOException { 617 jrtfs.createDirectory(this, attrs); 618 } 619 620 final InputStream newInputStream(OpenOption... options) throws IOException { 621 if (options.length > 0) { 622 for (OpenOption opt : options) { 623 if (opt != READ) { 624 throw new UnsupportedOperationException("'" + opt + "' not allowed"); 625 } 626 } 627 } 628 return jrtfs.newInputStream(this); 629 } 630 631 final DirectoryStream<Path> newDirectoryStream(Filter<? super Path> filter) 632 throws IOException { 633 return new JrtDirectoryStream(this, filter); 634 } 635 636 final void delete() throws IOException { 637 jrtfs.deleteFile(this, true); 638 } 639 640 final void deleteIfExists() throws IOException { 641 jrtfs.deleteFile(this, false); 642 } 643 644 final JrtFileAttributes getAttributes(LinkOption... options) throws IOException { 645 JrtFileAttributes zfas = jrtfs.getFileAttributes(this, options); 646 if (zfas == null) { 647 throw new NoSuchFileException(toString()); 648 } 649 return zfas; 650 } 651 652 final void setAttribute(String attribute, Object value, LinkOption... options) 653 throws IOException { 654 JrtFileAttributeView.setAttribute(this, attribute, value); 655 } 656 657 final Map<String, Object> readAttributes(String attributes, LinkOption... options) 658 throws IOException { 659 return JrtFileAttributeView.readAttributes(this, attributes, options); 660 } 661 662 final void setTimes(FileTime mtime, FileTime atime, FileTime ctime) 663 throws IOException { 664 jrtfs.setTimes(this, mtime, atime, ctime); 665 } 666 667 final FileStore getFileStore() throws IOException { 668 // each JrtFileSystem only has one root (as requested for now) 669 if (exists()) { 670 return jrtfs.getFileStore(this); 671 } 672 throw new NoSuchFileException(path); 673 } 674 675 final boolean isSameFile(Path other) throws IOException { 676 if (this == other || this.equals(other)) { 677 return true; 678 } 679 if (other == null || this.getFileSystem() != other.getFileSystem()) { 680 return false; 681 } 682 this.checkAccess(); 683 JrtPath o = (JrtPath) other; 684 o.checkAccess(); 685 return this.getResolvedPath().equals(o.getResolvedPath()) || 686 jrtfs.isSameFile(this, o); 687 } 688 689 final SeekableByteChannel newByteChannel(Set<? extends OpenOption> options, 690 FileAttribute<?>... attrs) 691 throws IOException 692 { 693 return jrtfs.newByteChannel(this, options, attrs); 694 } 695 696 final FileChannel newFileChannel(Set<? extends OpenOption> options, 697 FileAttribute<?>... attrs) 698 throws IOException { 699 return jrtfs.newFileChannel(this, options, attrs); 700 } 701 702 final void checkAccess(AccessMode... modes) throws IOException { 703 if (modes.length == 0) { // check if the path exists 704 jrtfs.checkNode(this); // no need to follow link. the "link" node 705 // is built from real node under "/module" 706 } else { 707 boolean w = false; 708 for (AccessMode mode : modes) { 709 switch (mode) { 710 case READ: 711 break; 712 case WRITE: 713 w = true; 714 break; 715 case EXECUTE: 716 throw new AccessDeniedException(toString()); 717 default: 718 throw new UnsupportedOperationException(); 719 } 720 } 721 jrtfs.checkNode(this); 722 if (w && jrtfs.isReadOnly()) { 723 throw new AccessDeniedException(toString()); 724 } 725 } 726 } 727 728 final boolean exists() { 729 try { 730 return jrtfs.exists(this); 731 } catch (IOException x) {} 732 return false; 733 } 734 735 final OutputStream newOutputStream(OpenOption... options) throws IOException { 736 if (options.length == 0) { 737 return jrtfs.newOutputStream(this, CREATE_NEW, WRITE); 738 } 739 return jrtfs.newOutputStream(this, options); 740 } 741 742 final void move(JrtPath target, CopyOption... options) throws IOException { 743 if (this.jrtfs == target.jrtfs) { 744 jrtfs.copyFile(true, this, target, options); 745 } else { 746 copyToTarget(target, options); 747 delete(); 748 } 749 } 750 751 final void copy(JrtPath target, CopyOption... options) throws IOException { 752 if (this.jrtfs == target.jrtfs) { 753 jrtfs.copyFile(false, this, target, options); 754 } else { 755 copyToTarget(target, options); 756 } 757 } 758 759 private void copyToTarget(JrtPath target, CopyOption... options) 760 throws IOException { 761 boolean replaceExisting = false; 762 boolean copyAttrs = false; 763 for (CopyOption opt : options) { 764 if (opt == REPLACE_EXISTING) { 765 replaceExisting = true; 766 } else if (opt == COPY_ATTRIBUTES) { 767 copyAttrs = true; 768 } 769 } 770 // attributes of source file 771 BasicFileAttributes jrtfas = getAttributes(); 772 // check if target exists 773 boolean exists; 774 if (replaceExisting) { 775 try { 776 target.deleteIfExists(); 777 exists = false; 778 } catch (DirectoryNotEmptyException x) { 779 exists = true; 780 } 781 } else { 782 exists = target.exists(); 783 } 784 if (exists) { 785 throw new FileAlreadyExistsException(target.toString()); 786 } 787 if (jrtfas.isDirectory()) { 788 // create directory or file 789 target.createDirectory(); 790 } else { 791 try (InputStream is = jrtfs.newInputStream(this); 792 OutputStream os = target.newOutputStream()) { 793 byte[] buf = new byte[8192]; 794 int n; 795 while ((n = is.read(buf)) != -1) { 796 os.write(buf, 0, n); 797 } 798 } 799 } 800 if (copyAttrs) { 801 BasicFileAttributeView view = 802 Files.getFileAttributeView(target, BasicFileAttributeView.class); 803 try { 804 view.setTimes(jrtfas.lastModifiedTime(), 805 jrtfas.lastAccessTime(), 806 jrtfas.creationTime()); 807 } catch (IOException x) { 808 try { 809 target.delete(); // rollback? 810 } catch (IOException ignore) {} 811 throw x; 812 } 813 } 814 } 815} 816