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