1 /* 2 * Copyright (c) 1997, 2014, 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 sun.rmi.server; 27 28 import java.io.ByteArrayOutputStream; 29 import java.io.File; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.ObjectInput; 34 import java.io.ObjectInputStream; 35 import java.io.OutputStream; 36 import java.io.PrintStream; 37 import java.io.PrintWriter; 38 import java.io.Serializable; 39 import java.lang.Process; 40 import java.lang.reflect.InvocationTargetException; 41 import java.lang.reflect.Method; 42 import java.net.InetAddress; 43 import java.net.ServerSocket; 44 import java.net.Socket; 45 import java.net.SocketAddress; 46 import java.net.SocketException; 47 import java.nio.file.Files; 48 import java.nio.channels.Channel; 49 import java.nio.channels.ServerSocketChannel; 50 import java.rmi.AccessException; 51 import java.rmi.AlreadyBoundException; 52 import java.rmi.ConnectException; 53 import java.rmi.ConnectIOException; 54 import java.rmi.MarshalledObject; 55 import java.rmi.NoSuchObjectException; 56 import java.rmi.NotBoundException; 57 import java.rmi.Remote; 58 import java.rmi.RemoteException; 59 import java.rmi.activation.ActivationDesc; 60 import java.rmi.activation.ActivationException; 61 import java.rmi.activation.ActivationGroupDesc; 62 import java.rmi.activation.ActivationGroup; 63 import java.rmi.activation.ActivationGroupID; 64 import java.rmi.activation.ActivationID; 65 import java.rmi.activation.ActivationInstantiator; 66 import java.rmi.activation.ActivationMonitor; 67 import java.rmi.activation.ActivationSystem; 68 import java.rmi.activation.Activator; 69 import java.rmi.activation.UnknownGroupException; 70 import java.rmi.activation.UnknownObjectException; 71 import java.rmi.registry.Registry; 72 import java.rmi.server.ObjID; 73 import java.rmi.server.RMIClassLoader; 74 import java.rmi.server.RMIClientSocketFactory; 75 import java.rmi.server.RMIServerSocketFactory; 76 import java.rmi.server.RemoteObject; 77 import java.rmi.server.RemoteServer; 78 import java.rmi.server.UnicastRemoteObject; 79 import java.security.AccessControlException; 80 import java.security.AccessController; 81 import java.security.AllPermission; 82 import java.security.CodeSource; 83 import java.security.Permission; 84 import java.security.PermissionCollection; 85 import java.security.Permissions; 86 import java.security.Policy; 87 import java.security.PrivilegedAction; 88 import java.security.PrivilegedExceptionAction; 89 import java.security.cert.Certificate; 90 import java.text.MessageFormat; 91 import java.util.ArrayList; 92 import java.util.Arrays; 93 import java.util.Date; 94 import java.util.Enumeration; 95 import java.util.HashMap; 96 import java.util.HashSet; 97 import java.util.Iterator; 98 import java.util.List; 99 import java.util.Map; 100 import java.util.MissingResourceException; 101 import java.util.Properties; 102 import java.util.ResourceBundle; 103 import java.util.Set; 104 import java.util.concurrent.ConcurrentHashMap; 105 import sun.rmi.log.LogHandler; 106 import sun.rmi.log.ReliableLog; 107 import sun.rmi.registry.RegistryImpl; 108 import sun.rmi.runtime.NewThreadAction; 109 import sun.rmi.transport.LiveRef; 110 import sun.security.provider.PolicyFile; 111 import com.sun.rmi.rmid.ExecPermission; 112 import com.sun.rmi.rmid.ExecOptionPermission; 113 114 /** 115 * The Activator facilitates remote object activation. A "faulting" 116 * remote reference calls the activator's <code>activate</code> method 117 * to obtain a "live" reference to a activatable remote object. Upon 118 * receiving a request for activation, the activator looks up the 119 * activation descriptor for the activation identifier, id, determines 120 * the group in which the object should be activated and invokes the 121 * activate method on the object's activation group (described by the 122 * remote interface <code>ActivationInstantiator</code>). The 123 * activator initiates the execution of activation groups as 124 * necessary. For example, if an activation group for a specific group 125 * identifier is not already executing, the activator will spawn a 126 * child process for the activation group. <p> 127 * 128 * The activator is responsible for monitoring and detecting when 129 * activation groups fail so that it can remove stale remote references 130 * from its internal tables. <p> 131 * 132 * @author Ann Wollrath 133 * @since 1.2 134 */ 135 public class Activation implements Serializable { 136 137 /** indicate compatibility with JDK 1.2 version of class */ 138 private static final long serialVersionUID = 2921265612698155191L; 139 private static final byte MAJOR_VERSION = 1; 140 private static final byte MINOR_VERSION = 0; 141 142 /** exec policy object */ 143 private static Object execPolicy; 144 private static Method execPolicyMethod; 145 private static boolean debugExec; 146 147 /** maps activation id to its respective group id */ 148 private Map<ActivationID,ActivationGroupID> idTable = 149 new ConcurrentHashMap<>(); 150 /** maps group id to its GroupEntry groups */ 151 private Map<ActivationGroupID,GroupEntry> groupTable = 152 new ConcurrentHashMap<>(); 153 154 private byte majorVersion = MAJOR_VERSION; 155 private byte minorVersion = MINOR_VERSION; 156 157 /** number of simultaneous group exec's */ 158 private transient int groupSemaphore; 159 /** counter for numbering groups */ 160 private transient int groupCounter; 161 /** reliable log to hold descriptor table */ 162 private transient ReliableLog log; 163 /** number of updates since last snapshot */ 164 private transient int numUpdates; 165 166 /** the java command */ 167 // accessed by GroupEntry 168 private transient String[] command; 169 /** timeout on wait for child process to be created or destroyed */ 170 private static final long groupTimeout = 171 getInt("sun.rmi.activation.groupTimeout", 60000); 172 /** take snapshot after this many updates */ 173 private static final int snapshotInterval = 174 getInt("sun.rmi.activation.snapshotInterval", 200); 175 /** timeout on wait for child process to be created */ 176 private static final long execTimeout = 177 getInt("sun.rmi.activation.execTimeout", 30000); 178 179 private static final Object initLock = new Object(); 180 private static boolean initDone = false; 181 182 // this should be a *private* method since it is privileged 183 private static int getInt(String name, int def) { 184 return AccessController.doPrivileged( 185 (PrivilegedAction<Integer>) () -> Integer.getInteger(name, def)); 186 } 187 188 private transient Activator activator; 189 private transient Activator activatorStub; 190 private transient ActivationSystem system; 191 private transient ActivationSystem systemStub; 192 private transient ActivationMonitor monitor; 193 private transient Registry registry; 194 private transient volatile boolean shuttingDown = false; 195 private transient volatile Object startupLock; 196 private transient Thread shutdownHook; 197 198 private static ResourceBundle resources = null; 199 200 /** 201 * Create an uninitialized instance of Activation that can be 202 * populated with log data. This is only called when the initial 203 * snapshot is taken during the first incarnation of rmid. 204 */ 205 private Activation() {} 206 207 /** 208 * Recover activation state from the reliable log and initialize 209 * activation services. 210 */ 211 private static void startActivation(int port, 212 RMIServerSocketFactory ssf, 213 String logName, 214 String[] childArgs) 215 throws Exception 216 { 217 ReliableLog log = new ReliableLog(logName, new ActLogHandler()); 218 Activation state = (Activation) log.recover(); 219 state.init(port, ssf, log, childArgs); 220 } 221 222 /** 223 * Initialize the Activation instantiation; start activation 224 * services. 225 */ 226 private void init(int port, 227 RMIServerSocketFactory ssf, 228 ReliableLog log, 229 String[] childArgs) 230 throws Exception 231 { 232 // initialize 233 this.log = log; 234 numUpdates = 0; 235 shutdownHook = new ShutdownHook(); 236 groupSemaphore = getInt("sun.rmi.activation.groupThrottle", 3); 237 groupCounter = 0; 238 Runtime.getRuntime().addShutdownHook(shutdownHook); 239 240 // Use array size of 0, since the value from calling size() 241 // may be out of date by the time toArray() is called. 242 ActivationGroupID[] gids = 243 groupTable.keySet().toArray(new ActivationGroupID[0]); 244 245 synchronized (startupLock = new Object()) { 246 // all the remote methods briefly synchronize on startupLock 247 // (via checkShutdown) to make sure they don't happen in the 248 // middle of this block. This block must not cause any such 249 // incoming remote calls to happen, or deadlock would result! 250 activator = new ActivatorImpl(port, ssf); 251 activatorStub = (Activator) RemoteObject.toStub(activator); 252 system = new ActivationSystemImpl(port, ssf); 253 systemStub = (ActivationSystem) RemoteObject.toStub(system); 254 monitor = new ActivationMonitorImpl(port, ssf); 255 initCommand(childArgs); 256 registry = new SystemRegistryImpl(port, null, ssf, systemStub); 257 258 if (ssf != null) { 259 synchronized (initLock) { 260 initDone = true; 261 initLock.notifyAll(); 262 } 263 } 264 } 265 startupLock = null; 266 267 // restart services 268 for (int i = gids.length; --i >= 0; ) { 269 try { 270 getGroupEntry(gids[i]).restartServices(); 271 } catch (UnknownGroupException e) { 272 System.err.println( 273 getTextResource("rmid.restart.group.warning")); 274 e.printStackTrace(); 275 } 276 } 277 } 278 279 /** 280 * Previous versions used HashMap instead of ConcurrentHashMap. 281 * Replace any HashMaps found during deserialization with 282 * ConcurrentHashMaps. 283 */ 284 private void readObject(ObjectInputStream ois) 285 throws IOException, ClassNotFoundException 286 { 287 ois.defaultReadObject(); 288 if (! (groupTable instanceof ConcurrentHashMap)) { 289 groupTable = new ConcurrentHashMap<>(groupTable); 290 } 291 if (! (idTable instanceof ConcurrentHashMap)) { 292 idTable = new ConcurrentHashMap<>(idTable); 293 } 294 } 295 296 private static class SystemRegistryImpl extends RegistryImpl { 297 298 private static final String NAME = ActivationSystem.class.getName(); 299 private static final long serialVersionUID = 4877330021609408794L; 300 private ActivationSystem systemStub = null; 301 302 SystemRegistryImpl(int port, 303 RMIClientSocketFactory csf, 304 RMIServerSocketFactory ssf, 305 ActivationSystem systemStub) 306 throws RemoteException 307 { 308 super(port, csf, ssf); 309 assert systemStub != null; 310 synchronized (this) { 311 this.systemStub = systemStub; 312 notifyAll(); 313 } 314 } 315 316 /** 317 * Waits for systemStub to be initialized and returns its 318 * initialized value. Any remote call that uses systemStub must 319 * call this method to get it instead of using direct field 320 * access. This is necessary because the super() call in the 321 * constructor exports this object before systemStub is initialized 322 * (see JDK-8023541), allowing remote calls to come in during this 323 * time. We can't use checkShutdown() like other nested classes 324 * because this is a static class. 325 */ 326 private synchronized ActivationSystem getSystemStub() { 327 boolean interrupted = false; 328 329 while (systemStub == null) { 330 try { 331 wait(); 332 } catch (InterruptedException ie) { 333 interrupted = true; 334 } 335 } 336 337 if (interrupted) { 338 Thread.currentThread().interrupt(); 339 } 340 341 return systemStub; 342 } 343 344 /** 345 * Returns the activation system stub if the specified name 346 * matches the activation system's class name, otherwise 347 * returns the result of invoking super.lookup with the specified 348 * name. 349 */ 350 public Remote lookup(String name) 351 throws RemoteException, NotBoundException 352 { 353 if (name.equals(NAME)) { 354 return getSystemStub(); 355 } else { 356 return super.lookup(name); 357 } 358 } 359 360 public String[] list() throws RemoteException { 361 String[] list1 = super.list(); 362 int length = list1.length; 363 String[] list2 = new String[length + 1]; 364 if (length > 0) { 365 System.arraycopy(list1, 0, list2, 0, length); 366 } 367 list2[length] = NAME; 368 return list2; 369 } 370 371 public void bind(String name, Remote obj) 372 throws RemoteException, AlreadyBoundException, AccessException 373 { 374 if (name.equals(NAME)) { 375 throw new AccessException( 376 "binding ActivationSystem is disallowed"); 377 } else { 378 RegistryImpl.checkAccess("ActivationSystem.bind"); 379 super.bind(name, obj); 380 } 381 } 382 383 public void unbind(String name) 384 throws RemoteException, NotBoundException, AccessException 385 { 386 if (name.equals(NAME)) { 387 throw new AccessException( 388 "unbinding ActivationSystem is disallowed"); 389 } else { 390 RegistryImpl.checkAccess("ActivationSystem.unbind"); 391 super.unbind(name); 392 } 393 } 394 395 396 public void rebind(String name, Remote obj) 397 throws RemoteException, AccessException 398 { 399 if (name.equals(NAME)) { 400 throw new AccessException( 401 "binding ActivationSystem is disallowed"); 402 } else { 403 RegistryImpl.checkAccess("ActivationSystem.rebind"); 404 super.rebind(name, obj); 405 } 406 } 407 } 408 409 410 class ActivatorImpl extends RemoteServer implements Activator { 411 // Because ActivatorImpl has a fixed ObjID, it can be 412 // called by clients holding stale remote references. Each of 413 // its remote methods, then, must check startupLock (calling 414 // checkShutdown() is easiest). 415 416 private static final long serialVersionUID = -3654244726254566136L; 417 418 /** 419 * Construct a new Activator on a specified port. 420 */ 421 ActivatorImpl(int port, RMIServerSocketFactory ssf) 422 throws RemoteException 423 { 424 /* Server ref must be created and assigned before remote object 425 * 'this' can be exported. 426 */ 427 LiveRef lref = 428 new LiveRef(new ObjID(ObjID.ACTIVATOR_ID), port, null, ssf); 429 UnicastServerRef uref = new UnicastServerRef(lref); 430 ref = uref; 431 uref.exportObject(this, null, false); 432 } 433 434 public MarshalledObject<? extends Remote> activate(ActivationID id, 435 boolean force) 436 throws ActivationException, UnknownObjectException, RemoteException 437 { 438 checkShutdown(); 439 return getGroupEntry(id).activate(id, force); 440 } 441 } 442 443 class ActivationMonitorImpl extends UnicastRemoteObject 444 implements ActivationMonitor 445 { 446 private static final long serialVersionUID = -6214940464757948867L; 447 448 ActivationMonitorImpl(int port, RMIServerSocketFactory ssf) 449 throws RemoteException 450 { 451 super(port, null, ssf); 452 } 453 454 public void inactiveObject(ActivationID id) 455 throws UnknownObjectException, RemoteException 456 { 457 try { 458 checkShutdown(); 459 } catch (ActivationException e) { 460 return; 461 } 462 RegistryImpl.checkAccess("Activator.inactiveObject"); 463 getGroupEntry(id).inactiveObject(id); 464 } 465 466 public void activeObject(ActivationID id, 467 MarshalledObject<? extends Remote> mobj) 468 throws UnknownObjectException, RemoteException 469 { 470 try { 471 checkShutdown(); 472 } catch (ActivationException e) { 473 return; 474 } 475 RegistryImpl.checkAccess("ActivationSystem.activeObject"); 476 getGroupEntry(id).activeObject(id, mobj); 477 } 478 479 public void inactiveGroup(ActivationGroupID id, 480 long incarnation) 481 throws UnknownGroupException, RemoteException 482 { 483 try { 484 checkShutdown(); 485 } catch (ActivationException e) { 486 return; 487 } 488 RegistryImpl.checkAccess("ActivationMonitor.inactiveGroup"); 489 getGroupEntry(id).inactiveGroup(incarnation, false); 490 } 491 } 492 493 494 /** 495 * SameHostOnlyServerRef checks that access is from a local client 496 * before the parameters are deserialized. The unmarshalCustomCallData 497 * hook is used to check the network address of the caller 498 * with RegistryImpl.checkAccess(). 499 * The kind of access is retained for an exception if one is thrown. 500 */ 501 static class SameHostOnlyServerRef extends UnicastServerRef { 502 private static final long serialVersionUID = 1234L; 503 private String accessKind; // an exception message 504 505 /** 506 * Construct a new SameHostOnlyServerRef from a LiveRef. 507 * @param lref a LiveRef 508 */ 509 SameHostOnlyServerRef(LiveRef lref, String accessKind) { 510 super(lref); 511 this.accessKind = accessKind; 512 } 513 514 @Override 515 protected void unmarshalCustomCallData(ObjectInput in) throws IOException, ClassNotFoundException { 516 RegistryImpl.checkAccess(accessKind); 517 super.unmarshalCustomCallData(in); 518 } 519 } 520 521 class ActivationSystemImpl 522 extends RemoteServer 523 implements ActivationSystem 524 { 525 private static final long serialVersionUID = 9100152600327688967L; 526 527 // Because ActivationSystemImpl has a fixed ObjID, it can be 528 // called by clients holding stale remote references. Each of 529 // its remote methods, then, must check startupLock (calling 530 // checkShutdown() is easiest). 531 ActivationSystemImpl(int port, RMIServerSocketFactory ssf) 532 throws RemoteException 533 { 534 /* Server ref must be created and assigned before remote object 535 * 'this' can be exported. 536 */ 537 LiveRef lref = new LiveRef(new ObjID(4), port, null, ssf); 538 UnicastServerRef uref = new SameHostOnlyServerRef(lref, 539 "ActivationSystem.nonLocalAccess"); 540 ref = uref; 541 uref.exportObject(this, null); 542 } 543 544 public ActivationID registerObject(ActivationDesc desc) 545 throws ActivationException, UnknownGroupException, RemoteException 546 { 547 checkShutdown(); 548 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 549 // during unmarshallCustomData and is not applicable to local access. 550 ActivationGroupID groupID = desc.getGroupID(); 551 ActivationID id = new ActivationID(activatorStub); 552 getGroupEntry(groupID).registerObject(id, desc, true); 553 return id; 554 } 555 556 public void unregisterObject(ActivationID id) 557 throws ActivationException, UnknownObjectException, RemoteException 558 { 559 checkShutdown(); 560 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 561 // during unmarshallCustomData and is not applicable to local access. 562 getGroupEntry(id).unregisterObject(id, true); 563 } 564 565 public ActivationGroupID registerGroup(ActivationGroupDesc desc) 566 throws ActivationException, RemoteException 567 { 568 checkShutdown(); 569 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 570 // during unmarshallCustomData and is not applicable to local access. 571 checkArgs(desc, null); 572 573 ActivationGroupID id = new ActivationGroupID(systemStub); 574 GroupEntry entry = new GroupEntry(id, desc); 575 // table insertion must take place before log update 576 groupTable.put(id, entry); 577 addLogRecord(new LogRegisterGroup(id, desc)); 578 return id; 579 } 580 581 public ActivationMonitor activeGroup(ActivationGroupID id, 582 ActivationInstantiator group, 583 long incarnation) 584 throws ActivationException, UnknownGroupException, RemoteException 585 { 586 checkShutdown(); 587 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 588 // during unmarshallCustomData and is not applicable to local access. 589 590 getGroupEntry(id).activeGroup(group, incarnation); 591 return monitor; 592 } 593 594 public void unregisterGroup(ActivationGroupID id) 595 throws ActivationException, UnknownGroupException, RemoteException 596 { 597 checkShutdown(); 598 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 599 // during unmarshallCustomData and is not applicable to local access. 600 601 // remove entry before unregister so state is updated before 602 // logged 603 removeGroupEntry(id).unregisterGroup(true); 604 } 605 606 public ActivationDesc setActivationDesc(ActivationID id, 607 ActivationDesc desc) 608 throws ActivationException, UnknownObjectException, RemoteException 609 { 610 checkShutdown(); 611 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 612 // during unmarshallCustomData and is not applicable to local access. 613 614 if (!getGroupID(id).equals(desc.getGroupID())) { 615 throw new ActivationException( 616 "ActivationDesc contains wrong group"); 617 } 618 return getGroupEntry(id).setActivationDesc(id, desc, true); 619 } 620 621 public ActivationGroupDesc setActivationGroupDesc(ActivationGroupID id, 622 ActivationGroupDesc desc) 623 throws ActivationException, UnknownGroupException, RemoteException 624 { 625 checkShutdown(); 626 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 627 // during unmarshallCustomData and is not applicable to local access. 628 629 checkArgs(desc, null); 630 return getGroupEntry(id).setActivationGroupDesc(id, desc, true); 631 } 632 633 public ActivationDesc getActivationDesc(ActivationID id) 634 throws ActivationException, UnknownObjectException, RemoteException 635 { 636 checkShutdown(); 637 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 638 // during unmarshallCustomData and is not applicable to local access. 639 640 return getGroupEntry(id).getActivationDesc(id); 641 } 642 643 public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id) 644 throws ActivationException, UnknownGroupException, RemoteException 645 { 646 checkShutdown(); 647 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 648 // during unmarshallCustomData and is not applicable to local access. 649 650 return getGroupEntry(id).desc; 651 } 652 653 /** 654 * Shutdown the activation system. Destroys all groups spawned by 655 * the activation daemon and exits the activation daemon. 656 */ 657 public void shutdown() throws AccessException { 658 // RegistryImpl.checkAccess() is done in the SameHostOnlyServerRef 659 // during unmarshallCustomData and is not applicable to local access. 660 661 Object lock = startupLock; 662 if (lock != null) { 663 synchronized (lock) { 664 // nothing 665 } 666 } 667 668 synchronized (Activation.this) { 669 if (!shuttingDown) { 670 shuttingDown = true; 671 (new Shutdown()).start(); 672 } 673 } 674 } 675 } 676 677 private void checkShutdown() throws ActivationException { 678 // if the startup critical section is running, wait until it 679 // completes/fails before continuing with the remote call. 680 Object lock = startupLock; 681 if (lock != null) { 682 synchronized (lock) { 683 // nothing 684 } 685 } 686 687 if (shuttingDown == true) { 688 throw new ActivationException( 689 "activation system shutting down"); 690 } 691 } 692 693 private static void unexport(Remote obj) { 694 for (;;) { 695 try { 696 if (UnicastRemoteObject.unexportObject(obj, false) == true) { 697 break; 698 } else { 699 Thread.sleep(100); 700 } 701 } catch (Exception e) { 702 continue; 703 } 704 } 705 } 706 707 /** 708 * Thread to shutdown rmid. 709 */ 710 private class Shutdown extends Thread { 711 Shutdown() { 712 super("rmid Shutdown"); 713 } 714 715 public void run() { 716 try { 717 /* 718 * Unexport activation system services 719 */ 720 unexport(activator); 721 unexport(system); 722 723 // destroy all child processes (groups) 724 for (GroupEntry groupEntry : groupTable.values()) { 725 groupEntry.shutdown(); 726 } 727 728 Runtime.getRuntime().removeShutdownHook(shutdownHook); 729 730 /* 731 * Unexport monitor safely since all processes are destroyed. 732 */ 733 unexport(monitor); 734 735 /* 736 * Close log file, fix for 4243264: rmid shutdown thread 737 * interferes with remote calls in progress. Make sure 738 * the log file is only closed when it is impossible for 739 * its closure to interfere with any pending remote calls. 740 * We close the log when all objects in the rmid VM are 741 * unexported. 742 */ 743 try { 744 synchronized (log) { 745 log.close(); 746 } 747 } catch (IOException e) { 748 } 749 750 } finally { 751 /* 752 * Now exit... A System.exit should only be done if 753 * the RMI activation system daemon was started up 754 * by the main method below (in which should always 755 * be the case since the Activation constructor is private). 756 */ 757 System.err.println(getTextResource("rmid.daemon.shutdown")); 758 System.exit(0); 759 } 760 } 761 } 762 763 /** Thread to destroy children in the event of abnormal termination. */ 764 private class ShutdownHook extends Thread { 765 ShutdownHook() { 766 super("rmid ShutdownHook"); 767 } 768 769 public void run() { 770 synchronized (Activation.this) { 771 shuttingDown = true; 772 } 773 774 // destroy all child processes (groups) quickly 775 for (GroupEntry groupEntry : groupTable.values()) { 776 groupEntry.shutdownFast(); 777 } 778 } 779 } 780 781 /** 782 * Returns the groupID for a given id of an object in the group. 783 * Throws UnknownObjectException if the object is not registered. 784 */ 785 private ActivationGroupID getGroupID(ActivationID id) 786 throws UnknownObjectException 787 { 788 ActivationGroupID groupID = idTable.get(id); 789 if (groupID != null) { 790 return groupID; 791 } 792 throw new UnknownObjectException("unknown object: " + id); 793 } 794 795 /** 796 * Returns the group entry for the group id, optionally removing it. 797 * Throws UnknownGroupException if the group is not registered. 798 */ 799 private GroupEntry getGroupEntry(ActivationGroupID id, boolean rm) 800 throws UnknownGroupException 801 { 802 if (id.getClass() == ActivationGroupID.class) { 803 GroupEntry entry; 804 if (rm) { 805 entry = groupTable.remove(id); 806 } else { 807 entry = groupTable.get(id); 808 } 809 if (entry != null && !entry.removed) { 810 return entry; 811 } 812 } 813 throw new UnknownGroupException("group unknown"); 814 } 815 816 /** 817 * Returns the group entry for the group id. Throws 818 * UnknownGroupException if the group is not registered. 819 */ 820 private GroupEntry getGroupEntry(ActivationGroupID id) 821 throws UnknownGroupException 822 { 823 return getGroupEntry(id, false); 824 } 825 826 /** 827 * Removes and returns the group entry for the group id. Throws 828 * UnknownGroupException if the group is not registered. 829 */ 830 private GroupEntry removeGroupEntry(ActivationGroupID id) 831 throws UnknownGroupException 832 { 833 return getGroupEntry(id, true); 834 } 835 836 /** 837 * Returns the group entry for the object's id. Throws 838 * UnknownObjectException if the object is not registered or the 839 * object's group is not registered. 840 */ 841 private GroupEntry getGroupEntry(ActivationID id) 842 throws UnknownObjectException 843 { 844 ActivationGroupID gid = getGroupID(id); 845 GroupEntry entry = groupTable.get(gid); 846 if (entry != null && !entry.removed) { 847 return entry; 848 } 849 throw new UnknownObjectException("object's group removed"); 850 } 851 852 /** 853 * Container for group information: group's descriptor, group's 854 * instantiator, flag to indicate pending group creation, and 855 * table of the objects that are activated in the group. 856 * 857 * WARNING: GroupEntry objects should not be written into log file 858 * updates. GroupEntrys are inner classes of Activation and they 859 * can not be serialized independent of this class. If the 860 * complete Activation system is written out as a log update, the 861 * point of having updates is nullified. 862 */ 863 private class GroupEntry implements Serializable { 864 865 /** indicate compatibility with JDK 1.2 version of class */ 866 private static final long serialVersionUID = 7222464070032993304L; 867 private static final int MAX_TRIES = 2; 868 private static final int NORMAL = 0; 869 private static final int CREATING = 1; 870 private static final int TERMINATE = 2; 871 private static final int TERMINATING = 3; 872 873 ActivationGroupDesc desc = null; 874 ActivationGroupID groupID = null; 875 long incarnation = 0; 876 Map<ActivationID,ObjectEntry> objects = new HashMap<>(); 877 Set<ActivationID> restartSet = new HashSet<>(); 878 879 transient ActivationInstantiator group = null; 880 transient int status = NORMAL; 881 transient long waitTime = 0; 882 transient String groupName = null; 883 transient Process child = null; 884 transient boolean removed = false; 885 transient Watchdog watchdog = null; 886 887 GroupEntry(ActivationGroupID groupID, ActivationGroupDesc desc) { 888 this.groupID = groupID; 889 this.desc = desc; 890 } 891 892 void restartServices() { 893 Iterator<ActivationID> iter = null; 894 895 synchronized (this) { 896 if (restartSet.isEmpty()) { 897 return; 898 } 899 900 /* 901 * Clone the restartSet so the set does not have to be locked 902 * during iteration. Locking the restartSet could cause 903 * deadlock if an object we are restarting caused another 904 * object in this group to be activated. 905 */ 906 iter = (new HashSet<ActivationID>(restartSet)).iterator(); 907 } 908 909 while (iter.hasNext()) { 910 ActivationID id = iter.next(); 911 try { 912 activate(id, true); 913 } catch (Exception e) { 914 if (shuttingDown) { 915 return; 916 } 917 System.err.println( 918 getTextResource("rmid.restart.service.warning")); 919 e.printStackTrace(); 920 } 921 } 922 } 923 924 synchronized void activeGroup(ActivationInstantiator inst, 925 long instIncarnation) 926 throws ActivationException, UnknownGroupException 927 { 928 if (incarnation != instIncarnation) { 929 throw new ActivationException("invalid incarnation"); 930 } 931 932 if (group != null) { 933 if (group.equals(inst)) { 934 return; 935 } else { 936 throw new ActivationException("group already active"); 937 } 938 } 939 940 if (child != null && status != CREATING) { 941 throw new ActivationException("group not being created"); 942 } 943 944 group = inst; 945 status = NORMAL; 946 notifyAll(); 947 } 948 949 private void checkRemoved() throws UnknownGroupException { 950 if (removed) { 951 throw new UnknownGroupException("group removed"); 952 } 953 } 954 955 private ObjectEntry getObjectEntry(ActivationID id) 956 throws UnknownObjectException 957 { 958 if (removed) { 959 throw new UnknownObjectException("object's group removed"); 960 } 961 ObjectEntry objEntry = objects.get(id); 962 if (objEntry == null) { 963 throw new UnknownObjectException("object unknown"); 964 } 965 return objEntry; 966 } 967 968 synchronized void registerObject(ActivationID id, 969 ActivationDesc desc, 970 boolean addRecord) 971 throws UnknownGroupException, ActivationException 972 { 973 checkRemoved(); 974 objects.put(id, new ObjectEntry(desc)); 975 if (desc.getRestartMode() == true) { 976 restartSet.add(id); 977 } 978 979 // table insertion must take place before log update 980 idTable.put(id, groupID); 981 982 if (addRecord) { 983 addLogRecord(new LogRegisterObject(id, desc)); 984 } 985 } 986 987 synchronized void unregisterObject(ActivationID id, boolean addRecord) 988 throws UnknownGroupException, ActivationException 989 { 990 ObjectEntry objEntry = getObjectEntry(id); 991 objEntry.removed = true; 992 objects.remove(id); 993 if (objEntry.desc.getRestartMode() == true) { 994 restartSet.remove(id); 995 } 996 997 // table removal must take place before log update 998 idTable.remove(id); 999 if (addRecord) { 1000 addLogRecord(new LogUnregisterObject(id)); 1001 } 1002 } 1003 1004 synchronized void unregisterGroup(boolean addRecord) 1005 throws UnknownGroupException, ActivationException 1006 { 1007 checkRemoved(); 1008 removed = true; 1009 for (Map.Entry<ActivationID,ObjectEntry> entry : 1010 objects.entrySet()) 1011 { 1012 ActivationID id = entry.getKey(); 1013 idTable.remove(id); 1014 ObjectEntry objEntry = entry.getValue(); 1015 objEntry.removed = true; 1016 } 1017 objects.clear(); 1018 restartSet.clear(); 1019 reset(); 1020 childGone(); 1021 1022 // removal should be recorded before log update 1023 if (addRecord) { 1024 addLogRecord(new LogUnregisterGroup(groupID)); 1025 } 1026 } 1027 1028 synchronized ActivationDesc setActivationDesc(ActivationID id, 1029 ActivationDesc desc, 1030 boolean addRecord) 1031 throws UnknownObjectException, UnknownGroupException, 1032 ActivationException 1033 { 1034 ObjectEntry objEntry = getObjectEntry(id); 1035 ActivationDesc oldDesc = objEntry.desc; 1036 objEntry.desc = desc; 1037 if (desc.getRestartMode() == true) { 1038 restartSet.add(id); 1039 } else { 1040 restartSet.remove(id); 1041 } 1042 // restart information should be recorded before log update 1043 if (addRecord) { 1044 addLogRecord(new LogUpdateDesc(id, desc)); 1045 } 1046 1047 return oldDesc; 1048 } 1049 1050 synchronized ActivationDesc getActivationDesc(ActivationID id) 1051 throws UnknownObjectException, UnknownGroupException 1052 { 1053 return getObjectEntry(id).desc; 1054 } 1055 1056 synchronized ActivationGroupDesc setActivationGroupDesc( 1057 ActivationGroupID id, 1058 ActivationGroupDesc desc, 1059 boolean addRecord) 1060 throws UnknownGroupException, ActivationException 1061 { 1062 checkRemoved(); 1063 ActivationGroupDesc oldDesc = this.desc; 1064 this.desc = desc; 1065 // state update should occur before log update 1066 if (addRecord) { 1067 addLogRecord(new LogUpdateGroupDesc(id, desc)); 1068 } 1069 return oldDesc; 1070 } 1071 1072 synchronized void inactiveGroup(long incarnation, boolean failure) 1073 throws UnknownGroupException 1074 { 1075 checkRemoved(); 1076 if (this.incarnation != incarnation) { 1077 throw new UnknownGroupException("invalid incarnation"); 1078 } 1079 1080 reset(); 1081 if (failure) { 1082 terminate(); 1083 } else if (child != null && status == NORMAL) { 1084 status = TERMINATE; 1085 watchdog.noRestart(); 1086 } 1087 } 1088 1089 synchronized void activeObject(ActivationID id, 1090 MarshalledObject<? extends Remote> mobj) 1091 throws UnknownObjectException 1092 { 1093 getObjectEntry(id).stub = mobj; 1094 } 1095 1096 synchronized void inactiveObject(ActivationID id) 1097 throws UnknownObjectException 1098 { 1099 getObjectEntry(id).reset(); 1100 } 1101 1102 private synchronized void reset() { 1103 group = null; 1104 for (ObjectEntry objectEntry : objects.values()) { 1105 objectEntry.reset(); 1106 } 1107 } 1108 1109 private void childGone() { 1110 if (child != null) { 1111 child = null; 1112 watchdog.dispose(); 1113 watchdog = null; 1114 status = NORMAL; 1115 notifyAll(); 1116 } 1117 } 1118 1119 private void terminate() { 1120 if (child != null && status != TERMINATING) { 1121 child.destroy(); 1122 status = TERMINATING; 1123 waitTime = System.currentTimeMillis() + groupTimeout; 1124 notifyAll(); 1125 } 1126 } 1127 1128 /* 1129 * Fallthrough from TERMINATE to TERMINATING 1130 * is intentional 1131 */ 1132 @SuppressWarnings("fallthrough") 1133 private void await() { 1134 while (true) { 1135 switch (status) { 1136 case NORMAL: 1137 return; 1138 case TERMINATE: 1139 terminate(); 1140 case TERMINATING: 1141 try { 1142 child.exitValue(); 1143 } catch (IllegalThreadStateException e) { 1144 long now = System.currentTimeMillis(); 1145 if (waitTime > now) { 1146 try { 1147 wait(waitTime - now); 1148 } catch (InterruptedException ee) { 1149 } 1150 continue; 1151 } 1152 // REMIND: print message that group did not terminate? 1153 } 1154 childGone(); 1155 return; 1156 case CREATING: 1157 try { 1158 wait(); 1159 } catch (InterruptedException e) { 1160 } 1161 } 1162 } 1163 } 1164 1165 // no synchronization to avoid delay wrt getInstantiator 1166 void shutdownFast() { 1167 Process p = child; 1168 if (p != null) { 1169 p.destroy(); 1170 } 1171 } 1172 1173 synchronized void shutdown() { 1174 reset(); 1175 terminate(); 1176 await(); 1177 } 1178 1179 MarshalledObject<? extends Remote> activate(ActivationID id, 1180 boolean force) 1181 throws ActivationException 1182 { 1183 Exception detail = null; 1184 1185 /* 1186 * Attempt to activate object and reattempt (several times) 1187 * if activation fails due to communication problems. 1188 */ 1189 for (int tries = MAX_TRIES; tries > 0; tries--) { 1190 ActivationInstantiator inst; 1191 long currentIncarnation; 1192 1193 // look up object to activate 1194 ObjectEntry objEntry; 1195 synchronized (this) { 1196 objEntry = getObjectEntry(id); 1197 // if not forcing activation, return cached stub 1198 if (!force && objEntry.stub != null) { 1199 return objEntry.stub; 1200 } 1201 inst = getInstantiator(groupID); 1202 currentIncarnation = incarnation; 1203 } 1204 1205 boolean groupInactive = false; 1206 boolean failure = false; 1207 // activate object 1208 try { 1209 return objEntry.activate(id, force, inst); 1210 } catch (NoSuchObjectException e) { 1211 groupInactive = true; 1212 detail = e; 1213 } catch (ConnectException e) { 1214 groupInactive = true; 1215 failure = true; 1216 detail = e; 1217 } catch (ConnectIOException e) { 1218 groupInactive = true; 1219 failure = true; 1220 detail = e; 1221 } catch (InactiveGroupException e) { 1222 groupInactive = true; 1223 detail = e; 1224 } catch (RemoteException e) { 1225 // REMIND: wait some here before continuing? 1226 if (detail == null) { 1227 detail = e; 1228 } 1229 } 1230 1231 if (groupInactive) { 1232 // group has failed or is inactive; mark inactive 1233 try { 1234 System.err.println( 1235 MessageFormat.format( 1236 getTextResource("rmid.group.inactive"), 1237 detail.toString())); 1238 detail.printStackTrace(); 1239 getGroupEntry(groupID). 1240 inactiveGroup(currentIncarnation, failure); 1241 } catch (UnknownGroupException e) { 1242 // not a problem 1243 } 1244 } 1245 } 1246 1247 /** 1248 * signal that group activation failed, nested exception 1249 * specifies what exception occurred when the group did not 1250 * activate 1251 */ 1252 throw new ActivationException("object activation failed after " + 1253 MAX_TRIES + " tries", detail); 1254 } 1255 1256 /** 1257 * Returns the instantiator for the group specified by id and 1258 * entry. If the group is currently inactive, exec some 1259 * bootstrap code to create the group. 1260 */ 1261 private ActivationInstantiator getInstantiator(ActivationGroupID id) 1262 throws ActivationException 1263 { 1264 assert Thread.holdsLock(this); 1265 1266 await(); 1267 if (group != null) { 1268 return group; 1269 } 1270 checkRemoved(); 1271 boolean acquired = false; 1272 1273 try { 1274 groupName = Pstartgroup(); 1275 acquired = true; 1276 String[] argv = activationArgs(desc); 1277 checkArgs(desc, argv); 1278 1279 if (debugExec) { 1280 StringBuilder sb = new StringBuilder(argv[0]); 1281 int j; 1282 for (j = 1; j < argv.length; j++) { 1283 sb.append(' '); 1284 sb.append(argv[j]); 1285 } 1286 System.err.println( 1287 MessageFormat.format( 1288 getTextResource("rmid.exec.command"), 1289 sb.toString())); 1290 } 1291 1292 try { 1293 child = Runtime.getRuntime().exec(argv); 1294 status = CREATING; 1295 ++incarnation; 1296 watchdog = new Watchdog(); 1297 watchdog.start(); 1298 addLogRecord(new LogGroupIncarnation(id, incarnation)); 1299 1300 // handle child I/O streams before writing to child 1301 PipeWriter.plugTogetherPair 1302 (child.getInputStream(), System.out, 1303 child.getErrorStream(), System.err); 1304 try (MarshalOutputStream out = 1305 new MarshalOutputStream(child.getOutputStream())) { 1306 out.writeObject(id); 1307 out.writeObject(desc); 1308 out.writeLong(incarnation); 1309 out.flush(); 1310 } 1311 1312 1313 } catch (IOException e) { 1314 terminate(); 1315 throw new ActivationException( 1316 "unable to create activation group", e); 1317 } 1318 1319 try { 1320 long now = System.currentTimeMillis(); 1321 long stop = now + execTimeout; 1322 do { 1323 wait(stop - now); 1324 if (group != null) { 1325 return group; 1326 } 1327 now = System.currentTimeMillis(); 1328 } while (status == CREATING && now < stop); 1329 } catch (InterruptedException e) { 1330 } 1331 1332 terminate(); 1333 throw new ActivationException( 1334 (removed ? 1335 "activation group unregistered" : 1336 "timeout creating child process")); 1337 } finally { 1338 if (acquired) { 1339 Vstartgroup(); 1340 } 1341 } 1342 } 1343 1344 /** 1345 * Waits for process termination and then restarts services. 1346 */ 1347 private class Watchdog extends Thread { 1348 private final Process groupProcess = child; 1349 private final long groupIncarnation = incarnation; 1350 private boolean canInterrupt = true; 1351 private boolean shouldQuit = false; 1352 private boolean shouldRestart = true; 1353 1354 Watchdog() { 1355 super("WatchDog-" + groupName + "-" + incarnation); 1356 setDaemon(true); 1357 } 1358 1359 public void run() { 1360 1361 if (shouldQuit) { 1362 return; 1363 } 1364 1365 /* 1366 * Wait for the group to crash or exit. 1367 */ 1368 try { 1369 groupProcess.waitFor(); 1370 } catch (InterruptedException exit) { 1371 return; 1372 } 1373 1374 boolean restart = false; 1375 synchronized (GroupEntry.this) { 1376 if (shouldQuit) { 1377 return; 1378 } 1379 canInterrupt = false; 1380 interrupted(); // clear interrupt bit 1381 /* 1382 * Since the group crashed, we should 1383 * reset the entry before activating objects 1384 */ 1385 if (groupIncarnation == incarnation) { 1386 restart = shouldRestart && !shuttingDown; 1387 reset(); 1388 childGone(); 1389 } 1390 } 1391 1392 /* 1393 * Activate those objects that require restarting 1394 * after a crash. 1395 */ 1396 if (restart) { 1397 restartServices(); 1398 } 1399 } 1400 1401 /** 1402 * Marks this thread as one that is no longer needed. 1403 * If the thread is in a state in which it can be interrupted, 1404 * then the thread is interrupted. 1405 */ 1406 void dispose() { 1407 shouldQuit = true; 1408 if (canInterrupt) { 1409 interrupt(); 1410 } 1411 } 1412 1413 /** 1414 * Marks this thread as no longer needing to restart objects. 1415 */ 1416 void noRestart() { 1417 shouldRestart = false; 1418 } 1419 } 1420 } 1421 1422 private String[] activationArgs(ActivationGroupDesc desc) { 1423 ActivationGroupDesc.CommandEnvironment cmdenv; 1424 cmdenv = desc.getCommandEnvironment(); 1425 1426 // argv is the literal command to exec 1427 List<String> argv = new ArrayList<>(); 1428 1429 // Command name/path 1430 argv.add((cmdenv != null && cmdenv.getCommandPath() != null) 1431 ? cmdenv.getCommandPath() 1432 : command[0]); 1433 1434 // Group-specific command options 1435 if (cmdenv != null && cmdenv.getCommandOptions() != null) { 1436 argv.addAll(Arrays.asList(cmdenv.getCommandOptions())); 1437 } 1438 1439 // Properties become -D parameters 1440 Properties props = desc.getPropertyOverrides(); 1441 if (props != null) { 1442 for (Enumeration<?> p = props.propertyNames(); 1443 p.hasMoreElements();) 1444 { 1445 String name = (String) p.nextElement(); 1446 /* Note on quoting: it would be wrong 1447 * here, since argv will be passed to 1448 * Runtime.exec, which should not parse 1449 * arguments or split on whitespace. 1450 */ 1451 argv.add("-D" + name + "=" + props.getProperty(name)); 1452 } 1453 } 1454 1455 /* Finally, rmid-global command options (e.g. -C options) 1456 * and the classname 1457 */ 1458 for (int i = 1; i < command.length; i++) { 1459 argv.add(command[i]); 1460 } 1461 1462 String[] realArgv = new String[argv.size()]; 1463 System.arraycopy(argv.toArray(), 0, realArgv, 0, realArgv.length); 1464 1465 return realArgv; 1466 } 1467 1468 private void checkArgs(ActivationGroupDesc desc, String[] cmd) 1469 throws SecurityException, ActivationException 1470 { 1471 /* 1472 * Check exec command using execPolicy object 1473 */ 1474 if (execPolicyMethod != null) { 1475 if (cmd == null) { 1476 cmd = activationArgs(desc); 1477 } 1478 try { 1479 execPolicyMethod.invoke(execPolicy, desc, cmd); 1480 } catch (InvocationTargetException e) { 1481 Throwable targetException = e.getTargetException(); 1482 if (targetException instanceof SecurityException) { 1483 throw (SecurityException) targetException; 1484 } else { 1485 throw new ActivationException( 1486 execPolicyMethod.getName() + ": unexpected exception", 1487 e); 1488 } 1489 } catch (Exception e) { 1490 throw new ActivationException( 1491 execPolicyMethod.getName() + ": unexpected exception", e); 1492 } 1493 } 1494 } 1495 1496 private static class ObjectEntry implements Serializable { 1497 1498 private static final long serialVersionUID = -5500114225321357856L; 1499 1500 /** descriptor for object */ 1501 ActivationDesc desc; 1502 /** the stub (if active) */ 1503 volatile transient MarshalledObject<? extends Remote> stub = null; 1504 volatile transient boolean removed = false; 1505 1506 ObjectEntry(ActivationDesc desc) { 1507 this.desc = desc; 1508 } 1509 1510 synchronized MarshalledObject<? extends Remote> 1511 activate(ActivationID id, 1512 boolean force, 1513 ActivationInstantiator inst) 1514 throws RemoteException, ActivationException 1515 { 1516 MarshalledObject<? extends Remote> nstub = stub; 1517 if (removed) { 1518 throw new UnknownObjectException("object removed"); 1519 } else if (!force && nstub != null) { 1520 return nstub; 1521 } 1522 1523 nstub = inst.newInstance(id, desc); 1524 stub = nstub; 1525 /* 1526 * stub could be set to null by a group reset, so return 1527 * the newstub here to prevent returning null. 1528 */ 1529 return nstub; 1530 } 1531 1532 void reset() { 1533 stub = null; 1534 } 1535 } 1536 1537 /** 1538 * Add a record to the activation log. If the number of updates 1539 * passes a predetermined threshold, record a snapshot. 1540 */ 1541 private void addLogRecord(LogRecord rec) throws ActivationException { 1542 synchronized (log) { 1543 checkShutdown(); 1544 try { 1545 log.update(rec, true); 1546 } catch (Exception e) { 1547 numUpdates = snapshotInterval; 1548 System.err.println(getTextResource("rmid.log.update.warning")); 1549 e.printStackTrace(); 1550 } 1551 if (++numUpdates < snapshotInterval) { 1552 return; 1553 } 1554 try { 1555 log.snapshot(this); 1556 numUpdates = 0; 1557 } catch (Exception e) { 1558 System.err.println( 1559 getTextResource("rmid.log.snapshot.warning")); 1560 e.printStackTrace(); 1561 try { 1562 // shutdown activation system because snapshot failed 1563 system.shutdown(); 1564 } catch (RemoteException ignore) { 1565 // can't happen 1566 } 1567 // warn the client of the original update problem 1568 throw new ActivationException("log snapshot failed", e); 1569 } 1570 } 1571 } 1572 1573 /** 1574 * Handler for the log that knows how to take the initial snapshot 1575 * and apply an update (a LogRecord) to the current state. 1576 */ 1577 private static class ActLogHandler extends LogHandler { 1578 1579 ActLogHandler() { 1580 } 1581 1582 public Object initialSnapshot() 1583 { 1584 /** 1585 * Return an empty Activation object. Log will update 1586 * this object with recovered state. 1587 */ 1588 return new Activation(); 1589 } 1590 1591 public Object applyUpdate(Object update, Object state) 1592 throws Exception 1593 { 1594 return ((LogRecord) update).apply(state); 1595 } 1596 1597 } 1598 1599 /** 1600 * Abstract class for all log records. The subclass contains 1601 * specific update information and implements the apply method 1602 * that applys the update information contained in the record 1603 * to the current state. 1604 */ 1605 private static abstract class LogRecord implements Serializable { 1606 /** indicate compatibility with JDK 1.2 version of class */ 1607 private static final long serialVersionUID = 8395140512322687529L; 1608 abstract Object apply(Object state) throws Exception; 1609 } 1610 1611 /** 1612 * Log record for registering an object. 1613 */ 1614 private static class LogRegisterObject extends LogRecord { 1615 /** indicate compatibility with JDK 1.2 version of class */ 1616 private static final long serialVersionUID = -6280336276146085143L; 1617 private ActivationID id; 1618 private ActivationDesc desc; 1619 1620 LogRegisterObject(ActivationID id, ActivationDesc desc) { 1621 this.id = id; 1622 this.desc = desc; 1623 } 1624 1625 Object apply(Object state) { 1626 try { 1627 ((Activation) state).getGroupEntry(desc.getGroupID()). 1628 registerObject(id, desc, false); 1629 } catch (Exception ignore) { 1630 System.err.println( 1631 MessageFormat.format( 1632 getTextResource("rmid.log.recover.warning"), 1633 "LogRegisterObject")); 1634 ignore.printStackTrace(); 1635 } 1636 return state; 1637 } 1638 } 1639 1640 /** 1641 * Log record for unregistering an object. 1642 */ 1643 private static class LogUnregisterObject extends LogRecord { 1644 /** indicate compatibility with JDK 1.2 version of class */ 1645 private static final long serialVersionUID = 6269824097396935501L; 1646 private ActivationID id; 1647 1648 LogUnregisterObject(ActivationID id) { 1649 this.id = id; 1650 } 1651 1652 Object apply(Object state) { 1653 try { 1654 ((Activation) state).getGroupEntry(id). 1655 unregisterObject(id, false); 1656 } catch (Exception ignore) { 1657 System.err.println( 1658 MessageFormat.format( 1659 getTextResource("rmid.log.recover.warning"), 1660 "LogUnregisterObject")); 1661 ignore.printStackTrace(); 1662 } 1663 return state; 1664 } 1665 } 1666 1667 /** 1668 * Log record for registering a group. 1669 */ 1670 private static class LogRegisterGroup extends LogRecord { 1671 /** indicate compatibility with JDK 1.2 version of class */ 1672 private static final long serialVersionUID = -1966827458515403625L; 1673 private ActivationGroupID id; 1674 private ActivationGroupDesc desc; 1675 1676 LogRegisterGroup(ActivationGroupID id, ActivationGroupDesc desc) { 1677 this.id = id; 1678 this.desc = desc; 1679 } 1680 1681 Object apply(Object state) { 1682 // modify state directly; cant ask a nonexistent GroupEntry 1683 // to register itself. 1684 ((Activation) state).groupTable.put(id, ((Activation) state).new 1685 GroupEntry(id, desc)); 1686 return state; 1687 } 1688 } 1689 1690 /** 1691 * Log record for udpating an activation desc 1692 */ 1693 private static class LogUpdateDesc extends LogRecord { 1694 /** indicate compatibility with JDK 1.2 version of class */ 1695 private static final long serialVersionUID = 545511539051179885L; 1696 1697 private ActivationID id; 1698 private ActivationDesc desc; 1699 1700 LogUpdateDesc(ActivationID id, ActivationDesc desc) { 1701 this.id = id; 1702 this.desc = desc; 1703 } 1704 1705 Object apply(Object state) { 1706 try { 1707 ((Activation) state).getGroupEntry(id). 1708 setActivationDesc(id, desc, false); 1709 } catch (Exception ignore) { 1710 System.err.println( 1711 MessageFormat.format( 1712 getTextResource("rmid.log.recover.warning"), 1713 "LogUpdateDesc")); 1714 ignore.printStackTrace(); 1715 } 1716 return state; 1717 } 1718 } 1719 1720 /** 1721 * Log record for unregistering a group. 1722 */ 1723 private static class LogUpdateGroupDesc extends LogRecord { 1724 /** indicate compatibility with JDK 1.2 version of class */ 1725 private static final long serialVersionUID = -1271300989218424337L; 1726 private ActivationGroupID id; 1727 private ActivationGroupDesc desc; 1728 1729 LogUpdateGroupDesc(ActivationGroupID id, ActivationGroupDesc desc) { 1730 this.id = id; 1731 this.desc = desc; 1732 } 1733 1734 Object apply(Object state) { 1735 try { 1736 ((Activation) state).getGroupEntry(id). 1737 setActivationGroupDesc(id, desc, false); 1738 } catch (Exception ignore) { 1739 System.err.println( 1740 MessageFormat.format( 1741 getTextResource("rmid.log.recover.warning"), 1742 "LogUpdateGroupDesc")); 1743 ignore.printStackTrace(); 1744 } 1745 return state; 1746 } 1747 } 1748 1749 /** 1750 * Log record for unregistering a group. 1751 */ 1752 private static class LogUnregisterGroup extends LogRecord { 1753 /** indicate compatibility with JDK 1.2 version of class */ 1754 private static final long serialVersionUID = -3356306586522147344L; 1755 private ActivationGroupID id; 1756 1757 LogUnregisterGroup(ActivationGroupID id) { 1758 this.id = id; 1759 } 1760 1761 Object apply(Object state) { 1762 GroupEntry entry = ((Activation) state).groupTable.remove(id); 1763 try { 1764 entry.unregisterGroup(false); 1765 } catch (Exception ignore) { 1766 System.err.println( 1767 MessageFormat.format( 1768 getTextResource("rmid.log.recover.warning"), 1769 "LogUnregisterGroup")); 1770 ignore.printStackTrace(); 1771 } 1772 return state; 1773 } 1774 } 1775 1776 /** 1777 * Log record for an active group incarnation 1778 */ 1779 private static class LogGroupIncarnation extends LogRecord { 1780 /** indicate compatibility with JDK 1.2 version of class */ 1781 private static final long serialVersionUID = 4146872747377631897L; 1782 private ActivationGroupID id; 1783 private long inc; 1784 1785 LogGroupIncarnation(ActivationGroupID id, long inc) { 1786 this.id = id; 1787 this.inc = inc; 1788 } 1789 1790 Object apply(Object state) { 1791 try { 1792 GroupEntry entry = ((Activation) state).getGroupEntry(id); 1793 entry.incarnation = inc; 1794 } catch (Exception ignore) { 1795 System.err.println( 1796 MessageFormat.format( 1797 getTextResource("rmid.log.recover.warning"), 1798 "LogGroupIncarnation")); 1799 ignore.printStackTrace(); 1800 } 1801 return state; 1802 } 1803 } 1804 1805 /** 1806 * Initialize command to exec a default group. 1807 */ 1808 private void initCommand(String[] childArgs) { 1809 command = new String[childArgs.length + 2]; 1810 AccessController.doPrivileged(new PrivilegedAction<Void>() { 1811 public Void run() { 1812 try { 1813 command[0] = System.getProperty("java.home") + 1814 File.separator + "bin" + File.separator + "java"; 1815 } catch (Exception e) { 1816 System.err.println( 1817 getTextResource("rmid.unfound.java.home.property")); 1818 command[0] = "java"; 1819 } 1820 return null; 1821 } 1822 }); 1823 System.arraycopy(childArgs, 0, command, 1, childArgs.length); 1824 command[command.length-1] = "sun.rmi.server.ActivationGroupInit"; 1825 } 1826 1827 private static void bomb(String error) { 1828 System.err.println("rmid: " + error); // $NON-NLS$ 1829 System.err.println(MessageFormat.format(getTextResource("rmid.usage"), 1830 "rmid")); 1831 System.exit(1); 1832 } 1833 1834 /** 1835 * The default policy for checking a command before it is executed 1836 * makes sure the appropriate com.sun.rmi.rmid.ExecPermission and 1837 * set of com.sun.rmi.rmid.ExecOptionPermissions have been granted. 1838 */ 1839 public static class DefaultExecPolicy { 1840 1841 public void checkExecCommand(ActivationGroupDesc desc, String[] cmd) 1842 throws SecurityException 1843 { 1844 PermissionCollection perms = getExecPermissions(); 1845 1846 /* 1847 * Check properties overrides. 1848 */ 1849 Properties props = desc.getPropertyOverrides(); 1850 if (props != null) { 1851 Enumeration<?> p = props.propertyNames(); 1852 while (p.hasMoreElements()) { 1853 String name = (String) p.nextElement(); 1854 String value = props.getProperty(name); 1855 String option = "-D" + name + "=" + value; 1856 try { 1857 checkPermission(perms, 1858 new ExecOptionPermission(option)); 1859 } catch (AccessControlException e) { 1860 if (value.equals("")) { 1861 checkPermission(perms, 1862 new ExecOptionPermission("-D" + name)); 1863 } else { 1864 throw e; 1865 } 1866 } 1867 } 1868 } 1869 1870 /* 1871 * Check group class name (allow nothing but the default), 1872 * code location (must be null), and data (must be null). 1873 */ 1874 String groupClassName = desc.getClassName(); 1875 if ((groupClassName != null && 1876 !groupClassName.equals( 1877 ActivationGroupImpl.class.getName())) || 1878 (desc.getLocation() != null) || 1879 (desc.getData() != null)) 1880 { 1881 throw new AccessControlException( 1882 "access denied (custom group implementation not allowed)"); 1883 } 1884 1885 /* 1886 * If group descriptor has a command environment, check 1887 * command and options. 1888 */ 1889 ActivationGroupDesc.CommandEnvironment cmdenv; 1890 cmdenv = desc.getCommandEnvironment(); 1891 if (cmdenv != null) { 1892 String path = cmdenv.getCommandPath(); 1893 if (path != null) { 1894 checkPermission(perms, new ExecPermission(path)); 1895 } 1896 1897 String[] options = cmdenv.getCommandOptions(); 1898 if (options != null) { 1899 for (String option : options) { 1900 checkPermission(perms, 1901 new ExecOptionPermission(option)); 1902 } 1903 } 1904 } 1905 } 1906 1907 /** 1908 * Prints warning message if installed Policy is the default Policy 1909 * implementation and globally granted permissions do not include 1910 * AllPermission or any ExecPermissions/ExecOptionPermissions. 1911 */ 1912 static void checkConfiguration() { 1913 Policy policy = 1914 AccessController.doPrivileged(new PrivilegedAction<Policy>() { 1915 public Policy run() { 1916 return Policy.getPolicy(); 1917 } 1918 }); 1919 if (!(policy instanceof PolicyFile)) { 1920 return; 1921 } 1922 PermissionCollection perms = getExecPermissions(); 1923 for (Enumeration<Permission> e = perms.elements(); 1924 e.hasMoreElements();) 1925 { 1926 Permission p = e.nextElement(); 1927 if (p instanceof AllPermission || 1928 p instanceof ExecPermission || 1929 p instanceof ExecOptionPermission) 1930 { 1931 return; 1932 } 1933 } 1934 System.err.println(getTextResource("rmid.exec.perms.inadequate")); 1935 } 1936 1937 private static PermissionCollection getExecPermissions() { 1938 /* 1939 * The approach used here is taken from the similar method 1940 * getLoaderAccessControlContext() in the class 1941 * sun.rmi.server.LoaderHandler. 1942 */ 1943 1944 // obtain permissions granted to all code in current policy 1945 PermissionCollection perms = AccessController.doPrivileged( 1946 new PrivilegedAction<PermissionCollection>() { 1947 public PermissionCollection run() { 1948 CodeSource codesource = 1949 new CodeSource(null, (Certificate[]) null); 1950 Policy p = Policy.getPolicy(); 1951 if (p != null) { 1952 return p.getPermissions(codesource); 1953 } else { 1954 return new Permissions(); 1955 } 1956 } 1957 }); 1958 1959 return perms; 1960 } 1961 1962 private static void checkPermission(PermissionCollection perms, 1963 Permission p) 1964 throws AccessControlException 1965 { 1966 if (!perms.implies(p)) { 1967 throw new AccessControlException( 1968 "access denied " + p.toString()); 1969 } 1970 } 1971 } 1972 1973 /** 1974 * Main program to start the activation system. <br> 1975 * The usage is as follows: rmid [-port num] [-log dir]. 1976 */ 1977 public static void main(String[] args) { 1978 boolean stop = false; 1979 1980 // Create and install the security manager if one is not installed 1981 // already. 1982 if (System.getSecurityManager() == null) { 1983 System.setSecurityManager(new SecurityManager()); 1984 } 1985 1986 try { 1987 int port = ActivationSystem.SYSTEM_PORT; 1988 RMIServerSocketFactory ssf = null; 1989 1990 /* 1991 * If rmid has an inherited channel (meaning that it was 1992 * launched from inetd), set the server socket factory to 1993 * return the inherited server socket. 1994 **/ 1995 Channel inheritedChannel = AccessController.doPrivileged( 1996 new PrivilegedExceptionAction<Channel>() { 1997 public Channel run() throws IOException { 1998 return System.inheritedChannel(); 1999 } 2000 }); 2001 2002 if (inheritedChannel != null && 2003 inheritedChannel instanceof ServerSocketChannel) 2004 { 2005 /* 2006 * Redirect System.err output to a file. 2007 */ 2008 AccessController.doPrivileged( 2009 new PrivilegedExceptionAction<Void>() { 2010 public Void run() throws IOException { 2011 boolean disable = Boolean.getBoolean( 2012 "sun.rmi.server.activation.disableErrRedirect"); 2013 if (disable) 2014 return null; 2015 2016 File file = 2017 Files.createTempFile("rmid-err", null).toFile(); 2018 PrintStream errStream = 2019 new PrintStream(new FileOutputStream(file)); 2020 System.setErr(errStream); 2021 return null; 2022 } 2023 }); 2024 2025 ServerSocket serverSocket = 2026 ((ServerSocketChannel) inheritedChannel).socket(); 2027 port = serverSocket.getLocalPort(); 2028 ssf = new ActivationServerSocketFactory(serverSocket); 2029 2030 System.err.println(new Date()); 2031 System.err.println(getTextResource( 2032 "rmid.inherited.channel.info") + 2033 ": " + inheritedChannel); 2034 } 2035 2036 String log = null; 2037 List<String> childArgs = new ArrayList<>(); 2038 2039 /* 2040 * Parse arguments 2041 */ 2042 for (int i = 0; i < args.length; i++) { 2043 if (args[i].equals("-port")) { 2044 if (ssf != null) { 2045 bomb(getTextResource("rmid.syntax.port.badarg")); 2046 } 2047 if ((i + 1) < args.length) { 2048 try { 2049 port = Integer.parseInt(args[++i]); 2050 } catch (NumberFormatException nfe) { 2051 bomb(getTextResource("rmid.syntax.port.badnumber")); 2052 } 2053 } else { 2054 bomb(getTextResource("rmid.syntax.port.missing")); 2055 } 2056 2057 } else if (args[i].equals("-log")) { 2058 if ((i + 1) < args.length) { 2059 log = args[++i]; 2060 } else { 2061 bomb(getTextResource("rmid.syntax.log.missing")); 2062 } 2063 2064 } else if (args[i].equals("-stop")) { 2065 stop = true; 2066 2067 } else if (args[i].startsWith("-C")) { 2068 childArgs.add(args[i].substring(2)); 2069 2070 } else { 2071 bomb(MessageFormat.format( 2072 getTextResource("rmid.syntax.illegal.option"), 2073 args[i])); 2074 } 2075 } 2076 2077 if (log == null) { 2078 if (ssf != null) { 2079 bomb(getTextResource("rmid.syntax.log.required")); 2080 } else { 2081 log = "log"; 2082 } 2083 } 2084 2085 debugExec = AccessController.doPrivileged( 2086 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("sun.rmi.server.activation.debugExec")); 2087 2088 /** 2089 * Determine class name for activation exec policy (if any). 2090 */ 2091 String execPolicyClassName = AccessController.doPrivileged( 2092 (PrivilegedAction<String>) () -> System.getProperty("sun.rmi.activation.execPolicy")); 2093 if (execPolicyClassName == null) { 2094 if (!stop) { 2095 DefaultExecPolicy.checkConfiguration(); 2096 } 2097 execPolicyClassName = "default"; 2098 } 2099 2100 /** 2101 * Initialize method for activation exec policy. 2102 */ 2103 if (!execPolicyClassName.equals("none")) { 2104 if (execPolicyClassName.equals("") || 2105 execPolicyClassName.equals("default")) 2106 { 2107 execPolicyClassName = DefaultExecPolicy.class.getName(); 2108 } 2109 2110 try { 2111 Class<?> execPolicyClass = getRMIClass(execPolicyClassName); 2112 @SuppressWarnings("deprecation") 2113 Object tmp = execPolicyClass.newInstance(); 2114 execPolicy = tmp; 2115 execPolicyMethod = 2116 execPolicyClass.getMethod("checkExecCommand", 2117 ActivationGroupDesc.class, 2118 String[].class); 2119 } catch (Exception e) { 2120 if (debugExec) { 2121 System.err.println( 2122 getTextResource("rmid.exec.policy.exception")); 2123 e.printStackTrace(); 2124 } 2125 bomb(getTextResource("rmid.exec.policy.invalid")); 2126 } 2127 } 2128 2129 if (stop == true) { 2130 final int finalPort = port; 2131 AccessController.doPrivileged(new PrivilegedAction<Void>() { 2132 public Void run() { 2133 System.setProperty("java.rmi.activation.port", 2134 Integer.toString(finalPort)); 2135 return null; 2136 } 2137 }); 2138 ActivationSystem system = ActivationGroup.getSystem(); 2139 system.shutdown(); 2140 System.exit(0); 2141 } 2142 2143 /* 2144 * Fix for 4173960: Create and initialize activation using 2145 * a static method, startActivation, which will build the 2146 * Activation state in two ways: if when rmid is run, no 2147 * log file is found, the ActLogHandler.recover(...) 2148 * method will create a new Activation instance. 2149 * Alternatively, if a logfile is available, a serialized 2150 * instance of activation will be read from the log's 2151 * snapshot file. Log updates will be applied to this 2152 * Activation object until rmid's state has been fully 2153 * recovered. In either case, only one instance of 2154 * Activation is created. 2155 */ 2156 startActivation(port, ssf, log, 2157 childArgs.toArray(new String[childArgs.size()])); 2158 2159 // prevent activator from exiting 2160 while (true) { 2161 try { 2162 Thread.sleep(Long.MAX_VALUE); 2163 } catch (InterruptedException e) { 2164 } 2165 } 2166 } catch (Exception e) { 2167 System.err.println( 2168 MessageFormat.format( 2169 getTextResource("rmid.unexpected.exception"), e)); 2170 e.printStackTrace(); 2171 } 2172 System.exit(1); 2173 } 2174 2175 /** 2176 * Retrieves text resources from the locale-specific properties file. 2177 */ 2178 private static String getTextResource(String key) { 2179 if (Activation.resources == null) { 2180 try { 2181 Activation.resources = ResourceBundle.getBundle( 2182 "sun.rmi.server.resources.rmid"); 2183 } catch (MissingResourceException mre) { 2184 } 2185 if (Activation.resources == null) { 2186 // throwing an Error is a bit extreme, methinks 2187 return ("[missing resource file: " + key + "]"); 2188 } 2189 } 2190 2191 String val = null; 2192 try { 2193 val = Activation.resources.getString (key); 2194 } catch (MissingResourceException mre) { 2195 } 2196 2197 if (val == null) { 2198 return ("[missing resource: " + key + "]"); 2199 } else { 2200 return val; 2201 } 2202 } 2203 2204 @SuppressWarnings("deprecation") 2205 private static Class<?> getRMIClass(String execPolicyClassName) throws Exception { 2206 return RMIClassLoader.loadClass(execPolicyClassName); 2207 } 2208 /* 2209 * Dijkstra semaphore operations to limit the number of subprocesses 2210 * rmid attempts to make at once. 2211 */ 2212 /** 2213 * Acquire the group semaphore and return a group name. Each 2214 * Pstartgroup must be followed by a Vstartgroup. The calling thread 2215 * will wait until there are fewer than <code>N</code> other threads 2216 * holding the group semaphore. The calling thread will then acquire 2217 * the semaphore and return. 2218 */ 2219 private synchronized String Pstartgroup() throws ActivationException { 2220 while (true) { 2221 checkShutdown(); 2222 // Wait until positive, then decrement. 2223 if (groupSemaphore > 0) { 2224 groupSemaphore--; 2225 return "Group-" + groupCounter++; 2226 } 2227 2228 try { 2229 wait(); 2230 } catch (InterruptedException e) { 2231 } 2232 } 2233 } 2234 2235 /** 2236 * Release the group semaphore. Every P operation must be 2237 * followed by a V operation. This may cause another thread to 2238 * wake up and return from its P operation. 2239 */ 2240 private synchronized void Vstartgroup() { 2241 // Increment and notify a waiter (not necessarily FIFO). 2242 groupSemaphore++; 2243 notifyAll(); 2244 } 2245 2246 /** 2247 * A server socket factory to use when rmid is launched via 'inetd' 2248 * with 'wait' status. This socket factory's 'createServerSocket' 2249 * method returns the server socket specified during construction that 2250 * is specialized to delay accepting requests until the 2251 * 'initDone' flag is 'true'. The server socket supplied to 2252 * the constructor should be the server socket obtained from the 2253 * ServerSocketChannel returned from the 'System.inheritedChannel' 2254 * method. 2255 **/ 2256 private static class ActivationServerSocketFactory 2257 implements RMIServerSocketFactory 2258 { 2259 private final ServerSocket serverSocket; 2260 2261 /** 2262 * Constructs an 'ActivationServerSocketFactory' with the specified 2263 * 'serverSocket'. 2264 **/ 2265 ActivationServerSocketFactory(ServerSocket serverSocket) { 2266 this.serverSocket = serverSocket; 2267 } 2268 2269 /** 2270 * Returns the server socket specified during construction wrapped 2271 * in a 'DelayedAcceptServerSocket'. 2272 **/ 2273 public ServerSocket createServerSocket(int port) 2274 throws IOException 2275 { 2276 return new DelayedAcceptServerSocket(serverSocket); 2277 } 2278 2279 } 2280 2281 /** 2282 * A server socket that delegates all public methods to the underlying 2283 * server socket specified at construction. The accept method is 2284 * overridden to delay calling accept on the underlying server socket 2285 * until the 'initDone' flag is 'true'. 2286 **/ 2287 private static class DelayedAcceptServerSocket extends ServerSocket { 2288 2289 private final ServerSocket serverSocket; 2290 2291 DelayedAcceptServerSocket(ServerSocket serverSocket) 2292 throws IOException 2293 { 2294 this.serverSocket = serverSocket; 2295 } 2296 2297 public void bind(SocketAddress endpoint) throws IOException { 2298 serverSocket.bind(endpoint); 2299 } 2300 2301 public void bind(SocketAddress endpoint, int backlog) 2302 throws IOException 2303 { 2304 serverSocket.bind(endpoint, backlog); 2305 } 2306 2307 public InetAddress getInetAddress() { 2308 return AccessController.doPrivileged( 2309 new PrivilegedAction<InetAddress>() { 2310 @Override 2311 public InetAddress run() { 2312 return serverSocket.getInetAddress(); 2313 } 2314 }); 2315 } 2316 2317 public int getLocalPort() { 2318 return serverSocket.getLocalPort(); 2319 } 2320 2321 public SocketAddress getLocalSocketAddress() { 2322 return AccessController.doPrivileged( 2323 new PrivilegedAction<SocketAddress>() { 2324 @Override 2325 public SocketAddress run() { 2326 return serverSocket.getLocalSocketAddress(); 2327 } 2328 }); 2329 } 2330 2331 /** 2332 * Delays calling accept on the underlying server socket until the 2333 * remote service is bound in the registry. 2334 **/ 2335 public Socket accept() throws IOException { 2336 synchronized (initLock) { 2337 try { 2338 while (!initDone) { 2339 initLock.wait(); 2340 } 2341 } catch (InterruptedException ignore) { 2342 throw new AssertionError(ignore); 2343 } 2344 } 2345 return serverSocket.accept(); 2346 } 2347 2348 public void close() throws IOException { 2349 serverSocket.close(); 2350 } 2351 2352 public ServerSocketChannel getChannel() { 2353 return serverSocket.getChannel(); 2354 } 2355 2356 public boolean isBound() { 2357 return serverSocket.isBound(); 2358 } 2359 2360 public boolean isClosed() { 2361 return serverSocket.isClosed(); 2362 } 2363 2364 public void setSoTimeout(int timeout) 2365 throws SocketException 2366 { 2367 serverSocket.setSoTimeout(timeout); 2368 } 2369 2370 public int getSoTimeout() throws IOException { 2371 return serverSocket.getSoTimeout(); 2372 } 2373 2374 public void setReuseAddress(boolean on) throws SocketException { 2375 serverSocket.setReuseAddress(on); 2376 } 2377 2378 public boolean getReuseAddress() throws SocketException { 2379 return serverSocket.getReuseAddress(); 2380 } 2381 2382 public String toString() { 2383 return serverSocket.toString(); 2384 } 2385 2386 public void setReceiveBufferSize(int size) 2387 throws SocketException 2388 { 2389 serverSocket.setReceiveBufferSize(size); 2390 } 2391 2392 public int getReceiveBufferSize() 2393 throws SocketException 2394 { 2395 return serverSocket.getReceiveBufferSize(); 2396 } 2397 } 2398 } 2399 2400 /** 2401 * PipeWriter plugs together two pairs of input and output streams by 2402 * providing readers for input streams and writing through to 2403 * appropriate output streams. Both output streams are annotated on a 2404 * per-line basis. 2405 * 2406 * @author Laird Dornin, much code borrowed from Peter Jones, Ken 2407 * Arnold and Ann Wollrath. 2408 */ 2409 class PipeWriter implements Runnable { 2410 2411 /** stream used for buffering lines */ 2412 private ByteArrayOutputStream bufOut; 2413 2414 /** count since last separator */ 2415 private int cLast; 2416 2417 /** current chunk of input being compared to lineSeparator.*/ 2418 private byte[] currSep; 2419 2420 private PrintWriter out; 2421 private InputStream in; 2422 2423 private String pipeString; 2424 private String execString; 2425 2426 private static String lineSeparator; 2427 private static int lineSeparatorLength; 2428 2429 private static int numExecs = 0; 2430 2431 static { 2432 lineSeparator = AccessController.doPrivileged( 2433 (PrivilegedAction<String>) () -> System.getProperty("line.separator")); 2434 lineSeparatorLength = lineSeparator.length(); 2435 } 2436 2437 /** 2438 * Create a new PipeWriter object. All methods of PipeWriter, 2439 * except plugTogetherPair, are only accesible to PipeWriter 2440 * itself. Synchronization is unnecessary on functions that will 2441 * only be used internally in PipeWriter. 2442 * 2443 * @param in input stream from which pipe input flows 2444 * @param out output stream to which log messages will be sent 2445 * @param dest String which tags output stream as 'out' or 'err' 2446 * @param nExecs number of execed processes, Activation groups. 2447 */ 2448 private PipeWriter 2449 (InputStream in, OutputStream out, String tag, int nExecs) { 2450 2451 this.in = in; 2452 this.out = new PrintWriter(out); 2453 2454 bufOut = new ByteArrayOutputStream(); 2455 currSep = new byte[lineSeparatorLength]; 2456 2457 /* set unique pipe/pair annotations */ 2458 execString = ":ExecGroup-" + 2459 Integer.toString(nExecs) + ':' + tag + ':'; 2460 } 2461 2462 /** 2463 * Create a thread to listen and read from input stream, in. buffer 2464 * the data that is read until a marker which equals lineSeparator 2465 * is read. Once such a string has been discovered; write out an 2466 * annotation string followed by the buffered data and a line 2467 * separator. 2468 */ 2469 public void run() { 2470 byte[] buf = new byte[256]; 2471 int count; 2472 2473 try { 2474 /* read bytes till there are no more. */ 2475 while ((count = in.read(buf)) != -1) { 2476 write(buf, 0, count); 2477 } 2478 2479 /* flush internal buffer... may not have ended on a line 2480 * separator, we also need a last annotation if 2481 * something was left. 2482 */ 2483 String lastInBuffer = bufOut.toString(); 2484 bufOut.reset(); 2485 if (lastInBuffer.length() > 0) { 2486 out.println (createAnnotation() + lastInBuffer); 2487 out.flush(); // add a line separator 2488 // to make output nicer 2489 } 2490 2491 } catch (IOException e) { 2492 } 2493 } 2494 2495 /** 2496 * Write a subarray of bytes. Pass each through write byte method. 2497 */ 2498 private void write(byte b[], int off, int len) throws IOException { 2499 2500 if (len < 0) { 2501 throw new ArrayIndexOutOfBoundsException(len); 2502 } 2503 for (int i = 0; i < len; ++ i) { 2504 write(b[off + i]); 2505 } 2506 } 2507 2508 /** 2509 * Write a byte of data to the stream. If we have not matched a 2510 * line separator string, then the byte is appended to the internal 2511 * buffer. If we have matched a line separator, then the currently 2512 * buffered line is sent to the output writer with a prepended 2513 * annotation string. 2514 */ 2515 private void write(byte b) throws IOException { 2516 int i = 0; 2517 2518 /* shift current to the left */ 2519 for (i = 1 ; i < (currSep.length); i ++) { 2520 currSep[i-1] = currSep[i]; 2521 } 2522 currSep[i-1] = b; 2523 bufOut.write(b); 2524 2525 /* enough characters for a separator? */ 2526 if ( (cLast >= (lineSeparatorLength - 1)) && 2527 (lineSeparator.equals(new String(currSep))) ) { 2528 2529 cLast = 0; 2530 2531 /* write prefix through to underlying byte stream */ 2532 out.print(createAnnotation() + bufOut.toString()); 2533 out.flush(); 2534 bufOut.reset(); 2535 2536 if (out.checkError()) { 2537 throw new IOException 2538 ("PipeWriter: IO Exception when"+ 2539 " writing to output stream."); 2540 } 2541 2542 } else { 2543 cLast++; 2544 } 2545 } 2546 2547 /** 2548 * Create an annotation string to be printed out after 2549 * a new line and end of stream. 2550 */ 2551 private String createAnnotation() { 2552 2553 /* construct prefix for log messages: 2554 * date/time stamp... 2555 */ 2556 return ((new Date()).toString() + 2557 /* ... print pair # ... */ 2558 (execString)); 2559 } 2560 2561 /** 2562 * Allow plugging together two pipes at a time, to associate 2563 * output from an execed process. This is the only publicly 2564 * accessible method of this object; this helps ensure that 2565 * synchronization will not be an issue in the annotation 2566 * process. 2567 * 2568 * @param in input stream from which pipe input comes 2569 * @param out output stream to which log messages will be sent 2570 * @param in1 input stream from which pipe input comes 2571 * @param out1 output stream to which log messages will be sent 2572 */ 2573 static void plugTogetherPair(InputStream in, 2574 OutputStream out, 2575 InputStream in1, 2576 OutputStream out1) { 2577 Thread inThread = null; 2578 Thread outThread = null; 2579 2580 int nExecs = getNumExec(); 2581 2582 /* start RMI threads to read output from child process */ 2583 inThread = AccessController.doPrivileged( 2584 new NewThreadAction(new PipeWriter(in, out, "out", nExecs), 2585 "out", true)); 2586 outThread = AccessController.doPrivileged( 2587 new NewThreadAction(new PipeWriter(in1, out1, "err", nExecs), 2588 "err", true)); 2589 inThread.start(); 2590 outThread.start(); 2591 } 2592 2593 private static synchronized int getNumExec() { 2594 return numExecs++; 2595 } 2596 }