提交 edc21737 编写于 作者: K ksrini

7146424: Wildcard expansion for single entry classpath

Reviewed-by: dholmes, darcy, jjh, sherman
上级 175e85db
...@@ -153,7 +153,6 @@ ifeq ($(PLATFORM), windows) ...@@ -153,7 +153,6 @@ ifeq ($(PLATFORM), windows)
ifndef LOCAL_RESOURCE_FILE ifndef LOCAL_RESOURCE_FILE
@$(ECHO) $(OBJDIR)/$(PROGRAM).res >> $@ @$(ECHO) $(OBJDIR)/$(PROGRAM).res >> $@
endif endif
@$(ECHO) setargv.obj >> $@
@$(ECHO) Created $@ @$(ECHO) Created $@
$(ACTUAL_PROGRAM):: $(OBJDIR)/$(PROGRAM)$(EXE_SUFFIX) $(ACTUAL_PROGRAM):: $(OBJDIR)/$(PROGRAM)$(EXE_SUFFIX)
......
...@@ -90,7 +90,8 @@ endif # SYSTEM_ZLIB ...@@ -90,7 +90,8 @@ endif # SYSTEM_ZLIB
# add platform specific files # add platform specific files
ifeq ($(PLATFORM), windows) ifeq ($(PLATFORM), windows)
FILES_c += java_md.c FILES_c += java_md.c \
cmdtoargs.c
else # NIXES else # NIXES
FILES_c += java_md_common.c FILES_c += java_md_common.c
ifeq ($(PLATFORM), macosx) ifeq ($(PLATFORM), macosx)
...@@ -149,7 +150,11 @@ ifeq ($(PLATFORM), windows) ...@@ -149,7 +150,11 @@ ifeq ($(PLATFORM), windows)
-export:JLI_ReportErrorMessage \ -export:JLI_ReportErrorMessage \
-export:JLI_ReportErrorMessageSys \ -export:JLI_ReportErrorMessageSys \
-export:JLI_ReportMessage \ -export:JLI_ReportMessage \
-export:JLI_ReportExceptionDescription -export:JLI_ReportExceptionDescription \
-export:JLI_MemAlloc \
-export:JLI_CmdToArgs \
-export:JLI_GetStdArgc \
-export:JLI_GetStdArgs
endif # PLATFORM endif # PLATFORM
OTHER_INCLUDES += -I$(LAUNCHER_SHARE_SRC) OTHER_INCLUDES += -I$(LAUNCHER_SHARE_SRC)
......
# #
# Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # This code is free software; you can redistribute it and/or modify it
...@@ -34,6 +34,8 @@ SUNWprivate_1.1 { ...@@ -34,6 +34,8 @@ SUNWprivate_1.1 {
JLI_ReportErrorMessageSys; JLI_ReportErrorMessageSys;
JLI_ReportMessage; JLI_ReportMessage;
JLI_ReportExceptionDescription; JLI_ReportExceptionDescription;
JLI_GetStdArgs;
JLI_GetStdArgc;
local: local:
*; *;
}; };
...@@ -104,7 +104,6 @@ static jboolean ParseArguments(int *pargc, char ***pargv, ...@@ -104,7 +104,6 @@ static jboolean ParseArguments(int *pargc, char ***pargv,
static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv,
InvocationFunctions *ifn); InvocationFunctions *ifn);
static jstring NewPlatformString(JNIEnv *env, char *s); static jstring NewPlatformString(JNIEnv *env, char *s);
static jobjectArray NewPlatformStringArray(JNIEnv *env, char **strv, int strc);
static jclass LoadMainClass(JNIEnv *env, int mode, char *name); static jclass LoadMainClass(JNIEnv *env, int mode, char *name);
static void TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv); static void TranslateApplicationArgs(int jargc, const char **jargv, int *pargc, char ***pargv);
...@@ -160,7 +159,7 @@ static jboolean IsWildCardEnabled(); ...@@ -160,7 +159,7 @@ static jboolean IsWildCardEnabled();
* Running Java code in primordial thread caused many problems. We will * Running Java code in primordial thread caused many problems. We will
* create a new thread to invoke JVM. See 6316197 for more information. * create a new thread to invoke JVM. See 6316197 for more information.
*/ */
static jlong threadStackSize = 0; /* stack size of the new thread */ static jlong threadStackSize = 0; /* stack size of the new thread */
static jlong maxHeapSize = 0; /* max heap size */ static jlong maxHeapSize = 0; /* max heap size */
static jlong initialHeapSize = 0; /* inital heap size */ static jlong initialHeapSize = 0; /* inital heap size */
...@@ -202,6 +201,14 @@ JLI_Launch(int argc, char ** argv, /* main argc, argc */ ...@@ -202,6 +201,14 @@ JLI_Launch(int argc, char ** argv, /* main argc, argc */
InitLauncher(javaw); InitLauncher(javaw);
DumpState(); DumpState();
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
/* /*
* Make sure the specified version of the JRE is running. * Make sure the specified version of the JRE is running.
...@@ -222,15 +229,6 @@ JLI_Launch(int argc, char ** argv, /* main argc, argc */ ...@@ -222,15 +229,6 @@ JLI_Launch(int argc, char ** argv, /* main argc, argc */
*/ */
SelectVersion(argc, argv, &main_class); SelectVersion(argc, argv, &main_class);
if (JLI_IsTraceLauncher()) {
int i;
printf("Command line args:\n");
for (i = 0; i < argc ; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
AddOption("-Dsun.java.launcher.diag=true", NULL);
}
CreateExecutionEnvironment(&argc, &argv, CreateExecutionEnvironment(&argc, &argv,
jrepath, sizeof(jrepath), jrepath, sizeof(jrepath),
jvmpath, sizeof(jvmpath), jvmpath, sizeof(jvmpath),
...@@ -435,8 +433,8 @@ JavaMain(void * _args) ...@@ -435,8 +433,8 @@ JavaMain(void * _args)
"([Ljava/lang/String;)V"); "([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID); CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build argument array */ /* Build platform specific argument array */
mainArgs = NewPlatformStringArray(env, argv, argc); mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs); CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */ /* Invoke main method. */
...@@ -1120,8 +1118,9 @@ InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn) ...@@ -1120,8 +1118,9 @@ InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
static jclass helperClass = NULL; static jclass helperClass = NULL;
static jclass jclass
GetLauncherHelperClass(JNIEnv *env) { GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) { if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env, NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper")); "sun/launcher/LauncherHelper"));
...@@ -1165,7 +1164,7 @@ NewPlatformString(JNIEnv *env, char *s) ...@@ -1165,7 +1164,7 @@ NewPlatformString(JNIEnv *env, char *s)
* Returns a new array of Java string objects for the specified * Returns a new array of Java string objects for the specified
* array of platform strings. * array of platform strings.
*/ */
static jobjectArray jobjectArray
NewPlatformStringArray(JNIEnv *env, char **strv, int strc) NewPlatformStringArray(JNIEnv *env, char **strv, int strc)
{ {
jarray cls; jarray cls;
...@@ -1210,7 +1209,7 @@ LoadMainClass(JNIEnv *env, int mode, char *name) ...@@ -1210,7 +1209,7 @@ LoadMainClass(JNIEnv *env, int mode, char *name)
end = CounterGet(); end = CounterGet();
printf("%ld micro seconds to load main class\n", printf("%ld micro seconds to load main class\n",
(long)(jint)Counter2Micros(end-start)); (long)(jint)Counter2Micros(end-start));
printf("----_JAVA_LAUNCHER_DEBUG----\n"); printf("----%s----\n", JLDEBUG_ENV_ENTRY);
} }
return (jclass)result; return (jclass)result;
...@@ -1745,7 +1744,6 @@ FreeKnownVMs() ...@@ -1745,7 +1744,6 @@ FreeKnownVMs()
JLI_MemFree(knownVMs); JLI_MemFree(knownVMs);
} }
/* /*
* Displays the splash screen according to the jar file name * Displays the splash screen according to the jar file name
* and image file names stored in environment variables * and image file names stored in environment variables
......
...@@ -219,6 +219,10 @@ typedef jclass (JNICALL FindClassFromBootLoader_t(JNIEnv *env, ...@@ -219,6 +219,10 @@ typedef jclass (JNICALL FindClassFromBootLoader_t(JNIEnv *env,
const char *name)); const char *name));
jclass FindBootStrapClass(JNIEnv *env, const char *classname); jclass FindBootStrapClass(JNIEnv *env, const char *classname);
jobjectArray CreateApplicationArgs(JNIEnv *env, char **strv, int argc);
jobjectArray NewPlatformStringArray(JNIEnv *env, char **strv, int strc);
jclass GetLauncherHelperClass(JNIEnv *env);
int JNICALL JavaMain(void * args); /* entry point */ int JNICALL JavaMain(void * args); /* entry point */
enum LaunchMode { // cf. sun.launcher.LauncherHelper enum LaunchMode { // cf. sun.launcher.LauncherHelper
......
/* /*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -102,9 +102,9 @@ JLI_TraceLauncher(const char* fmt, ...) ...@@ -102,9 +102,9 @@ JLI_TraceLauncher(const char* fmt, ...)
void void
JLI_SetTraceLauncher() JLI_SetTraceLauncher()
{ {
if (getenv("_JAVA_LAUNCHER_DEBUG") != 0) { if (getenv(JLDEBUG_ENV_ENTRY) != 0) {
_launcher_debug = JNI_TRUE; _launcher_debug = JNI_TRUE;
JLI_TraceLauncher("----_JAVA_LAUNCHER_DEBUG----\n"); JLI_TraceLauncher("----%s----\n", JLDEBUG_ENV_ENTRY);
} }
} }
......
/* /*
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <jni.h> #include <jni.h>
#define JLDEBUG_ENV_ENTRY "_JAVA_LAUNCHER_DEBUG"
void *JLI_MemAlloc(size_t size); void *JLI_MemAlloc(size_t size);
void *JLI_MemRealloc(void *ptr, size_t size); void *JLI_MemRealloc(void *ptr, size_t size);
...@@ -37,6 +38,14 @@ char *JLI_StringDup(const char *s1); ...@@ -37,6 +38,14 @@ char *JLI_StringDup(const char *s1);
void JLI_MemFree(void *ptr); void JLI_MemFree(void *ptr);
int JLI_StrCCmp(const char *s1, const char* s2); int JLI_StrCCmp(const char *s1, const char* s2);
typedef struct {
char *arg;
jboolean has_wildcard;
} StdArg;
StdArg *JLI_GetStdArgs();
int JLI_GetStdArgc();
#define JLI_StrLen(p1) strlen((p1)) #define JLI_StrLen(p1) strlen((p1))
#define JLI_StrChr(p1, p2) strchr((p1), (p2)) #define JLI_StrChr(p1, p2) strchr((p1), (p2))
#define JLI_StrRChr(p1, p2) strrchr((p1), (p2)) #define JLI_StrRChr(p1, p2) strrchr((p1), (p2))
...@@ -58,6 +67,7 @@ int JLI_StrCCmp(const char *s1, const char* s2); ...@@ -58,6 +67,7 @@ int JLI_StrCCmp(const char *s1, const char* s2);
#define JLI_StrCaseCmp(p1, p2) stricmp((p1), (p2)) #define JLI_StrCaseCmp(p1, p2) stricmp((p1), (p2))
#define JLI_StrNCaseCmp(p1, p2, p3) strnicmp((p1), (p2), (p3)) #define JLI_StrNCaseCmp(p1, p2, p3) strnicmp((p1), (p2), (p3))
#define JLI_Snprintf _snprintf #define JLI_Snprintf _snprintf
void JLI_CmdToArgs(char *cmdline);
#else #else
#include <unistd.h> #include <unistd.h>
#include <strings.h> #include <strings.h>
......
/* /*
* Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -87,22 +87,41 @@ WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow) ...@@ -87,22 +87,41 @@ WinMain(HINSTANCE inst, HINSTANCE previnst, LPSTR cmdline, int cmdshow)
const jboolean const_javaw = JNI_TRUE; const jboolean const_javaw = JNI_TRUE;
__initenv = _environ; __initenv = _environ;
margc = __argc;
margv = __argv;
#else /* JAVAW */ #else /* JAVAW */
int int
main(int argc, char ** argv) main(int argc, char **argv)
{ {
int margc; int margc;
char** margv; char** margv;
const jboolean const_javaw = JNI_FALSE; const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
{
int i = 0;
if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
printf("Windows original main args:\n");
for (i = 0 ; i < __argc ; i++) {
printf("wwwd_args[%d] = %s\n", i, __argv[i]);
}
}
}
JLI_CmdToArgs(GetCommandLine());
margc = JLI_GetStdArgc();
// add one more to mark the end
margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
{
int i = 0;
StdArg *stdargs = JLI_GetStdArgs();
for (i = 0 ; i < margc ; i++) {
margv[i] = stdargs[i].arg;
}
margv[i] = NULL;
}
#else /* *NIXES */
margc = argc; margc = argc;
margv = argv; margv = argv;
#endif /* JAVAW */ #endif /* WIN32 */
return JLI_Launch(margc, margv, return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs, sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath, sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
......
/* /*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -129,11 +129,11 @@ struct WildcardIterator_ ...@@ -129,11 +129,11 @@ struct WildcardIterator_
HANDLE handle; HANDLE handle;
char *firstFile; /* Stupid FindFirstFile...FindNextFile */ char *firstFile; /* Stupid FindFirstFile...FindNextFile */
}; };
// since this is used repeatedly we keep it here.
static WIN32_FIND_DATA find_data;
static WildcardIterator static WildcardIterator
WildcardIterator_for(const char *wildcard) WildcardIterator_for(const char *wildcard)
{ {
WIN32_FIND_DATA find_data;
WildcardIterator it = NEW_(WildcardIterator); WildcardIterator it = NEW_(WildcardIterator);
HANDLE handle = FindFirstFile(wildcard, &find_data); HANDLE handle = FindFirstFile(wildcard, &find_data);
if (handle == INVALID_HANDLE_VALUE) if (handle == INVALID_HANDLE_VALUE)
...@@ -146,7 +146,6 @@ WildcardIterator_for(const char *wildcard) ...@@ -146,7 +146,6 @@ WildcardIterator_for(const char *wildcard)
static char * static char *
WildcardIterator_next(WildcardIterator it) WildcardIterator_next(WildcardIterator it)
{ {
WIN32_FIND_DATA find_data;
if (it->firstFile != NULL) { if (it->firstFile != NULL) {
char *firstFile = it->firstFile; char *firstFile = it->firstFile;
it->firstFile = NULL; it->firstFile = NULL;
...@@ -412,7 +411,7 @@ JLI_WildcardExpandClasspath(const char *classpath) ...@@ -412,7 +411,7 @@ JLI_WildcardExpandClasspath(const char *classpath)
FileList_expandWildcards(fl); FileList_expandWildcards(fl);
expanded = FileList_join(fl, PATH_SEPARATOR); expanded = FileList_join(fl, PATH_SEPARATOR);
FileList_free(fl); FileList_free(fl);
if (getenv("_JAVA_LAUNCHER_DEBUG") != 0) if (getenv(JLDEBUG_ENV_ENTRY) != 0)
printf("Expanded wildcards:\n" printf("Expanded wildcards:\n"
" before: \"%s\"\n" " before: \"%s\"\n"
" after : \"%s\"\n", " after : \"%s\"\n",
......
...@@ -48,6 +48,9 @@ import java.lang.reflect.Modifier; ...@@ -48,6 +48,9 @@ import java.lang.reflect.Modifier;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -69,8 +72,6 @@ public enum LauncherHelper { ...@@ -69,8 +72,6 @@ public enum LauncherHelper {
private static StringBuilder outBuf = new StringBuilder(); private static StringBuilder outBuf = new StringBuilder();
private static ResourceBundle javarb = null;
private static final String INDENT = " "; private static final String INDENT = " ";
private static final String VM_SETTINGS = "VM settings:"; private static final String VM_SETTINGS = "VM settings:";
private static final String PROP_SETTINGS = "Property settings:"; private static final String PROP_SETTINGS = "Property settings:";
...@@ -78,6 +79,7 @@ public enum LauncherHelper { ...@@ -78,6 +79,7 @@ public enum LauncherHelper {
// sync with java.c and sun.misc.VM // sync with java.c and sun.misc.VM
private static final String diagprop = "sun.java.launcher.diag"; private static final String diagprop = "sun.java.launcher.diag";
final static boolean trace = sun.misc.VM.getSavedProperty(diagprop) != null;
private static final String defaultBundleName = private static final String defaultBundleName =
"sun.launcher.resources.launcher"; "sun.launcher.resources.launcher";
...@@ -428,7 +430,7 @@ public enum LauncherHelper { ...@@ -428,7 +430,7 @@ public enum LauncherHelper {
if (msgKey != null) { if (msgKey != null) {
ostream.println(getLocalizedMessage(msgKey, args)); ostream.println(getLocalizedMessage(msgKey, args));
} }
if (sun.misc.VM.getSavedProperty(diagprop) != null) { if (trace) {
if (t != null) { if (t != null) {
t.printStackTrace(); t.printStackTrace();
} else { } else {
...@@ -532,4 +534,82 @@ public enum LauncherHelper { ...@@ -532,4 +534,82 @@ public enum LauncherHelper {
} }
return null; // keep the compiler happy return null; // keep the compiler happy
} }
static String[] expandArgs(String[] argArray) {
List<StdArg> aList = new ArrayList<>();
for (String x : argArray) {
aList.add(new StdArg(x));
}
return expandArgs(aList);
}
static String[] expandArgs(List<StdArg> argList) {
ArrayList<String> out = new ArrayList<>();
if (trace) {
System.err.println("Incoming arguments:");
}
for (StdArg a : argList) {
if (trace) {
System.err.println(a);
}
if (a.needsExpansion) {
File x = new File(a.arg);
File parent = x.getParentFile();
String glob = x.getName();
if (parent == null) {
parent = new File(".");
}
try (DirectoryStream<Path> dstream =
Files.newDirectoryStream(parent.toPath(), glob)) {
int entries = 0;
for (Path p : dstream) {
out.add(p.normalize().toString());
entries++;
}
if (entries == 0) {
out.add(a.arg);
}
} catch (Exception e) {
out.add(a.arg);
if (trace) {
System.err.println("Warning: passing argument as-is " + a);
System.err.print(e);
}
}
} else {
out.add(a.arg);
}
}
String[] oarray = new String[out.size()];
out.toArray(oarray);
if (trace) {
System.err.println("Expanded arguments:");
for (String x : oarray) {
System.err.println(x);
}
}
return oarray;
}
/* duplicate of the native StdArg struct */
private static class StdArg {
final String arg;
final boolean needsExpansion;
StdArg(String arg, boolean expand) {
this.arg = arg;
this.needsExpansion = expand;
}
// protocol: first char indicates whether expansion is required
// 'T' = true ; needs expansion
// 'F' = false; needs no expansion
StdArg(String in) {
this.arg = in.substring(1);
needsExpansion = in.charAt(0) == 'T';
}
public String toString() {
return "StdArg{" + "arg=" + arg + ", needsExpansion=" + needsExpansion + '}';
}
}
} }
...@@ -136,3 +136,4 @@ java.launcher.jar.error1=\ ...@@ -136,3 +136,4 @@ java.launcher.jar.error1=\
Error: An unexpected error occurred while trying to open file {0} Error: An unexpected error occurred while trying to open file {0}
java.launcher.jar.error2=manifest not found in {0} java.launcher.jar.error2=manifest not found in {0}
java.launcher.jar.error3=no main manifest attribute, in {0} java.launcher.jar.error3=no main manifest attribute, in {0}
java.launcher.init.error=initialization error
/*
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "java.h"
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include "manifest_info.h"
#include "version_comp.h"
#ifdef __solaris__
#include <thread.h>
#else
#include <pthread.h>
#endif
#define JVM_DLL "libjvm.so"
#define JAVA_DLL "libjava.so"
/* help jettison the LD_LIBRARY_PATH settings in the future */
#ifndef SETENV_REQUIRED
#define SETENV_REQUIRED
#endif
/*
* If a processor / os combination has the ability to run binaries of
* two data models and cohabitation of jre/jdk bits with both data
* models is supported, then DUAL_MODE is defined. When DUAL_MODE is
* defined, the architecture names for the narrow and wide version of
* the architecture are defined in LIBARCH64NAME and LIBARCH32NAME.
* Currently only Solaris on sparc/sparcv9 and i586/amd64 is DUAL_MODE;
* linux i586/amd64 could be defined as DUAL_MODE but that is not the
* current policy.
*/
#ifdef __solaris__
# define DUAL_MODE
# ifndef LIBARCH32NAME
# error "The macro LIBARCH32NAME was not defined on the compile line"
# endif
# ifndef LIBARCH64NAME
# error "The macro LIBARCH64NAME was not defined on the compile line"
# endif
# include <sys/systeminfo.h>
# include <sys/elf.h>
# include <stdio.h>
#endif
/* pointer to environment */
extern char **environ;
/*
* A collection of useful strings. One should think of these as #define
* entries, but actual strings can be more efficient (with many compilers).
*/
#ifdef __linux__
static const char *system_dir = "/usr/java";
static const char *user_dir = "/java";
#else /* Solaris */
static const char *system_dir = "/usr/jdk";
static const char *user_dir = "/jdk";
#endif
/* Store the name of the executable once computed */
static char *execname = NULL;
/*
* Flowchart of launcher execs and options processing on unix
*
* The selection of the proper vm shared library to open depends on
* several classes of command line options, including vm "flavor"
* options (-client, -server) and the data model options, -d32 and
* -d64, as well as a version specification which may have come from
* the command line or from the manifest of an executable jar file.
* The vm selection options are not passed to the running
* virtual machine; they must be screened out by the launcher.
*
* The version specification (if any) is processed first by the
* platform independent routine SelectVersion. This may result in
* the exec of the specified launcher version.
*
* Previously the launcher modified the LD_LIBRARY_PATH appropriately for the
* desired data model path, regardless if data models matched or not. The
* launcher subsequently exec'ed the desired executable, in order to make the
* LD_LIBRARY_PATH path available, for the runtime linker.
*
* Now, in most cases,the launcher will dlopen the target libjvm.so. All
* required libraries are loaded by the runtime linker, using the
* $RPATH/$ORIGIN baked into the shared libraries at compile time. Therefore,
* in most cases, the launcher will only exec, if the data models are
* mismatched, and will not set any environment variables, regardless of the
* data models.
*
* However, if the environment contains a LD_LIBRARY_PATH, this will cause the
* launcher to inspect the LD_LIBRARY_PATH. The launcher will check
* a. if the LD_LIBRARY_PATH's first component is the the path to the desired
* libjvm.so
* b. if any other libjvm.so is found in any of the paths.
* If case b is true, then the launcher will set the LD_LIBRARY_PATH to the
* desired JRE and reexec, in order to propagate the environment.
*
* Main
* (incoming argv)
* |
* \|/
* SelectVersion
* (selects the JRE version, note: not data model)
* |
* \|/
* CreateExecutionEnvironment
* (determines desired data model)
* |
* |
* \|/
* Have Desired Model ? --> NO --> Is Dual-Mode ? --> NO --> Exit(with error)
* | |
* | |
* | \|/
* | YES
* | |
* | |
* | \|/
* | CheckJvmType
* | (removes -client, -server etc.)
* | |
* | |
* \|/ \|/
* YES Find the desired executable/library
* | |
* | |
* \|/ \|/
* CheckJvmType RequiresSetenv
* (removes -client, -server, etc.)
* |
* |
* \|/
* TranslateDashJArgs...
* (Prepare to pass args to vm)
* |
* |
* \|/
* ParseArguments
* (removes -d32 and -d64 if any,
* processes version options,
* creates argument list for vm,
* etc.)
* |
* |
* \|/
* RequiresSetenv
* Is LD_LIBRARY_PATH
* and friends set ? --> NO --> Have Desired Model ? NO --> Re-exec --> Main
* YES YES --> Continue
* |
* |
* \|/
* Path is desired JRE ? YES --> Have Desired Model ? NO --> Re-exec --> Main
* NO YES --> Continue
* |
* |
* \|/
* Paths have well known
* jvm paths ? --> NO --> Have Desired Model ? NO --> Re-exec --> Main
* YES YES --> Continue
* |
* |
* \|/
* Does libjvm.so exit
* in any of them ? --> NO --> Have Desired Model ? NO --> Re-exec --> Main
* YES YES --> Continue
* |
* |
* \|/
* Set the LD_LIBRARY_PATH
* |
* |
* \|/
* Re-exec
* |
* |
* \|/
* Main
*/
static const char * SetExecname(char **argv);
static jboolean GetJVMPath(const char *jrepath, const char *jvmtype,
char *jvmpath, jint jvmpathsize, const char * arch);
static jboolean GetJREPath(char *path, jint pathsize, const char * arch, jboolean speculative);
#define GetArch() GetArchPath(CURRENT_DATA_MODEL)
const char *
GetArchPath(int nbits)
{
switch(nbits) {
#ifdef DUAL_MODE
case 32:
return LIBARCH32NAME;
case 64:
return LIBARCH64NAME;
#endif /* DUAL_MODE */
default:
return LIBARCHNAME;
}
}
#ifdef SETENV_REQUIRED
static jboolean
JvmExists(const char *path) {
char tmp[PATH_MAX + 1];
struct stat statbuf;
JLI_Snprintf(tmp, PATH_MAX, "%s/%s", path, JVM_DLL);
if (stat(tmp, &statbuf) == 0) {
return JNI_TRUE;
}
return JNI_FALSE;
}
/*
* contains a lib/$LIBARCH/{server,client}/libjvm.so ?
*/
static jboolean
ContainsLibJVM(int wanted, const char *env) {
char clientPattern[PATH_MAX + 1];
char serverPattern[PATH_MAX + 1];
char *envpath;
char *path;
jboolean clientPatternFound;
jboolean serverPatternFound;
/* fastest path */
if (env == NULL) {
return JNI_FALSE;
}
/* the usual suspects */
JLI_Snprintf(clientPattern, PATH_MAX, "lib/%s/client", GetArchPath(wanted));
JLI_Snprintf(serverPattern, PATH_MAX, "lib/%s/server", GetArchPath(wanted));
/* to optimize for time, test if any of our usual suspects are present. */
clientPatternFound = JLI_StrStr(env, clientPattern) != NULL;
serverPatternFound = JLI_StrStr(env, serverPattern) != NULL;
if (clientPatternFound == JNI_FALSE && serverPatternFound == JNI_FALSE) {
return JNI_FALSE;
}
/*
* we have a suspicious path component, check if it contains a libjvm.so
*/
envpath = JLI_StringDup(env);
for (path = JLI_StrTok(envpath, ":"); path != NULL; path = JLI_StrTok(NULL, ":")) {
if (clientPatternFound && JLI_StrStr(path, clientPattern) != NULL) {
if (JvmExists(path)) {
JLI_MemFree(envpath);
return JNI_TRUE;
}
}
if (serverPatternFound && JLI_StrStr(path, serverPattern) != NULL) {
if (JvmExists(path)) {
JLI_MemFree(envpath);
return JNI_TRUE;
}
}
}
JLI_MemFree(envpath);
return JNI_FALSE;
}
/*
* Test whether the environment variable needs to be set, see flowchart.
*/
static jboolean
RequiresSetenv(int wanted, const char *jvmpath) {
char jpath[PATH_MAX + 1];
char *llp;
char *dmllp = NULL;
char *p; /* a utility pointer */
llp = getenv("LD_LIBRARY_PATH");
#ifdef __solaris__
dmllp = (CURRENT_DATA_MODEL == 32)
? getenv("LD_LIBRARY_PATH_32")
: getenv("LD_LIBRARY_PATH_64");
#endif /* __solaris__ */
/* no environment variable is a good environment variable */
if (llp == NULL && dmllp == NULL) {
return JNI_FALSE;
}
#ifdef __linux
/*
* On linux, if a binary is running as sgid or suid, glibc sets
* LD_LIBRARY_PATH to the empty string for security purposes. (In contrast,
* on Solaris the LD_LIBRARY_PATH variable for a privileged binary does not
* lose its settings; but the dynamic linker does apply more scrutiny to the
* path.) The launcher uses the value of LD_LIBRARY_PATH to prevent an exec
* loop, here and further downstream. Therefore, if we are running sgid or
* suid, this function's setting of LD_LIBRARY_PATH will be ineffective and
* we should case a return from the calling function. Getting the right
* libraries will be handled by the RPATH. In reality, this check is
* redundant, as the previous check for a non-null LD_LIBRARY_PATH will
* return back to the calling function forthwith, it is left here to safe
* guard against any changes, in the glibc's existing security policy.
*/
if ((getgid() != getegid()) || (getuid() != geteuid())) {
return JNI_FALSE;
}
#endif /* __linux */
/*
* Prevent recursions. Since LD_LIBRARY_PATH is the one which will be set by
* previous versions of the JRE, thus it is the only path that matters here.
* So we check to see if the desired JRE is set.
*/
JLI_StrNCpy(jpath, jvmpath, PATH_MAX);
p = JLI_StrRChr(jpath, '/');
*p = '\0';
if (llp != NULL && JLI_StrNCmp(llp, jpath, JLI_StrLen(jpath)) == 0) {
return JNI_FALSE;
}
/* scrutinize all the paths further */
if (llp != NULL && ContainsLibJVM(wanted, llp)) {
return JNI_TRUE;
}
if (dmllp != NULL && ContainsLibJVM(wanted, dmllp)) {
return JNI_TRUE;
}
return JNI_FALSE;
}
#endif /* SETENV_REQUIRED */
void
CreateExecutionEnvironment(int *pargc, char ***pargv,
char jrepath[], jint so_jrepath,
char jvmpath[], jint so_jvmpath) {
/*
* First, determine if we are running the desired data model. If we
* are running the desired data model, all the error messages
* associated with calling GetJREPath, ReadKnownVMs, etc. should be
* output. However, if we are not running the desired data model,
* some of the errors should be suppressed since it is more
* informative to issue an error message based on whether or not the
* os/processor combination has dual mode capabilities.
*/
jboolean jvmpathExists;
/* Compute/set the name of the executable */
SetExecname(*pargv);
/* Check data model flags, and exec process, if needed */
{
char *arch = (char *)GetArch(); /* like sparc or sparcv9 */
char * jvmtype = NULL;
int argc = *pargc;
char **argv = *pargv;
int running = CURRENT_DATA_MODEL;
int wanted = running; /* What data mode is being
asked for? Current model is
fine unless another model
is asked for */
#ifdef SETENV_REQUIRED
jboolean mustsetenv = JNI_FALSE;
char *runpath = NULL; /* existing effective LD_LIBRARY_PATH setting */
char* new_runpath = NULL; /* desired new LD_LIBRARY_PATH string */
char* newpath = NULL; /* path on new LD_LIBRARY_PATH */
char* lastslash = NULL;
char** newenvp = NULL; /* current environment */
#ifdef __solaris__
char* dmpath = NULL; /* data model specific LD_LIBRARY_PATH,
Solaris only */
#endif /* __solaris__ */
#endif /* SETENV_REQUIRED */
char** newargv = NULL;
int newargc = 0;
/*
* Starting in 1.5, all unix platforms accept the -d32 and -d64
* options. On platforms where only one data-model is supported
* (e.g. ia-64 Linux), using the flag for the other data model is
* an error and will terminate the program.
*/
{ /* open new scope to declare local variables */
int i;
newargv = (char **)JLI_MemAlloc((argc+1) * sizeof(char*));
newargv[newargc++] = argv[0];
/* scan for data model arguments and remove from argument list;
last occurrence determines desired data model */
for (i=1; i < argc; i++) {
if (JLI_StrCmp(argv[i], "-J-d64") == 0 || JLI_StrCmp(argv[i], "-d64") == 0) {
wanted = 64;
continue;
}
if (JLI_StrCmp(argv[i], "-J-d32") == 0 || JLI_StrCmp(argv[i], "-d32") == 0) {
wanted = 32;
continue;
}
newargv[newargc++] = argv[i];
if (IsJavaArgs()) {
if (argv[i][0] != '-') continue;
} else {
if (JLI_StrCmp(argv[i], "-classpath") == 0 || JLI_StrCmp(argv[i], "-cp") == 0) {
i++;
if (i >= argc) break;
newargv[newargc++] = argv[i];
continue;
}
if (argv[i][0] != '-') { i++; break; }
}
}
/* copy rest of args [i .. argc) */
while (i < argc) {
newargv[newargc++] = argv[i++];
}
newargv[newargc] = NULL;
/*
* newargv has all proper arguments here
*/
argc = newargc;
argv = newargv;
}
/* If the data model is not changing, it is an error if the
jvmpath does not exist */
if (wanted == running) {
/* Find out where the JRE is that we will be using. */
if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {
JLI_ReportErrorMessage(JRE_ERROR1);
exit(2);
}
/* Find the specified JVM type */
if (ReadKnownVMs(jrepath, arch, JNI_FALSE) < 1) {
JLI_ReportErrorMessage(CFG_ERROR7);
exit(1);
}
jvmpath[0] = '\0';
jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
JLI_ReportErrorMessage(CFG_ERROR9);
exit(4);
}
if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, arch )) {
JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
exit(4);
}
/*
* we seem to have everything we need, so without further ado
* we return back, otherwise proceed to set the environment.
*/
#ifdef SETENV_REQUIRED
mustsetenv = RequiresSetenv(wanted, jvmpath);
JLI_TraceLauncher("mustsetenv: %s\n", mustsetenv ? "TRUE" : "FALSE");
if (mustsetenv == JNI_FALSE) {
return;
}
#else
return;
#endif /* SETENV_REQUIRED */
} else { /* do the same speculatively or exit */
#ifdef DUAL_MODE
if (running != wanted) {
/* Find out where the JRE is that we will be using. */
if (!GetJREPath(jrepath, so_jrepath, GetArchPath(wanted), JNI_TRUE)) {
/* give up and let other code report error message */
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
}
/*
* Read in jvm.cfg for target data model and process vm
* selection options.
*/
if (ReadKnownVMs(jrepath, GetArchPath(wanted), JNI_TRUE) < 1) {
/* give up and let other code report error message */
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
}
jvmpath[0] = '\0';
jvmtype = CheckJvmType(pargc, pargv, JNI_TRUE);
if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
JLI_ReportErrorMessage(CFG_ERROR9);
exit(4);
}
/* exec child can do error checking on the existence of the path */
jvmpathExists = GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, GetArchPath(wanted));
#ifdef SETENV_REQUIRED
mustsetenv = RequiresSetenv(wanted, jvmpath);
#endif /* SETENV_REQUIRED */
}
#else
JLI_ReportErrorMessage(JRE_ERROR2, wanted);
exit(1);
#endif
}
#ifdef SETENV_REQUIRED
if (mustsetenv) {
/*
* We will set the LD_LIBRARY_PATH as follows:
*
* o $JVMPATH (directory portion only)
* o $JRE/lib/$LIBARCHNAME
* o $JRE/../lib/$LIBARCHNAME
*
* followed by the user's previous effective LD_LIBRARY_PATH, if
* any.
*/
#ifdef __solaris__
/*
* Starting in Solaris 7, ld.so.1 supports three LD_LIBRARY_PATH
* variables:
*
* 1. LD_LIBRARY_PATH -- used for 32 and 64 bit searches if
* data-model specific variables are not set.
*
* 2. LD_LIBRARY_PATH_64 -- overrides and replaces LD_LIBRARY_PATH
* for 64-bit binaries.
*
* 3. LD_LIBRARY_PATH_32 -- overrides and replaces LD_LIBRARY_PATH
* for 32-bit binaries.
*
* The vm uses LD_LIBRARY_PATH to set the java.library.path system
* property. To shield the vm from the complication of multiple
* LD_LIBRARY_PATH variables, if the appropriate data model
* specific variable is set, we will act as if LD_LIBRARY_PATH had
* the value of the data model specific variant and the data model
* specific variant will be unset. Note that the variable for the
* *wanted* data model must be used (if it is set), not simply the
* current running data model.
*/
switch (wanted) {
case 0:
if (running == 32) {
dmpath = getenv("LD_LIBRARY_PATH_32");
wanted = 32;
} else {
dmpath = getenv("LD_LIBRARY_PATH_64");
wanted = 64;
}
break;
case 32:
dmpath = getenv("LD_LIBRARY_PATH_32");
break;
case 64:
dmpath = getenv("LD_LIBRARY_PATH_64");
break;
default:
JLI_ReportErrorMessage(JRE_ERROR3, __LINE__);
exit(1); /* unknown value in wanted */
break;
}
/*
* If dmpath is NULL, the relevant data model specific variable is
* not set and normal LD_LIBRARY_PATH should be used.
*/
if (dmpath == NULL) {
runpath = getenv("LD_LIBRARY_PATH");
} else {
runpath = dmpath;
}
#else
/*
* If not on Solaris, assume only a single LD_LIBRARY_PATH
* variable.
*/
runpath = getenv("LD_LIBRARY_PATH");
#endif /* __solaris__ */
/* runpath contains current effective LD_LIBRARY_PATH setting */
jvmpath = JLI_StringDup(jvmpath);
new_runpath = JLI_MemAlloc(((runpath != NULL) ? JLI_StrLen(runpath) : 0) +
2 * JLI_StrLen(jrepath) + 2 * JLI_StrLen(arch) +
JLI_StrLen(jvmpath) + 52);
newpath = new_runpath + JLI_StrLen("LD_LIBRARY_PATH=");
/*
* Create desired LD_LIBRARY_PATH value for target data model.
*/
{
/* remove the name of the .so from the JVM path */
lastslash = JLI_StrRChr(jvmpath, '/');
if (lastslash)
*lastslash = '\0';
sprintf(new_runpath, "LD_LIBRARY_PATH="
"%s:"
"%s/lib/%s:"
"%s/../lib/%s",
jvmpath,
#ifdef DUAL_MODE
jrepath, GetArchPath(wanted),
jrepath, GetArchPath(wanted)
#else
jrepath, arch,
jrepath, arch
#endif
);
/*
* Check to make sure that the prefix of the current path is the
* desired environment variable setting, though the RequiresSetenv
* checks if the desired runpath exists, this logic does a more
* comprehensive check.
*/
if (runpath != NULL &&
JLI_StrNCmp(newpath, runpath, JLI_StrLen(newpath)) == 0 &&
(runpath[JLI_StrLen(newpath)] == 0 || runpath[JLI_StrLen(newpath)] == ':') &&
(running == wanted) /* data model does not have to be changed */
#ifdef __solaris__
&& (dmpath == NULL) /* data model specific variables not set */
#endif
) {
return;
}
}
/*
* Place the desired environment setting onto the prefix of
* LD_LIBRARY_PATH. Note that this prevents any possible infinite
* loop of execv() because we test for the prefix, above.
*/
if (runpath != 0) {
JLI_StrCat(new_runpath, ":");
JLI_StrCat(new_runpath, runpath);
}
if (putenv(new_runpath) != 0) {
exit(1); /* problem allocating memory; LD_LIBRARY_PATH not set
properly */
}
/*
* Unix systems document that they look at LD_LIBRARY_PATH only
* once at startup, so we have to re-exec the current executable
* to get the changed environment variable to have an effect.
*/
#ifdef __solaris__
/*
* If dmpath is not NULL, remove the data model specific string
* in the environment for the exec'ed child.
*/
if (dmpath != NULL)
(void)UnsetEnv((wanted == 32) ? "LD_LIBRARY_PATH_32" : "LD_LIBRARY_PATH_64");
#endif
newenvp = environ;
}
#endif /* SETENV_REQUIRED */
{
char *newexec = execname;
#ifdef DUAL_MODE
/*
* If the data model is being changed, the path to the
* executable must be updated accordingly; the executable name
* and directory the executable resides in are separate. In the
* case of 32 => 64, the new bits are assumed to reside in, e.g.
* "olddir/LIBARCH64NAME/execname"; in the case of 64 => 32,
* the bits are assumed to be in "olddir/../execname". For example,
*
* olddir/sparcv9/execname
* olddir/amd64/execname
*
* for Solaris SPARC and Linux amd64, respectively.
*/
if (running != wanted) {
char *oldexec = JLI_StrCpy(JLI_MemAlloc(JLI_StrLen(execname) + 1), execname);
char *olddir = oldexec;
char *oldbase = JLI_StrRChr(oldexec, '/');
newexec = JLI_MemAlloc(JLI_StrLen(execname) + 20);
*oldbase++ = 0;
sprintf(newexec, "%s/%s/%s", olddir,
((wanted == 64) ? LIBARCH64NAME : ".."), oldbase);
argv[0] = newexec;
}
#endif /* DUAL_MODE */
JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
(void) fflush(stdout);
(void) fflush(stderr);
#ifdef SETENV_REQUIRED
if (mustsetenv) {
execve(newexec, argv, newenvp);
} else {
execv(newexec, argv);
}
#else
execv(newexec, argv);
#endif /* SETENV_REQUIRED */
JLI_ReportErrorMessageSys(JRE_ERROR4, newexec);
#ifdef DUAL_MODE
if (running != wanted) {
JLI_ReportErrorMessage(JRE_ERROR5, wanted, running);
#ifdef __solaris__
#ifdef __sparc
JLI_ReportErrorMessage(JRE_ERROR6);
#else
JLI_ReportErrorMessage(JRE_ERROR7);
#endif /* __sparc */
}
#endif /* __solaris__ */
#endif /* DUAL_MODE */
}
exit(1);
}
}
/*
* On Solaris VM choosing is done by the launcher (java.c).
*/
static jboolean
GetJVMPath(const char *jrepath, const char *jvmtype,
char *jvmpath, jint jvmpathsize, const char * arch)
{
struct stat s;
if (JLI_StrChr(jvmtype, '/')) {
JLI_Snprintf(jvmpath, jvmpathsize, "%s/" JVM_DLL, jvmtype);
} else {
JLI_Snprintf(jvmpath, jvmpathsize, "%s/lib/%s/%s/" JVM_DLL, jrepath, arch, jvmtype);
}
JLI_TraceLauncher("Does `%s' exist ... ", jvmpath);
if (stat(jvmpath, &s) == 0) {
JLI_TraceLauncher("yes.\n");
return JNI_TRUE;
} else {
JLI_TraceLauncher("no.\n");
return JNI_FALSE;
}
}
/*
* Find path to JRE based on .exe's location or registry settings.
*/
static jboolean
GetJREPath(char *path, jint pathsize, const char * arch, jboolean speculative)
{
char libjava[MAXPATHLEN];
if (GetApplicationHome(path, pathsize)) {
/* Is JRE co-located with the application? */
JLI_Snprintf(libjava, sizeof(libjava), "%s/lib/%s/" JAVA_DLL, path, arch);
if (access(libjava, F_OK) == 0) {
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
/* Does the app ship a private JRE in <apphome>/jre directory? */
JLI_Snprintf(libjava, sizeof(libjava), "%s/jre/lib/%s/" JAVA_DLL, path, arch);
if (access(libjava, F_OK) == 0) {
JLI_StrCat(path, "/jre");
JLI_TraceLauncher("JRE path is %s\n", path);
return JNI_TRUE;
}
}
if (!speculative)
JLI_ReportErrorMessage(JRE_ERROR8 JAVA_DLL);
return JNI_FALSE;
}
jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
void *libjvm;
JLI_TraceLauncher("JVM path is %s\n", jvmpath);
libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
if (libjvm == NULL) {
#if defined(__solaris__) && defined(__sparc) && !defined(_LP64) /* i.e. 32-bit sparc */
FILE * fp;
Elf32_Ehdr elf_head;
int count;
int location;
fp = fopen(jvmpath, "r");
if (fp == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/* read in elf header */
count = fread((void*)(&elf_head), sizeof(Elf32_Ehdr), 1, fp);
fclose(fp);
if (count < 1) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
/*
* Check for running a server vm (compiled with -xarch=v8plus)
* on a stock v8 processor. In this case, the machine type in
* the elf header would not be included the architecture list
* provided by the isalist command, which is turn is gotten from
* sysinfo. This case cannot occur on 64-bit hardware and thus
* does not have to be checked for in binaries with an LP64 data
* model.
*/
if (elf_head.e_machine == EM_SPARC32PLUS) {
char buf[257]; /* recommended buffer size from sysinfo man
page */
long length;
char* location;
length = sysinfo(SI_ISALIST, buf, 257);
if (length > 0) {
location = JLI_StrStr(buf, "sparcv8plus ");
if (location == NULL) {
JLI_ReportErrorMessage(JVM_ERROR3);
return JNI_FALSE;
}
}
}
#endif
JLI_ReportErrorMessage(DLL_ERROR1, __LINE__);
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
ifn->CreateJavaVM = (CreateJavaVM_t)
dlsym(libjvm, "JNI_CreateJavaVM");
if (ifn->CreateJavaVM == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
if (ifn->GetDefaultJavaVMInitArgs == NULL) {
JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* If app is "/foo/bin/javac", or "/foo/bin/sparcv9/javac" then put
* "/foo" into buf.
*/
jboolean
GetApplicationHome(char *buf, jint bufsize)
{
if (execname != NULL) {
JLI_Snprintf(buf, bufsize, "%s", execname);
buf[bufsize-1] = '\0';
} else {
return JNI_FALSE;
}
if (JLI_StrRChr(buf, '/') == 0) {
buf[0] = '\0';
return JNI_FALSE;
}
*(JLI_StrRChr(buf, '/')) = '\0'; /* executable file */
if (JLI_StrLen(buf) < 4 || JLI_StrRChr(buf, '/') == 0) {
buf[0] = '\0';
return JNI_FALSE;
}
if (JLI_StrCmp("/bin", buf + JLI_StrLen(buf) - 4) != 0)
*(JLI_StrRChr(buf, '/')) = '\0'; /* sparcv9 or amd64 */
if (JLI_StrLen(buf) < 4 || JLI_StrCmp("/bin", buf + JLI_StrLen(buf) - 4) != 0) {
buf[0] = '\0';
return JNI_FALSE;
}
*(JLI_StrRChr(buf, '/')) = '\0'; /* bin */
return JNI_TRUE;
}
/*
* Return true if the named program exists
*/
static int
ProgramExists(char *name)
{
struct stat sb;
if (stat(name, &sb) != 0) return 0;
if (S_ISDIR(sb.st_mode)) return 0;
return (sb.st_mode & S_IEXEC) != 0;
}
/*
* Find a command in a directory, returning the path.
*/
static char *
Resolve(char *indir, char *cmd)
{
char name[PATH_MAX + 2], *real;
if ((JLI_StrLen(indir) + JLI_StrLen(cmd) + 1) > PATH_MAX) return 0;
JLI_Snprintf(name, sizeof(name), "%s%c%s", indir, FILE_SEPARATOR, cmd);
if (!ProgramExists(name)) return 0;
real = JLI_MemAlloc(PATH_MAX + 2);
if (!realpath(name, real))
JLI_StrCpy(real, name);
return real;
}
/*
* Find a path for the executable
*/
static char *
FindExecName(char *program)
{
char cwdbuf[PATH_MAX+2];
char *path;
char *tmp_path;
char *f;
char *result = NULL;
/* absolute path? */
if (*program == FILE_SEPARATOR ||
(FILE_SEPARATOR=='\\' && JLI_StrRChr(program, ':')))
return Resolve("", program+1);
/* relative path? */
if (JLI_StrRChr(program, FILE_SEPARATOR) != 0) {
char buf[PATH_MAX+2];
return Resolve(getcwd(cwdbuf, sizeof(cwdbuf)), program);
}
/* from search path? */
path = getenv("PATH");
if (!path || !*path) path = ".";
tmp_path = JLI_MemAlloc(JLI_StrLen(path) + 2);
JLI_StrCpy(tmp_path, path);
for (f=tmp_path; *f && result==0; ) {
char *s = f;
while (*f && (*f != PATH_SEPARATOR)) ++f;
if (*f) *f++ = 0;
if (*s == FILE_SEPARATOR)
result = Resolve(s, program);
else {
/* relative path element */
char dir[2*PATH_MAX];
JLI_Snprintf(dir, sizeof(dir), "%s%c%s", getcwd(cwdbuf, sizeof(cwdbuf)),
FILE_SEPARATOR, s);
result = Resolve(dir, program);
}
if (result != 0) break;
}
JLI_MemFree(tmp_path);
return result;
}
/*
* Compute the name of the executable
*
* In order to re-exec securely we need the absolute path of the
* executable. On Solaris getexecname(3c) may not return an absolute
* path so we use dladdr to get the filename of the executable and
* then use realpath to derive an absolute path. From Solaris 9
* onwards the filename returned in DL_info structure from dladdr is
* an absolute pathname so technically realpath isn't required.
* On Linux we read the executable name from /proc/self/exe.
* As a fallback, and for platforms other than Solaris and Linux,
* we use FindExecName to compute the executable name.
*/
static const char*
SetExecname(char **argv)
{
char* exec_path = NULL;
#if defined(__solaris__)
{
Dl_info dlinfo;
int (*fptr)();
fptr = (int (*)())dlsym(RTLD_DEFAULT, "main");
if (fptr == NULL) {
JLI_ReportErrorMessage(DLL_ERROR3, dlerror());
return JNI_FALSE;
}
if (dladdr((void*)fptr, &dlinfo)) {
char *resolved = (char*)JLI_MemAlloc(PATH_MAX+1);
if (resolved != NULL) {
exec_path = realpath(dlinfo.dli_fname, resolved);
if (exec_path == NULL) {
JLI_MemFree(resolved);
}
}
}
}
#elif defined(__linux__)
{
const char* self = "/proc/self/exe";
char buf[PATH_MAX+1];
int len = readlink(self, buf, PATH_MAX);
if (len >= 0) {
buf[len] = '\0'; /* readlink doesn't nul terminate */
exec_path = JLI_StringDup(buf);
}
}
#else /* !__solaris__ && !__linux */
{
/* Not implemented */
}
#endif
if (exec_path == NULL) {
exec_path = FindExecName(argv[0]);
}
execname = exec_path;
return exec_path;
}
void JLI_ReportErrorMessage(const char* fmt, ...) {
va_list vl;
va_start(vl, fmt);
vfprintf(stderr, fmt, vl);
fprintf(stderr, "\n");
va_end(vl);
}
void JLI_ReportErrorMessageSys(const char* fmt, ...) {
va_list vl;
char *emsg;
/*
* TODO: its safer to use strerror_r but is not available on
* Solaris 8. Until then....
*/
emsg = strerror(errno);
if (emsg != NULL) {
fprintf(stderr, "%s\n", emsg);
}
va_start(vl, fmt);
vfprintf(stderr, fmt, vl);
fprintf(stderr, "\n");
va_end(vl);
}
void JLI_ReportExceptionDescription(JNIEnv * env) {
(*env)->ExceptionDescribe(env);
}
/*
* Since using the file system as a registry is a bit risky, perform
* additional sanity checks on the identified directory to validate
* it as a valid jre/sdk.
*
* Return 0 if the tests fail; otherwise return non-zero (true).
*
* Note that checking for anything more than the existence of an
* executable object at bin/java relative to the path being checked
* will break the regression tests.
*/
static int
CheckSanity(char *path, char *dir)
{
char buffer[PATH_MAX];
if (JLI_StrLen(path) + JLI_StrLen(dir) + 11 > PATH_MAX)
return (0); /* Silently reject "impossibly" long paths */
JLI_Snprintf(buffer, sizeof(buffer), "%s/%s/bin/java", path, dir);
return ((access(buffer, X_OK) == 0) ? 1 : 0);
}
/*
* Determine if there is an acceptable JRE in the directory dirname.
* Upon locating the "best" one, return a fully qualified path to
* it. "Best" is defined as the most advanced JRE meeting the
* constraints contained in the manifest_info. If no JRE in this
* directory meets the constraints, return NULL.
*
* Note that we don't check for errors in reading the directory
* (which would be done by checking errno). This is because it
* doesn't matter if we get an error reading the directory, or
* we just don't find anything interesting in the directory. We
* just return NULL in either case.
*
* The historical names of j2sdk and j2re were changed to jdk and
* jre respecively as part of the 1.5 rebranding effort. Since the
* former names are legacy on Linux, they must be recognized for
* all time. Fortunately, this is a minor cost.
*/
static char
*ProcessDir(manifest_info *info, char *dirname)
{
DIR *dirp;
struct dirent *dp;
char *best = NULL;
int offset;
int best_offset = 0;
char *ret_str = NULL;
char buffer[PATH_MAX];
if ((dirp = opendir(dirname)) == NULL)
return (NULL);
do {
if ((dp = readdir(dirp)) != NULL) {
offset = 0;
if ((JLI_StrNCmp(dp->d_name, "jre", 3) == 0) ||
(JLI_StrNCmp(dp->d_name, "jdk", 3) == 0))
offset = 3;
else if (JLI_StrNCmp(dp->d_name, "j2re", 4) == 0)
offset = 4;
else if (JLI_StrNCmp(dp->d_name, "j2sdk", 5) == 0)
offset = 5;
if (offset > 0) {
if ((JLI_AcceptableRelease(dp->d_name + offset,
info->jre_version)) && CheckSanity(dirname, dp->d_name))
if ((best == NULL) || (JLI_ExactVersionId(
dp->d_name + offset, best + best_offset) > 0)) {
if (best != NULL)
JLI_MemFree(best);
best = JLI_StringDup(dp->d_name);
best_offset = offset;
}
}
}
} while (dp != NULL);
(void) closedir(dirp);
if (best == NULL)
return (NULL);
else {
ret_str = JLI_MemAlloc(JLI_StrLen(dirname) + JLI_StrLen(best) + 2);
sprintf(ret_str, "%s/%s", dirname, best);
JLI_MemFree(best);
return (ret_str);
}
}
/*
* This is the global entry point. It examines the host for the optimal
* JRE to be used by scanning a set of directories. The set of directories
* is platform dependent and can be overridden by the environment
* variable JAVA_VERSION_PATH.
*
* This routine itself simply determines the set of appropriate
* directories before passing control onto ProcessDir().
*/
char*
LocateJRE(manifest_info* info)
{
char *path;
char *home;
char *target = NULL;
char *dp;
char *cp;
/*
* Start by getting JAVA_VERSION_PATH
*/
if (info->jre_restrict_search) {
path = JLI_StringDup(system_dir);
} else if ((path = getenv("JAVA_VERSION_PATH")) != NULL) {
path = JLI_StringDup(path);
} else {
if ((home = getenv("HOME")) != NULL) {
path = (char *)JLI_MemAlloc(JLI_StrLen(home) + \
JLI_StrLen(system_dir) + JLI_StrLen(user_dir) + 2);
sprintf(path, "%s%s:%s", home, user_dir, system_dir);
} else {
path = JLI_StringDup(system_dir);
}
}
/*
* Step through each directory on the path. Terminate the scan with
* the first directory with an acceptable JRE.
*/
cp = dp = path;
while (dp != NULL) {
cp = JLI_StrChr(dp, (int)':');
if (cp != NULL)
*cp = '\0';
if ((target = ProcessDir(info, dp)) != NULL)
break;
dp = cp;
if (dp != NULL)
dp++;
}
JLI_MemFree(path);
return (target);
}
/*
* Given a path to a jre to execute, this routine checks if this process
* is indeed that jre. If not, it exec's that jre.
*
* We want to actually check the paths rather than just the version string
* built into the executable, so that given version specification (and
* JAVA_VERSION_PATH) will yield the exact same Java environment, regardless
* of the version of the arbitrary launcher we start with.
*/
void
ExecJRE(char *jre, char **argv)
{
char wanted[PATH_MAX];
const char* progname = GetProgramName();
/*
* Resolve the real path to the directory containing the selected JRE.
*/
if (realpath(jre, wanted) == NULL) {
JLI_ReportErrorMessage(JRE_ERROR9, jre);
exit(1);
}
/*
* Resolve the real path to the currently running launcher.
*/
SetExecname(argv);
if (execname == NULL) {
JLI_ReportErrorMessage(JRE_ERROR10);
exit(1);
}
/*
* If the path to the selected JRE directory is a match to the initial
* portion of the path to the currently executing JRE, we have a winner!
* If so, just return.
*/
if (JLI_StrNCmp(wanted, execname, JLI_StrLen(wanted)) == 0)
return; /* I am the droid you were looking for */
/*
* This should never happen (because of the selection code in SelectJRE),
* but check for "impossibly" long path names just because buffer overruns
* can be so deadly.
*/
if (JLI_StrLen(wanted) + JLI_StrLen(progname) + 6 > PATH_MAX) {
JLI_ReportErrorMessage(JRE_ERROR11);
exit(1);
}
/*
* Construct the path and exec it.
*/
(void)JLI_StrCat(JLI_StrCat(wanted, "/bin/"), progname);
argv[0] = JLI_StringDup(progname);
if (JLI_IsTraceLauncher()) {
int i;
printf("ReExec Command: %s (%s)\n", wanted, argv[0]);
printf("ReExec Args:");
for (i = 1; argv[i] != NULL; i++)
printf(" %s", argv[i]);
printf("\n");
}
JLI_TraceLauncher("TRACER_MARKER:About to EXEC\n");
(void)fflush(stdout);
(void)fflush(stderr);
execv(wanted, argv);
JLI_ReportErrorMessageSys(JRE_ERROR12, wanted);
exit(1);
}
/*
* "Borrowed" from Solaris 10 where the unsetenv() function is being added
* to libc thanks to SUSv3 (Standard Unix Specification, version 3). As
* such, in the fullness of time this will appear in libc on all relevant
* Solaris/Linux platforms and maybe even the Windows platform. At that
* time, this stub can be removed.
*
* This implementation removes the environment locking for multithreaded
* applications. (We don't have access to these mutexes within libc and
* the launcher isn't multithreaded.) Note that what remains is platform
* independent, because it only relies on attributes that a POSIX environment
* defines.
*
* Returns 0 on success, -1 on failure.
*
* Also removed was the setting of errno. The only value of errno set
* was EINVAL ("Invalid Argument").
*/
/*
* s1(environ) is name=value
* s2(name) is name(not the form of name=value).
* if names match, return value of 1, else return 0
*/
static int
match_noeq(const char *s1, const char *s2)
{
while (*s1 == *s2++) {
if (*s1++ == '=')
return (1);
}
if (*s1 == '=' && s2[-1] == '\0')
return (1);
return (0);
}
/*
* added for SUSv3 standard
*
* Delete entry from environ.
* Do not free() memory! Other threads may be using it.
* Keep it around forever.
*/
static int
borrowed_unsetenv(const char *name)
{
long idx; /* index into environ */
if (name == NULL || *name == '\0' ||
JLI_StrChr(name, '=') != NULL) {
return (-1);
}
for (idx = 0; environ[idx] != NULL; idx++) {
if (match_noeq(environ[idx], name))
break;
}
if (environ[idx] == NULL) {
/* name not found but still a success */
return (0);
}
/* squeeze up one entry */
do {
environ[idx] = environ[idx+1];
} while (environ[++idx] != NULL);
return (0);
}
/* --- End of "borrowed" code --- */
/*
* Wrapper for unsetenv() function.
*/
int
UnsetEnv(char *name)
{
return(borrowed_unsetenv(name));
}
/* --- Splash Screen shared library support --- */
static const char* SPLASHSCREEN_SO = "libsplashscreen.so";
static void* hSplashLib = NULL;
void* SplashProcAddress(const char* name) {
if (!hSplashLib) {
hSplashLib = dlopen(SPLASHSCREEN_SO, RTLD_LAZY | RTLD_GLOBAL);
}
if (hSplashLib) {
void* sym = dlsym(hSplashLib, name);
return sym;
} else {
return NULL;
}
}
void SplashFreeLibrary() {
if (hSplashLib) {
dlclose(hSplashLib);
hSplashLib = NULL;
}
}
const char *
jlong_format_specifier() {
return "%lld";
}
/*
* Block current thread and continue execution in a new thread
*/
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
#ifdef __solaris__
thread_t tid;
long flags = 0;
if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
void * tmp;
thr_join(tid, NULL, &tmp);
rslt = (int)tmp;
} else {
/* See below. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#else
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size);
}
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp);
rslt = (int)tmp;
} else {
/*
* Continue execution in current thread if for some reason (e.g. out of
* memory/LWP) a new thread can't be created. This will likely fail
* later in continuation as JNI_CreateJavaVM needs to create quite a
* few new threads, anyway, just give it a try..
*/
rslt = continuation(args);
}
pthread_attr_destroy(&attr);
#endif
return rslt;
}
/* Coarse estimation of number of digits assuming the worst case is a 64-bit pid. */
#define MAX_PID_STR_SZ 20
void SetJavaLauncherPlatformProps() {
/* Linux only */
#ifdef __linux__
const char *substr = "-Dsun.java.launcher.pid=";
char *pid_prop_str = (char *)JLI_MemAlloc(JLI_StrLen(substr) + MAX_PID_STR_SZ + 1);
sprintf(pid_prop_str, "%s%d", substr, getpid());
AddOption(pid_prop_str, NULL);
#endif
}
jboolean
IsJavaw()
{
/* noop on UNIX */
return JNI_FALSE;
}
void
InitLauncher(jboolean javaw)
{
JLI_SetTraceLauncher();
}
/*
* The implementation for finding classes from the bootstrap
* class loader, refer to java.h
*/
static FindClassFromBootLoader_t *findBootClass = NULL;
jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{
if (findBootClass == NULL) {
findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
"JVM_FindClassFromBootLoader");
if (findBootClass == NULL) {
JLI_ReportErrorMessage(DLL_ERROR4,
"JVM_FindClassFromBootLoader");
return NULL;
}
}
return findBootClass(env, classname);
}
...@@ -502,3 +502,19 @@ FindBootStrapClass(JNIEnv *env, const char* classname) ...@@ -502,3 +502,19 @@ FindBootStrapClass(JNIEnv *env, const char* classname)
return findBootClass(env, classname); return findBootClass(env, classname);
} }
StdArg
*JLI_GetStdArgs()
{
return NULL;
}
int
JLI_GetStdArgc() {
return 0;
}
jobjectArray
CreateApplicationArgs(JNIEnv *env, char **strv, int argc)
{
return NewPlatformStringArray(env, strv, argc);
}
...@@ -1357,3 +1357,89 @@ ProcessPlatformOption(const char *arg) ...@@ -1357,3 +1357,89 @@ ProcessPlatformOption(const char *arg)
{ {
return JNI_FALSE; return JNI_FALSE;
} }
/*
* At this point we have the arguments to the application, and we need to
* check with original stdargs in order to compare which of these truly
* needs expansion. cmdtoargs will specify this if it finds a bare
* (unquoted) argument containing a glob character(s) ie. * or ?
*/
jobjectArray
CreateApplicationArgs(JNIEnv *env, char **strv, int argc)
{
int i, j, idx, tlen;
jobjectArray outArray, inArray;
char *ostart, *astart, **nargv;
jboolean needs_expansion = JNI_FALSE;
jmethodID mid;
int stdargc;
StdArg *stdargs;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (argc == 0) {
return NewPlatformStringArray(env, strv, argc);
}
// the holy grail we need to compare with.
stdargs = JLI_GetStdArgs();
stdargc = JLI_GetStdArgc();
// sanity check, this should never happen
if (argc > stdargc) {
JLI_TraceLauncher("Warning: app args is larger than the original, %d %d\n", argc, stdargc);
JLI_TraceLauncher("passing arguments as-is.\n");
return NewPlatformStringArray(env, strv, argc);
}
// sanity check, match the args we have, to the holy grail
idx = stdargc - argc;
ostart = stdargs[idx].arg;
astart = strv[0];
// sanity check, ensure that the first argument of the arrays are the same
if (JLI_StrCmp(ostart, astart) != 0) {
// some thing is amiss the args don't match
JLI_TraceLauncher("Warning: app args parsing error\n");
JLI_TraceLauncher("passing arguments as-is\n");
return NewPlatformStringArray(env, strv, argc);
}
// make a copy of the args which will be expanded in java if required.
nargv = (char **)JLI_MemAlloc(argc * sizeof(char*));
for (i = 0, j = idx; i < argc; i++, j++) {
jboolean arg_expand = (JLI_StrCmp(stdargs[j].arg, strv[i]) == 0)
? stdargs[j].has_wildcard
: JNI_FALSE;
if (needs_expansion == JNI_FALSE)
needs_expansion = arg_expand;
// indicator char + String + NULL terminator, the java method will strip
// out the first character, the indicator character, so no matter what
// we add the indicator
tlen = 1 + JLI_StrLen(strv[i]) + 1;
nargv[i] = (char *) JLI_MemAlloc(tlen);
JLI_Snprintf(nargv[i], tlen, "%c%s", arg_expand ? 'T' : 'F', strv[i]);
JLI_TraceLauncher("%s\n", nargv[i]);
}
if (!needs_expansion) {
// clean up any allocated memory and return back the old arguments
for (i = 0 ; i < argc ; i++) {
JLI_MemFree(nargv[i]);
}
JLI_MemFree(nargv);
return NewPlatformStringArray(env, strv, argc);
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"expandArgs",
"([Ljava/lang/String;)[Ljava/lang/String;"));
// expand the arguments that require expansion, the java method will strip
// out the indicator character.
inArray = NewPlatformStringArray(env, nargv, argc);
outArray = (*env)->CallStaticObjectMethod(env, cls, mid, inArray);
for (i = 0; i < argc; i++) {
JLI_MemFree(nargv[i]);
}
JLI_MemFree(nargv);
return outArray;
}
...@@ -36,7 +36,13 @@ import java.io.FileNotFoundException; ...@@ -36,7 +36,13 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Arrrghs extends TestHelper { public class Arrrghs extends TestHelper {
private Arrrghs(){} private Arrrghs(){}
...@@ -75,7 +81,7 @@ public class Arrrghs extends TestHelper { ...@@ -75,7 +81,7 @@ public class Arrrghs extends TestHelper {
/* /*
* This method detects the cookie in the output stream of the process. * This method detects the cookie in the output stream of the process.
*/ */
private static boolean detectCookie(InputStream istream, private boolean detectCookie(InputStream istream,
String expectedArguments) throws IOException { String expectedArguments) throws IOException {
BufferedReader rd = new BufferedReader(new InputStreamReader(istream)); BufferedReader rd = new BufferedReader(new InputStreamReader(istream));
boolean retval = false; boolean retval = false;
...@@ -105,7 +111,7 @@ public class Arrrghs extends TestHelper { ...@@ -105,7 +111,7 @@ public class Arrrghs extends TestHelper {
return retval; return retval;
} }
private static boolean doTest0(ProcessBuilder pb, String expectedArguments) { private boolean doReExecTest0(ProcessBuilder pb, String expectedArguments) {
boolean retval = false; boolean retval = false;
try { try {
pb.redirectErrorStream(true); pb.redirectErrorStream(true);
...@@ -121,26 +127,27 @@ public class Arrrghs extends TestHelper { ...@@ -121,26 +127,27 @@ public class Arrrghs extends TestHelper {
} }
/** /**
* This method return true if the expected and detected arguments are the same. * This method returns true if the expected and detected arguments are the same.
* Quoting could cause dissimilar testArguments and expected arguments. * Quoting could cause dissimilar testArguments and expected arguments.
*/ */
static int doTest(String testArguments, String expectedPattern) { int doReExecTest(String testArguments, String expectedPattern) {
ProcessBuilder pb = new ProcessBuilder(javaCmd, ProcessBuilder pb = new ProcessBuilder(javaCmd,
VersionStr, testArguments); VersionStr, testArguments);
Map<String, String> env = pb.environment(); Map<String, String> env = pb.environment();
env.put("_JAVA_LAUNCHER_DEBUG", "true"); env.put(JLDEBUG_KEY, "true");
return doTest0(pb, testArguments) ? 0 : 1; return doReExecTest0(pb, testArguments) ? 0 : 1;
} }
/** /**
* A convenience method for identical test pattern and expected arguments * A convenience method for identical test pattern and expected arguments
*/ */
static int doTest(String testPattern) { int doReExecTest(String testPattern) {
return doTest(testPattern, testPattern); return doReExecTest(testPattern, testPattern);
} }
static void quoteParsingTests() { @Test
void testQuoteParsingThroughReExec() {
/* /*
* Tests for 6214916 * Tests for 6214916
* These tests require that a JVM (any JVM) be installed in the system registry. * These tests require that a JVM (any JVM) be installed in the system registry.
...@@ -154,103 +161,433 @@ public class Arrrghs extends TestHelper { ...@@ -154,103 +161,433 @@ public class Arrrghs extends TestHelper {
} }
// Basic test // Basic test
testExitValue += doTest("-a -b -c -d"); testExitValue += doReExecTest("-a -b -c -d");
// Basic test with many spaces // Basic test with many spaces
testExitValue += doTest("-a -b -c -d"); testExitValue += doReExecTest("-a -b -c -d");
// Quoted whitespace does matter ? // Quoted whitespace does matter ?
testExitValue += doTest("-a \"\"-b -c\"\" -d"); testExitValue += doReExecTest("-a \"\"-b -c\"\" -d");
// Escaped quotes outside of quotes as literals // Escaped quotes outside of quotes as literals
testExitValue += doTest("-a \\\"-b -c\\\" -d"); testExitValue += doReExecTest("-a \\\"-b -c\\\" -d");
// Check for escaped quotes inside of quotes as literal // Check for escaped quotes inside of quotes as literal
testExitValue += doTest("-a \"-b \\\"stuff\\\"\" -c -d"); testExitValue += doReExecTest("-a \"-b \\\"stuff\\\"\" -c -d");
// A quote preceeded by an odd number of slashes is a literal quote // A quote preceeded by an odd number of slashes is a literal quote
testExitValue += doTest("-a -b\\\\\\\" -c -d"); testExitValue += doReExecTest("-a -b\\\\\\\" -c -d");
// A quote preceeded by an even number of slashes is a literal quote // A quote preceeded by an even number of slashes is a literal quote
// see 6214916. // see 6214916.
testExitValue += doTest("-a -b\\\\\\\\\" -c -d"); testExitValue += doReExecTest("-a -b\\\\\\\\\" -c -d");
// Make sure that whitespace doesn't interfere with the removal of the // Make sure that whitespace doesn't interfere with the removal of the
// appropriate tokens. (space-tab-space preceeds -jre-restict-search). // appropriate tokens. (space-tab-space preceeds -jre-restict-search).
testExitValue += doTest("-a -b \t -jre-restrict-search -c -d","-a -b -c -d"); testExitValue += doReExecTest("-a -b \t -jre-restrict-search -c -d", "-a -b -c -d");
// Make sure that the mJRE tokens being stripped, aren't stripped if // Make sure that the mJRE tokens being stripped, aren't stripped if
// they happen to appear as arguments to the main class. // they happen to appear as arguments to the main class.
testExitValue += doTest("foo -version:1.1+"); testExitValue += doReExecTest("foo -version:1.1+");
System.out.println("Completed arguments quoting tests with " + System.out.println("Completed arguments quoting tests with "
testExitValue + " errors"); + testExitValue + " errors");
}
// the pattern we hope to see in the output
static final Pattern ArgPattern = Pattern.compile("\\s*argv\\[[0-9]*\\].*=.*");
void checkArgumentParsing(String inArgs, String... expArgs) throws IOException {
List<String> scratchpad = new ArrayList<>();
scratchpad.add("set " + JLDEBUG_KEY + "=true");
// GAK, -version needs to be added so that windows can flush its stderr
// exiting the process prematurely can terminate the stderr.
scratchpad.add(javaCmd + " -version " + inArgs);
File batFile = new File("atest.bat");
java.nio.file.Files.deleteIfExists(batFile.toPath());
createFile(batFile, scratchpad);
TestResult tr = doExec(batFile.getName());
ArrayList<String> expList = new ArrayList<>();
expList.add(javaCmd);
expList.add("-version");
expList.addAll(Arrays.asList(expArgs));
List<String> gotList = new ArrayList<>();
for (String x : tr.testOutput) {
Matcher m = ArgPattern.matcher(x);
if (m.matches()) {
String a[] = x.split("=");
gotList.add(a[a.length - 1].trim());
}
}
if (!gotList.equals(expList)) {
System.out.println(tr);
System.out.println("Expected args:");
System.out.println(expList);
System.out.println("Obtained args:");
System.out.println(gotList);
throw new RuntimeException("Error: args do not match");
}
System.out.println("\'" + inArgs + "\'" + " - Test passed");
} }
/*
* This tests general quoting and are specific to Windows, *nixes
* need not worry about this, these have been tested with Windows
* implementation and those that are known to work are used against
* the java implementation. Note that the ProcessBuilder gets in the
* way when testing some of these arguments, therefore we need to
* create and execute a .bat file containing the arguments.
*/
@Test
void testArgumentParsing() throws IOException {
if (!isWindows)
return;
// no quotes
checkArgumentParsing("a b c d", "a", "b", "c", "d");
// single quotes
checkArgumentParsing("\"a b c d\"", "a b c d");
//double quotes
checkArgumentParsing("\"\"a b c d\"\"", "a", "b", "c", "d");
// triple quotes
checkArgumentParsing("\"\"\"a b c d\"\"\"", "\"a b c d\"");
// a literal within single quotes
checkArgumentParsing("\"a\"b c d\"e\"", "ab", "c", "de");
// a literal within double quotes
checkArgumentParsing("\"\"a\"b c d\"e\"\"", "ab c de");
// a literal quote
checkArgumentParsing("a\\\"b", "a\"b");
// double back-slash
checkArgumentParsing("\"a b c d\\\\\"", "a b c d\\");
// triple back-slash
checkArgumentParsing("a\\\\\\\"b", "a\\\"b");
// dangling quote
checkArgumentParsing("\"a b c\"\"", "a b c\"");
// expansions of white space separators
checkArgumentParsing("a b", "a", "b");
checkArgumentParsing("a\tb", "a", "b");
checkArgumentParsing("a \t b", "a", "b");
checkArgumentParsing("\"C:\\TEST A\\\\\"", "C:\\TEST A\\");
checkArgumentParsing("\"\"C:\\TEST A\\\\\"\"", "C:\\TEST", "A\\");
// MS Windows tests
// triple back-slash
checkArgumentParsing("a\\\\\\d", "a\\\\\\d");
// triple back-slash in quotes
checkArgumentParsing("\"a\\\\\\d\"", "a\\\\\\d");
// slashes separating characters
checkArgumentParsing("X\\Y\\Z", "X\\Y\\Z");
checkArgumentParsing("\\X\\Y\\Z", "\\X\\Y\\Z");
// literals within dangling quotes, etc.
checkArgumentParsing("\"a b c\" d e", "a b c", "d", "e");
checkArgumentParsing("\"ab\\\"c\" \"\\\\\" d", "ab\"c", "\\", "d");
checkArgumentParsing("a\\\\\\c d\"e f\"g h", "a\\\\\\c", "de fg", "h");
checkArgumentParsing("a\\\\\\\"b c d", "a\\\"b", "c", "d");
checkArgumentParsing("a\\\\\\\\\"g c\" d e", "a\\\\g c", "d", "e");
// treatment of back-slashes
checkArgumentParsing("*\\", "*\\");
checkArgumentParsing("*/", "*/");
checkArgumentParsing(".\\*", ".\\*");
checkArgumentParsing("./*", "./*");
checkArgumentParsing("..\\..\\*", "..\\..\\*");
checkArgumentParsing("../../*", "../../*");
checkArgumentParsing("..\\..\\", "..\\..\\");
checkArgumentParsing("../../", "../../");
}
private void initEmptyDir(File emptyDir) throws IOException {
if (emptyDir.exists()) {
recursiveDelete(emptyDir);
}
emptyDir.mkdir();
}
private void initDirWithJavaFiles(File libDir) throws IOException {
if (libDir.exists()) {
recursiveDelete(libDir);
}
libDir.mkdirs();
ArrayList<String> scratchpad = new ArrayList<>();
scratchpad.add("package lib;");
scratchpad.add("public class Fbo {");
scratchpad.add("public static void main(String... args){Foo.f();}");
scratchpad.add("public static void f(){}");
scratchpad.add("}");
createFile(new File(libDir, "Fbo.java"), scratchpad);
scratchpad.clear();
scratchpad.add("package lib;");
scratchpad.add("public class Foo {");
scratchpad.add("public static void main(String... args){");
scratchpad.add("for (String x : args) {");
scratchpad.add("System.out.println(x);");
scratchpad.add("}");
scratchpad.add("Fbo.f();");
scratchpad.add("}");
scratchpad.add("public static void f(){}");
scratchpad.add("}");
createFile(new File(libDir, "Foo.java"), scratchpad);
}
void checkArgumentWildcard(String inArgs, String... expArgs) throws IOException {
String[] in = {inArgs};
checkArgumentWildcard(in, expArgs);
// now add arbitrary arguments before and after
String[] outInArgs = { "-Q", inArgs, "-R"};
String[] outExpArgs = new String[expArgs.length + 2];
outExpArgs[0] = "-Q";
System.arraycopy(expArgs, 0, outExpArgs, 1, expArgs.length);
outExpArgs[expArgs.length + 1] = "-R";
checkArgumentWildcard(outInArgs, outExpArgs);
}
void checkArgumentWildcard(String[] inArgs, String[] expArgs) throws IOException {
ArrayList<String> argList = new ArrayList<>();
argList.add(javaCmd);
argList.add("-cp");
argList.add("lib" + File.separator + "*");
argList.add("lib.Foo");
argList.addAll(Arrays.asList(inArgs));
String[] cmds = new String[argList.size()];
argList.toArray(cmds);
TestResult tr = doExec(cmds);
if (!tr.isOK()) {
System.out.println(tr);
throw new RuntimeException("Error: classpath single entry wildcard entry");
}
ArrayList<String> expList = new ArrayList<>();
expList.addAll(Arrays.asList(expArgs));
List<String> gotList = new ArrayList<>();
for (String x : tr.testOutput) {
gotList.add(x.trim());
}
if (!gotList.equals(expList)) {
System.out.println(tr);
System.out.println("Expected args:");
System.out.println(expList);
System.out.println("Obtained args:");
System.out.println(gotList);
throw new RuntimeException("Error: args do not match");
}
System.out.print("\'");
for (String x : inArgs) {
System.out.print(x + " ");
}
System.out.println("\'" + " - Test passed");
}
/*
* These tests are not expected to work on *nixes, and are ignored.
*/
@Test
void testWildCardArgumentProcessing() throws IOException {
if (!isWindows)
return;
File cwd = new File(".");
File libDir = new File(cwd, "lib");
initDirWithJavaFiles(libDir);
initEmptyDir(new File(cwd, "empty"));
// test if javac (the command) can compile *.java
TestResult tr = doExec(javacCmd, libDir.getName() + File.separator + "*.java");
if (!tr.isOK()) {
System.out.println(tr);
throw new RuntimeException("Error: compiling java wildcards");
}
// use the jar cmd to create jars using the ? wildcard
File jarFoo = new File(libDir, "Foo.jar");
tr = doExec(jarCmd, "cvf", jarFoo.getAbsolutePath(), "lib" + File.separator + "F?o.class");
if (!tr.isOK()) {
System.out.println(tr);
throw new RuntimeException("Error: creating jar with wildcards");
}
// now the litmus test!, this should work
checkArgumentWildcard("a", "a");
// test for basic expansion
checkArgumentWildcard("lib\\F*java", "lib\\Fbo.java", "lib\\Foo.java");
// basic expansion in quotes
checkArgumentWildcard("\"lib\\F*java\"", "lib\\F*java");
checkArgumentWildcard("lib\\**", "lib\\Fbo.class", "lib\\Fbo.java",
"lib\\Foo.class", "lib\\Foo.jar", "lib\\Foo.java");
checkArgumentWildcard("lib\\*?", "lib\\Fbo.class", "lib\\Fbo.java",
"lib\\Foo.class", "lib\\Foo.jar", "lib\\Foo.java");
checkArgumentWildcard("lib\\?*", "lib\\Fbo.class", "lib\\Fbo.java",
"lib\\Foo.class", "lib\\Foo.jar", "lib\\Foo.java");
checkArgumentWildcard("lib\\?", "lib\\?");
// test for basic expansion
checkArgumentWildcard("lib\\*java", "lib\\Fbo.java", "lib\\Foo.java");
// basic expansion in quotes
checkArgumentWildcard("\"lib\\*.java\"", "lib\\*.java");
// suffix expansion
checkArgumentWildcard("lib\\*.class", "lib\\Fbo.class", "lib\\Foo.class");
// suffix expansion in quotes
checkArgumentWildcard("\"lib\\*.class\"", "lib\\*.class");
// check for ? expansion now
checkArgumentWildcard("lib\\F?o.java", "lib\\Fbo.java", "lib\\Foo.java");
// check ? in quotes
checkArgumentWildcard("\"lib\\F?o.java\"", "lib\\F?o.java");
// check ? as suffixes
checkArgumentWildcard("lib\\F?o.????", "lib\\Fbo.java", "lib\\Foo.java");
// check ? in a leading role
checkArgumentWildcard("lib\\???.java", "lib\\Fbo.java", "lib\\Foo.java");
checkArgumentWildcard("\"lib\\???.java\"", "lib\\???.java");
// check ? prefixed with -
checkArgumentWildcard("-?", "-?");
// check * prefixed with -
checkArgumentWildcard("-*", "-*");
// check on empty directory
checkArgumentWildcard("empty\\*", "empty\\*");
checkArgumentWildcard("empty\\**", "empty\\**");
checkArgumentWildcard("empty\\?", "empty\\?");
checkArgumentWildcard("empty\\??", "empty\\??");
checkArgumentWildcard("empty\\*?", "empty\\*?");
checkArgumentWildcard("empty\\?*", "empty\\?*");
}
void doArgumentCheck(String inArgs, String... expArgs) {
Map<String, String> env = new HashMap<>();
env.put(JLDEBUG_KEY, "true");
TestResult tr = doExec(env, javaCmd, inArgs);
System.out.println(tr);
int sindex = tr.testOutput.indexOf("Command line args:");
if (sindex < 0) {
System.out.println(tr);
throw new RuntimeException("Error: no output");
}
sindex++; // skip over the tag
List<String> gotList = new ArrayList<>();
for (String x : tr.testOutput.subList(sindex, sindex + expArgs.length)) {
String a[] = x.split("=");
gotList.add(a[a.length - 1].trim());
}
List<String> expList = Arrays.asList(expArgs);
if (!gotList.equals(expList)) {
System.out.println(tr);
System.out.println("Expected args:");
System.out.println(expList);
System.out.println("Obtained args:");
System.out.println(gotList);
throw new RuntimeException("Error: args do not match");
}
}
/* /*
* These tests are usually run on non-existent targets to check error results * These tests are usually run on non-existent targets to check error results
*/ */
static void runBasicErrorMessageTests() { @Test
void testBasicErrorMessages() {
// Tests for 5030233 // Tests for 5030233
TestResult tr = doExec(javaCmd, "-cp"); TestResult tr = doExec(javaCmd, "-cp");
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
tr = doExec(javaCmd, "-classpath"); tr = doExec(javaCmd, "-classpath");
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
tr = doExec(javaCmd, "-jar"); tr = doExec(javaCmd, "-jar");
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
tr = doExec(javacCmd, "-cp"); tr = doExec(javacCmd, "-cp");
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// Test for 6356475 "REGRESSION:"java -X" from cmdline fails" // Test for 6356475 "REGRESSION:"java -X" from cmdline fails"
tr = doExec(javaCmd, "-X"); tr = doExec(javaCmd, "-X");
tr.checkPositive(); tr.checkPositive();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
tr = doExec(javaCmd, "-help"); tr = doExec(javaCmd, "-help");
tr.checkPositive(); tr.checkPositive();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// 6753938, test for non-negative exit value for an incorrectly formed // 6753938, test for non-negative exit value for an incorrectly formed
// command line, '% java' // command line, '% java'
tr = doExec(javaCmd); tr = doExec(javaCmd);
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// 6753938, test for non-negative exit value for an incorrectly formed // 6753938, test for non-negative exit value for an incorrectly formed
// command line, '% java -Xcomp' // command line, '% java -Xcomp'
tr = doExec(javaCmd, "-Xcomp"); tr = doExec(javaCmd, "-Xcomp");
tr.checkNegative(); tr.checkNegative();
tr.isNotZeroOutput(); tr.isNotZeroOutput();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// 7151434, test for non-negative exit value for an incorrectly formed // 7151434, test for non-negative exit value for an incorrectly formed
// command line, '% java -jar -W', note the bogus -W // command line, '% java -jar -W', note the bogus -W
tr = doExec(javaCmd, "-jar", "-W"); tr = doExec(javaCmd, "-jar", "-W");
tr.checkNegative(); tr.checkNegative();
tr.contains("Unrecognized option: -W"); tr.contains("Unrecognized option: -W");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
} }
/* /*
* Tests various dispositions of the main method, these tests are limited * Tests various dispositions of the main method, these tests are limited
* to English locales as they check for error messages that are localized. * to English locales as they check for error messages that are localized.
*/ */
static void runMainMethodTests() throws FileNotFoundException { @Test
void testMainMethod() throws FileNotFoundException {
if (!isEnglishLocale()) { if (!isEnglishLocale()) {
return; return;
} }
...@@ -262,55 +599,65 @@ public class Arrrghs extends TestHelper { ...@@ -262,55 +599,65 @@ public class Arrrghs extends TestHelper {
(String[])null); (String[])null);
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Could not find or load main class MIA"); tr.contains("Error: Could not find or load main class MIA");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "MIA"); tr = doExec(javaCmd, "-cp", "some.jar", "MIA");
tr.contains("Error: Could not find or load main class MIA"); tr.contains("Error: Could not find or load main class MIA");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// incorrect method access // incorrect method access
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"private static void main(String[] args){}"); "private static void main(String[] args){}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method not found in class Foo"); tr.contains("Error: Main method not found in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method not found in class Foo"); tr.contains("Error: Main method not found in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// incorrect return type // incorrect return type
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"public static int main(String[] args){return 1;}"); "public static int main(String[] args){return 1;}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method must return a value of type void in class Foo"); tr.contains("Error: Main method must return a value of type void in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method must return a value of type void in class Foo"); tr.contains("Error: Main method must return a value of type void in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// incorrect parameter type // incorrect parameter type
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"public static void main(Object[] args){}"); "public static void main(Object[] args){}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method not found in class Foo"); tr.contains("Error: Main method not found in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method not found in class Foo"); tr.contains("Error: Main method not found in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// incorrect method type - non-static // incorrect method type - non-static
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
"public void main(String[] args){}"); "public void main(String[] args){}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("Error: Main method is not static in class Foo"); tr.contains("Error: Main method is not static in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("Error: Main method is not static in class Foo"); tr.contains("Error: Main method is not static in class Foo");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// amongst a potpourri of kindred main methods, is the right one chosen ? // amongst a potpourri of kindred main methods, is the right one chosen ?
createJar(new File("some.jar"), new File("Foo"), createJar(new File("some.jar"), new File("Foo"),
...@@ -322,25 +669,29 @@ public class Arrrghs extends TestHelper { ...@@ -322,25 +669,29 @@ public class Arrrghs extends TestHelper {
"public static void main(String[] args) {System.out.println(\"THE_CHOSEN_ONE\");}"); "public static void main(String[] args) {System.out.println(\"THE_CHOSEN_ONE\");}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.contains("THE_CHOSEN_ONE"); tr.contains("THE_CHOSEN_ONE");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-cp", "some.jar", "Foo"); tr = doExec(javaCmd, "-cp", "some.jar", "Foo");
tr.contains("THE_CHOSEN_ONE"); tr.contains("THE_CHOSEN_ONE");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// test for extraneous whitespace in the Main-Class attribute // test for extraneous whitespace in the Main-Class attribute
createJar(" Foo ", new File("some.jar"), new File("Foo"), createJar(" Foo ", new File("some.jar"), new File("Foo"),
"public static void main(String... args){}"); "public static void main(String... args){}");
tr = doExec(javaCmd, "-jar", "some.jar"); tr = doExec(javaCmd, "-jar", "some.jar");
tr.checkPositive(); tr.checkPositive();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
} }
/* /*
* tests 6968053, ie. we turn on the -Xdiag (for now) flag and check if * tests 6968053, ie. we turn on the -Xdiag (for now) flag and check if
* the suppressed stack traces are exposed, ignore these tests for localized * the suppressed stack traces are exposed, ignore these tests for localized
* locales, limiting to English only. * locales, limiting to English only.
*/ */
static void runDiagOptionTests() throws FileNotFoundException { @Test
void testDiagOptions() throws FileNotFoundException {
if (!isEnglishLocale()) { // only english version if (!isEnglishLocale()) { // only english version
return; return;
} }
...@@ -351,48 +702,51 @@ public class Arrrghs extends TestHelper { ...@@ -351,48 +702,51 @@ public class Arrrghs extends TestHelper {
tr = doExec(javaCmd, "-Xdiag", "-jar", "some.jar"); tr = doExec(javaCmd, "-Xdiag", "-jar", "some.jar");
tr.contains("Error: Could not find or load main class MIA"); tr.contains("Error: Could not find or load main class MIA");
tr.contains("java.lang.ClassNotFoundException: MIA"); tr.contains("java.lang.ClassNotFoundException: MIA");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// use classpath to check // use classpath to check
tr = doExec(javaCmd, "-Xdiag", "-cp", "some.jar", "MIA"); tr = doExec(javaCmd, "-Xdiag", "-cp", "some.jar", "MIA");
tr.contains("Error: Could not find or load main class MIA"); tr.contains("Error: Could not find or load main class MIA");
tr.contains("java.lang.ClassNotFoundException: MIA"); tr.contains("java.lang.ClassNotFoundException: MIA");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
// a missing class on the classpath // a missing class on the classpath
tr = doExec(javaCmd, "-Xdiag", "NonExistentClass"); tr = doExec(javaCmd, "-Xdiag", "NonExistentClass");
tr.contains("Error: Could not find or load main class NonExistentClass"); tr.contains("Error: Could not find or load main class NonExistentClass");
tr.contains("java.lang.ClassNotFoundException: NonExistentClass"); tr.contains("java.lang.ClassNotFoundException: NonExistentClass");
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
} }
static void test6894719() { @Test
static void testJreRestrictSearchFlag() {
// test both arguments to ensure they exist // test both arguments to ensure they exist
TestResult tr = null; TestResult tr = null;
tr = doExec(javaCmd, tr = doExec(javaCmd,
"-no-jre-restrict-search", "-version"); "-no-jre-restrict-search", "-version");
tr.checkPositive(); tr.checkPositive();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
tr = doExec(javaCmd, tr = doExec(javaCmd,
"-jre-restrict-search", "-version"); "-jre-restrict-search", "-version");
tr.checkPositive(); tr.checkPositive();
System.out.println(tr); if (!tr.testStatus)
System.out.println(tr);
} }
/** /**
* @param args the command line arguments * @param args the command line arguments
* @throws java.io.FileNotFoundException * @throws java.io.FileNotFoundException
*/ */
public static void main(String[] args) throws FileNotFoundException { public static void main(String[] args) throws Exception {
if (debug) { if (debug) {
System.out.println("Starting Arrrghs tests"); System.out.println("Starting Arrrghs tests");
} }
quoteParsingTests(); Arrrghs a = new Arrrghs();
runBasicErrorMessageTests(); a.run(args);
runMainMethodTests();
test6894719();
runDiagOptionTests();
if (testExitValue > 0) { if (testExitValue > 0) {
System.out.println("Total of " + testExitValue + " failed"); System.out.println("Total of " + testExitValue + " failed");
System.exit(1); System.exit(1);
......
...@@ -21,6 +21,12 @@ ...@@ -21,6 +21,12 @@
* questions. * questions.
*/ */
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Set; import java.util.Set;
...@@ -63,6 +69,8 @@ public class TestHelper { ...@@ -63,6 +69,8 @@ public class TestHelper {
static final String javawCmd; static final String javawCmd;
static final String java64Cmd; static final String java64Cmd;
static final String javacCmd; static final String javacCmd;
static final String jarCmd;
static final JavaCompiler compiler; static final JavaCompiler compiler;
static final boolean debug = Boolean.getBoolean("TestHelper.Debug"); static final boolean debug = Boolean.getBoolean("TestHelper.Debug");
...@@ -131,6 +139,15 @@ public class TestHelper { ...@@ -131,6 +139,15 @@ public class TestHelper {
: new File(binDir, "javac"); : new File(binDir, "javac");
javacCmd = javacCmdFile.getAbsolutePath(); javacCmd = javacCmdFile.getAbsolutePath();
File jarCmdFile = (isWindows)
? new File(binDir, "jar.exe")
: new File(binDir, "jar");
jarCmd = jarCmdFile.getAbsolutePath();
if (!jarCmdFile.canExecute()) {
throw new RuntimeException("java <" + TestHelper.jarCmd +
"> must exist and should be executable");
}
if (isWindows) { if (isWindows) {
File javawCmdFile = new File(binDir, "javaw.exe"); File javawCmdFile = new File(binDir, "javaw.exe");
javawCmd = javawCmdFile.getAbsolutePath(); javawCmd = javawCmdFile.getAbsolutePath();
...@@ -158,6 +175,35 @@ public class TestHelper { ...@@ -158,6 +175,35 @@ public class TestHelper {
java64Cmd = null; java64Cmd = null;
} }
} }
void run(String[] args) throws Exception {
int passed = 0, failed = 0;
final Pattern p = (args != null && args.length > 0)
? Pattern.compile(args[0])
: null;
for (Method m : this.getClass().getDeclaredMethods()) {
boolean selected = (p == null)
? m.isAnnotationPresent(Test.class)
: p.matcher(m.getName()).matches();
if (selected) {
try {
m.invoke(this, (Object[]) null);
System.out.println(m.getName() + ": OK");
passed++;
} catch (Throwable ex) {
System.out.printf("Test %s failed: %s %n", m, ex.getCause());
failed++;
}
}
}
System.out.printf("Passed: %d, Failed %d%n", passed, failed);
if (failed > 0) {
throw new RuntimeException("Tests failed: " + failed);
}
if (passed == 0 && failed == 0) {
throw new AssertionError("No test(s) selected: passed = " +
passed + ", failed = " + failed + " ??????????");
}
}
/* /*
* is a dual mode available in the test jdk * is a dual mode available in the test jdk
...@@ -395,6 +441,7 @@ public class TestHelper { ...@@ -395,6 +441,7 @@ public class TestHelper {
List<String> testOutput; List<String> testOutput;
Map<String, String> env; Map<String, String> env;
Throwable t; Throwable t;
boolean testStatus;
public TestResult(String str, int rv, List<String> oList, public TestResult(String str, int rv, List<String> oList,
Map<String, String> env, Throwable t) { Map<String, String> env, Throwable t) {
...@@ -405,6 +452,7 @@ public class TestHelper { ...@@ -405,6 +452,7 @@ public class TestHelper {
testOutput = oList; testOutput = oList;
this.env = env; this.env = env;
this.t = t; this.t = t;
testStatus = true;
} }
void appendError(String x) { void appendError(String x) {
...@@ -418,12 +466,14 @@ public class TestHelper { ...@@ -418,12 +466,14 @@ public class TestHelper {
void checkNegative() { void checkNegative() {
if (exitValue == 0) { if (exitValue == 0) {
appendError("test must not return 0 exit value"); appendError("test must not return 0 exit value");
testStatus = false;
testExitValue++; testExitValue++;
} }
} }
void checkPositive() { void checkPositive() {
if (exitValue != 0) { if (exitValue != 0) {
testStatus = false;
appendError("test did not return 0 exit value"); appendError("test did not return 0 exit value");
testExitValue++; testExitValue++;
} }
...@@ -435,6 +485,7 @@ public class TestHelper { ...@@ -435,6 +485,7 @@ public class TestHelper {
boolean isZeroOutput() { boolean isZeroOutput() {
if (!testOutput.isEmpty()) { if (!testOutput.isEmpty()) {
testStatus = false;
appendError("No message from cmd please"); appendError("No message from cmd please");
testExitValue++; testExitValue++;
return false; return false;
...@@ -444,6 +495,7 @@ public class TestHelper { ...@@ -444,6 +495,7 @@ public class TestHelper {
boolean isNotZeroOutput() { boolean isNotZeroOutput() {
if (testOutput.isEmpty()) { if (testOutput.isEmpty()) {
testStatus = false;
appendError("Missing message"); appendError("Missing message");
testExitValue++; testExitValue++;
return false; return false;
...@@ -454,6 +506,7 @@ public class TestHelper { ...@@ -454,6 +506,7 @@ public class TestHelper {
@Override @Override
public String toString() { public String toString() {
status.println("++++Begin Test Info++++"); status.println("++++Begin Test Info++++");
status.println("Test Status: " + (testStatus ? "PASS" : "FAIL"));
status.println("++++Test Environment++++"); status.println("++++Test Environment++++");
for (String x : env.keySet()) { for (String x : env.keySet()) {
indentStatus(x + "=" + env.get(x)); indentStatus(x + "=" + env.get(x));
...@@ -496,4 +549,10 @@ public class TestHelper { ...@@ -496,4 +549,10 @@ public class TestHelper {
return false; return false;
} }
} }
/**
* Indicates that the annotated method is a test method.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {}
} }
...@@ -160,7 +160,7 @@ public class ToolsOpts extends TestHelper { ...@@ -160,7 +160,7 @@ public class ToolsOpts extends TestHelper {
for (String arg[] : optionPatterns) { for (String arg[] : optionPatterns) {
jpos = indexOfJoption(arg); jpos = indexOfJoption(arg);
//Build a cmd string for output in results reporting. //Build a cmd string for output in results reporting.
String cmdString = javacCmd + JBCP_PREPEND + sTestJar; String cmdString = javacCmd + " " + JBCP_PREPEND + sTestJar;
for (String opt : arg) { for (String opt : arg) {
cmdString = cmdString.concat(" " + opt); cmdString = cmdString.concat(" " + opt);
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册