1 /* 2 * Copyright (c) 2009, 2011, 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 org.openjdk.jigsaw; 27 28 import java.lang.module.*; 29 import java.io.*; 30 import java.net.URI; 31 import java.nio.file.*; 32 import java.security.*; 33 import java.security.cert.*; 34 import java.util.*; 35 import java.util.jar.*; 36 import java.util.zip.*; 37 38 import static java.nio.file.StandardCopyOption.*; 39 40 /** 41 * A simple module library which stores data directly in the filesystem 42 * 43 * @see Library 44 */ 45 46 // ## TODO: Move remaining parent-searching logic upward into Library class 47 48 // On-disk library layout 49 // 50 // $LIB/%jigsaw-library 51 // com.foo.bar/1.2.3/info (= module-info.class) 52 // index (list of defined classes) 53 // config (resolved configuration, if a root) 54 // classes/com/foo/bar/... 55 // resources/com/foo/bar/... 56 // lib/libbar.so 57 // bin/bar 58 // signer (signer's certchain & timestamp) 59 // 60 // ## Issue: Concurrent access to the module library 61 // ## e.g. a module is being removed while a running application 62 // ## is depending on it 63 64 public final class SimpleLibrary 65 extends Library 66 { 67 68 private static abstract class MetaData { 69 70 protected final int maxMajorVersion; 71 protected final int maxMinorVersion; 72 protected int majorVersion; 73 protected int minorVersion; 74 private final FileConstants.Type type; 75 private final File file; 76 77 protected MetaData(int maxMajor, int maxMinor, 78 FileConstants.Type t, File f) 79 { 80 maxMajorVersion = majorVersion = maxMajor; 81 maxMinorVersion = minorVersion = maxMinor; 82 type = t; 83 file = f; 84 } 85 86 protected abstract void storeRest(DataOutputStream out) 87 throws IOException; 88 89 void store() throws IOException { 90 try (OutputStream fos = new FileOutputStream(file); 91 BufferedOutputStream bos = new BufferedOutputStream(fos); 92 DataOutputStream out = new DataOutputStream(bos)) { 93 out.writeInt(FileConstants.MAGIC); 94 out.writeShort(type.value()); 95 out.writeShort(majorVersion); 96 out.writeShort(minorVersion); 97 storeRest(out); 98 } 99 } 100 101 protected abstract void loadRest(DataInputStream in) 102 throws IOException; 103 104 protected void load() throws IOException { 105 try (InputStream fis = new FileInputStream(file); 106 BufferedInputStream bis = new BufferedInputStream(fis); 107 DataInputStream in = new DataInputStream(fis)) { 108 if (in.readInt() != FileConstants.MAGIC) 109 throw new IOException(file + ": Invalid magic number"); 110 if (in.readShort() != type.value()) 111 throw new IOException(file + ": Invalid file type"); 112 int maj = in.readShort(); 113 int min = in.readShort(); 114 if ( maj > maxMajorVersion 115 || (maj == maxMajorVersion && min > maxMinorVersion)) { 116 throw new IOException(file 117 + ": Futuristic version number"); 118 } 119 majorVersion = maj; 120 minorVersion = min; 121 loadRest(in); 122 } catch (EOFException x) { 123 throw new IOException(file + ": Invalid library metadata", x); 124 } 125 } 126 } 127 128 /** 129 * Defines the storage options that SimpleLibrary supports. 130 */ 131 public static enum StorageOption { 132 DEFLATED, 133 } 134 135 private static final class Header 136 extends MetaData 137 { 138 private static final String FILE 139 = FileConstants.META_PREFIX + "jigsaw-library"; 140 141 private static final int MAJOR_VERSION = 0; 142 private static final int MINOR_VERSION = 1; 143 144 private static final int DEFLATED = 1 << 0; 145 146 private File parent; 147 // location of native libs for this library (may be outside the library) 148 // null:default, to use a per-module 'lib' directory 149 private File natlibs; 150 // location of native cmds for this library (may be outside the library) 151 // null:default, to use a per-module 'bin' directory 152 private File natcmds; 153 // location of config files for this library (may be outside the library) 154 // null:default, to use a per-module 'etc' directory 155 private File configs; 156 private Set<StorageOption> opts; 157 158 public File parent() { return parent; } 159 public File natlibs() { return natlibs; } 160 public File natcmds() { return natcmds; } 161 public File configs() { return configs; } 162 public boolean isDeflated() { 163 return opts.contains(StorageOption.DEFLATED); 164 } 165 166 private Header(File root) { 167 super(MAJOR_VERSION, MINOR_VERSION, 168 FileConstants.Type.LIBRARY_HEADER, 169 new File(root, FILE)); 170 } 171 172 private Header(File root, File parent, File natlibs, File natcmds, 173 File configs, Set<StorageOption> opts) { 174 this(root); 175 this.parent = parent; 176 this.natlibs = natlibs; 177 this.natcmds = natcmds; 178 this.configs = configs; 179 this.opts = new HashSet<>(opts); 180 } 181 182 private void storePath(File p, DataOutputStream out) throws IOException { 183 if (p != null) { 184 out.writeByte(1); 185 out.writeUTF(Files.convertSeparator(p.toString())); 186 } else { 187 out.write(0); 188 } 189 } 190 191 protected void storeRest(DataOutputStream out) throws IOException { 192 int flags = 0; 193 if (isDeflated()) 194 flags |= DEFLATED; 195 out.writeShort(flags); 196 197 storePath(parent, out); 198 storePath(natlibs, out); 199 storePath(natcmds, out); 200 storePath(configs, out); 201 } 202 203 private File loadPath(DataInputStream in) throws IOException { 204 if (in.readByte() != 0) 205 return new File(Files.platformSeparator(in.readUTF())); 206 return null; 207 } 208 209 protected void loadRest(DataInputStream in) throws IOException { 210 opts = new HashSet<StorageOption>(); 211 int flags = in.readShort(); 212 if ((flags & DEFLATED) == DEFLATED) 213 opts.add(StorageOption.DEFLATED); 214 parent = loadPath(in); 215 natlibs = loadPath(in); 216 natcmds = loadPath(in); 217 configs = loadPath(in); 218 } 219 220 private static Header load(File f) throws IOException { 221 Header h = new Header(f); 222 h.load(); 223 return h; 224 } 225 } 226 227 private final File root; 228 private final File canonicalRoot; 229 private final File parentPath; 230 private final File natlibs; 231 private final File natcmds; 232 private final File configs; 233 private final SimpleLibrary parent; 234 private final Header hd; 235 236 public String name() { return root.toString(); } 237 public File root() { return canonicalRoot; } 238 public int majorVersion() { return hd.majorVersion; } 239 public int minorVersion() { return hd.minorVersion; } 240 public SimpleLibrary parent() { return parent; } 241 public File natlibs() { return natlibs; } 242 public File natcmds() { return natcmds; } 243 public File configs() { return configs; } 244 public boolean isDeflated() { return hd.isDeflated(); } 245 246 private URI location = null; 247 public URI location() { 248 if (location == null) 249 location = root().toURI(); 250 return location; 251 } 252 253 @Override 254 public String toString() { 255 return (this.getClass().getName() 256 + "[" + canonicalRoot 257 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]"); 258 } 259 260 261 private static File resolveAndEnsurePath(File path) throws IOException { 262 if (path == null) { return null; } 263 264 File p = path.getCanonicalFile(); 265 if (!p.exists()) { 266 Files.mkdirs(p, p.toString()); 267 } else { 268 Files.ensureIsDirectory(p); 269 Files.ensureWriteable(p); 270 } 271 return p; 272 } 273 274 private File relativize(File path) throws IOException { 275 if (path == null) { return null; } 276 // Return the path relative to the canonical root 277 return (canonicalRoot.toPath().relativize(path.toPath().toRealPath())).toFile(); 278 } 279 280 // Opens an existing library 281 private SimpleLibrary(File path) throws IOException { 282 root = path; 283 canonicalRoot = root.getCanonicalFile(); 284 Files.ensureIsDirectory(root); 285 hd = Header.load(root); 286 287 parentPath = hd.parent(); 288 parent = parentPath != null ? open(parentPath) : null; 289 290 natlibs = hd.natlibs() == null ? null : 291 new File(canonicalRoot, hd.natlibs().toString()).getCanonicalFile(); 292 natcmds = hd.natcmds() == null ? null : 293 new File(canonicalRoot, hd.natcmds().toString()).getCanonicalFile(); 294 configs = hd.configs() == null ? null : 295 new File(canonicalRoot, hd.configs().toString()).getCanonicalFile(); 296 } 297 298 // Creates a new library 299 private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds, 300 File configs, Set<StorageOption> opts) 301 throws IOException 302 { 303 root = path; 304 canonicalRoot = root.getCanonicalFile(); 305 if (root.exists()) { 306 Files.ensureIsDirectory(root); 307 if (root.list().length != 0) 308 throw new IOException(root + ": Already Exists"); 309 Files.ensureWriteable(root); 310 } else 311 Files.mkdirs(root, root.toString()); 312 313 this.parent = parentPath != null ? open(parentPath) : null; 314 this.parentPath = parentPath != null ? this.parent.root() : null; 315 316 this.natlibs = resolveAndEnsurePath(natlibs); 317 this.natcmds = resolveAndEnsurePath(natcmds); 318 this.configs = resolveAndEnsurePath(configs); 319 320 hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs), 321 relativize(this.natcmds), relativize(this.configs), opts); 322 hd.store(); 323 } 324 325 public static SimpleLibrary create(File path, File parent, File natlibs, 326 File natcmds, File configs, 327 Set<StorageOption> opts) 328 throws IOException 329 { 330 return new SimpleLibrary(path, parent, natlibs, natcmds, configs, opts); 331 } 332 333 public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts) 334 throws IOException 335 { 336 return new SimpleLibrary(path, parent, null, null, null, opts); 337 } 338 339 public static SimpleLibrary create(File path, File parent) 340 throws IOException 341 { 342 return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet()); 343 } 344 345 public static SimpleLibrary create(File path, Set<StorageOption> opts) 346 throws IOException 347 { 348 // ## Should default parent to $JAVA_HOME/lib/modules 349 return SimpleLibrary.create(path, null, opts); 350 } 351 352 public static SimpleLibrary open(File path) 353 throws IOException 354 { 355 return new SimpleLibrary(path); 356 } 357 358 private static final JigsawModuleSystem jms 359 = JigsawModuleSystem.instance(); 360 361 private static final class Index 362 extends MetaData 363 { 364 365 private static String FILE = "index"; 366 367 private static int MAJOR_VERSION = 0; 368 private static int MINOR_VERSION = 1; 369 370 private Set<String> publicClasses; 371 public Set<String> publicClasses() { return publicClasses; } 372 373 private Set<String> otherClasses; 374 public Set<String> otherClasses() { return otherClasses; } 375 376 private Index(File root) { 377 super(MAJOR_VERSION, MINOR_VERSION, 378 FileConstants.Type.LIBRARY_MODULE_INDEX, 379 new File(root, FILE)); 380 // Unsorted on input, because we don't need it sorted 381 publicClasses = new HashSet<String>(); 382 otherClasses = new HashSet<String>(); 383 } 384 385 private void storeSet(Set<String> cnset, DataOutputStream out) 386 throws IOException 387 { 388 // Sorted on output, because we can afford it 389 List<String> cns = new ArrayList<String>(cnset); 390 Collections.sort(cns); 391 out.writeInt(cns.size()); 392 for (String cn : cns) 393 out.writeUTF(cn); 394 } 395 396 protected void storeRest(DataOutputStream out) 397 throws IOException 398 { 399 storeSet(publicClasses, out); 400 storeSet(otherClasses, out); 401 } 402 403 private void loadSet(DataInputStream in, Set<String> cnset) 404 throws IOException 405 { 406 int n = in.readInt(); 407 for (int i = 0; i < n; i++) 408 cnset.add(in.readUTF()); 409 } 410 411 protected void loadRest(DataInputStream in) 412 throws IOException 413 { 414 loadSet(in, publicClasses); 415 loadSet(in, otherClasses); 416 } 417 418 private static Index load(File f) 419 throws IOException 420 { 421 Index ix = new Index(f); 422 ix.load(); 423 return ix; 424 } 425 426 } 427 428 private static final class StoredConfiguration 429 extends MetaData 430 { 431 432 private static String FILE = "config"; 433 434 private static int MAJOR_VERSION = 0; 435 private static int MINOR_VERSION = 1; 436 437 private Configuration<Context> cf; 438 439 private static void delete(File root) { 440 new File(root, FILE).delete(); 441 } 442 443 private StoredConfiguration(File root, Configuration<Context> conf) 444 { 445 super(MAJOR_VERSION, MINOR_VERSION, 446 FileConstants.Type.LIBRARY_MODULE_CONFIG, 447 new File(root, FILE)); 448 cf = conf; 449 } 450 451 protected void storeRest(DataOutputStream out) 452 throws IOException 453 { 454 assert cf.roots().size() == 1; 455 out.writeUTF(cf.roots().iterator().next().toString()); 456 // Contexts 457 out.writeInt(cf.contexts().size()); 458 for (Context cx : cf.contexts()) { 459 out.writeUTF(cx.name()); 460 // Module ids, and their libraries 461 out.writeInt(cx.modules().size()); 462 for (ModuleId mid : cx.modules()) { 463 out.writeUTF(mid.toString()); 464 File lp = cx.findLibraryPathForModule(mid); 465 if (lp == null) 466 out.writeUTF(""); 467 else 468 out.writeUTF(lp.toString()); 469 } 470 // Local class map 471 out.writeInt(cx.localClasses().size()); 472 for (Map.Entry<String,ModuleId> me 473 : cx.moduleForLocalClassMap().entrySet()) { 474 out.writeUTF(me.getKey()); 475 out.writeUTF(me.getValue().toString()); 476 } 477 // Remote package map 478 out.writeInt(cx.contextForRemotePackageMap().size()); 479 for (Map.Entry<String,String> me 480 : cx.contextForRemotePackageMap().entrySet()) { 481 out.writeUTF(me.getKey()); 482 out.writeUTF(me.getValue()); 483 } 484 // Suppliers 485 out.writeInt(cx.remoteContexts().size()); 486 for (String cxn : cx.remoteContexts()) { 487 out.writeUTF(cxn); 488 } 489 } 490 } 491 492 protected void loadRest(DataInputStream in) 493 throws IOException 494 { 495 String root = in.readUTF(); 496 ModuleId rmid = jms.parseModuleId(root); 497 cf = new Configuration<Context>(rmid); 498 // Contexts 499 int nContexts = in.readInt(); 500 for (int i = 0; i < nContexts; i++) { 501 Context cx = new Context(); 502 String cxn = in.readUTF(); 503 // Module ids 504 int nModules = in.readInt(); 505 for (int j = 0; j < nModules; j++) { 506 ModuleId mid = jms.parseModuleId(in.readUTF()); 507 cx.add(mid); 508 cf.put(mid.name(), cx); 509 String lps = in.readUTF(); 510 if (lps.length() > 0) 511 cx.putLibraryPathForModule(mid, new File(lps)); 512 } 513 cx.freeze(); 514 assert cx.name().equals(cxn); 515 cf.add(cx); 516 // Local class map 517 int nClasses = in.readInt(); 518 for (int j = 0; j < nClasses; j++) 519 cx.putModuleForLocalClass(in.readUTF(), 520 jms.parseModuleId(in.readUTF())); 521 // Remote package map 522 int nPackages = in.readInt(); 523 for (int j = 0; j < nPackages; j++) 524 cx.putContextForRemotePackage(in.readUTF(), in.readUTF()); 525 526 // Suppliers 527 int nSuppliers = in.readInt(); 528 for (int j = 0; j < nSuppliers; j++) 529 cx.addSupplier(in.readUTF()); 530 } 531 532 } 533 534 private static StoredConfiguration load(File f) 535 throws IOException 536 { 537 StoredConfiguration sp = new StoredConfiguration(f, null); 538 sp.load(); 539 return sp; 540 } 541 542 } 543 544 private static final class Signers 545 extends MetaData { 546 547 private static String FILE = "signer"; 548 private static int MAJOR_VERSION = 0; 549 private static int MINOR_VERSION = 1; 550 551 private CertificateFactory cf = null; 552 private Set<CodeSigner> signers; 553 private Set<CodeSigner> signers() { return signers; } 554 555 private Signers(File root, Set<CodeSigner> signers) { 556 super(MAJOR_VERSION, MINOR_VERSION, 557 FileConstants.Type.LIBRARY_MODULE_SIGNER, 558 new File(root, FILE)); 559 this.signers = signers; 560 } 561 562 protected void storeRest(DataOutputStream out) 563 throws IOException 564 { 565 out.writeInt(signers.size()); 566 for (CodeSigner signer : signers) { 567 try { 568 CertPath signerCertPath = signer.getSignerCertPath(); 569 out.write(signerCertPath.getEncoded("PkiPath")); 570 Timestamp ts = signer.getTimestamp(); 571 out.writeByte((ts != null) ? 1 : 0); 572 if (ts != null) { 573 out.writeLong(ts.getTimestamp().getTime()); 574 out.write(ts.getSignerCertPath().getEncoded("PkiPath")); 575 } 576 } catch (CertificateEncodingException cee) { 577 throw new IOException(cee); 578 } 579 } 580 } 581 582 protected void loadRest(DataInputStream in) 583 throws IOException 584 { 585 int size = in.readInt(); 586 for (int i = 0; i < size; i++) { 587 try { 588 if (cf == null) 589 cf = CertificateFactory.getInstance("X.509"); 590 CertPath signerCertPath = cf.generateCertPath(in, "PkiPath"); 591 int b = in.readByte(); 592 if (b != 0) { 593 Date timestamp = new Date(in.readLong()); 594 CertPath tsaCertPath = cf.generateCertPath(in, "PkiPath"); 595 Timestamp ts = new Timestamp(timestamp, tsaCertPath); 596 signers.add(new CodeSigner(signerCertPath, ts)); 597 } else { 598 signers.add(new CodeSigner(signerCertPath, null)); 599 } 600 } catch (CertificateException ce) { 601 throw new IOException(ce); 602 } 603 } 604 } 605 606 private static Signers load(File f) 607 throws IOException 608 { 609 Signers signers = new Signers(f, new HashSet<CodeSigner>()); 610 signers.load(); 611 return signers; 612 } 613 } 614 615 private void gatherLocalModuleIds(File mnd, Set<ModuleId> mids) 616 throws IOException 617 { 618 if (!mnd.isDirectory()) 619 throw new IOException(mnd + ": Not a directory"); 620 if (!mnd.canRead()) 621 throw new IOException(mnd + ": Not readable"); 622 for (String v : mnd.list()) { 623 mids.add(jms.parseModuleId(mnd.getName(), v)); 624 } 625 } 626 627 private void gatherLocalModuleIds(Set<ModuleId> mids) 628 throws IOException 629 { 630 File[] mnds = root.listFiles(); 631 for (File mnd : mnds) { 632 if (mnd.getName().startsWith(FileConstants.META_PREFIX)) 633 continue; 634 gatherLocalModuleIds(mnd, mids); 635 } 636 } 637 638 protected void gatherLocalModuleIds(String moduleName, 639 Set<ModuleId> mids) 640 throws IOException 641 { 642 if (moduleName == null) { 643 gatherLocalModuleIds(mids); 644 return; 645 } 646 File mnd = new File(root, moduleName); 647 if (mnd.exists()) 648 gatherLocalModuleIds(mnd, mids); 649 } 650 651 private void checkModuleId(ModuleId mid) { 652 Version v = mid.version(); 653 if (v == null) 654 return; 655 if (!(v instanceof JigsawVersion)) 656 throw new IllegalArgumentException(mid + ": Not a Jigsaw module id"); 657 } 658 659 private File moduleDir(ModuleId mid) { 660 Version v = mid.version(); 661 String vs = (v != null) ? v.toString() : "default"; 662 return new File(new File(root, mid.name()), vs); 663 } 664 665 private void checkModuleDir(File md) 666 throws IOException 667 { 668 if (!md.isDirectory()) 669 throw new IOException(md + ": Not a directory"); 670 if (!md.canRead()) 671 throw new IOException(md + ": Not readable"); 672 } 673 674 private File findModuleDir(ModuleId mid) 675 throws IOException 676 { 677 checkModuleId(mid); 678 File md = moduleDir(mid); 679 if (!md.exists()) 680 return null; 681 checkModuleDir(md); 682 return md; 683 } 684 685 private void deleteModuleDir(ModuleId mid) 686 throws IOException 687 { 688 File md = findModuleDir(mid); 689 if (md == null) 690 return; 691 Files.deleteTree(md); 692 File mnd = md.getParentFile(); 693 if (mnd.list().length == 0) { 694 if (!mnd.delete()) 695 throw new IOException(mnd + ": Cannot delete"); 696 } 697 } 698 699 public byte[] readLocalModuleInfoBytes(ModuleId mid) 700 throws IOException 701 { 702 File md = findModuleDir(mid); 703 if (md == null) 704 return null; 705 return Files.load(new File(md, "info")); 706 } 707 708 public CodeSigner[] readLocalCodeSigners(ModuleId mid) 709 throws IOException 710 { 711 File md = findModuleDir(mid); 712 if (md == null) 713 return null; 714 // Only one signer is currently supported 715 File f = new File(md, "signer"); 716 // ## concurrency issues : what is the expected behavior if file is 717 // ## removed by another thread/process here? 718 if (!f.exists()) 719 return null; 720 return Signers.load(md).signers().toArray(new CodeSigner[0]); 721 } 722 723 // ## Close all zip files when we close this library 724 private Map<ModuleId, Object> contentForModule = new HashMap<>(); 725 private Object NONE = new Object(); 726 727 private Object findContent(ModuleId mid) 728 throws IOException 729 { 730 Object o = contentForModule.get(mid); 731 if (o != null) 732 return o; 733 if (o == NONE) 734 return null; 735 File md = findModuleDir(mid); 736 if (md == null) { 737 contentForModule.put(mid, NONE); 738 return null; 739 } 740 File cf = new File(md, "classes"); 741 if (cf.isFile()) { 742 ZipFile zf = new ZipFile(cf); 743 contentForModule.put(mid, zf); 744 return zf; 745 } 746 if (cf.isDirectory()) { 747 contentForModule.put(mid, cf); 748 return cf; 749 } 750 contentForModule.put(mid, NONE); 751 return null; 752 } 753 754 private byte[] loadContent(ZipFile zf, String path) 755 throws IOException 756 { 757 ZipEntry ze = zf.getEntry(path); 758 if (ze == null) 759 return null; 760 return Files.load(zf.getInputStream(ze), (int)ze.getSize()); 761 } 762 763 private byte[] loadContent(ModuleId mid, String path) 764 throws IOException 765 { 766 Object o = findContent(mid); 767 if (o == null) 768 return null; 769 if (o instanceof ZipFile) { 770 ZipFile zf = (ZipFile)o; 771 ZipEntry ze = zf.getEntry(path); 772 if (ze == null) 773 return null; 774 return Files.load(zf.getInputStream(ze), (int)ze.getSize()); 775 } 776 if (o instanceof File) { 777 File f = new File((File)o, path); 778 if (!f.exists()) 779 return null; 780 return Files.load(f); 781 } 782 assert false; 783 return null; 784 } 785 786 private URI locateContent(ModuleId mid, String path) 787 throws IOException 788 { 789 Object o = findContent(mid); 790 if (o == null) 791 return null; 792 if (o instanceof ZipFile) { 793 ZipFile zf = (ZipFile)o; 794 ZipEntry ze = zf.getEntry(path); 795 if (ze == null) 796 return null; 797 return URI.create("jar:" 798 + new File(zf.getName()).toURI().toString() 799 + "!/" + path); 800 } 801 if (o instanceof File) { 802 File f = new File((File)o, path); 803 if (!f.exists()) 804 return null; 805 return f.toURI(); 806 } 807 assert false; 808 return null; 809 } 810 811 public byte[] readLocalClass(ModuleId mid, String className) 812 throws IOException 813 { 814 return loadContent(mid, className.replace('.', '/') + ".class"); 815 } 816 817 public List<String> listLocalClasses(ModuleId mid, boolean all) 818 throws IOException 819 { 820 File md = findModuleDir(mid); 821 if (md == null) 822 return null; 823 Index ix = Index.load(md); 824 int os = all ? ix.otherClasses().size() : 0; 825 ArrayList<String> cns 826 = new ArrayList<String>(ix.publicClasses().size() + os); 827 cns.addAll(ix.publicClasses()); 828 if (all) 829 cns.addAll(ix.otherClasses()); 830 return cns; 831 } 832 833 public Configuration<Context> readConfiguration(ModuleId mid) 834 throws IOException 835 { 836 File md = findModuleDir(mid); 837 if (md == null) { 838 if (parent != null) 839 return parent.readConfiguration(mid); 840 return null; 841 } 842 StoredConfiguration scf = StoredConfiguration.load(md); 843 return scf.cf; 844 } 845 846 private boolean addToIndex(ClassInfo ci, Index ix) 847 throws IOException 848 { 849 if (ci.isModuleInfo()) 850 return false; 851 if (ci.moduleName() != null) { 852 // ## From early Jigsaw development; can probably delete now 853 throw new IOException("Old-style class file with" 854 + " module attribute"); 855 } 856 if (ci.isPublic()) 857 ix.publicClasses().add(ci.name()); 858 else 859 ix.otherClasses().add(ci.name()); 860 return true; 861 } 862 863 private void reIndex(ModuleId mid) 864 throws IOException 865 { 866 867 File md = findModuleDir(mid); 868 if (md == null) 869 throw new IllegalArgumentException(mid + ": No such module"); 870 File cd = new File(md, "classes"); 871 final Index ix = new Index(md); 872 873 if (cd.isDirectory()) { 874 Files.walkTree(cd, new Files.Visitor<File>() { 875 public void accept(File f) throws IOException { 876 if (f.getPath().endsWith(".class")) 877 addToIndex(ClassInfo.read(f), ix); 878 } 879 }); 880 } else if (cd.isFile()) { 881 FileInputStream fis = new FileInputStream(cd); 882 ZipInputStream zis = new ZipInputStream(fis); 883 ZipEntry ze; 884 while ((ze = zis.getNextEntry()) != null) { 885 if (!ze.getName().endsWith(".class")) 886 continue; 887 addToIndex(ClassInfo.read(Files.nonClosingStream(zis), 888 ze.getSize(), 889 mid + ":" + ze.getName()), 890 ix); 891 } 892 } 893 894 ix.store(); 895 } 896 897 /** 898 * Strip the debug attributes from the classes in a given module 899 * directory. 900 */ 901 private void strip(File md) throws IOException { 902 File classes = new File(md, "classes"); 903 if (classes.isFile()) { 904 File pf = new File(md, "classes.pack"); 905 try (JarFile jf = new JarFile(classes); 906 FileOutputStream out = new FileOutputStream(pf)) 907 { 908 Pack200.Packer packer = Pack200.newPacker(); 909 Map<String,String> p = packer.properties(); 910 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE); 911 packer.pack(jf, out); 912 } 913 914 try (OutputStream out = new FileOutputStream(classes); 915 JarOutputStream jos = new JarOutputStream(out)) 916 { 917 Pack200.Unpacker unpacker = Pack200.newUnpacker(); 918 unpacker.unpack(pf, jos); 919 } finally { 920 pf.delete(); 921 } 922 } 923 } 924 925 private void install(Manifest mf, File dst, boolean strip) 926 throws IOException 927 { 928 if (mf.classes().size() > 1) 929 throw new IllegalArgumentException("Multiple module-class" 930 + " directories" 931 + " not yet supported"); 932 if (mf.classes().size() < 1) 933 throw new IllegalArgumentException("At least one module-class" 934 + " directory required"); 935 File classes = mf.classes().get(0); 936 final String mn = mf.module(); 937 938 File mif = new File(classes, "module-info.class"); 939 File src = null; 940 if (mif.exists()) { 941 src = classes; 942 } else { 943 src = new File(classes, mn); 944 mif = new File(src, "module-info.class"); 945 } 946 byte[] bs = Files.load(mif); 947 ModuleInfo mi = jms.parseModuleInfo(bs); 948 if (!mi.id().name().equals(mn)) { 949 // ## Need a more appropriate throwable here 950 throw new Error(mif + " is for module " + mi.id().name() 951 + ", not " + mn); 952 } 953 String m = mi.id().name(); 954 JigsawVersion v = (JigsawVersion)mi.id().version(); 955 String vs = (v == null) ? "default" : v.toString(); 956 File mdst = new File(new File(dst, m), vs); 957 if (mdst.exists()) 958 Files.deleteTree(mdst); 959 Files.mkdirs(mdst, "module"); 960 Files.store(bs, new File(mdst, "info")); 961 File cldst = new File(mdst, "classes"); 962 963 // Delete the config file, if one exists 964 StoredConfiguration.delete(mdst); 965 966 if (false) { 967 968 // ## Retained for now in case we later want to add an option 969 // ## to install into a tree rather than a zip file 970 971 // Copy class files and build index 972 final Index ix = new Index(mdst); 973 Files.copyTree(src, cldst, new Files.Filter<File>() { 974 public boolean accept(File f) throws IOException { 975 if (f.isDirectory()) 976 return true; 977 if (f.getName().endsWith(".class")) { 978 return addToIndex(ClassInfo.read(f), ix); 979 } else { 980 return true; 981 } 982 }}); 983 ix.store(); 984 } else { 985 FileOutputStream fos 986 = new FileOutputStream(new File(mdst, "classes")); 987 JarOutputStream jos 988 = new JarOutputStream(new BufferedOutputStream(fos)); 989 try { 990 991 // Copy class files and build index 992 final Index ix = new Index(mdst); 993 Files.storeTree(src, jos, isDeflated(), new Files.Filter<File>() { 994 public boolean accept(File f) throws IOException { 995 if (f.isDirectory()) 996 return true; 997 if (f.getName().endsWith(".class")) { 998 return addToIndex(ClassInfo.read(f), ix); 999 } else { 1000 return true; 1001 } 1002 }}); 1003 ix.store(); 1004 } finally { 1005 jos.close(); 1006 } 1007 if (strip) 1008 strip(mdst); 1009 } 1010 1011 } 1012 1013 private void install(Collection<Manifest> mfs, File dst, boolean strip) 1014 throws IOException 1015 { 1016 for (Manifest mf : mfs) 1017 install(mf, dst, strip); 1018 } 1019 1020 public void installFromManifests(Collection<Manifest> mfs, boolean strip) 1021 throws ConfigurationException, IOException 1022 { 1023 install(mfs, root, strip); 1024 configure(null); 1025 } 1026 1027 @Override 1028 public void installFromManifests(Collection<Manifest> mfs) 1029 throws ConfigurationException, IOException 1030 { 1031 installFromManifests(mfs, false); 1032 } 1033 1034 private ModuleFileVerifier.Parameters mfvParams; 1035 1036 private ModuleId install(InputStream is, boolean verifySignature, boolean strip) 1037 throws ConfigurationException, IOException, SignatureException 1038 { 1039 BufferedInputStream bin = new BufferedInputStream(is); 1040 DataInputStream in = new DataInputStream(bin); 1041 File md = null; 1042 try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) { 1043 byte[] mib = mr.readStart(); 1044 ModuleInfo mi = jms.parseModuleInfo(mib); 1045 md = moduleDir(mi.id()); 1046 ModuleId mid = mi.id(); 1047 if (md.exists()) 1048 throw new ConfigurationException(mid + ": Already installed"); 1049 if (!md.mkdirs()) 1050 throw new IOException(md + ": Cannot create"); 1051 1052 if (verifySignature && mr.hasSignature()) { 1053 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr); 1054 if (mfvParams == null) { 1055 mfvParams = new SignedModule.VerifierParameters(); 1056 } 1057 // Verify the module signature and validate the signer's 1058 // certificate chain 1059 Set<CodeSigner> signers = mfv.verifySignature(mfvParams); 1060 1061 // Verify the module header hash and the module info hash 1062 mfv.verifyHashesStart(mfvParams); 1063 1064 // ## Check policy - is signer trusted and what permissions 1065 // ## should be granted? 1066 1067 // Store signer info 1068 new Signers(md, signers).store(); 1069 1070 // Read and verify the rest of the hashes 1071 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs()); 1072 mfv.verifyHashesRest(mfvParams); 1073 } else { 1074 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs()); 1075 } 1076 1077 if (strip) 1078 strip(md); 1079 reIndex(mid); // ## Could do this while reading module file 1080 return mid; 1081 1082 } catch (IOException | SignatureException x) { 1083 if (md != null && md.exists()) { 1084 try { 1085 ModuleFile.Reader.remove(md); 1086 } catch (IOException y) { 1087 y.initCause(x); 1088 throw y; 1089 } 1090 } 1091 throw x; 1092 } 1093 } 1094 1095 private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip) 1096 throws ConfigurationException, IOException, SignatureException 1097 { 1098 File md = null; 1099 try (JarFile jf = new JarFile(mf, verifySignature)) { 1100 ModuleInfo mi = jf.getModuleInfo(); 1101 if (mi == null) 1102 throw new ConfigurationException(mf + ": not a modular JAR file"); 1103 1104 md = moduleDir(mi.id()); 1105 ModuleId mid = mi.id(); 1106 if (md.exists()) 1107 throw new ConfigurationException(mid + ": Already installed"); 1108 if (!md.mkdirs()) 1109 throw new IOException(md + ": Cannot create"); 1110 1111 boolean signed = false; 1112 1113 // copy the jar file to the module library 1114 File classesDir = new File(md, "classes"); 1115 try (FileOutputStream fos = new FileOutputStream(classesDir); 1116 BufferedOutputStream bos = new BufferedOutputStream(fos); 1117 JarOutputStream jos = new JarOutputStream(bos)) { 1118 jos.setLevel(0); 1119 1120 Enumeration<JarEntry> entries = jf.entries(); 1121 while (entries.hasMoreElements()) { 1122 JarEntry je = entries.nextElement(); 1123 try (InputStream is = jf.getInputStream(je)) { 1124 if (je.getName().equals(JarFile.MODULEINFO_NAME)) { 1125 java.nio.file.Files.copy(is, md.toPath().resolve("info")); 1126 } else { 1127 writeJarEntry(is, je, jos); 1128 } 1129 } 1130 if (!signed) { 1131 String name = je.getName().toUpperCase(Locale.ENGLISH); 1132 signed = name.startsWith("META-INF/") 1133 && name.endsWith(".SF"); 1134 } 1135 } 1136 } 1137 1138 try { 1139 if (verifySignature && signed) { 1140 // validate the code signers 1141 Set<CodeSigner> signers = getSigners(jf); 1142 SignedModule.validateSigners(signers); 1143 // store the signers 1144 new Signers(md, signers).store(); 1145 } 1146 } catch (CertificateException ce) { 1147 throw new SignatureException(ce); 1148 } 1149 1150 if (strip) 1151 strip(md); 1152 reIndex(mid); 1153 return mid; 1154 } catch (IOException | SignatureException x) { 1155 if (md != null && md.exists()) { 1156 try { 1157 Files.deleteTree(md); 1158 } catch (IOException y) { 1159 y.initCause(x); 1160 throw y; 1161 } 1162 } 1163 throw x; 1164 } 1165 } 1166 1167 /** 1168 * Returns the set of signers of the specified jar file. Each signer 1169 * must have signed all relevant entries. 1170 */ 1171 private static Set<CodeSigner> getSigners(JarFile jf) 1172 throws SignatureException 1173 { 1174 Set<CodeSigner> signers = new HashSet<>(); 1175 Enumeration<JarEntry> entries = jf.entries(); 1176 while (entries.hasMoreElements()) { 1177 JarEntry je = entries.nextElement(); 1178 String name = je.getName().toUpperCase(Locale.ENGLISH); 1179 if (name.endsWith("/") || isSigningRelated(name)) 1180 continue; 1181 1182 // A signed modular jar can be signed by multiple signers. 1183 // However, all entries must be signed by each of these signers. 1184 // Signers that only sign a subset of entries are ignored. 1185 CodeSigner[] jeSigners = je.getCodeSigners(); 1186 if (jeSigners == null || jeSigners.length == 0) 1187 throw new SignatureException("Found unsigned entry in " 1188 + "signed modular JAR"); 1189 1190 Set<CodeSigner> jeSignerSet = 1191 new HashSet<>(Arrays.asList(jeSigners)); 1192 if (signers.isEmpty()) 1193 signers.addAll(jeSignerSet); 1194 else { 1195 if (signers.retainAll(jeSignerSet) && signers.isEmpty()) 1196 throw new SignatureException("No signers in common in " 1197 + "signed modular JAR"); 1198 } 1199 } 1200 return signers; 1201 } 1202 1203 // true if file is part of the signature mechanism itself 1204 private static boolean isSigningRelated(String name) { 1205 if (!name.startsWith("META-INF/")) { 1206 return false; 1207 } 1208 name = name.substring(9); 1209 if (name.indexOf('/') != -1) { 1210 return false; 1211 } 1212 if (name.endsWith(".DSA") || 1213 name.endsWith(".RSA") || 1214 name.endsWith(".SF") || 1215 name.endsWith(".EC") || 1216 name.startsWith("SIG-") || 1217 name.equals("MANIFEST.MF")) { 1218 return true; 1219 } 1220 return false; 1221 } 1222 1223 private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos) 1224 throws IOException, SignatureException 1225 { 1226 JarEntry entry = new JarEntry(je.getName()); 1227 entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED); 1228 entry.setTime(je.getTime()); 1229 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { 1230 int size = 0; 1231 byte[] bs = new byte[1024]; 1232 int cc = 0; 1233 // This will throw a SecurityException if a signature is invalid. 1234 while ((cc = is.read(bs)) > 0) { 1235 baos.write(bs, 0, cc); 1236 size += cc; 1237 } 1238 if (!isDeflated()) { 1239 entry.setSize(size); 1240 entry.setCrc(je.getCrc()); 1241 entry.setCompressedSize(size); 1242 } 1243 jos.putNextEntry(entry); 1244 if (baos.size() > 0) 1245 baos.writeTo(jos); 1246 jos.closeEntry(); 1247 } catch (SecurityException se) { 1248 throw new SignatureException(se); 1249 } 1250 } 1251 1252 private ModuleId install(File mf, boolean verifySignature, boolean strip) 1253 throws ConfigurationException, IOException, SignatureException 1254 { 1255 ModuleId mid; 1256 if (mf.getName().endsWith(".jar")) 1257 mid = installFromJarFile(mf, verifySignature, strip); 1258 else { 1259 // Assume jmod file 1260 try (FileInputStream in = new FileInputStream(mf)) { 1261 mid = install(in, verifySignature, strip); 1262 } 1263 } 1264 return mid; 1265 } 1266 1267 public void install(Collection<File> mfs, boolean verifySignature, boolean strip) 1268 throws ConfigurationException, IOException, SignatureException 1269 { 1270 List<ModuleId> mids = new ArrayList<>(); 1271 boolean complete = false; 1272 Throwable ox = null; 1273 try { 1274 for (File mf : mfs) 1275 mids.add(install(mf, verifySignature, strip)); 1276 configure(mids); 1277 complete = true; 1278 } catch (IOException|ConfigurationException x) { 1279 ox = x; 1280 throw x; 1281 } finally { 1282 if (!complete) { 1283 try { 1284 for (ModuleId mid : mids) 1285 deleteModuleDir(mid); 1286 } catch (IOException x) { 1287 if (ox != null) 1288 x.initCause(ox); 1289 throw x; 1290 } 1291 } 1292 } 1293 } 1294 1295 @Override 1296 public void install(Collection<File> mfs, boolean verifySignature) 1297 throws ConfigurationException, IOException, SignatureException 1298 { 1299 install(mfs, verifySignature, false); 1300 } 1301 1302 // Public entry point, since the Resolver itself is package-private 1303 // 1304 public Resolution resolve(Collection<ModuleIdQuery> midqs) 1305 throws ConfigurationException, IOException 1306 { 1307 return Resolver.run(this, midqs); 1308 } 1309 1310 public void install(Resolution res, boolean verifySignature, boolean strip) 1311 throws ConfigurationException, IOException, SignatureException 1312 { 1313 // ## Handle case of installing multiple root modules 1314 assert res.rootQueries.size() == 1; 1315 ModuleIdQuery midq = res.rootQueries.iterator().next(); 1316 ModuleInfo root = null; 1317 for (ModuleInfo mi : res.modules) { 1318 if (midq.matches(mi.id())) { 1319 root = mi; 1320 break; 1321 } 1322 } 1323 assert root != null; 1324 1325 // Download 1326 // 1327 for (ModuleId mid : res.modulesNeeded()) { 1328 URI u = res.locationForName.get(mid.name()); 1329 assert u != null; 1330 RemoteRepository rr = repositoryList().firstRepository(); 1331 assert rr != null; 1332 install(rr.fetch(mid), verifySignature, strip); 1333 res.locationForName.put(mid.name(), location()); 1334 // ## If something goes wrong, delete all our modules 1335 } 1336 1337 // Configure 1338 // 1339 Configuration<Context> cf 1340 = Configurator.configure(this, res); 1341 new StoredConfiguration(moduleDir(root.id()), cf).store(); 1342 } 1343 1344 @Override 1345 public void install(Resolution res, boolean verifySignature) 1346 throws ConfigurationException, IOException, SignatureException 1347 { 1348 install(res, verifySignature, false); 1349 } 1350 1351 /** 1352 * <p> Pre-install one or more modules to an arbitrary destination 1353 * directory. </p> 1354 * 1355 * <p> A pre-installed module has the same format as within the library 1356 * itself, except that there is never a configuration file. </p> 1357 * 1358 * <p> This method is provided for use by the module-packaging tool. </p> 1359 * 1360 * @param mfs 1361 * The manifest describing the contents of the modules to be 1362 * pre-installed 1363 * 1364 * @param dst 1365 * The destination directory, with one subdirectory per module 1366 * name, each of which contains one subdirectory per version 1367 */ 1368 public void preInstall(Collection<Manifest> mfs, File dst) 1369 throws IOException 1370 { 1371 Files.mkdirs(dst, "module destination"); 1372 install(mfs, dst, false); 1373 } 1374 1375 public void preInstall(Manifest mf, File dst) 1376 throws IOException 1377 { 1378 preInstall(Collections.singleton(mf), dst); 1379 } 1380 1381 /** 1382 * <p> Update the configurations of any root modules affected by the 1383 * copying of the named modules, in pre-installed format, into this 1384 * library. </p> 1385 * 1386 * @param mids 1387 * The module ids of the new or updated modules, or 1388 * {@code null} if the configuration of every root module 1389 * should be (re)computed 1390 */ 1391 public void configure(List<ModuleId> mids) 1392 throws ConfigurationException, IOException 1393 { 1394 // ## mids not used yet 1395 for (ModuleInfo mi : listLocalRootModuleInfos()) { 1396 // ## We could be a lot more clever about this! 1397 Configuration<Context> cf 1398 = Configurator.configure(this, mi.id().toQuery()); 1399 new StoredConfiguration(moduleDir(mi.id()), cf).store(); 1400 } 1401 } 1402 1403 public URI findLocalResource(ModuleId mid, String name) 1404 throws IOException 1405 { 1406 return locateContent(mid, name); 1407 } 1408 1409 public File findLocalNativeLibrary(ModuleId mid, String name) 1410 throws IOException 1411 { 1412 File f = natlibs(); 1413 if (f == null) { 1414 f = findModuleDir(mid); 1415 if (f == null) 1416 return null; 1417 f = new File(f, "lib"); 1418 } 1419 f = new File(f, name); 1420 if (!f.exists()) 1421 return null; 1422 return f; 1423 } 1424 1425 public File classPath(ModuleId mid) 1426 throws IOException 1427 { 1428 File md = findModuleDir(mid); 1429 if (md == null) { 1430 if (parent != null) 1431 return parent.classPath(mid); 1432 return null; 1433 } 1434 // ## Check for other formats here 1435 return new File(md, "classes"); 1436 } 1437 1438 /** 1439 * <p> Re-index the classes of the named previously-installed modules, and 1440 * then update the configurations of any affected root modules. </p> 1441 * 1442 * <p> This method is intended for use during development, when a build 1443 * process may update a previously-installed module in place, adding or 1444 * removing classes. </p> 1445 * 1446 * @param mids 1447 * The module ids of the new or updated modules, or 1448 * {@code null} if the configuration of every root module 1449 * should be (re)computed 1450 */ 1451 public void reIndex(List<ModuleId> mids) 1452 throws ConfigurationException, IOException 1453 { 1454 for (ModuleId mid : mids) 1455 reIndex(mid); 1456 configure(mids); 1457 } 1458 1459 1460 // -- Repositories -- 1461 1462 private static class RepoList 1463 implements RemoteRepositoryList 1464 { 1465 1466 private static final int MINOR_VERSION = 0; 1467 private static final int MAJOR_VERSION = 0; 1468 1469 private final File root; 1470 private final File listFile; 1471 1472 private RepoList(File r) { 1473 root = new File(r, FileConstants.META_PREFIX + "repos"); 1474 listFile = new File(root, FileConstants.META_PREFIX + "list"); 1475 } 1476 1477 private static FileHeader fileHeader() { 1478 return (new FileHeader() 1479 .type(FileConstants.Type.REMOTE_REPO_LIST) 1480 .majorVersion(MAJOR_VERSION) 1481 .minorVersion(MINOR_VERSION)); 1482 } 1483 1484 private List<RemoteRepository> repos = null; 1485 private long nextRepoId = 0; 1486 1487 private File repoDir(long id) { 1488 return new File(root, Long.toHexString(id)); 1489 } 1490 1491 private void load() throws IOException { 1492 1493 repos = new ArrayList<>(); 1494 if (!root.exists() || !listFile.exists()) 1495 return; 1496 FileInputStream fin = new FileInputStream(listFile); 1497 DataInputStream in 1498 = new DataInputStream(new BufferedInputStream(fin)); 1499 try { 1500 1501 FileHeader fh = fileHeader(); 1502 fh.read(in); 1503 nextRepoId = in.readLong(); 1504 int n = in.readInt(); 1505 long[] ids = new long[n]; 1506 for (int i = 0; i < n; i++) 1507 ids[i] = in.readLong(); 1508 RemoteRepository parent = null; 1509 1510 // Load in reverse order so that parents are correct 1511 for (int i = n - 1; i >= 0; i--) { 1512 long id = ids[i]; 1513 RemoteRepository rr 1514 = RemoteRepository.open(repoDir(id), id, parent); 1515 repos.add(rr); 1516 parent = rr; 1517 } 1518 Collections.reverse(repos); 1519 1520 } finally { 1521 in.close(); 1522 } 1523 1524 } 1525 1526 private List<RemoteRepository> roRepos = null; 1527 1528 // Unmodifiable 1529 public List<RemoteRepository> repositories() throws IOException { 1530 if (repos == null) { 1531 load(); 1532 roRepos = Collections.unmodifiableList(repos); 1533 } 1534 return roRepos; 1535 } 1536 1537 public RemoteRepository firstRepository() throws IOException { 1538 repositories(); 1539 return repos.isEmpty() ? null : repos.get(0); 1540 } 1541 1542 private void store() throws IOException { 1543 File newfn = new File(root, "list.new"); 1544 FileOutputStream fout = new FileOutputStream(newfn); 1545 DataOutputStream out 1546 = new DataOutputStream(new BufferedOutputStream(fout)); 1547 try { 1548 try { 1549 fileHeader().write(out); 1550 out.writeLong(nextRepoId); 1551 out.writeInt(repos.size()); 1552 for (RemoteRepository rr : repos) 1553 out.writeLong(rr.id()); 1554 } finally { 1555 out.close(); 1556 } 1557 } catch (IOException x) { 1558 newfn.delete(); 1559 throw x; 1560 } 1561 java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE); 1562 } 1563 1564 public RemoteRepository add(URI u, int position) 1565 throws IOException 1566 { 1567 1568 if (repos == null) 1569 load(); 1570 for (RemoteRepository rr : repos) { 1571 if (rr.location().equals(u)) // ## u not canonical 1572 throw new IllegalStateException(u + ": Already in" 1573 + " repository list"); 1574 } 1575 if (!root.exists()) { 1576 if (!root.mkdir()) 1577 throw new IOException(root + ": Cannot create directory"); 1578 } 1579 1580 if (repos.size() == Integer.MAX_VALUE) 1581 throw new IllegalStateException("Too many repositories"); 1582 if (position < 0) 1583 throw new IllegalArgumentException("Invalid index"); 1584 1585 long id = nextRepoId++; 1586 RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id); 1587 try { 1588 rr.updateCatalog(true); 1589 } catch (IOException x) { 1590 repoDir(id).delete(); 1591 throw x; 1592 } 1593 1594 if (position >= repos.size()) { 1595 repos.add(rr); 1596 } else if (position >= 0) { 1597 List<RemoteRepository> prefix 1598 = new ArrayList<>(repos.subList(0, position)); 1599 List<RemoteRepository> suffix 1600 = new ArrayList<>(repos.subList(position, repos.size())); 1601 repos.clear(); 1602 repos.addAll(prefix); 1603 repos.add(rr); 1604 repos.addAll(suffix); 1605 } 1606 store(); 1607 1608 return rr; 1609 1610 } 1611 1612 public boolean remove(RemoteRepository rr) 1613 throws IOException 1614 { 1615 if (!repos.remove(rr)) 1616 return false; 1617 store(); 1618 File rd = repoDir(rr.id()); 1619 for (File f : rd.listFiles()) { 1620 if (!f.delete()) 1621 throw new IOException(f + ": Cannot delete"); 1622 } 1623 if (!rd.delete()) 1624 throw new IOException(rd + ": Cannot delete"); 1625 return true; 1626 } 1627 1628 public boolean areCatalogsStale() throws IOException { 1629 for (RemoteRepository rr : repos) { 1630 if (rr.isCatalogStale()) 1631 return true; 1632 } 1633 return false; 1634 } 1635 1636 public boolean updateCatalogs(boolean force) throws IOException { 1637 boolean updated = false; 1638 for (RemoteRepository rr : repos) { 1639 if (rr.updateCatalog(force)) 1640 updated = true; 1641 } 1642 return updated; 1643 } 1644 1645 } 1646 1647 private RemoteRepositoryList repoList = null; 1648 1649 public RemoteRepositoryList repositoryList() 1650 throws IOException 1651 { 1652 if (repoList == null) 1653 repoList = new RepoList(root); 1654 return repoList; 1655 } 1656 1657 } --- EOF ---