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