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