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