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 ModuleInfo moduleInfo = jms.parseModuleInfo(mr.getModuleInfoBytes()); 1163 File md = moduleDictionary.add(moduleInfo); 1164 mi = moduleInfo; 1165 if (verifySignature && mr.hasSignature()) { 1166 // Verify the module signature 1167 SignedModule sm = new SignedModule(mr); 1168 Set<CodeSigner> signers = sm.verifySignature(); 1169 1170 // Validate the signers 1171 try { 1172 SignedModule.validateSigners(signers); 1173 } catch (CertificateException x) { 1174 throw new SignatureException(x); 1175 } 1176 1177 // ## TODO: Check policy and determine if signer is trusted 1178 // ## and what permissions should be granted. 1179 // ## If there is no policy entry, show signers and prompt 1180 // ## user to accept before proceeding. 1181 1182 // Verify the module header hash and the module info hash 1183 sm.verifyHashesStart(); 1184 1185 // Extract remainder of the module file, and calculate hashes 1186 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs()); 1187 1188 // Verify the rest of the hashes 1189 sm.verifyHashesRest(); 1190 1191 // Store signer info 1192 new Signers(md, signers).store(); 1193 } else { 1194 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs()); 1195 } 1196 1197 if (strip) 1198 strip(md); 1199 reIndex(mi.id()); // ## Could do this while reading module file 1200 1201 return mi.id(); 1202 1203 } catch (ConfigurationException | IOException | SignatureException | 1204 ModuleFileParserException x) { // ## should we catch Throwable 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 | 1395 ModuleFileParserException x) { // ## catch throwable?? 1396 try { 1397 for (ModuleId mid : mids) { 1398 ModuleInfo mi = readLocalModuleInfo(mid); 1399 if (mi != null) { 1400 moduleDictionary.remove(mi); 1401 } 1402 } 1403 } catch (IOException y) { 1404 x.addSuppressed(y); 1405 } 1406 throw x; 1407 } finally { 1408 if (complete) { 1409 moduleDictionary.store(); 1410 } 1411 fc.close(); 1412 } 1413 } 1414 1415 @Override 1416 public void install(Collection<File> mfs, boolean verifySignature) 1417 throws ConfigurationException, IOException, SignatureException, 1418 ModuleFileParserException 1419 { 1420 install(mfs, verifySignature, false); 1421 } 1422 1423 // Public entry point, since the Resolver itself is package-private 1424 // 1425 @Override 1426 public Resolution resolve(Collection<ModuleIdQuery> midqs) 1427 throws ConfigurationException, IOException 1428 { 1429 try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) { 1430 fc.lock(); 1431 return Resolver.run(this, midqs); 1432 } 1433 } 1434 1435 public void install(Resolution res, boolean verifySignature, boolean strip) 1436 throws ConfigurationException, IOException, SignatureException 1437 { 1438 boolean complete = false; 1439 FileChannel fc = FileChannel.open(lockf.toPath(), WRITE); 1440 try { 1441 fc.lock(); 1442 moduleDictionary.load(); 1443 1444 // ## Handle case of installing multiple root modules 1445 assert res.rootQueries.size() == 1; 1446 ModuleIdQuery midq = res.rootQueries.iterator().next(); 1447 ModuleInfo root = null; 1448 for (String mn : res.moduleViewForName.keySet()) { 1449 ModuleView mv = res.moduleViewForName.get(mn); 1450 if (midq.matches(mv.id())) { 1451 root = mv.moduleInfo(); 1452 break; 1453 } 1454 } 1455 assert root != null; 1456 1457 // Download 1458 // 1459 for (ModuleId mid : res.modulesNeeded()) { 1460 URI u = res.locationForName.get(mid.name()); 1461 assert u != null; 1462 RemoteRepository rr = repositoryList().firstRepository(); 1463 assert rr != null; 1464 installWhileLocked(rr.fetch(mid), verifySignature, strip); 1465 res.locationForName.put(mid.name(), location()); 1466 // ## If something goes wrong, delete all our modules 1467 } 1468 1469 // Configure 1470 // 1471 configureWhileModuleDirectoryLocked(res.modulesNeeded()); 1472 complete = true; 1473 } catch (ConfigurationException | IOException | SignatureException | 1474 ModuleFileParserException x) { // ## catch throwable?? 1475 try { 1476 for (ModuleId mid : res.modulesNeeded()) { 1477 ModuleInfo mi = readLocalModuleInfo(mid); 1478 if (mi != null) { 1479 moduleDictionary.remove(mi); 1480 } 1481 } 1482 } catch (IOException y) { 1483 x.addSuppressed(y); 1484 } 1485 throw x; 1486 } finally { 1487 if (complete) { 1488 moduleDictionary.store(); 1489 } 1490 fc.close(); 1491 } 1492 } 1493 1494 @Override 1495 public void install(Resolution res, boolean verifySignature) 1496 throws ConfigurationException, IOException, SignatureException, 1497 ModuleFileParserException 1498 { 1499 install(res, verifySignature, false); 1500 } 1501 1502 @Override 1503 public void removeForcibly(List<ModuleId> mids) 1504 throws IOException 1505 { 1506 try { 1507 remove(mids, true, false); 1508 } catch (ConfigurationException x) { 1509 throw new Error("should not be thrown when forcibly removing", x); 1510 } 1511 } 1512 1513 @Override 1514 public void remove(List<ModuleId> mids, boolean dry) 1515 throws ConfigurationException, IOException 1516 { 1517 remove(mids, false, dry); 1518 } 1519 1520 private void remove(List<ModuleId> mids, boolean force, boolean dry) 1521 throws ConfigurationException, IOException 1522 { 1523 IOException ioe = null; 1524 1525 try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) { 1526 fc.lock(); 1527 for (ModuleId mid : mids) { 1528 // ## Should we support alias and/or non-default view names?? 1529 if (moduleDictionary.findDeclaringModuleDir(mid) == null) 1530 throw new IllegalArgumentException(mid + ": No such module"); 1531 } 1532 if (!force) 1533 ensureNotInConfiguration(mids); 1534 if (dry) 1535 return; 1536 1537 // The library may be altered after this point, so the modules 1538 // dictionary needs to be refreshed 1539 List<IOException> excs = removeWhileLocked(mids); 1540 try { 1541 moduleDictionary.refresh(); 1542 moduleDictionary.store(); 1543 } catch (IOException x) { 1544 excs.add(x); 1545 } 1546 if (!excs.isEmpty()) { 1547 ioe = excs.remove(0); 1548 for (IOException x : excs) 1549 ioe.addSuppressed(x); 1550 } 1551 } finally { 1552 if (ioe != null) 1553 throw ioe; 1554 } 1555 } 1556 1557 private void ensureNotInConfiguration(List<ModuleId> mids) 1558 throws ConfigurationException, IOException 1559 { 1560 // ## We do not know if a root module in a child library depends on one 1561 // ## of the 'to be removed' modules. We would break it's configuration. 1562 1563 // check each root configuration for reference to a module in mids 1564 for (ModuleId rootid : libraryRoots()) { 1565 // skip any root modules being removed 1566 if (mids.contains(rootid)) 1567 continue; 1568 1569 Configuration<Context> cf = readConfiguration(rootid); 1570 for (Context cx : cf.contexts()) { 1571 for (ModuleId mid : cx.modules()) { 1572 if (mids.contains(mid)) 1573 throw new ConfigurationException(mid + 1574 ": being used by " + rootid); 1575 } 1576 } 1577 } 1578 } 1579 1580 private static final String TRASH = ".trash"; 1581 // lazy initialization of Random 1582 private static class LazyInitialization { 1583 static final Random random = new Random(); 1584 } 1585 private static Path moduleTrashDir(File trash, ModuleId mid) 1586 throws IOException 1587 { 1588 String mn = mid.name(); 1589 Version version = mid.version(); 1590 String v = (version != null) ? version.toString() : "default"; 1591 for (;;) { 1592 long n = LazyInitialization.random.nextLong(); 1593 n = (n == Long.MIN_VALUE) ? 0 : Math.abs(n); 1594 String modTrashName = mn + '_' + v + '_' + Long.toString(n); 1595 File mtd = new File(trash, modTrashName); 1596 if (!mtd.exists()) 1597 return mtd.toPath(); 1598 } 1599 } 1600 1601 private List<IOException> removeWhileLocked(List<ModuleId> mids) { 1602 List<IOException> excs = new ArrayList<>(); 1603 // First move the modules to the .trash dir 1604 for (ModuleId mid : mids) { 1605 try { 1606 File md = moduleDir(root, mid); 1607 java.nio.file.Files.move(md.toPath(), 1608 moduleTrashDir(trash, mid), 1609 ATOMIC_MOVE); 1610 File p = md.getParentFile(); 1611 if (p.list().length == 0) 1612 java.nio.file.Files.delete(p.toPath()); 1613 } catch (IOException x) { 1614 excs.add(x); 1615 } 1616 } 1617 for (String tm : trash.list()) 1618 excs.addAll(ModuleFile.Reader.remove(new File(trash, tm))); 1619 1620 return excs; 1621 } 1622 1623 /** 1624 * <p> Pre-install one or more modules to an arbitrary destination 1625 * directory. </p> 1626 * 1627 * <p> A pre-installed module has the same format as within the library 1628 * itself, except that there is never a configuration file. </p> 1629 * 1630 * <p> This method is provided for use by the module-packaging tool. </p> 1631 * 1632 * @param mfs 1633 * The manifest describing the contents of the modules to be 1634 * pre-installed 1635 * 1636 * @param dst 1637 * The destination directory, with one subdirectory per module 1638 * name, each of which contains one subdirectory per version 1639 */ 1640 public void preInstall(Collection<Manifest> mfs, File dst) 1641 throws IOException 1642 { 1643 Files.mkdirs(dst, "module destination"); 1644 try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) { 1645 fc.lock(); 1646 for (Manifest mf : mfs) { 1647 installWhileLocked(mf, dst, false); 1648 } 1649 // no update to the module directory 1650 } 1651 } 1652 1653 public void preInstall(Manifest mf, File dst) 1654 throws IOException 1655 { 1656 preInstall(Collections.singleton(mf), dst); 1657 } 1658 1659 /** 1660 * Refresh the module library. 1661 */ 1662 public void refresh() throws IOException { 1663 try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) { 1664 fc.lock(); 1665 moduleDictionary.refresh(); 1666 moduleDictionary.store(); 1667 } 1668 } 1669 1670 /** 1671 * <p> Update the configurations of any root modules affected by the 1672 * copying of the named modules, in pre-installed format, into this 1673 * library. </p> 1674 * 1675 * @param mids 1676 * The module ids of the new or updated modules, or 1677 * {@code null} if the configuration of every root module 1678 * should be (re)computed 1679 */ 1680 public void configure(Collection<ModuleId> mids) 1681 throws ConfigurationException, IOException 1682 { 1683 try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) { 1684 fc.lock(); 1685 configureWhileModuleDirectoryLocked(mids); 1686 } 1687 } 1688 1689 private void configureWhileModuleDirectoryLocked(Collection<ModuleId> mids) 1690 throws ConfigurationException, IOException 1691 { 1692 // ## mids not used yet 1693 for (ModuleId mid : libraryRoots()) { 1694 // ## We could be a lot more clever about this! 1695 Configuration<Context> cf 1696 = Configurator.configure(this, mid.toQuery()); 1697 File md = moduleDictionary.findDeclaringModuleDir(mid); 1698 new StoredConfiguration(md, cf).store(); 1699 } 1700 } 1701 1702 private List<ModuleId> libraryRoots() 1703 throws IOException 1704 { 1705 List<ModuleId> roots = new ArrayList<>(); 1706 for (ModuleId mid : listLocalDeclaringModuleIds()) { 1707 // each module can have multiple entry points, but 1708 // only one configuration for each module. 1709 ModuleInfo mi = readModuleInfo(mid); 1710 for (ModuleView mv : mi.views()) { 1711 if (mv.mainClass() != null) { 1712 roots.add(mid); 1713 break; 1714 } 1715 } 1716 } 1717 return roots; 1718 } 1719 1720 public URI findLocalResource(ModuleId mid, String name) 1721 throws IOException 1722 { 1723 return locateContent(mid, name); 1724 } 1725 1726 public File findLocalNativeLibrary(ModuleId mid, String name) 1727 throws IOException 1728 { 1729 File f = natlibs(); 1730 if (f == null) { 1731 f = moduleDictionary.findDeclaringModuleDir(mid); 1732 if (f == null) 1733 return null; 1734 f = new File(f, "lib"); 1735 } 1736 f = new File(f, name); 1737 if (!f.exists()) 1738 return null; 1739 return f; 1740 } 1741 1742 public File classPath(ModuleId mid) 1743 throws IOException 1744 { 1745 File md = moduleDictionary.findDeclaringModuleDir(mid); 1746 if (md == null) { 1747 if (parent != null) 1748 return parent.classPath(mid); 1749 return null; 1750 } 1751 // ## Check for other formats here 1752 return new File(md, "classes"); 1753 } 1754 1755 /** 1756 * <p> Re-index the classes of the named previously-installed modules, and 1757 * then update the configurations of any affected root modules. </p> 1758 * 1759 * <p> This method is intended for use during development, when a build 1760 * process may update a previously-installed module in place, adding or 1761 * removing classes. </p> 1762 * 1763 * @param mids 1764 * The module ids of the new or updated modules, or 1765 * {@code null} if the configuration of every root module 1766 * should be (re)computed 1767 */ 1768 public void reIndex(List<ModuleId> mids) 1769 throws ConfigurationException, IOException 1770 { 1771 for (ModuleId mid : mids) 1772 reIndex(mid); 1773 configure(mids); 1774 } 1775 1776 private static final class ModuleDictionary 1777 { 1778 private static final String FILE 1779 = FileConstants.META_PREFIX + "mids"; 1780 1781 private final File root; 1782 private final File file; 1783 private Map<String,Set<ModuleId>> moduleIdsForName; 1784 private Map<ModuleId,ModuleId> providingModuleIds; 1785 private Set<ModuleId> modules; 1786 private long lastUpdated; 1787 1788 ModuleDictionary(File root) { 1789 this.root = root; 1790 this.file = new File(root, FileConstants.META_PREFIX + "mids"); 1791 this.providingModuleIds = new LinkedHashMap<>(); 1792 this.moduleIdsForName = new LinkedHashMap<>(); 1793 this.modules = new HashSet<>(); 1794 this.lastUpdated = -1; 1795 } 1796 1797 private static FileHeader fileHeader() { 1798 return (new FileHeader() 1799 .type(FileConstants.Type.LIBRARY_MODULE_IDS) 1800 .majorVersion(Header.MAJOR_VERSION) 1801 .minorVersion(Header.MINOR_VERSION)); 1802 } 1803 1804 void load() throws IOException { 1805 if (lastUpdated == file.lastModified()) 1806 return; 1807 1808 providingModuleIds = new LinkedHashMap<>(); 1809 moduleIdsForName = new LinkedHashMap<>(); 1810 modules = new HashSet<>(); 1811 lastUpdated = file.lastModified(); 1812 1813 try (FileInputStream fin = new FileInputStream(file); 1814 DataInputStream in = new DataInputStream(new BufferedInputStream(fin))) 1815 { 1816 FileHeader fh = fileHeader(); 1817 fh.read(in); 1818 int nMids = in.readInt(); 1819 for (int j = 0; j < nMids; j++) { 1820 ModuleId mid = jms.parseModuleId(in.readUTF()); 1821 ModuleId pmid = jms.parseModuleId(in.readUTF()); 1822 providingModuleIds.put(mid, pmid); 1823 addModuleId(mid); 1824 addModuleId(pmid); 1825 if (mid.equals(pmid)) 1826 modules.add(mid); 1827 } 1828 } 1829 } 1830 1831 void store() throws IOException { 1832 File newfn = new File(root, "mids.new"); 1833 FileOutputStream fout = new FileOutputStream(newfn); 1834 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fout)); 1835 try { 1836 try { 1837 fileHeader().write(out); 1838 out.writeInt(providingModuleIds.size()); 1839 for (Map.Entry<ModuleId, ModuleId> e : providingModuleIds.entrySet()) { 1840 out.writeUTF(e.getKey().toString()); 1841 out.writeUTF(e.getValue().toString()); 1842 } 1843 } finally { 1844 out.close(); 1845 } 1846 } catch (IOException x) { 1847 newfn.delete(); 1848 throw x; 1849 } 1850 java.nio.file.Files.move(newfn.toPath(), file.toPath(), ATOMIC_MOVE); 1851 } 1852 1853 void gatherLocalModuleIds(String moduleName, Set<ModuleId> mids) 1854 throws IOException 1855 { 1856 if (lastUpdated != file.lastModified()) 1857 load(); 1858 1859 if (moduleName == null) { 1860 mids.addAll(providingModuleIds.keySet()); 1861 } else { 1862 Set<ModuleId> res = moduleIdsForName.get(moduleName); 1863 if (res != null) 1864 mids.addAll(res); 1865 } 1866 } 1867 1868 ModuleId getDeclaringModule(ModuleId mid) throws IOException { 1869 if (lastUpdated != file.lastModified()) 1870 load(); 1871 1872 ModuleId pmid = providingModuleIds.get(mid); 1873 if (pmid != null && !pmid.equals(providingModuleIds.get(pmid))) { 1874 // mid is an alias 1875 pmid = providingModuleIds.get(pmid); 1876 } 1877 return pmid; 1878 } 1879 1880 File findDeclaringModuleDir(ModuleId mid) 1881 throws IOException 1882 { 1883 ModuleId dmid = getDeclaringModule(mid); 1884 if (dmid == null) 1885 return null; 1886 1887 File md = moduleDir(root, dmid); 1888 assert md.exists(); 1889 checkModuleDir(md); 1890 return md; 1891 } 1892 1893 Set<ModuleId> modules() throws IOException { 1894 if (lastUpdated != file.lastModified()) 1895 load(); 1896 return modules; 1897 } 1898 1899 void addModuleId(ModuleId mid) { 1900 Set<ModuleId> mids = moduleIdsForName.get(mid.name()); 1901 if (mids == null) { 1902 mids = new HashSet<>(); 1903 moduleIdsForName.put(mid.name(), mids); 1904 } 1905 mids.add(mid); 1906 } 1907 1908 File add(ModuleInfo mi) 1909 throws ConfigurationException, IOException 1910 { 1911 File md = ensureNewModule(mi); 1912 addToDirectory(mi); 1913 return md; 1914 } 1915 1916 private void addToDirectory(ModuleInfo mi) { 1917 modules.add(mi.id()); 1918 for (ModuleView view : mi.views()) { 1919 providingModuleIds.put(view.id(), mi.id()); 1920 addModuleId(view.id()); 1921 for (ModuleId alias : view.aliases()) { 1922 providingModuleIds.put(alias, view.id()); 1923 addModuleId(alias); 1924 } 1925 } 1926 } 1927 1928 void remove(ModuleInfo mi) throws IOException { 1929 modules.remove(mi.id()); 1930 for (ModuleView view : mi.views()) { 1931 providingModuleIds.remove(view.id()); 1932 Set<ModuleId> mids = moduleIdsForName.get(view.id().name()); 1933 if (mids != null) 1934 mids.remove(view.id()); 1935 for (ModuleId alias : view.aliases()) { 1936 providingModuleIds.remove(alias); 1937 mids = moduleIdsForName.get(alias.name()); 1938 if (mids != null) 1939 mids.remove(view.id()); 1940 } 1941 } 1942 File md = moduleDir(root, mi.id()); 1943 delete(md); 1944 } 1945 1946 private void delete(File md) throws IOException { 1947 if (!md.exists()) 1948 return; 1949 1950 checkModuleDir(md); 1951 ModuleFile.Reader.remove(md); 1952 File parent = md.getParentFile(); 1953 if (parent.list().length == 0) 1954 parent.delete(); 1955 } 1956 1957 void refresh() throws IOException { 1958 providingModuleIds = new LinkedHashMap<>(); 1959 moduleIdsForName = new LinkedHashMap<>(); 1960 modules = new HashSet<>(); 1961 1962 try (DirectoryStream<Path> ds = java.nio.file.Files.newDirectoryStream(root.toPath())) { 1963 for (Path mnp : ds) { 1964 String mn = mnp.toFile().getName(); 1965 if (mn.startsWith(FileConstants.META_PREFIX) || 1966 TRASH.equals(mn)) { 1967 continue; 1968 } 1969 1970 try (DirectoryStream<Path> mds = java.nio.file.Files.newDirectoryStream(mnp)) { 1971 for (Path versionp : mds) { 1972 File v = versionp.toFile(); 1973 if (!v.isDirectory()) { 1974 throw new IOException(versionp + ": Not a directory"); 1975 } 1976 modules.add(jms.parseModuleId(mn, v.getName())); 1977 } 1978 } 1979 } 1980 } 1981 for (ModuleId mid : modules) { 1982 byte[] bs = Files.load(new File(moduleDir(root, mid), "info")); 1983 ModuleInfo mi = jms.parseModuleInfo(bs); 1984 addToDirectory(mi); 1985 } 1986 } 1987 1988 private File ensureNewModule(ModuleInfo mi) 1989 throws ConfigurationException, IOException 1990 { 1991 for (ModuleView view : mi.views()) { 1992 if (providingModuleIds.containsKey(view.id())) { 1993 throw new ConfigurationException("module view " + view.id() 1994 + " already installed"); 1995 } 1996 for (ModuleId alias : view.aliases()) { 1997 ModuleId mid = alias; 1998 if (providingModuleIds.containsKey(mid)) { 1999 throw new ConfigurationException("alias " + alias 2000 + " already installed"); 2001 } 2002 } 2003 } 2004 File md = moduleDir(root, mi.id()); 2005 if (md.exists()) { 2006 throw new ConfigurationException("module " + mi.id() 2007 + " already installed"); 2008 } 2009 if (!md.mkdirs()) { 2010 throw new IOException(md + ": Cannot create"); 2011 } 2012 return md; 2013 } 2014 } 2015 2016 // -- Repositories -- 2017 2018 private static class RepoList 2019 implements RemoteRepositoryList 2020 { 2021 2022 private static final int MINOR_VERSION = 0; 2023 private static final int MAJOR_VERSION = 0; 2024 2025 private final File root; 2026 private final File listFile; 2027 2028 private RepoList(File r) { 2029 root = new File(r, FileConstants.META_PREFIX + "repos"); 2030 listFile = new File(root, FileConstants.META_PREFIX + "list"); 2031 } 2032 2033 private static FileHeader fileHeader() { 2034 return (new FileHeader() 2035 .type(FileConstants.Type.REMOTE_REPO_LIST) 2036 .majorVersion(MAJOR_VERSION) 2037 .minorVersion(MINOR_VERSION)); 2038 } 2039 2040 private List<RemoteRepository> repos = null; 2041 private long nextRepoId = 0; 2042 2043 private File repoDir(long id) { 2044 return new File(root, Long.toHexString(id)); 2045 } 2046 2047 private void load() throws IOException { 2048 2049 repos = new ArrayList<>(); 2050 if (!root.exists() || !listFile.exists()) 2051 return; 2052 FileInputStream fin = new FileInputStream(listFile); 2053 DataInputStream in 2054 = new DataInputStream(new BufferedInputStream(fin)); 2055 try { 2056 2057 FileHeader fh = fileHeader(); 2058 fh.read(in); 2059 nextRepoId = in.readLong(); 2060 int n = in.readInt(); 2061 long[] ids = new long[n]; 2062 for (int i = 0; i < n; i++) 2063 ids[i] = in.readLong(); 2064 RemoteRepository parent = null; 2065 2066 // Load in reverse order so that parents are correct 2067 for (int i = n - 1; i >= 0; i--) { 2068 long id = ids[i]; 2069 RemoteRepository rr 2070 = RemoteRepository.open(repoDir(id), id, parent); 2071 repos.add(rr); 2072 parent = rr; 2073 } 2074 Collections.reverse(repos); 2075 2076 } finally { 2077 in.close(); 2078 } 2079 2080 } 2081 2082 private List<RemoteRepository> roRepos = null; 2083 2084 // Unmodifiable 2085 public List<RemoteRepository> repositories() throws IOException { 2086 if (repos == null) { 2087 load(); 2088 roRepos = Collections.unmodifiableList(repos); 2089 } 2090 return roRepos; 2091 } 2092 2093 public RemoteRepository firstRepository() throws IOException { 2094 repositories(); 2095 return repos.isEmpty() ? null : repos.get(0); 2096 } 2097 2098 private void store() throws IOException { 2099 File newfn = new File(root, "list.new"); 2100 FileOutputStream fout = new FileOutputStream(newfn); 2101 DataOutputStream out 2102 = new DataOutputStream(new BufferedOutputStream(fout)); 2103 try { 2104 try { 2105 fileHeader().write(out); 2106 out.writeLong(nextRepoId); 2107 out.writeInt(repos.size()); 2108 for (RemoteRepository rr : repos) 2109 out.writeLong(rr.id()); 2110 } finally { 2111 out.close(); 2112 } 2113 } catch (IOException x) { 2114 newfn.delete(); 2115 throw x; 2116 } 2117 java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE); 2118 } 2119 2120 public RemoteRepository add(URI u, int position) 2121 throws IOException 2122 { 2123 2124 if (repos == null) 2125 load(); 2126 for (RemoteRepository rr : repos) { 2127 if (rr.location().equals(u)) // ## u not canonical 2128 throw new IllegalStateException(u + ": Already in" 2129 + " repository list"); 2130 } 2131 if (!root.exists()) { 2132 if (!root.mkdir()) 2133 throw new IOException(root + ": Cannot create directory"); 2134 } 2135 2136 if (repos.size() == Integer.MAX_VALUE) 2137 throw new IllegalStateException("Too many repositories"); 2138 if (position < 0) 2139 throw new IllegalArgumentException("Invalid index"); 2140 2141 long id = nextRepoId++; 2142 RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id); 2143 try { 2144 rr.updateCatalog(true); 2145 } catch (IOException x) { 2146 rr.delete(); 2147 nextRepoId--; 2148 throw x; 2149 } 2150 2151 if (position >= repos.size()) { 2152 repos.add(rr); 2153 } else if (position >= 0) { 2154 List<RemoteRepository> prefix 2155 = new ArrayList<>(repos.subList(0, position)); 2156 List<RemoteRepository> suffix 2157 = new ArrayList<>(repos.subList(position, repos.size())); 2158 repos.clear(); 2159 repos.addAll(prefix); 2160 repos.add(rr); 2161 repos.addAll(suffix); 2162 } 2163 store(); 2164 2165 return rr; 2166 2167 } 2168 2169 public boolean remove(RemoteRepository rr) 2170 throws IOException 2171 { 2172 if (!repos.remove(rr)) 2173 return false; 2174 store(); 2175 File rd = repoDir(rr.id()); 2176 for (File f : rd.listFiles()) { 2177 if (!f.delete()) 2178 throw new IOException(f + ": Cannot delete"); 2179 } 2180 if (!rd.delete()) 2181 throw new IOException(rd + ": Cannot delete"); 2182 return true; 2183 } 2184 2185 public boolean areCatalogsStale() throws IOException { 2186 for (RemoteRepository rr : repos) { 2187 if (rr.isCatalogStale()) 2188 return true; 2189 } 2190 return false; 2191 } 2192 2193 public boolean updateCatalogs(boolean force) throws IOException { 2194 boolean updated = false; 2195 for (RemoteRepository rr : repos) { 2196 if (rr.updateCatalog(force)) 2197 updated = true; 2198 } 2199 return updated; 2200 } 2201 2202 } 2203 2204 private RemoteRepositoryList repoList = null; 2205 2206 public RemoteRepositoryList repositoryList() 2207 throws IOException 2208 { 2209 if (repoList == null) 2210 repoList = new RepoList(root); 2211 return repoList; 2212 } 2213 2214 }