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