/* * 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; }