blob: 5a1c3f83d40ffaa5d8dba0b48d12b18ff77b6677 [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
Raphael692d2b52012-01-26 10:57:08 -080019// Indicate we want at least all Windows Server 2003 (5.2) APIs.
20// Note: default is set by system/core/include/arch/windows/AndroidConfig.h to 0x0500
21// which is Win2K. However our minimum SDK tools requirement is Win XP (0x0501).
22// However we do need 0x0502 to get access to the WOW-64-32 constants for the
23// registry, except we'll need to be careful since they are not available on XP.
24#undef _WIN32_WINNT
25#define _WIN32_WINNT 0x0502
26// Indicate we want at least all IE 5 shell APIs
27#define _WIN32_IE 0x0500
28
Raphael628920b2011-11-22 10:40:13 -080029#include "find_java.h"
Raphael22fd3c12011-12-02 21:58:23 -080030#include <shlobj.h>
Raphael692d2b52012-01-26 10:57:08 -080031#include <ctype.h>
32
33// Define some types missing in MingW
34#ifndef LSTATUS
35typedef LONG LSTATUS;
36#endif
Raphael628920b2011-11-22 10:40:13 -080037
Raphael22fd3c12011-12-02 21:58:23 -080038// Check whether we can find $PATH/java.exe
39static bool checkPath(CPath *inOutPath) {
40 inOutPath->addPath("java.exe");
41
42 bool result = false;
43 PVOID oldWow64Value = disableWow64FsRedirection();
44 if (inOutPath->fileExists()) {
45 // Make sure we can actually run "java -version".
46 CString cmd;
47 cmd.setf("\"%s\" -version", inOutPath->cstr());
48 int code = execWait(cmd.cstr());
49 result = (code == 0);
50 }
51
52 revertWow64FsRedirection(oldWow64Value);
53 return result;
54}
55
56// Check whether we can find $PATH/bin/java.exe
57static bool checkBinPath(CPath *inOutPath) {
58 inOutPath->addPath("bin");
59 return checkPath(inOutPath);
60}
61
62// Search java.exe in the environment
Raphael628920b2011-11-22 10:40:13 -080063bool findJavaInEnvPath(CPath *outJavaPath) {
64 SetLastError(0);
Raphael22fd3c12011-12-02 21:58:23 -080065
66 const char* envPath = getenv("JAVA_HOME");
67 if (envPath != NULL) {
68 CPath p(envPath);
69 if (checkBinPath(&p)) {
Raphaeld2d69992012-01-24 15:06:56 -080070 if (gIsDebug) msgBox("Java found via JAVA_HOME: %s", p.cstr());
Raphael22fd3c12011-12-02 21:58:23 -080071 *outJavaPath = p;
72 return true;
73 }
74 }
75
76 envPath = getenv("PATH");
Raphael628920b2011-11-22 10:40:13 -080077 if (!envPath) return false;
78
79 CArray<CString> *paths = CString(envPath).split(';');
80 for(int i = 0; i < paths->size(); i++) {
81 CPath p((*paths)[i].cstr());
Raphael22fd3c12011-12-02 21:58:23 -080082 if (checkPath(&p)) {
Raphaeld2d69992012-01-24 15:06:56 -080083 if (gIsDebug) msgBox("Java found via env PATH: %s", p.cstr());
Raphael22fd3c12011-12-02 21:58:23 -080084 *outJavaPath = p;
85 delete paths;
86 return true;
Raphael628920b2011-11-22 10:40:13 -080087 }
88 }
89
90 delete paths;
91 return false;
92}
93
Raphael22fd3c12011-12-02 21:58:23 -080094// --------------
95
96bool getRegValue(const char *keyPath, const char *keyName, REGSAM access, CString *outValue) {
97 HKEY key;
98 LSTATUS status = RegOpenKeyExA(
99 HKEY_LOCAL_MACHINE, // hKey
100 keyPath, // lpSubKey
101 0, // ulOptions
102 KEY_READ | access, // samDesired,
103 &key); // phkResult
104 if (status == ERROR_SUCCESS) {
105
106 LSTATUS ret = ERROR_MORE_DATA;
107 DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough
108 char* buffer = (char*) malloc(size);
109
110 while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) {
111 ret = RegQueryValueExA(
112 key, // hKey
113 keyName, // lpValueName
114 NULL, // lpReserved
115 NULL, // lpType
116 (LPBYTE) buffer, // lpData
117 &size); // lpcbData
118
119 if (ret == ERROR_MORE_DATA) {
120 size *= 2;
121 buffer = (char*) realloc(buffer, size);
122 } else {
123 buffer[size] = 0;
124 }
125 }
126
127 if (ret != ERROR_MORE_DATA) outValue->set(buffer);
128
129 free(buffer);
130 RegCloseKey(key);
131
132 return (ret != ERROR_MORE_DATA);
133 }
134
Raphael628920b2011-11-22 10:40:13 -0800135 return false;
136}
137
Raphael22fd3c12011-12-02 21:58:23 -0800138bool exploreJavaRegistry(const char *entry, REGSAM access, CPath *outJavaPath) {
139
140 // Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion]
141 CPath subKey("SOFTWARE\\JavaSoft\\");
142 subKey.addPath(entry);
143
144 CString currVersion;
145 if (getRegValue(subKey.cstr(), "CurrentVersion", access, &currVersion)) {
146 // CurrentVersion should be something like "1.7".
147 // We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome]
148 subKey.addPath(currVersion);
149 CPath javaHome;
150 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
151 if (checkBinPath(&javaHome)) {
152 *outJavaPath = javaHome;
153 return true;
154 }
155 }
156 }
157
Raphael628920b2011-11-22 10:40:13 -0800158 return false;
159}
160
Raphael22fd3c12011-12-02 21:58:23 -0800161bool findJavaInRegistry(CPath *outJavaPath) {
162 // We'll do the registry test 3 times: first using the default mode,
163 // then forcing the use of the 32-bit registry then forcing the use of
164 // 64-bit registry. On Windows 2k, the 2 latter will fail since the
165 // flags are not supported. On a 32-bit OS the 64-bit is obviously
166 // useless and the 2 first test should be equivalent so we just
167 // need the first case.
168
169 // Check the JRE first, then the JDK.
170 if (exploreJavaRegistry("Java Runtime Environment", 0, outJavaPath) ||
171 exploreJavaRegistry("Java Development Kit", 0, outJavaPath)) {
172 return true;
173 }
174
175 // Check the real sysinfo state (not the one hidden by WOW64) for x86
176 SYSTEM_INFO sysInfo;
177 GetNativeSystemInfo(&sysInfo);
178
Raphael692d2b52012-01-26 10:57:08 -0800179 // Only try to access the WOW64-32 redirected keys on a 64-bit system.
180 // There's no point in doing that on a 32-bit system.
Raphael22fd3c12011-12-02 21:58:23 -0800181 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
182 if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath) ||
183 exploreJavaRegistry("Java Development Kit", KEY_WOW64_32KEY, outJavaPath)) {
184 return true;
185 }
186
187 if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_64KEY, outJavaPath) ||
188 exploreJavaRegistry("Java Development Kit", KEY_WOW64_64KEY, outJavaPath)) {
189 return true;
190 }
191 }
192
193 return false;
194}
195
196// --------------
197
198static bool checkProgramFiles(CPath *outJavaPath) {
199
200 char programFilesPath[MAX_PATH + 1];
201 HRESULT result = SHGetFolderPathA(
202 NULL, // hwndOwner
203 CSIDL_PROGRAM_FILES, // nFolder
204 NULL, // hToken
205 SHGFP_TYPE_CURRENT, // dwFlags
206 programFilesPath); // pszPath
207 if (FAILED(result)) return false;
208
209 CPath path(programFilesPath);
210 path.addPath("Java");
211
212 // Do we have a C:\\Program Files\\Java directory?
213 if (!path.dirExists()) return false;
214
215 CPath glob(path);
216 glob.addPath("j*");
217
218 bool found = false;
219 WIN32_FIND_DATAA findData;
220 HANDLE findH = FindFirstFileA(glob.cstr(), &findData);
221 if (findH == INVALID_HANDLE_VALUE) return false;
222 do {
223 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
224 CPath temp(path);
225 temp.addPath(findData.cFileName);
226 // Check C:\\Program Files[x86]\\Java\\{jdk,jre}*\\bin\\java.exe
227 if (checkBinPath(&temp)) {
228 found = true;
229 *outJavaPath = temp;
230 }
231 }
232 } while (!found && FindNextFileA(findH, &findData) != 0);
233 FindClose(findH);
234
235 return found;
236}
237
238bool findJavaInProgramFiles(CPath *outJavaPath) {
239 // Check the C:\\Program Files (x86) directory
240 // With WOW64 fs redirection in place by default, we should get the x86
241 // version on a 64-bit OS since this app is a 32-bit itself.
242 if (checkProgramFiles(outJavaPath)) return true;
243
244 // Check the real sysinfo state (not the one hidden by WOW64) for x86
245 SYSTEM_INFO sysInfo;
246 GetNativeSystemInfo(&sysInfo);
247
248 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
249 // On a 64-bit OS, try again by disabling the fs redirection so
250 // that we can try the real C:\\Program Files directory.
251 PVOID oldWow64Value = disableWow64FsRedirection();
252 bool found = checkProgramFiles(outJavaPath);
253 revertWow64FsRedirection(oldWow64Value);
254 return found;
255 }
256
257 return false;
258}
259
260// --------------
261
Raphaeld2d69992012-01-24 15:06:56 -0800262bool getJavaVersion(CPath &javaPath, CString *version) {
Raphael22fd3c12011-12-02 21:58:23 -0800263 bool result = false;
264
Raphaeld2d69992012-01-24 15:06:56 -0800265 // Run "java -version", which outputs something like to *STDERR*:
266 //
267 // java version "1.6.0_29"
268 // Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
269 // Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
270 //
271 // We want to capture the first line, and more exactly the "1.6" part.
272
273
Raphael22fd3c12011-12-02 21:58:23 -0800274 CString cmd;
275 cmd.setf("\"%s\" -version", javaPath.cstr());
276
Raphaeld2d69992012-01-24 15:06:56 -0800277 SECURITY_ATTRIBUTES saAttr;
Raphael22fd3c12011-12-02 21:58:23 -0800278 STARTUPINFO startup;
279 PROCESS_INFORMATION pinfo;
280
281 // Want to inherit pipe handle
282 ZeroMemory(&saAttr, sizeof(saAttr));
283 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
284 saAttr.bInheritHandle = TRUE;
285 saAttr.lpSecurityDescriptor = NULL;
286
287 // Create pipe for stdout
288 HANDLE stdoutPipeRd, stdoutPipeWt;
289 if (!CreatePipe(
290 &stdoutPipeRd, // hReadPipe,
291 &stdoutPipeWt, // hWritePipe,
292 &saAttr, // lpPipeAttributes,
293 0)) { // nSize (0=default buffer size)
294 displayLastError("CreatePipe failed: ");
295 return false;
296 }
Raphaeld2d69992012-01-24 15:06:56 -0800297 if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) {
298 displayLastError("SetHandleInformation failed: ");
299 return false;
300 }
Raphael22fd3c12011-12-02 21:58:23 -0800301
302 ZeroMemory(&pinfo, sizeof(pinfo));
303
304 ZeroMemory(&startup, sizeof(startup));
305 startup.cb = sizeof(startup);
Raphaeld2d69992012-01-24 15:06:56 -0800306 startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
Raphael22fd3c12011-12-02 21:58:23 -0800307 startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
Raphaeld2d69992012-01-24 15:06:56 -0800308 // Capture both stderr and stdout
309 startup.hStdError = stdoutPipeWt;
310 startup.hStdOutput = stdoutPipeWt;
311 startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
Raphael22fd3c12011-12-02 21:58:23 -0800312
Raphaeld2d69992012-01-24 15:06:56 -0800313 BOOL ok = CreateProcessA(
314 NULL, // program path
315 (LPSTR) cmd.cstr(), // command-line
316 NULL, // process handle is not inheritable
317 NULL, // thread handle is not inheritable
318 TRUE, // yes, inherit some handles
319 0, // process creation flags
320 NULL, // use parent's environment block
321 NULL, // use parent's starting directory
322 &startup, // startup info, i.e. std handles
Raphael22fd3c12011-12-02 21:58:23 -0800323 &pinfo);
324
Raphaeld2d69992012-01-24 15:06:56 -0800325 if (gIsConsole && !ok) displayLastError("CreateProcess failed: ");
326
327 // Close the write-end of the output pipe (we're only reading from it)
328 CloseHandle(stdoutPipeWt);
329
330 // Read from the output pipe. We don't need to read everything,
331 // the first line should be 'Java version "1.2.3_45"\r\n'
332 // so reading about 32 chars is all we need.
333 char first32[32 + 1];
334 int index = 0;
335 first32[0] = 0;
336
337 if (ok) {
338
339 #define SIZE 1024
340 char buffer[SIZE];
341 DWORD sizeRead = 0;
342
343 while (ok) {
344 // Keep reading in the same buffer location
345 ok = ReadFile(stdoutPipeRd, // hFile
346 buffer, // lpBuffer
347 SIZE, // DWORD buffer size to read
348 &sizeRead, // DWORD buffer size read
349 NULL); // overlapped
350 if (!ok || sizeRead == 0 || sizeRead > SIZE) break;
351
352 // Copy up to the first 32 characters
353 if (index < 32) {
354 DWORD n = 32 - index;
355 if (n > sizeRead) n = sizeRead;
356 // copy as lowercase to simplify checks later
357 for (char *b = buffer; n > 0; n--, b++, index++) {
358 char c = *b;
359 if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
360 first32[index] = c;
361 }
362 first32[index] = 0;
363 }
364 }
365
Raphael22fd3c12011-12-02 21:58:23 -0800366 WaitForSingleObject(pinfo.hProcess, INFINITE);
367
368 DWORD exitCode;
369 if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {
370 // this should not return STILL_ACTIVE (259)
371 result = exitCode == 0;
372 }
Raphaeld2d69992012-01-24 15:06:56 -0800373
Raphael22fd3c12011-12-02 21:58:23 -0800374 CloseHandle(pinfo.hProcess);
375 CloseHandle(pinfo.hThread);
376 }
377 CloseHandle(stdoutPipeRd);
Raphael22fd3c12011-12-02 21:58:23 -0800378
Raphaeld2d69992012-01-24 15:06:56 -0800379 if (index > 0) {
380 // Look for a few keywords in the output however we don't
381 // care about specific ordering or case-senstiviness.
382 // We only captures roughtly the first line in lower case.
383 char *j = strstr(first32, "java");
384 char *v = strstr(first32, "version");
385 if (gIsDebug && gIsConsole && (!j || !v)) {
386 fprintf(stderr, "Error: keywords 'java version' not found in '%s'\n", first32);
387 }
388 if (j != NULL && v != NULL) {
389 // Now extract the first thing that looks like digit.digit
390 for (int i = 0; i < index - 2; i++) {
391 if (isdigit(first32[i]) &&
392 first32[i+1] == '.' &&
393 isdigit(first32[i+2])) {
394 version->set(first32 + i, 3);
395 result = true;
396 break;
397 }
398 }
399 }
Raphael22fd3c12011-12-02 21:58:23 -0800400 }
401
402 return result;
403}
404
405
Raphael628920b2011-11-22 10:40:13 -0800406#endif /* _WIN32 */