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