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