/* | |
* Copyright (C) 2014 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. | |
*/ | |
#include "stdafx.h" | |
#include "JavaFinder.h" | |
#include "utils.h" | |
#include <algorithm> // std::sort and std::unique | |
#define _CRT_SECURE_NO_WARNINGS | |
// -------------- | |
#define JF_REGISTRY_KEY _T("Software\\Android\\FindJava2") | |
#define JF_REGISTRY_VALUE_PATH _T("JavaPath") | |
#define JF_REGISTRY_VALUE_VERS _T("JavaVers") | |
// -------------- | |
// 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 TCHAR *start, | |
int length, | |
CString *outVersionStr, | |
int *outVersionInt) { | |
const TCHAR *end = start + length; | |
for (const TCHAR *c = start; c < end - 2; c++) { | |
if (isdigit(c[0]) && | |
c[1] == '.' && | |
isdigit(c[2])) { | |
const TCHAR *e = c + 2; | |
while (isdigit(e[1])) { | |
e++; | |
} | |
outVersionStr->SetString(c, e - c + 1); | |
// major is currently only 1 digit | |
int major = (*c - '0'); | |
// add minor | |
int minor = 0; | |
for (int m = 1; *e != '.'; e--, m *= 10) { | |
minor += (*e - '0') * m; | |
} | |
*outVersionInt = JAVA_VERS_TO_INT(major, minor); | |
return true; | |
} | |
} | |
return false; | |
} | |
// Tries to invoke the java.exe at the given path and extract it's | |
// version number. | |
// - outVersionStr: not null, will capture version as a string (e.g. "1.6") | |
// - outVersionInt: not null, will capture version as an int (see JavaPath.h). | |
bool getJavaVersion(CPath &javaPath, CString *outVersionStr, int *outVersionInt) { | |
bool result = false; | |
// Run "java -version", which outputs something to *STDERR* like this: | |
// | |
// 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.Format(_T("\"%s\" -version"), (LPCTSTR) javaPath); | |
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) | |
// In FindJava2, we do not report these errors. Leave commented for reference. | |
// // if (gIsConsole || gIsDebug) displayLastError("CreatePipe failed: "); | |
return false; | |
} | |
if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) { | |
// In FindJava2, we do not report these errors. Leave commented for reference. | |
// // 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 = CreateProcess( | |
NULL, // program path | |
(LPTSTR)((LPCTSTR) cmd),// 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); | |
// In FindJava2, we do not report these errors. Leave commented for reference. | |
// // 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. | |
TCHAR 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 | |
// Note: ReadFile uses a char buffer, not a TCHAR one. | |
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 capture roughtly the first line in lower case. | |
TCHAR *j = _tcsstr(first32, _T("java")); | |
TCHAR *v = _tcsstr(first32, _T("version")); | |
// In FindJava2, we do not report these errors. Leave commented for reference. | |
// // if ((gIsConsole || gIsDebug) && (!j || !v)) { | |
// // fprintf(stderr, "Error: keywords 'java version' not found in '%s'\n", first32); | |
// // } | |
if (j != NULL && v != NULL) { | |
result = extractJavaVersion(first32, index, outVersionStr, outVersionInt); | |
} | |
} | |
return result; | |
} | |
// -------------- | |
// Checks 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) { | |
// Append java.exe to path if not already present | |
CString &p = (CString&)*inOutPath; | |
int n = p.GetLength(); | |
if (n < 9 || p.Right(9).CompareNoCase(_T("\\java.exe")) != 0) { | |
inOutPath->Append(_T("java.exe")); | |
} | |
int result = 0; | |
PVOID oldWow64Value = disableWow64FsRedirection(); | |
if (inOutPath->FileExists()) { | |
// Run java -version | |
// Reject the version if it's not at least our current minimum. | |
CString versionStr; | |
if (!getJavaVersion(*inOutPath, &versionStr, &result)) { | |
result = 0; | |
} | |
} | |
revertWow64FsRedirection(oldWow64Value); | |
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) { | |
// Append bin to path if not already present | |
CString &p = (CString&)*inOutPath; | |
int n = p.GetLength(); | |
if (n < 4 || p.Right(4).CompareNoCase(_T("\\bin")) != 0) { | |
inOutPath->Append(_T("bin")); | |
} | |
return checkPath(inOutPath); | |
} | |
// Search java.exe in the environment | |
static void findJavaInEnvPath(std::set<CJavaPath> *outPaths) { | |
::SetLastError(0); | |
const TCHAR* envPath = _tgetenv(_T("JAVA_HOME")); | |
if (envPath != NULL) { | |
CPath p(envPath); | |
int v = checkBinPath(&p); | |
if (v > 0) { | |
outPaths->insert(CJavaPath(v, p)); | |
} | |
} | |
envPath = _tgetenv(_T("PATH")); | |
if (envPath != NULL) { | |
// Otherwise look at the entries in the current path. | |
// If we find more than one, keep the one with the highest version. | |
CString pathTokens(envPath); | |
int curPos = 0; | |
CString tok; | |
do { | |
tok = pathTokens.Tokenize(_T(";"), curPos); | |
if (!tok.IsEmpty()) { | |
CPath p(tok); | |
int v = checkPath(&p); | |
if (v > 0) { | |
outPaths->insert(CJavaPath(v, p)); | |
} | |
} | |
} while (!tok.IsEmpty()); | |
} | |
} | |
// -------------- | |
static bool getRegValue(const TCHAR *keyPath, | |
const TCHAR *keyName, | |
REGSAM access, | |
CString *outValue) { | |
HKEY key; | |
LSTATUS status = RegOpenKeyEx( | |
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 | |
TCHAR* buffer = (TCHAR*)malloc(size); | |
while (ret == ERROR_MORE_DATA && size < (1 << 16) /*64 KB*/) { | |
ret = RegQueryValueEx( | |
key, // hKey | |
keyName, // lpValueName | |
NULL, // lpReserved | |
NULL, // lpType | |
(LPBYTE)buffer, // lpData | |
&size); // lpcbData | |
if (ret == ERROR_MORE_DATA) { | |
size *= 2; | |
buffer = (TCHAR*)realloc(buffer, size); | |
} else { | |
buffer[size] = 0; | |
} | |
} | |
if (ret != ERROR_MORE_DATA) { | |
outValue->SetString(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 TCHAR *entry, REGSAM access, std::set<CJavaPath> *outPaths) { | |
// Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion] | |
CPath rootKey(_T("SOFTWARE\\JavaSoft\\")); | |
rootKey.Append(entry); | |
CString currentVersion; | |
CPath subKey(rootKey); | |
if (getRegValue(subKey, _T("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.Append(currentVersion); | |
CString value; | |
if (getRegValue(subKey, _T("JavaHome"), access, &value)) { | |
CPath javaHome(value); | |
int v = checkBinPath(&javaHome); | |
if (v > 0) { | |
outPaths->insert(CJavaPath(v, javaHome)); | |
} | |
} | |
} | |
// Try again, but this time look at all the versions available | |
HKEY javaHomeKey; | |
LSTATUS status = RegOpenKeyEx( | |
HKEY_LOCAL_MACHINE, // hKey | |
_T("SOFTWARE\\JavaSoft"), // lpSubKey | |
0, // ulOptions | |
KEY_READ | access, // samDesired | |
&javaHomeKey); // phkResult | |
if (status == ERROR_SUCCESS) { | |
TCHAR name[MAX_PATH + 1]; | |
DWORD index = 0; | |
CPath javaHome; | |
for (LONG result = ERROR_SUCCESS; result == ERROR_SUCCESS; index++) { | |
DWORD nameLen = MAX_PATH; | |
name[nameLen] = 0; | |
result = RegEnumKeyEx( | |
javaHomeKey, // hKey | |
index, // dwIndex | |
name, // lpName | |
&nameLen, // lpcName | |
NULL, // lpReserved | |
NULL, // lpClass | |
NULL, // lpcClass, | |
NULL); // lpftLastWriteTime | |
if (result == ERROR_SUCCESS && nameLen < MAX_PATH) { | |
name[nameLen] = 0; | |
CPath subKey(rootKey); | |
subKey.Append(name); | |
CString value; | |
if (getRegValue(subKey, _T("JavaHome"), access, &value)) { | |
CPath javaHome(value); | |
int v = checkBinPath(&javaHome); | |
if (v > 0) { | |
outPaths->insert(CJavaPath(v, javaHome)); | |
} | |
} | |
} | |
} | |
RegCloseKey(javaHomeKey); | |
} | |
return 0; | |
} | |
static void findJavaInRegistry(std::set<CJavaPath> *outPaths) { | |
// We'll do the registry test 3 times: first using the default mode, | |
// then forcing the use of the 32-bit registry then forcing the use of | |
// 64-bit registry. On Windows 2k, the 2 latter will fail since the | |
// flags are not supported. On a 32-bit OS the 64-bit is obviously | |
// useless and the 2 first tests should be equivalent so we just | |
// need the first case. | |
// Check the JRE first, then the JDK. | |
exploreJavaRegistry(_T("Java Runtime Environment"), 0, outPaths); | |
exploreJavaRegistry(_T("Java Development Kit"), 0, outPaths); | |
// Get the app sysinfo state (the one hidden by WOW64) | |
SYSTEM_INFO sysInfo; | |
GetSystemInfo(&sysInfo); | |
WORD programArch = sysInfo.wProcessorArchitecture; | |
// Check the real sysinfo state (not the one hidden by WOW64) for x86 | |
GetNativeSystemInfo(&sysInfo); | |
WORD actualArch = sysInfo.wProcessorArchitecture; | |
// Only try to access the WOW64-32 redirected keys on a 64-bit system. | |
// There's no point in doing this on a 32-bit system. | |
if (actualArch == PROCESSOR_ARCHITECTURE_AMD64) { | |
if (programArch != PROCESSOR_ARCHITECTURE_INTEL) { | |
// If we did the 32-bit case earlier, don't do it twice. | |
exploreJavaRegistry(_T("Java Runtime Environment"), KEY_WOW64_32KEY, outPaths); | |
exploreJavaRegistry(_T("Java Development Kit"), KEY_WOW64_32KEY, outPaths); | |
} else if (programArch != PROCESSOR_ARCHITECTURE_AMD64) { | |
// If we did the 64-bit case earlier, don't do it twice. | |
exploreJavaRegistry(_T("Java Runtime Environment"), KEY_WOW64_64KEY, outPaths); | |
exploreJavaRegistry(_T("Java Development Kit"), KEY_WOW64_64KEY, outPaths); | |
} | |
} | |
} | |
// -------------- | |
static void checkProgramFiles(std::set<CJavaPath> *outPaths) { | |
TCHAR programFilesPath[MAX_PATH + 1]; | |
HRESULT result = SHGetFolderPath( | |
NULL, // hwndOwner | |
CSIDL_PROGRAM_FILES, // nFolder | |
NULL, // hToken | |
SHGFP_TYPE_CURRENT, // dwFlags | |
programFilesPath); // pszPath | |
CPath path(programFilesPath); | |
path.Append(_T("Java")); | |
// Do we have a C:\\Program Files\\Java directory? | |
if (!path.IsDirectory()) { | |
return; | |
} | |
CPath glob(path); | |
glob.Append(_T("j*")); | |
WIN32_FIND_DATA findData; | |
HANDLE findH = FindFirstFile(glob, &findData); | |
if (findH == INVALID_HANDLE_VALUE) { | |
return; | |
} | |
do { | |
if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { | |
CPath temp(path); | |
temp.Append(findData.cFileName); | |
// Check C:\\Program Files[x86]\\Java\\j*\\bin\\java.exe | |
int v = checkBinPath(&temp); | |
if (v > 0) { | |
outPaths->insert(CJavaPath(v, temp)); | |
} | |
} | |
} while (FindNextFile(findH, &findData) != 0); | |
FindClose(findH); | |
} | |
static void findJavaInProgramFiles(std::set<CJavaPath> *outPaths) { | |
// Check the C:\\Program Files (x86) directory | |
// With WOW64 fs redirection in place by default, we should get the x86 | |
// version on a 64-bit OS since this app is a 32-bit itself. | |
checkProgramFiles(outPaths); | |
// Check the real sysinfo state (not the one hidden by WOW64) for x86 | |
SYSTEM_INFO sysInfo; | |
GetNativeSystemInfo(&sysInfo); | |
if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) { | |
// On a 64-bit OS, try again by disabling the fs redirection so | |
// that we can try the real C:\\Program Files directory. | |
PVOID oldWow64Value = disableWow64FsRedirection(); | |
checkProgramFiles(outPaths); | |
revertWow64FsRedirection(oldWow64Value); | |
} | |
} | |
//------ | |
CJavaFinder::CJavaFinder(int minVersion) : mMinVersion(minVersion) { | |
} | |
CJavaFinder::~CJavaFinder() { | |
} | |
/* | |
* Checks whether there's a recorded path in the registry and whether | |
* this path still points to a valid Java executable. | |
* Returns false if any of these do not match, | |
* Returns true if both condition match, | |
* outPath contains the result path when returning true. | |
*/ | |
CJavaPath CJavaFinder::getRegistryPath() { | |
CString existing; | |
CRegKey rk; | |
if (rk.Open(HKEY_CURRENT_USER, JF_REGISTRY_KEY, KEY_READ) == ERROR_SUCCESS) { | |
ULONG sLen = MAX_PATH; | |
TCHAR s[MAX_PATH + 1]; | |
if (rk.QueryStringValue(JF_REGISTRY_VALUE_PATH, s, &sLen) == ERROR_SUCCESS) { | |
existing.SetString(s); | |
} | |
rk.Close(); | |
} | |
if (!existing.IsEmpty()) { | |
CJavaPath javaPath; | |
if (checkJavaPath(existing, &javaPath)) { | |
return javaPath; | |
} | |
} | |
return CJavaPath::sEmpty; | |
} | |
bool CJavaFinder::setRegistryPath(const CJavaPath &javaPath) { | |
CRegKey rk; | |
if (rk.Create(HKEY_CURRENT_USER, JF_REGISTRY_KEY) == ERROR_SUCCESS) { | |
bool ok = rk.SetStringValue(JF_REGISTRY_VALUE_PATH, javaPath.mPath, REG_SZ) == ERROR_SUCCESS && | |
rk.SetStringValue(JF_REGISTRY_VALUE_VERS, javaPath.getVersion(), REG_SZ) == ERROR_SUCCESS; | |
rk.Close(); | |
return ok; | |
} | |
return false; | |
} | |
void CJavaFinder::findJavaPaths(std::set<CJavaPath> *paths) { | |
findJavaInEnvPath(paths); | |
findJavaInProgramFiles(paths); | |
findJavaInRegistry(paths); | |
// Exclude any entries that do not match the minimum version. | |
// The set is going to be fairly small so it's easier to do it here | |
// than add the filter logic in all the static methods above. | |
if (mMinVersion > 0) { | |
for (auto it = paths->begin(); it != paths->end(); ) { | |
if (it->mVersion < mMinVersion) { | |
it = paths->erase(it); // C++11 set.erase returns an iterator to the *next* element | |
} else { | |
++it; | |
} | |
} | |
} | |
} | |
bool CJavaFinder::checkJavaPath(const CString &path, CJavaPath *outPath) { | |
CPath p(path); | |
// try this path (if it ends with java.exe) or path\\java.exe | |
int v = checkPath(&p); | |
if (v == 0) { | |
// reset path and try path\\bin\\java.exe | |
p = CPath(path); | |
v = checkBinPath(&p); | |
} | |
if (v > 0) { | |
outPath->set(v, p); | |
return v >= mMinVersion; | |
} | |
return false; | |
} | |