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