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