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