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