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