/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef _WIN32
// Indicate we want at least all Windows Server 2003 (5.2) APIs.
// Note: default is set by system/core/include/arch/windows/AndroidConfig.h to 0x0500
// which is Win2K. However our minimum SDK tools requirement is Win XP (0x0501).
// However we do need 0x0502 to get access to the WOW-64-32 constants for the
// registry, except we'll need to be careful since they are not available on XP.
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0502
// Indicate we want at least all IE 5 shell APIs
#define _WIN32_IE 0x0500
#include "find_java.h"
#include <shlobj.h>
#include <ctype.h>
// Define some types missing in MingW
#ifndef LSTATUS
typedef LONG LSTATUS;
#endif
// Check to see if the application is running in 32-bit or 64-bit mode. In other words, this will
// return false if you run a 32-bit build even on a 64-bit machine.
static bool isApplication64() {
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
// Note: The constant name here is a bit misleading, as it actually covers all 64-bit processors
// and not just AMD.
// See also: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724958(v=vs.85).aspx
return (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64);
}
// Extract the first thing that looks like (digit.digit+).
// Note: this will break when java reports a version with major > 9.
// However it will reasonably cope with "1.10", if that ever happens.
static bool extractJavaVersion(const char *start,
int length,
CString *outVersionStr,
int *outVersionInt) {
const char *end = start + length;
for (const char *c = start; c < end - 2; c++) {
if (isdigit(c[0]) &&
c[1] == '.' &&
isdigit(c[2])) {
const char *e = c+2;
while (isdigit(e[1])) {
e++;
}
if (outVersionStr != NULL) {
outVersionStr->set(c, e - c + 1);
}
if (outVersionInt != NULL) {
// add major * 1000, currently only 1 digit
int value = (*c - '0') * 1000;
// add minor
for (int m = 1; *e != '.'; e--, m *= 10) {
value += (*e - '0') * m;
}
*outVersionInt = value;
}
return true;
}
}
return false;
}
// Check if the passed in path is a path to a JDK
static bool isJdkPath(const CPath &path) {
// If the files "bin/java.exe" and "lib/tools.jar" exist, we're probably a JDK.
CPath pathBin(path);
pathBin.addPath("bin");
pathBin.addPath("java.exe");
CPath pathTools(path);
pathTools.addPath("lib");
pathTools.addPath("tools.jar");
return (pathBin.fileExists() && pathTools.fileExists());
}
// Check whether we can find $PATH/java.exe.
// inOutPath should be the directory where we're looking at.
// In output, it will be the java path we tested.
// Returns the java version integer found (e.g. 1006 for 1.6).
// Return 0 in case of error.
static int checkPath(CPath *inOutPath) {
inOutPath->addPath("java.exe");
int result = 0;
if (inOutPath->fileExists()) {
// Run java -version
// Reject the version if it's not at least our current minimum.
if (!getJavaVersion(*inOutPath, NULL /*versionStr*/, &result)) {
result = 0;
}
}
return result;
}
// Check whether we can find $PATH/bin/java.exe
// Returns the Java version found (e.g. 1006 for 1.6) or 0 in case of error.
static int checkBinPath(CPath *inOutPath) {
inOutPath->addPath("bin");
return checkPath(inOutPath);
}
// Test for the existence of java.exe in a custom path
int findJavaInPath(const CPath &path, CPath *outJavaPath, bool isJdk, int minVersion) {
SetLastError(0);
int version = 0;
CPath temp(path);
if (!isJdk) {
version = checkPath(&temp);
}
else {
if (isJdkPath(temp)) {
version = checkBinPath(&temp);
}
}
if (version >= minVersion) {
if (gIsDebug) {
fprintf(stderr, "Java %d found in path: %s\n", version, temp.cstr());
}
*outJavaPath = temp;
return version;
}
return 0;
}
// Search java.exe in the environment
int findJavaInEnvPath(CPath *outJavaPath, bool isJdk, int minVersion) {
SetLastError(0);
const char* envPath = getenv("JAVA_HOME");
if (envPath != NULL) {
CPath p(envPath);
if (!isJdk || isJdkPath(p)) {
int v = checkBinPath(&p);
if (v >= minVersion) {
if (gIsDebug) {
fprintf(stderr, "Java %d found via JAVA_HOME: %s\n", v, p.cstr());
}
*outJavaPath = p;
// As an optimization for runtime, if we find a suitable java
// version in JAVA_HOME we won't waste time looking at the PATH.
return v;
}
}
}
int currVersion = 0;
envPath = getenv("PATH");
if (!envPath) return currVersion;
// Otherwise look at the entries in the current path.
// If we find more than one, keep the one with the highest version.
CArray<CString> *paths = CString(envPath).split(';');
for(int i = 0; i < paths->size(); i++) {
CPath p((*paths)[i].cstr());
if (isJdk && !isJdkPath(p)) {
continue;
}
int v = checkPath(&p);
if (v >= minVersion && v > currVersion) {
if (gIsDebug) {
fprintf(stderr, "Java %d found via env PATH: %s\n", v, p.cstr());
}
currVersion = v;
*outJavaPath = p;
}
}
delete paths;
return currVersion;
}
// --------------
static bool getRegValue(const char *keyPath,
const char *keyName,
REGSAM access,
CString *outValue) {
HKEY key;
LSTATUS status = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, // hKey
keyPath, // lpSubKey
0, // ulOptions
KEY_READ | access, // samDesired,
&key); // phkResult
if (status == ERROR_SUCCESS) {
LSTATUS ret = ERROR_MORE_DATA;
DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough
char* buffer = (char*) malloc(size);
while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) {
ret = RegQueryValueExA(
key, // hKey
keyName, // lpValueName
NULL, // lpReserved
NULL, // lpType
(LPBYTE) buffer, // lpData
&size); // lpcbData
if (ret == ERROR_MORE_DATA) {
size *= 2;
buffer = (char*) realloc(buffer, size);
} else {
buffer[size] = 0;
}
}
if (ret != ERROR_MORE_DATA) outValue->set(buffer);
free(buffer);
RegCloseKey(key);
return (ret != ERROR_MORE_DATA);
}
return false;
}
// Explore the registry to find a suitable version of Java.
// Returns an int which is the version of Java found (e.g. 1006 for 1.6) and the
// matching path in outJavaPath.
// Returns 0 if nothing suitable was found.
static int exploreJavaRegistry(const char *entry, REGSAM access, int minVersion,
CPath *outJavaPath) {
// Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion]
CPath rootKey("SOFTWARE\\JavaSoft\\");
rootKey.addPath(entry);
int versionInt = 0;
CString currentVersion;
CPath subKey(rootKey);
if (getRegValue(subKey.cstr(), "CurrentVersion", access, ¤tVersion)) {
// CurrentVersion should be something like "1.7".
// We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome]
subKey.addPath(currentVersion);
CPath javaHome;
if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
versionInt = checkBinPath(&javaHome);
if (versionInt >= 0) {
if (gIsDebug) {
fprintf(stderr,
"Java %d found via registry: %s\n",
versionInt, javaHome.cstr());
}
*outJavaPath = javaHome;
}
if (versionInt >= minVersion) {
// Heuristic: if the current version is good enough, stop here
return versionInt;
}
}
}
// Try again, but this time look at all the versions available
HKEY javaHomeKey;
LSTATUS status = RegOpenKeyExA(
HKEY_LOCAL_MACHINE, // hKey
"SOFTWARE\\JavaSoft", // lpSubKey
0, // ulOptions
KEY_READ | access, // samDesired
&javaHomeKey); // phkResult
if (status == ERROR_SUCCESS) {
char name[256];
DWORD index = 0;
CPath javaHome;
for (LONG result = ERROR_SUCCESS; result == ERROR_SUCCESS; index++) {
DWORD nameLen = 255;
name[nameLen] = 0;
result = RegEnumKeyExA(
javaHomeKey, // hKey
index, // dwIndex
name, // lpName
&nameLen, // lpcName
NULL, // lpReserved
NULL, // lpClass
NULL, // lpcClass,
NULL); // lpftLastWriteTime
if (result == ERROR_SUCCESS && nameLen < 256) {
name[nameLen] = 0;
CPath subKey(rootKey);
subKey.addPath(name);
if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
int v = checkBinPath(&javaHome);
if (v > versionInt) {
if (gIsDebug) {
fprintf(stderr,
"Java %d found via registry: %s\n",
versionInt, javaHome.cstr());
}
*outJavaPath = javaHome;
versionInt = v;
}
}
}
}
RegCloseKey(javaHomeKey);
}
return 0;
}
static bool getMaxJavaInRegistry(const char *entry, REGSAM access, CPath *outJavaPath, int *inOutVersion) {
CPath path;
int version = exploreJavaRegistry(entry, access, *inOutVersion, &path);
if (version > *inOutVersion) {
*outJavaPath = path;
*inOutVersion = version;
return true;
}
return false;
}
int findJavaInRegistry(CPath *outJavaPath, bool isJdk, int minVersion) {
// Check the JRE first, then the JDK.
int version = minVersion - 1; // Inner methods check if they're greater than this version.
bool result = false;
result |= (!isJdk && getMaxJavaInRegistry("Java Runtime Environment", 0, outJavaPath, &version));
result |= getMaxJavaInRegistry("Java Development Kit", 0, outJavaPath, &version);
// Even if we're 64-bit, try again but check the 32-bit registry, looking for 32-bit java.
if (isApplication64()) {
result |= (!isJdk &&
getMaxJavaInRegistry("Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath, &version));
result |= getMaxJavaInRegistry("Java Development Kit", KEY_WOW64_32KEY, outJavaPath, &version);
}
return result ? version : 0;
}
// --------------
static bool checkProgramFiles(CPath *outJavaPath, int *inOutVersion, bool isJdk,
bool force32bit) {
char programFilesPath[MAX_PATH + 1];
int nFolder = force32bit ? CSIDL_PROGRAM_FILESX86 : CSIDL_PROGRAM_FILES;
HRESULT result = SHGetFolderPathA(
NULL, // hwndOwner
nFolder,
NULL, // hToken
SHGFP_TYPE_CURRENT, // dwFlags
programFilesPath); // pszPath
if (FAILED(result)) return false;
CPath path(programFilesPath);
path.addPath("Java");
// Do we have a C:\Program Files\Java directory?
if (!path.dirExists()) return false;
CPath glob(path);
glob.addPath("j*");
bool found = false;
WIN32_FIND_DATAA findData;
HANDLE findH = FindFirstFileA(glob.cstr(), &findData);
if (findH == INVALID_HANDLE_VALUE) return false;
do {
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
CPath temp(path);
temp.addPath(findData.cFileName);
// Check C:\Program Files\Java\j*\bin\java.exe
if (!isJdk || isJdkPath(temp)) {
int v = checkBinPath(&temp);
if (v > *inOutVersion) {
found = true;
*inOutVersion = v;
*outJavaPath = temp;
}
}
}
} while (!found && FindNextFileA(findH, &findData) != 0);
FindClose(findH);
return found;
}
int findJavaInProgramFiles(CPath *outJavaPath, bool isJdk, int minVersion) {
// Check the C:\Program Files directory
bool result = false;
int version = minVersion - 1; // Inner methods check if they're greater than this version.
result |= checkProgramFiles(outJavaPath, &version, isJdk, false);
// Even if we're 64-bit, try again but check the C:\Program Files (x86) directory, looking for
// 32-bit java.
if (isApplication64()) {
result |= checkProgramFiles(outJavaPath, &version, isJdk, true);
}
return result ? version : 0;
}
// --------------
// Tries to invoke the java.exe at the given path and extract it's
// version number.
// - outVersionStr: if not null, will capture version as a string (e.g. "1.6")
// - outVersionInt: if not null, will capture version as an int (major * 1000 + minor, e.g. 1006).
bool getJavaVersion(CPath &javaPath, CString *outVersionStr, int *outVersionInt) {
bool result = false;
// Run "java -version", which outputs something like to *STDERR*:
//
// java version "1.6.0_29"
// Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
// Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
//
// We want to capture the first line, and more exactly the "1.6" part.
CString cmd;
cmd.setf("\"%s\" -version", javaPath.cstr());
SECURITY_ATTRIBUTES saAttr;
STARTUPINFO startup;
PROCESS_INFORMATION pinfo;
// Want to inherit pipe handle
ZeroMemory(&saAttr, sizeof(saAttr));
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
// Create pipe for stdout
HANDLE stdoutPipeRd, stdoutPipeWt;
if (!CreatePipe(
&stdoutPipeRd, // hReadPipe,
&stdoutPipeWt, // hWritePipe,
&saAttr, // lpPipeAttributes,
0)) { // nSize (0=default buffer size)
if (gIsConsole || gIsDebug) displayLastError("CreatePipe failed: ");
return false;
}
if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) {
if (gIsConsole || gIsDebug) displayLastError("SetHandleInformation failed: ");
return false;
}
ZeroMemory(&pinfo, sizeof(pinfo));
ZeroMemory(&startup, sizeof(startup));
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
// Capture both stderr and stdout
startup.hStdError = stdoutPipeWt;
startup.hStdOutput = stdoutPipeWt;
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
BOOL ok = CreateProcessA(
NULL, // program path
(LPSTR) cmd.cstr(), // command-line
NULL, // process handle is not inheritable
NULL, // thread handle is not inheritable
TRUE, // yes, inherit some handles
0, // process creation flags
NULL, // use parent's environment block
NULL, // use parent's starting directory
&startup, // startup info, i.e. std handles
&pinfo);
if ((gIsConsole || gIsDebug) && !ok) displayLastError("CreateProcess failed: ");
// Close the write-end of the output pipe (we're only reading from it)
CloseHandle(stdoutPipeWt);
// Read from the output pipe. We don't need to read everything,
// the first line should be 'Java version "1.2.3_45"\r\n'
// so reading about 32 chars is all we need.
char first32[32 + 1];
int index = 0;
first32[0] = 0;
if (ok) {
#define SIZE 1024
char buffer[SIZE];
DWORD sizeRead = 0;
while (ok) {
// Keep reading in the same buffer location
ok = ReadFile(stdoutPipeRd, // hFile
buffer, // lpBuffer
SIZE, // DWORD buffer size to read
&sizeRead, // DWORD buffer size read
NULL); // overlapped
if (!ok || sizeRead == 0 || sizeRead > SIZE) break;
// Copy up to the first 32 characters
if (index < 32) {
DWORD n = 32 - index;
if (n > sizeRead) n = sizeRead;
// copy as lowercase to simplify checks later
for (char *b = buffer; n > 0; n--, b++, index++) {
char c = *b;
if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
first32[index] = c;
}
first32[index] = 0;
}
}
WaitForSingleObject(pinfo.hProcess, INFINITE);
DWORD exitCode;
if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {
// this should not return STILL_ACTIVE (259)
result = exitCode == 0;
}
CloseHandle(pinfo.hProcess);
CloseHandle(pinfo.hThread);
}
CloseHandle(stdoutPipeRd);
if (result && index > 0) {
// Look for a few keywords in the output however we don't
// care about specific ordering or case-senstiviness.
// We only captures roughtly the first line in lower case.
char *j = strstr(first32, "java");
if (!j) {
j = strstr(first32, "openjdk");
}
char *v = strstr(first32, "version");
if ((gIsConsole || gIsDebug) && (!j || !v)) {
fprintf(stderr, "Error: keywords 'java|openjdk version' not found in '%s'\n", first32);
}
if (j != NULL && v != NULL) {
result = extractJavaVersion(first32, index, outVersionStr, outVersionInt);
}
}
return result;
}
#endif /* _WIN32 */