1/* Execute a Java program.
2   Copyright (C) 2001-2003, 2006 Free Software Foundation, Inc.
3   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2, or (at your option)
8   any later version.
9
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software Foundation,
17   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19#include <config.h>
20#include <alloca.h>
21
22/* Specification.  */
23#include "javaexec.h"
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28
29#include "execute.h"
30#include "classpath.h"
31#include "xsetenv.h"
32#include "sh-quote.h"
33#include "pathname.h"
34#include "xalloc.h"
35#include "xallocsa.h"
36#include "error.h"
37#include "gettext.h"
38
39#define _(str) gettext (str)
40
41
42/* Survey of Java virtual machines.
43
44   A = does it work without CLASSPATH being set
45   B = does it work with CLASSPATH being set to empty
46   C = option to set CLASSPATH, other than setting it in the environment
47   T = test for presence
48
49   Program    from         A B  C              T
50
51   $JAVA      unknown      N Y  n/a            true
52   gij        GCC 3.0      Y Y  n/a            gij --version >/dev/null
53   java       JDK 1.1.8    Y Y  -classpath P   java -version 2>/dev/null
54   jre        JDK 1.1.8    N Y  -classpath P   jre 2>/dev/null; test $? = 1
55   java       JDK 1.3.0    Y Y  -classpath P   java -version 2>/dev/null
56   jview      MS IE        Y Y  -cp P          jview -? >nul; %errorlevel% = 1
57
58   The CLASSPATH is a colon separated list of pathnames. (On Windows: a
59   semicolon separated list of pathnames.)
60
61   We try the Java virtual machines in the following order:
62     1. getenv ("JAVA"), because the user must be able to override our
63	preferences,
64     2. "gij", because it is a completely free JVM,
65     3. "java", because it is a standard JVM,
66     4. "jre", comes last because it requires a CLASSPATH environment variable,
67     5. "jview", on Windows only, because it is frequently installed.
68
69   We unset the JAVA_HOME environment variable, because a wrong setting of
70   this variable can confuse the JDK's javac.
71 */
72
73bool
74execute_java_class (const char *class_name,
75		    const char * const *classpaths,
76		    unsigned int classpaths_count,
77		    bool use_minimal_classpath,
78		    const char *exe_dir,
79		    const char * const *args,
80		    bool verbose, bool quiet,
81		    execute_fn *executer, void *private_data)
82{
83  bool err = false;
84  unsigned int nargs;
85  char *old_JAVA_HOME;
86
87  /* Count args.  */
88  {
89    const char * const *arg;
90
91    for (nargs = 0, arg = args; *arg != NULL; nargs++, arg++)
92     ;
93  }
94
95  /* First, try a class compiled to a native code executable.  */
96  if (exe_dir != NULL)
97    {
98      char *exe_pathname = concatenated_pathname (exe_dir, class_name, EXEEXT);
99      char *old_classpath;
100      char **argv = (char **) xallocsa ((1 + nargs + 1) * sizeof (char *));
101      unsigned int i;
102
103      /* Set CLASSPATH.  */
104      old_classpath =
105	set_classpath (classpaths, classpaths_count, use_minimal_classpath,
106		       verbose);
107
108      argv[0] = exe_pathname;
109      for (i = 0; i <= nargs; i++)
110	argv[1 + i] = (char *) args[i];
111
112      if (verbose)
113	{
114	  char *command = shell_quote_argv (argv);
115	  printf ("%s\n", command);
116	  free (command);
117	}
118
119      err = executer (class_name, exe_pathname, argv, private_data);
120
121      /* Reset CLASSPATH.  */
122      reset_classpath (old_classpath);
123
124      freesa (argv);
125
126      goto done1;
127    }
128
129  {
130    const char *java = getenv ("JAVA");
131    if (java != NULL && java[0] != '\0')
132      {
133	/* Because $JAVA may consist of a command and options, we use the
134	   shell.  Because $JAVA has been set by the user, we leave all
135	   all environment variables in place, including JAVA_HOME, and
136	   we don't erase the user's CLASSPATH.  */
137	char *old_classpath;
138	unsigned int command_length;
139	char *command;
140	char *argv[4];
141	const char * const *arg;
142	char *p;
143
144	/* Set CLASSPATH.  */
145	old_classpath =
146	  set_classpath (classpaths, classpaths_count, false,
147			 verbose);
148
149	command_length = strlen (java);
150	command_length += 1 + shell_quote_length (class_name);
151	for (arg = args; *arg != NULL; arg++)
152	  command_length += 1 + shell_quote_length (*arg);
153	command_length += 1;
154
155	command = (char *) xallocsa (command_length);
156	p = command;
157	/* Don't shell_quote $JAVA, because it may consist of a command
158	   and options.  */
159	memcpy (p, java, strlen (java));
160	p += strlen (java);
161	*p++ = ' ';
162	p = shell_quote_copy (p, class_name);
163	for (arg = args; *arg != NULL; arg++)
164	  {
165	    *p++ = ' ';
166	    p = shell_quote_copy (p, *arg);
167	  }
168	*p++ = '\0';
169	/* Ensure command_length was correctly calculated.  */
170	if (p - command > command_length)
171	  abort ();
172
173	if (verbose)
174	  printf ("%s\n", command);
175
176	argv[0] = "/bin/sh";
177	argv[1] = "-c";
178	argv[2] = command;
179	argv[3] = NULL;
180	err = executer (java, "/bin/sh", argv, private_data);
181
182	freesa (command);
183
184	/* Reset CLASSPATH.  */
185	reset_classpath (old_classpath);
186
187	goto done1;
188      }
189  }
190
191  /* Unset the JAVA_HOME environment variable.  */
192  old_JAVA_HOME = getenv ("JAVA_HOME");
193  if (old_JAVA_HOME != NULL)
194    {
195      old_JAVA_HOME = xstrdup (old_JAVA_HOME);
196      unsetenv ("JAVA_HOME");
197    }
198
199  {
200    static bool gij_tested;
201    static bool gij_present;
202
203    if (!gij_tested)
204      {
205	/* Test for presence of gij: "gij --version > /dev/null"  */
206	char *argv[3];
207	int exitstatus;
208
209	argv[0] = "gij";
210	argv[1] = "--version";
211	argv[2] = NULL;
212	exitstatus = execute ("gij", "gij", argv, false, false, true, true,
213			      true, false);
214	gij_present = (exitstatus == 0);
215	gij_tested = true;
216      }
217
218    if (gij_present)
219      {
220	char *old_classpath;
221	char **argv = (char **) xallocsa ((2 + nargs + 1) * sizeof (char *));
222	unsigned int i;
223
224	/* Set CLASSPATH.  */
225	old_classpath =
226	  set_classpath (classpaths, classpaths_count, use_minimal_classpath,
227			 verbose);
228
229	argv[0] = "gij";
230	argv[1] = (char *) class_name;
231	for (i = 0; i <= nargs; i++)
232	  argv[2 + i] = (char *) args[i];
233
234	if (verbose)
235	  {
236	    char *command = shell_quote_argv (argv);
237	    printf ("%s\n", command);
238	    free (command);
239	  }
240
241	err = executer ("gij", "gij", argv, private_data);
242
243	/* Reset CLASSPATH.  */
244	reset_classpath (old_classpath);
245
246	freesa (argv);
247
248	goto done2;
249      }
250  }
251
252  {
253    static bool java_tested;
254    static bool java_present;
255
256    if (!java_tested)
257      {
258	/* Test for presence of java: "java -version 2> /dev/null"  */
259	char *argv[3];
260	int exitstatus;
261
262	argv[0] = "java";
263	argv[1] = "-version";
264	argv[2] = NULL;
265	exitstatus = execute ("java", "java", argv, false, false, true, true,
266			      true, false);
267	java_present = (exitstatus == 0);
268	java_tested = true;
269      }
270
271    if (java_present)
272      {
273	char *old_classpath;
274	char **argv = (char **) xallocsa ((2 + nargs + 1) * sizeof (char *));
275	unsigned int i;
276
277	/* Set CLASSPATH.  We don't use the "-classpath ..." option because
278	   in JDK 1.1.x its argument should also contain the JDK's classes.zip,
279	   but we don't know its location.  (In JDK 1.3.0 it would work.)  */
280	old_classpath =
281	  set_classpath (classpaths, classpaths_count, use_minimal_classpath,
282			 verbose);
283
284	argv[0] = "java";
285	argv[1] = (char *) class_name;
286	for (i = 0; i <= nargs; i++)
287	  argv[2 + i] = (char *) args[i];
288
289	if (verbose)
290	  {
291	    char *command = shell_quote_argv (argv);
292	    printf ("%s\n", command);
293	    free (command);
294	  }
295
296	err = executer ("java", "java", argv, private_data);
297
298	/* Reset CLASSPATH.  */
299	reset_classpath (old_classpath);
300
301	freesa (argv);
302
303	goto done2;
304      }
305  }
306
307  {
308    static bool jre_tested;
309    static bool jre_present;
310
311    if (!jre_tested)
312      {
313	/* Test for presence of jre: "jre 2> /dev/null ; test $? = 1"  */
314	char *argv[2];
315	int exitstatus;
316
317	argv[0] = "jre";
318	argv[1] = NULL;
319	exitstatus = execute ("jre", "jre", argv, false, false, true, true,
320			      true, false);
321	jre_present = (exitstatus == 0 || exitstatus == 1);
322	jre_tested = true;
323      }
324
325    if (jre_present)
326      {
327	char *old_classpath;
328	char **argv = (char **) xallocsa ((2 + nargs + 1) * sizeof (char *));
329	unsigned int i;
330
331	/* Set CLASSPATH.  We don't use the "-classpath ..." option because
332	   in JDK 1.1.x its argument should also contain the JDK's classes.zip,
333	   but we don't know its location.  */
334	old_classpath =
335	  set_classpath (classpaths, classpaths_count, use_minimal_classpath,
336			 verbose);
337
338	argv[0] = "jre";
339	argv[1] = (char *) class_name;
340	for (i = 0; i <= nargs; i++)
341	  argv[2 + i] = (char *) args[i];
342
343	if (verbose)
344	  {
345	    char *command = shell_quote_argv (argv);
346	    printf ("%s\n", command);
347	    free (command);
348	  }
349
350	err = executer ("jre", "jre", argv, private_data);
351
352	/* Reset CLASSPATH.  */
353	reset_classpath (old_classpath);
354
355	freesa (argv);
356
357	goto done2;
358      }
359  }
360
361#if defined _WIN32 || defined __WIN32__ || defined __CYGWIN__
362  /* Win32, Cygwin */
363  {
364    static bool jview_tested;
365    static bool jview_present;
366
367    if (!jview_tested)
368      {
369	/* Test for presence of jview: "jview -? >nul ; test $? = 1"  */
370	char *argv[3];
371	int exitstatus;
372
373	argv[0] = "jview";
374	argv[1] = "-?";
375	argv[2] = NULL;
376	exitstatus = execute ("jview", "jview", argv, false, false, true, true,
377			      true, false);
378	jview_present = (exitstatus == 0 || exitstatus == 1);
379	jview_tested = true;
380      }
381
382    if (jview_present)
383      {
384	char *old_classpath;
385	char **argv = (char **) xallocsa ((2 + nargs + 1) * sizeof (char *));
386	unsigned int i;
387
388	/* Set CLASSPATH.  */
389	old_classpath =
390	  set_classpath (classpaths, classpaths_count, use_minimal_classpath,
391			 verbose);
392
393	argv[0] = "jview";
394	argv[1] = (char *) class_name;
395	for (i = 0; i <= nargs; i++)
396	  argv[2 + i] = (char *) args[i];
397
398	if (verbose)
399	  {
400	    char *command = shell_quote_argv (argv);
401	    printf ("%s\n", command);
402	    free (command);
403	  }
404
405	err = executer ("jview", "jview", argv, private_data);
406
407	/* Reset CLASSPATH.  */
408	reset_classpath (old_classpath);
409
410	freesa (argv);
411
412	goto done2;
413      }
414  }
415#endif
416
417  if (!quiet)
418    error (0, 0, _("Java virtual machine not found, try installing gij or set $JAVA"));
419  err = true;
420
421 done2:
422  if (old_JAVA_HOME != NULL)
423    {
424      xsetenv ("JAVA_HOME", old_JAVA_HOME, 1);
425      free (old_JAVA_HOME);
426    }
427
428 done1:
429  return err;
430}
431