1 /*
2  * Copyright (c) 1998, 2018, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.io.*;
25 import java.rmi.*;
26 import java.rmi.activation.*;
27 import java.rmi.registry.*;
28 import java.time.LocalTime;
29 import java.util.concurrent.TimeoutException;
30 
31 /**
32  * Utility class that creates an instance of rmid with a policy
33  * file of name <code>TestParams.defaultPolicy</code>.
34  *
35  * Activation groups should run with the same security manager as the
36  * test.
37  */
38 public class RMID extends JavaVM {
39 
40     // TODO: adjust these based on the timeout factor
41     // such as jcov.sleep.multiplier; see start(long) method.
42     // Also consider the test.timeout.factor property (a float).
43     private static final long TIMEOUT_SHUTDOWN_MS = 60_000L;
44     private static final long TIMEOUT_DESTROY_MS  = 10_000L;
45     private static final long STARTTIME_MS        = 15_000L;
46     private static final long POLLTIME_MS         = 100L;
47     private static final long TIMEOUT_BASE        = 240_000L;
48 
49     // when restart rmid, it may take more time than usual because of
50     // "port in use" by a possible interloper (check JDK-8168975),
51     // so need to set a longer timeout than STARTTIME_MS for restart.
52     private static final long RESTART_TIMEOUT = (long)(TIMEOUT_BASE * 0.9);
53     // Same reason to inheritedChannel in RMIDSelectorProvider.
54     // Put it here rather than in RMIDSelectorProvider to adjust
55     // both timeout values together.
56     private static long inheritedChannelTimeout;
57 
58     private static final String SYSTEM_NAME = ActivationSystem.class.getName();
59         // "java.rmi.activation.ActivationSystem"
60 
61     public static String MANAGER_OPTION="-Djava.security.manager=";
62 
63     /**
64      * Test port for rmid.
65      *
66      * May initially be 0, which means that the child rmid process will choose
67      * an ephemeral port and report it back to the parent process. This field
68      * will then be set to the child rmid's ephemeral port value.
69      */
70     private volatile int port;
71     //private final boolean ephemeralPort
72 
73     /** Initial log name */
74     protected static String log = "log";
75     /** rmid's logfile directory; currently must be "." */
76     protected static String LOGDIR = ".";
77 
78     /** The output message from the child rmid process that directly precedes
79      * the ephemeral port number.*/
80     public static final String EPHEMERAL_MSG = "RmidSelectorProvider-listening-On:";
81 
mesg(Object mesg)82     private static void mesg(Object mesg) {
83         System.err.println("RMID: " + mesg.toString());
84     }
85 
86     /** make test options and arguments */
makeOptions(int port, boolean debugExec, boolean enableSelectorProvider)87     private static String makeOptions(int port, boolean debugExec,
88                                       boolean enableSelectorProvider) {
89 
90         String options = " -Dsun.rmi.server.activation.debugExec=" +
91             debugExec;
92         // +
93         //" -Djava.compiler= ";
94 
95         // if test params set, want to propagate them
96         if (!TestParams.testSrc.equals("")) {
97             options += " -Dtest.src=" + TestParams.testSrc + " ";
98         }
99         //if (!TestParams.testClasses.equals("")) {
100         //    options += " -Dtest.classes=" + TestParams.testClasses + " ";
101         //}
102         options += " -Dtest.classes=" + TestParams.testClasses //;
103          +
104          " -Djava.rmi.server.logLevel=v ";
105 
106         // +
107         // " -Djava.security.debug=all ";
108 
109         // Set execTimeout to 60 sec (default is 30 sec)
110         // to avoid spurious timeouts on slow machines.
111         options += " -Dsun.rmi.activation.execTimeout=60000";
112 
113         // It's important to set handshakeTimeout to small value, for example
114         // 5 sec (default is 60 sec) to avoid wasting too much time when
115         // calling lookupSystem(port) in restart(), because
116         //   1. If use default value of this option, it will take about 2 minutes
117         //     to finish lookupSystem(port) in 2 loops in restart();
118         //   2. If set this option as 5 sec then lookupSystem(port) will return
119         //     very quickly.
120         options += " -Dsun.rmi.transport.tcp.handshakeTimeout=5000";
121 
122         if (port == 0 || enableSelectorProvider) {
123             // Ephemeral port, so have the rmid child process create the
124             // server socket channel and report its port number, over stdin.
125             options += " -classpath " + TestParams.testClassPath;
126             options += " --add-exports=java.base/sun.nio.ch=ALL-UNNAMED";
127             options += " -Djava.nio.channels.spi.SelectorProvider=RMIDSelectorProvider";
128             options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.port=" + port;
129             options += " -Dtest.java.rmi.testlibrary.RMIDSelectorProvider.timeout="
130                         + inheritedChannelTimeout;
131 
132             // Disable redirection of System.err to /tmp
133             options += " -Dsun.rmi.server.activation.disableErrRedirect=true";
134         }
135 
136         return options;
137     }
138 
makeArgs()139     private static String makeArgs() {
140         return makeArgs(false, 0);
141     }
142 
makeArgs(boolean includePortArg, int port)143     private static String makeArgs(boolean includePortArg, int port) {
144         // getAbsolutePath requires permission to read user.dir
145         String args =
146             " -log " + (new File(LOGDIR, log)).getAbsolutePath();
147 
148         // 0 = ephemeral port, do not include an explicit port number
149         if (includePortArg && port != 0) {
150             args += " -port " + port;
151         }
152 
153         // +
154         //      " -C-Djava.compiler= ";
155 
156         // if test params set, want to propagate them
157         if (!TestParams.testSrc.equals("")) {
158             args += " -C-Dtest.src=" + TestParams.testSrc;
159         }
160         if (!TestParams.testClasses.equals("")) {
161             args += " -C-Dtest.classes=" + TestParams.testClasses;
162         }
163 
164         if (!TestParams.testJavaOpts.equals("")) {
165             for (String a : TestParams.testJavaOpts.split(" +")) {
166                 args += " -C" + a;
167             }
168         }
169 
170         if (!TestParams.testVmOpts.equals("")) {
171             for (String a : TestParams.testVmOpts.split(" +")) {
172                 args += " -C" + a;
173             }
174         }
175 
176         args += " -C-Djava.rmi.server.useCodebaseOnly=false ";
177 
178         args += " " + getCodeCoverageArgs();
179         return args;
180     }
181 
182     /**
183      * Routine that creates an rmid that will run with or without a
184      * policy file.
185      */
createRMID()186     public static RMID createRMID() {
187         return createRMID(System.out, System.err, true, true,
188                           TestLibrary.getUnusedRandomPort());
189     }
190 
createRMID(OutputStream out, OutputStream err, boolean debugExec)191     public static RMID createRMID(OutputStream out, OutputStream err,
192                                   boolean debugExec)
193     {
194         return createRMID(out, err, debugExec, true,
195                           TestLibrary.getUnusedRandomPort());
196     }
197 
createRMID(OutputStream out, OutputStream err, boolean debugExec, boolean includePortArg, int port)198     public static RMID createRMID(OutputStream out, OutputStream err,
199                                   boolean debugExec, boolean includePortArg,
200                                   int port)
201     {
202         return createRMIDWithOptions(out, err, debugExec, includePortArg, port, "");
203     }
204 
205     /**
206      * Create a RMID on a specified port capturing stdout and stderr
207      * with additional command line options and whether to print out
208      * debugging information that is used for spawning activation groups.
209      *
210      * @param out the OutputStream where the normal output of the
211      *            rmid subprocess goes
212      * @param err the OutputStream where the error output of the
213      *            rmid subprocess goes
214      * @param debugExec whether to print out debugging information
215      * @param includePortArg whether to include port argument
216      * @param port the port on which rmid accepts requests
217      * @param additionalOptions additional command line options
218      * @return a RMID instance
219      */
createRMIDWithOptions(OutputStream out, OutputStream err, boolean debugExec, boolean includePortArg, int port, String additionalOptions)220     public static RMID createRMIDWithOptions(OutputStream out, OutputStream err,
221                                   boolean debugExec, boolean includePortArg,
222                                   int port, String additionalOptions)
223     {
224         String options = makeOptions(port, debugExec, false);
225         options += " " + additionalOptions;
226         String args = makeArgs(includePortArg, port);
227         RMID rmid = new RMID("sun.rmi.server.Activation", options, args,
228                              out, err, port);
229         rmid.setPolicyFile(TestParams.defaultRmidPolicy);
230 
231         return rmid;
232     }
233 
createRMIDOnEphemeralPort()234     public static RMID createRMIDOnEphemeralPort() {
235         return createRMID(System.out, System.err, true, false, 0);
236     }
237 
238     /**
239      * Create a RMID on an ephemeral port capturing stdout and stderr
240      * with additional command line options.
241      *
242      * @param additionalOptions additional command line options
243      * @return a RMID instance
244      */
createRMIDOnEphemeralPortWithOptions( String additionalOptions)245     public static RMID createRMIDOnEphemeralPortWithOptions(
246                                             String additionalOptions) {
247         return createRMIDWithOptions(System.out, System.err,
248                                      true, false, 0, additionalOptions);
249     }
250 
createRMIDOnEphemeralPort(OutputStream out, OutputStream err, boolean debugExec)251     public static RMID createRMIDOnEphemeralPort(OutputStream out,
252                                                  OutputStream err,
253                                                  boolean debugExec)
254     {
255         return createRMID(out, err, debugExec, false, 0);
256     }
257 
258 
259     /**
260      * Private constructor. RMID instances should be created
261      * using the static factory methods.
262      */
RMID(String classname, String options, String args, OutputStream out, OutputStream err, int port)263     private RMID(String classname, String options, String args,
264                    OutputStream out, OutputStream err, int port)
265     {
266         super(classname, options, args, out, err);
267         this.port = port;
268         long waitTime = (long)(TIMEOUT_BASE * TestLibrary.getTimeoutFactor());
269         inheritedChannelTimeout = (long)(waitTime * 0.8);
270     }
271 
272     /**
273      * Removes rmid's log file directory.
274      */
removeLog()275     public static void removeLog() {
276         File f = new File(LOGDIR, log);
277 
278         if (f.exists()) {
279             mesg("Removing rmid's old log file.");
280             String[] files = f.list();
281 
282             if (files != null) {
283                 for (int i=0; i<files.length; i++) {
284                     (new File(f, files[i])).delete();
285                 }
286             }
287 
288             if (! f.delete()) {
289                 mesg("Warning: unable to delete old log file.");
290             }
291         }
292     }
293 
294     /**
295      * This method is used for adding arguments to rmid (not its VM)
296      * for passing as VM options to its child group VMs.
297      * Returns the extra command line arguments required
298      * to turn on jcov code coverage analysis for rmid child VMs.
299      */
getCodeCoverageArgs()300     protected static String getCodeCoverageArgs() {
301         return TestLibrary.getExtraProperty("rmid.jcov.args","");
302     }
303 
304     /**
305      * Looks up the activation system in the registry on the given port,
306      * returning its stub, or null if it's not present. This method differs from
307      * ActivationGroup.getSystem() because this method looks on a specific port
308      * instead of using the java.rmi.activation.port property like
309      * ActivationGroup.getSystem() does. This method also returns null instead
310      * of throwing exceptions.
311      */
lookupSystem(int port)312     public static ActivationSystem lookupSystem(int port) {
313         try {
314             return (ActivationSystem)LocateRegistry.getRegistry(port).lookup(SYSTEM_NAME);
315         } catch (RemoteException | NotBoundException ex) {
316             return null;
317         }
318     }
319 
320     /**
321      * Starts rmid and waits up to the default timeout period
322      * to confirm that it's running.
323      */
start()324     public void start() throws IOException {
325         start(STARTTIME_MS);
326     }
327 
328     /**
329      * Starts rmid and waits up to the given timeout period
330      * to confirm that it's running.
331      */
start(long waitTime)332     public void start(long waitTime) throws IOException {
333 
334         // if rmid is already running, then the test will fail with
335         // a well recognized exception (port already in use...).
336 
337         mesg("Starting rmid on port " + port + ", at " + LocalTime.now());
338         int p = super.startAndGetPort();
339         if (p != -1)
340             port = p;
341         mesg("Started rmid on port " + port + ", at " + LocalTime.now());
342 
343         // int slopFactor = 1;
344         // try {
345         //     slopFactor = Integer.valueOf(
346         //         TestLibrary.getExtraProperty("jcov.sleep.multiplier","1"));
347         // } catch (NumberFormatException ignore) {}
348         // waitTime = waitTime * slopFactor;
349 
350         long startTime = System.currentTimeMillis();
351         long deadline = TestLibrary.computeDeadline(startTime, waitTime);
352 
353         while (true) {
354             try {
355                 Thread.sleep(POLLTIME_MS);
356             } catch (InterruptedException ie) {
357                 Thread.currentThread().interrupt();
358                 mesg("Starting rmid interrupted, giving up at " +
359                     (System.currentTimeMillis() - startTime) + "ms.");
360                 return;
361             }
362 
363             try {
364                 int status = vm.exitValue();
365                 waitFor(TIMEOUT_SHUTDOWN_MS);
366                 TestLibrary.bomb("Rmid process exited with status " + status + " after " +
367                     (System.currentTimeMillis() - startTime) + "ms.");
368             } catch (InterruptedException | TimeoutException e) {
369                 mesg(e);
370             } catch (IllegalThreadStateException ignore) { }
371 
372             // The rmid process is alive; check to see whether
373             // it responds to a remote call.
374 
375             mesg("looking up activation system, at " + LocalTime.now());
376             if (lookupSystem(port) != null) {
377                 /*
378                  * We need to set the java.rmi.activation.port value as the
379                  * activation system will use the property to determine the
380                  * port #.  The activation system will use this value if set.
381                  * If it isn't set, the activation system will set it to an
382                  * incorrect value.
383                  */
384                 System.setProperty("java.rmi.activation.port", Integer.toString(port));
385                 mesg("Started successfully after " +
386                     (System.currentTimeMillis() - startTime) + "ms, at " + LocalTime.now());
387                 return;
388             }
389 
390             mesg("after fail to looking up activation system, at " + LocalTime.now());
391             if (System.currentTimeMillis() > deadline) {
392                 TestLibrary.bomb("Failed to start rmid, giving up after " +
393                     (System.currentTimeMillis() - startTime) + "ms.", null);
394             }
395         }
396     }
397 
398     /**
399      * Destroys rmid and restarts it. Note that this does NOT clean up
400      * the log file, because it stores information about restartable
401      * and activatable objects that must be carried over to the new
402      * rmid instance.
403      */
restart()404     public void restart() throws IOException {
405         destroy();
406         options = makeOptions(port, true, true);
407         args = makeArgs();
408 
409         start(RESTART_TIMEOUT);
410     }
411 
412     /**
413      * Ask rmid to shutdown gracefully using a remote method call.
414      * catch any errors that might occur from rmid not being present
415      * at time of shutdown invocation. If the remote call is
416      * successful, wait for the process to terminate. Return true
417      * if the process terminated, otherwise return false.
418      */
shutdown()419     private boolean shutdown() throws InterruptedException {
420         mesg("shutdown()");
421         long startTime = System.currentTimeMillis();
422         ActivationSystem system = lookupSystem(port);
423         if (system == null) {
424             mesg("lookupSystem() returned null after " +
425                 (System.currentTimeMillis() - startTime) + "ms.");
426             return false;
427         }
428 
429         try {
430             mesg("ActivationSystem.shutdown()");
431             system.shutdown();
432         } catch (Exception e) {
433             mesg("Caught exception from ActivationSystem.shutdown():");
434             e.printStackTrace();
435         }
436 
437         try {
438             waitFor(TIMEOUT_SHUTDOWN_MS);
439             mesg("Shutdown successful after " +
440                 (System.currentTimeMillis() - startTime) + "ms.");
441             return true;
442         } catch (TimeoutException ex) {
443             mesg("Shutdown timed out after " +
444                 (System.currentTimeMillis() - startTime) + "ms:");
445             ex.printStackTrace();
446             return false;
447         }
448     }
449 
450     /**
451      * Ask rmid to shutdown gracefully but then destroy the rmid
452      * process if it does not exit by itself.  This method only works
453      * if rmid is a child process of the current VM.
454      */
destroy()455     public void destroy() {
456         if (vm == null) {
457             throw new IllegalStateException("can't wait for RMID that isn't running");
458         }
459 
460         long startTime = System.currentTimeMillis();
461 
462         // First, attempt graceful shutdown of the activation system.
463         try {
464             if (! shutdown()) {
465                 // Graceful shutdown failed, use Process.destroy().
466                 mesg("Destroying RMID process.");
467                 vm.destroy();
468                 try {
469                     waitFor(TIMEOUT_DESTROY_MS);
470                     mesg("Destroy successful after " +
471                         (System.currentTimeMillis() - startTime) + "ms.");
472                 } catch (TimeoutException ex) {
473                     mesg("Destroy timed out, giving up after " +
474                         (System.currentTimeMillis() - startTime) + "ms:");
475                     ex.printStackTrace();
476                 }
477             }
478         } catch (InterruptedException ie) {
479             mesg("Shutdown/destroy interrupted, giving up at " +
480                 (System.currentTimeMillis() - startTime) + "ms.");
481             ie.printStackTrace();
482             Thread.currentThread().interrupt();
483             return;
484         }
485 
486         vm = null;
487     }
488 
489     /**
490      * Shuts down rmid and then removes its log file.
491      */
cleanup()492     public void cleanup() {
493         destroy();
494         RMID.removeLog();
495     }
496 
497     /**
498      * Gets the port on which this rmid is listening.
499      */
getPort()500     public int getPort() {
501         return port;
502     }
503 }
504