blob: 508147b44e27b4eb24bac3d410c6b4ffeab6f1b2 [file] [log] [blame]
Raphael628920b2011-11-22 10:40:13 -08001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#ifdef _WIN32
18
19#include "find_java.h"
Raphael22fd3c12011-12-02 21:58:23 -080020#include <shlobj.h>
Raphael628920b2011-11-22 10:40:13 -080021
Raphael22fd3c12011-12-02 21:58:23 -080022// Check whether we can find $PATH/java.exe
23static bool checkPath(CPath *inOutPath) {
24 inOutPath->addPath("java.exe");
25
26 bool result = false;
27 PVOID oldWow64Value = disableWow64FsRedirection();
28 if (inOutPath->fileExists()) {
29 // Make sure we can actually run "java -version".
30 CString cmd;
31 cmd.setf("\"%s\" -version", inOutPath->cstr());
32 int code = execWait(cmd.cstr());
33 result = (code == 0);
34 }
35
36 revertWow64FsRedirection(oldWow64Value);
37 return result;
38}
39
40// Check whether we can find $PATH/bin/java.exe
41static bool checkBinPath(CPath *inOutPath) {
42 inOutPath->addPath("bin");
43 return checkPath(inOutPath);
44}
45
46// Search java.exe in the environment
Raphael628920b2011-11-22 10:40:13 -080047bool findJavaInEnvPath(CPath *outJavaPath) {
48 SetLastError(0);
Raphael22fd3c12011-12-02 21:58:23 -080049
50 const char* envPath = getenv("JAVA_HOME");
51 if (envPath != NULL) {
52 CPath p(envPath);
53 if (checkBinPath(&p)) {
Raphaeld2d69992012-01-24 15:06:56 -080054 if (gIsDebug) msgBox("Java found via JAVA_HOME: %s", p.cstr());
Raphael22fd3c12011-12-02 21:58:23 -080055 *outJavaPath = p;
56 return true;
57 }
58 }
59
60 envPath = getenv("PATH");
Raphael628920b2011-11-22 10:40:13 -080061 if (!envPath) return false;
62
63 CArray<CString> *paths = CString(envPath).split(';');
64 for(int i = 0; i < paths->size(); i++) {
65 CPath p((*paths)[i].cstr());
Raphael22fd3c12011-12-02 21:58:23 -080066 if (checkPath(&p)) {
Raphaeld2d69992012-01-24 15:06:56 -080067 if (gIsDebug) msgBox("Java found via env PATH: %s", p.cstr());
Raphael22fd3c12011-12-02 21:58:23 -080068 *outJavaPath = p;
69 delete paths;
70 return true;
Raphael628920b2011-11-22 10:40:13 -080071 }
72 }
73
74 delete paths;
75 return false;
76}
77
Raphael22fd3c12011-12-02 21:58:23 -080078// --------------
79
80bool getRegValue(const char *keyPath, const char *keyName, REGSAM access, CString *outValue) {
81 HKEY key;
82 LSTATUS status = RegOpenKeyExA(
83 HKEY_LOCAL_MACHINE, // hKey
84 keyPath, // lpSubKey
85 0, // ulOptions
86 KEY_READ | access, // samDesired,
87 &key); // phkResult
88 if (status == ERROR_SUCCESS) {
89
90 LSTATUS ret = ERROR_MORE_DATA;
91 DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough
92 char* buffer = (char*) malloc(size);
93
94 while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) {
95 ret = RegQueryValueExA(
96 key, // hKey
97 keyName, // lpValueName
98 NULL, // lpReserved
99 NULL, // lpType
100 (LPBYTE) buffer, // lpData
101 &size); // lpcbData
102
103 if (ret == ERROR_MORE_DATA) {
104 size *= 2;
105 buffer = (char*) realloc(buffer, size);
106 } else {
107 buffer[size] = 0;
108 }
109 }
110
111 if (ret != ERROR_MORE_DATA) outValue->set(buffer);
112
113 free(buffer);
114 RegCloseKey(key);
115
116 return (ret != ERROR_MORE_DATA);
117 }
118
Raphael628920b2011-11-22 10:40:13 -0800119 return false;
120}
121
Raphael22fd3c12011-12-02 21:58:23 -0800122bool exploreJavaRegistry(const char *entry, REGSAM access, CPath *outJavaPath) {
123
124 // Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion]
125 CPath subKey("SOFTWARE\\JavaSoft\\");
126 subKey.addPath(entry);
127
128 CString currVersion;
129 if (getRegValue(subKey.cstr(), "CurrentVersion", access, &currVersion)) {
130 // CurrentVersion should be something like "1.7".
131 // We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome]
132 subKey.addPath(currVersion);
133 CPath javaHome;
134 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
135 if (checkBinPath(&javaHome)) {
136 *outJavaPath = javaHome;
137 return true;
138 }
139 }
140 }
141
Raphael628920b2011-11-22 10:40:13 -0800142 return false;
143}
144
Raphael22fd3c12011-12-02 21:58:23 -0800145bool findJavaInRegistry(CPath *outJavaPath) {
146 // We'll do the registry test 3 times: first using the default mode,
147 // then forcing the use of the 32-bit registry then forcing the use of
148 // 64-bit registry. On Windows 2k, the 2 latter will fail since the
149 // flags are not supported. On a 32-bit OS the 64-bit is obviously
150 // useless and the 2 first test should be equivalent so we just
151 // need the first case.
152
153 // Check the JRE first, then the JDK.
154 if (exploreJavaRegistry("Java Runtime Environment", 0, outJavaPath) ||
155 exploreJavaRegistry("Java Development Kit", 0, outJavaPath)) {
156 return true;
157 }
158
159 // Check the real sysinfo state (not the one hidden by WOW64) for x86
160 SYSTEM_INFO sysInfo;
161 GetNativeSystemInfo(&sysInfo);
162
163 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
164 if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath) ||
165 exploreJavaRegistry("Java Development Kit", KEY_WOW64_32KEY, outJavaPath)) {
166 return true;
167 }
168
169 if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_64KEY, outJavaPath) ||
170 exploreJavaRegistry("Java Development Kit", KEY_WOW64_64KEY, outJavaPath)) {
171 return true;
172 }
173 }
174
175 return false;
176}
177
178// --------------
179
180static bool checkProgramFiles(CPath *outJavaPath) {
181
182 char programFilesPath[MAX_PATH + 1];
183 HRESULT result = SHGetFolderPathA(
184 NULL, // hwndOwner
185 CSIDL_PROGRAM_FILES, // nFolder
186 NULL, // hToken
187 SHGFP_TYPE_CURRENT, // dwFlags
188 programFilesPath); // pszPath
189 if (FAILED(result)) return false;
190
191 CPath path(programFilesPath);
192 path.addPath("Java");
193
194 // Do we have a C:\\Program Files\\Java directory?
195 if (!path.dirExists()) return false;
196
197 CPath glob(path);
198 glob.addPath("j*");
199
200 bool found = false;
201 WIN32_FIND_DATAA findData;
202 HANDLE findH = FindFirstFileA(glob.cstr(), &findData);
203 if (findH == INVALID_HANDLE_VALUE) return false;
204 do {
205 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
206 CPath temp(path);
207 temp.addPath(findData.cFileName);
208 // Check C:\\Program Files[x86]\\Java\\{jdk,jre}*\\bin\\java.exe
209 if (checkBinPath(&temp)) {
210 found = true;
211 *outJavaPath = temp;
212 }
213 }
214 } while (!found && FindNextFileA(findH, &findData) != 0);
215 FindClose(findH);
216
217 return found;
218}
219
220bool findJavaInProgramFiles(CPath *outJavaPath) {
221 // Check the C:\\Program Files (x86) directory
222 // With WOW64 fs redirection in place by default, we should get the x86
223 // version on a 64-bit OS since this app is a 32-bit itself.
224 if (checkProgramFiles(outJavaPath)) return true;
225
226 // Check the real sysinfo state (not the one hidden by WOW64) for x86
227 SYSTEM_INFO sysInfo;
228 GetNativeSystemInfo(&sysInfo);
229
230 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
231 // On a 64-bit OS, try again by disabling the fs redirection so
232 // that we can try the real C:\\Program Files directory.
233 PVOID oldWow64Value = disableWow64FsRedirection();
234 bool found = checkProgramFiles(outJavaPath);
235 revertWow64FsRedirection(oldWow64Value);
236 return found;
237 }
238
239 return false;
240}
241
242// --------------
243
Raphaeld2d69992012-01-24 15:06:56 -0800244bool getJavaVersion(CPath &javaPath, CString *version) {
Raphael22fd3c12011-12-02 21:58:23 -0800245 bool result = false;
246
Raphaeld2d69992012-01-24 15:06:56 -0800247 // Run "java -version", which outputs something like to *STDERR*:
248 //
249 // java version "1.6.0_29"
250 // Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
251 // Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
252 //
253 // We want to capture the first line, and more exactly the "1.6" part.
254
255
Raphael22fd3c12011-12-02 21:58:23 -0800256 CString cmd;
257 cmd.setf("\"%s\" -version", javaPath.cstr());
258
Raphaeld2d69992012-01-24 15:06:56 -0800259 SECURITY_ATTRIBUTES saAttr;
Raphael22fd3c12011-12-02 21:58:23 -0800260 STARTUPINFO startup;
261 PROCESS_INFORMATION pinfo;
262
263 // Want to inherit pipe handle
264 ZeroMemory(&saAttr, sizeof(saAttr));
265 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
266 saAttr.bInheritHandle = TRUE;
267 saAttr.lpSecurityDescriptor = NULL;
268
269 // Create pipe for stdout
270 HANDLE stdoutPipeRd, stdoutPipeWt;
271 if (!CreatePipe(
272 &stdoutPipeRd, // hReadPipe,
273 &stdoutPipeWt, // hWritePipe,
274 &saAttr, // lpPipeAttributes,
275 0)) { // nSize (0=default buffer size)
276 displayLastError("CreatePipe failed: ");
277 return false;
278 }
Raphaeld2d69992012-01-24 15:06:56 -0800279 if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) {
280 displayLastError("SetHandleInformation failed: ");
281 return false;
282 }
Raphael22fd3c12011-12-02 21:58:23 -0800283
284 ZeroMemory(&pinfo, sizeof(pinfo));
285
286 ZeroMemory(&startup, sizeof(startup));
287 startup.cb = sizeof(startup);
Raphaeld2d69992012-01-24 15:06:56 -0800288 startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
Raphael22fd3c12011-12-02 21:58:23 -0800289 startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
Raphaeld2d69992012-01-24 15:06:56 -0800290 // Capture both stderr and stdout
291 startup.hStdError = stdoutPipeWt;
292 startup.hStdOutput = stdoutPipeWt;
293 startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
Raphael22fd3c12011-12-02 21:58:23 -0800294
Raphaeld2d69992012-01-24 15:06:56 -0800295 BOOL ok = CreateProcessA(
296 NULL, // program path
297 (LPSTR) cmd.cstr(), // command-line
298 NULL, // process handle is not inheritable
299 NULL, // thread handle is not inheritable
300 TRUE, // yes, inherit some handles
301 0, // process creation flags
302 NULL, // use parent's environment block
303 NULL, // use parent's starting directory
304 &startup, // startup info, i.e. std handles
Raphael22fd3c12011-12-02 21:58:23 -0800305 &pinfo);
306
Raphaeld2d69992012-01-24 15:06:56 -0800307 if (gIsConsole && !ok) displayLastError("CreateProcess failed: ");
308
309 // Close the write-end of the output pipe (we're only reading from it)
310 CloseHandle(stdoutPipeWt);
311
312 // Read from the output pipe. We don't need to read everything,
313 // the first line should be 'Java version "1.2.3_45"\r\n'
314 // so reading about 32 chars is all we need.
315 char first32[32 + 1];
316 int index = 0;
317 first32[0] = 0;
318
319 if (ok) {
320
321 #define SIZE 1024
322 char buffer[SIZE];
323 DWORD sizeRead = 0;
324
325 while (ok) {
326 // Keep reading in the same buffer location
327 ok = ReadFile(stdoutPipeRd, // hFile
328 buffer, // lpBuffer
329 SIZE, // DWORD buffer size to read
330 &sizeRead, // DWORD buffer size read
331 NULL); // overlapped
332 if (!ok || sizeRead == 0 || sizeRead > SIZE) break;
333
334 // Copy up to the first 32 characters
335 if (index < 32) {
336 DWORD n = 32 - index;
337 if (n > sizeRead) n = sizeRead;
338 // copy as lowercase to simplify checks later
339 for (char *b = buffer; n > 0; n--, b++, index++) {
340 char c = *b;
341 if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
342 first32[index] = c;
343 }
344 first32[index] = 0;
345 }
346 }
347
Raphael22fd3c12011-12-02 21:58:23 -0800348 WaitForSingleObject(pinfo.hProcess, INFINITE);
349
350 DWORD exitCode;
351 if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {
352 // this should not return STILL_ACTIVE (259)
353 result = exitCode == 0;
354 }
Raphaeld2d69992012-01-24 15:06:56 -0800355
Raphael22fd3c12011-12-02 21:58:23 -0800356 CloseHandle(pinfo.hProcess);
357 CloseHandle(pinfo.hThread);
358 }
359 CloseHandle(stdoutPipeRd);
Raphael22fd3c12011-12-02 21:58:23 -0800360
Raphaeld2d69992012-01-24 15:06:56 -0800361 if (index > 0) {
362 // Look for a few keywords in the output however we don't
363 // care about specific ordering or case-senstiviness.
364 // We only captures roughtly the first line in lower case.
365 char *j = strstr(first32, "java");
366 char *v = strstr(first32, "version");
367 if (gIsDebug && gIsConsole && (!j || !v)) {
368 fprintf(stderr, "Error: keywords 'java version' not found in '%s'\n", first32);
369 }
370 if (j != NULL && v != NULL) {
371 // Now extract the first thing that looks like digit.digit
372 for (int i = 0; i < index - 2; i++) {
373 if (isdigit(first32[i]) &&
374 first32[i+1] == '.' &&
375 isdigit(first32[i+2])) {
376 version->set(first32 + i, 3);
377 result = true;
378 break;
379 }
380 }
381 }
Raphael22fd3c12011-12-02 21:58:23 -0800382 }
383
384 return result;
385}
386
387
Raphael628920b2011-11-22 10:40:13 -0800388#endif /* _WIN32 */