aboutsummaryrefslogtreecommitdiff
path: root/find_java2/src/JavaFinder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'find_java2/src/JavaFinder.cpp')
-rwxr-xr-xfind_java2/src/JavaFinder.cpp594
1 files changed, 594 insertions, 0 deletions
diff --git a/find_java2/src/JavaFinder.cpp b/find_java2/src/JavaFinder.cpp
new file mode 100755
index 000000000..60a2e230e
--- /dev/null
+++ b/find_java2/src/JavaFinder.cpp
@@ -0,0 +1,594 @@
+/*
+* 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, &currentVersion)) {
+ // 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;
+}
+