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