1 /*
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 // 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
29 #include "find_java.h"
30 #include <shlobj.h>
31 #include <ctype.h>
32
33 // Define some types missing in MingW
34 #ifndef LSTATUS
35 typedef LONG LSTATUS;
36 #endif
37
38
39 // Extract the first thing that looks like (digit.digit+).
40 // Note: this will break when java reports a version with major > 9.
41 // However it will reasonably cope with "1.10", if that ever happens.
extractJavaVersion(const char * start,int length,CString * outVersionStr,int * outVersionInt)42 static bool extractJavaVersion(const char *start,
43 int length,
44 CString *outVersionStr,
45 int *outVersionInt) {
46 const char *end = start + length;
47 for (const char *c = start; c < end - 2; c++) {
48 if (isdigit(c[0]) &&
49 c[1] == '.' &&
50 isdigit(c[2])) {
51 const char *e = c+2;
52 while (isdigit(e[1])) {
53 e++;
54 }
55 if (outVersionStr != NULL) {
56 outVersionStr->set(c, e - c + 1);
57 }
58 if (outVersionInt != NULL) {
59 // add major * 1000, currently only 1 digit
60 int value = (*c - '0') * 1000;
61 // add minor
62 for (int m = 1; *e != '.'; e--, m *= 10) {
63 value += (*e - '0') * m;
64 }
65 *outVersionInt = value;
66 }
67 return true;
68 }
69 }
70 return false;
71 }
72
73 // Check whether we can find $PATH/java.exe.
74 // inOutPath should be the directory where we're looking at.
75 // In output, it will be the java path we tested.
76 // Returns the java version integer found (e.g. 1006 for 1.6).
77 // Return 0 in case of error.
checkPath(CPath * inOutPath)78 static int checkPath(CPath *inOutPath) {
79 inOutPath->addPath("java.exe");
80
81 int result = 0;
82 PVOID oldWow64Value = disableWow64FsRedirection();
83 if (inOutPath->fileExists()) {
84 // Run java -version
85 // Reject the version if it's not at least our current minimum.
86 if (!getJavaVersion(*inOutPath, NULL /*versionStr*/, &result)) {
87 result = 0;
88 }
89 }
90
91 revertWow64FsRedirection(oldWow64Value);
92 return result;
93 }
94
95 // Check whether we can find $PATH/bin/java.exe
96 // Returns the Java version found (e.g. 1006 for 1.6) or 0 in case of error.
checkBinPath(CPath * inOutPath)97 static int checkBinPath(CPath *inOutPath) {
98 inOutPath->addPath("bin");
99 return checkPath(inOutPath);
100 }
101
102 // Search java.exe in the environment
findJavaInEnvPath(CPath * outJavaPath)103 int findJavaInEnvPath(CPath *outJavaPath) {
104 SetLastError(0);
105
106 int currVersion = 0;
107
108 const char* envPath = getenv("JAVA_HOME");
109 if (envPath != NULL) {
110 CPath p(envPath);
111 currVersion = checkBinPath(&p);
112 if (currVersion > 0) {
113 if (gIsDebug) {
114 fprintf(stderr, "Java %d found via JAVA_HOME: %s\n", currVersion, p.cstr());
115 }
116 *outJavaPath = p;
117 }
118 if (currVersion >= MIN_JAVA_VERSION) {
119 // As an optimization for runtime, if we find a suitable java
120 // version in JAVA_HOME we won't waste time looking at the PATH.
121 return currVersion;
122 }
123 }
124
125 envPath = getenv("PATH");
126 if (!envPath) return currVersion;
127
128 // Otherwise look at the entries in the current path.
129 // If we find more than one, keep the one with the highest version.
130
131 CArray<CString> *paths = CString(envPath).split(';');
132 for(int i = 0; i < paths->size(); i++) {
133 CPath p((*paths)[i].cstr());
134 int v = checkPath(&p);
135 if (v > currVersion) {
136 if (gIsDebug) {
137 fprintf(stderr, "Java %d found via env PATH: %s\n", v, p.cstr());
138 }
139 currVersion = v;
140 *outJavaPath = p;
141 }
142 }
143
144 delete paths;
145 return currVersion;
146 }
147
148 // --------------
149
getRegValue(const char * keyPath,const char * keyName,REGSAM access,CString * outValue)150 static bool getRegValue(const char *keyPath,
151 const char *keyName,
152 REGSAM access,
153 CString *outValue) {
154 HKEY key;
155 LSTATUS status = RegOpenKeyExA(
156 HKEY_LOCAL_MACHINE, // hKey
157 keyPath, // lpSubKey
158 0, // ulOptions
159 KEY_READ | access, // samDesired,
160 &key); // phkResult
161 if (status == ERROR_SUCCESS) {
162
163 LSTATUS ret = ERROR_MORE_DATA;
164 DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough
165 char* buffer = (char*) malloc(size);
166
167 while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) {
168 ret = RegQueryValueExA(
169 key, // hKey
170 keyName, // lpValueName
171 NULL, // lpReserved
172 NULL, // lpType
173 (LPBYTE) buffer, // lpData
174 &size); // lpcbData
175
176 if (ret == ERROR_MORE_DATA) {
177 size *= 2;
178 buffer = (char*) realloc(buffer, size);
179 } else {
180 buffer[size] = 0;
181 }
182 }
183
184 if (ret != ERROR_MORE_DATA) outValue->set(buffer);
185
186 free(buffer);
187 RegCloseKey(key);
188
189 return (ret != ERROR_MORE_DATA);
190 }
191
192 return false;
193 }
194
195 // Explore the registry to find a suitable version of Java.
196 // Returns an int which is the version of Java found (e.g. 1006 for 1.6) and the
197 // matching path in outJavaPath.
198 // Returns 0 if nothing suitable was found.
exploreJavaRegistry(const char * entry,REGSAM access,CPath * outJavaPath)199 static int exploreJavaRegistry(const char *entry, REGSAM access, CPath *outJavaPath) {
200
201 // Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion]
202 CPath rootKey("SOFTWARE\\JavaSoft\\");
203 rootKey.addPath(entry);
204
205 int versionInt = 0;
206 CString currentVersion;
207 CPath subKey(rootKey);
208 if (getRegValue(subKey.cstr(), "CurrentVersion", access, ¤tVersion)) {
209 // CurrentVersion should be something like "1.7".
210 // We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome]
211 subKey.addPath(currentVersion);
212 CPath javaHome;
213 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
214 versionInt = checkBinPath(&javaHome);
215 if (versionInt >= 0) {
216 if (gIsDebug) {
217 fprintf(stderr,
218 "Java %d found via registry: %s\n",
219 versionInt, javaHome.cstr());
220 }
221 *outJavaPath = javaHome;
222 }
223 if (versionInt >= MIN_JAVA_VERSION) {
224 // Heuristic: if the current version is good enough, stop here
225 return versionInt;
226 }
227 }
228 }
229
230 // Try again, but this time look at all the versions available
231 HKEY javaHomeKey;
232 LSTATUS status = RegOpenKeyExA(
233 HKEY_LOCAL_MACHINE, // hKey
234 "SOFTWARE\\JavaSoft", // lpSubKey
235 0, // ulOptions
236 KEY_READ | access, // samDesired
237 &javaHomeKey); // phkResult
238 if (status == ERROR_SUCCESS) {
239 char name[256];
240 DWORD index = 0;
241 CPath javaHome;
242 for (LONG result = ERROR_SUCCESS; result == ERROR_SUCCESS; index++) {
243 DWORD nameLen = 255;
244 name[nameLen] = 0;
245 result = RegEnumKeyExA(
246 javaHomeKey, // hKey
247 index, // dwIndex
248 name, // lpName
249 &nameLen, // lpcName
250 NULL, // lpReserved
251 NULL, // lpClass
252 NULL, // lpcClass,
253 NULL); // lpftLastWriteTime
254 if (result == ERROR_SUCCESS && nameLen < 256) {
255 name[nameLen] = 0;
256 CPath subKey(rootKey);
257 subKey.addPath(name);
258
259 if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {
260 int v = checkBinPath(&javaHome);
261 if (v > versionInt) {
262 if (gIsDebug) {
263 fprintf(stderr,
264 "Java %d found via registry: %s\n",
265 versionInt, javaHome.cstr());
266 }
267 *outJavaPath = javaHome;
268 versionInt = v;
269 }
270 }
271 }
272 }
273
274 RegCloseKey(javaHomeKey);
275 }
276
277 return 0;
278 }
279
getMaxJavaInRegistry(const char * entry,REGSAM access,CPath * outJavaPath,int * inOutVersion)280 static bool getMaxJavaInRegistry(const char *entry, REGSAM access, CPath *outJavaPath, int *inOutVersion) {
281 CPath path;
282 int version = exploreJavaRegistry(entry, access, &path);
283 if (version > *inOutVersion) {
284 *outJavaPath = path;
285 *inOutVersion = version;
286 return true;
287 }
288 return false;
289 }
290
findJavaInRegistry(CPath * outJavaPath)291 int findJavaInRegistry(CPath *outJavaPath) {
292 // We'll do the registry test 3 times: first using the default mode,
293 // then forcing the use of the 32-bit registry then forcing the use of
294 // 64-bit registry. On Windows 2k, the 2 latter will fail since the
295 // flags are not supported. On a 32-bit OS the 64-bit is obviously
296 // useless and the 2 first tests should be equivalent so we just
297 // need the first case.
298
299 // Check the JRE first, then the JDK.
300 int version = MIN_JAVA_VERSION - 1;
301 bool result = false;
302 result |= getMaxJavaInRegistry("Java Runtime Environment", 0, outJavaPath, &version);
303 result |= getMaxJavaInRegistry("Java Development Kit", 0, outJavaPath, &version);
304
305 // Get the app sysinfo state (the one hidden by WOW64)
306 SYSTEM_INFO sysInfo;
307 GetSystemInfo(&sysInfo);
308 WORD programArch = sysInfo.wProcessorArchitecture;
309 // Check the real sysinfo state (not the one hidden by WOW64) for x86
310 GetNativeSystemInfo(&sysInfo);
311 WORD actualArch = sysInfo.wProcessorArchitecture;
312
313 // Only try to access the WOW64-32 redirected keys on a 64-bit system.
314 // There's no point in doing this on a 32-bit system.
315 if (actualArch == PROCESSOR_ARCHITECTURE_AMD64) {
316 if (programArch != PROCESSOR_ARCHITECTURE_INTEL) {
317 // If we did the 32-bit case earlier, don't do it twice.
318 result |= getMaxJavaInRegistry(
319 "Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath, &version);
320 result |= getMaxJavaInRegistry(
321 "Java Development Kit", KEY_WOW64_32KEY, outJavaPath, &version);
322
323 } else if (programArch != PROCESSOR_ARCHITECTURE_AMD64) {
324 // If we did the 64-bit case earlier, don't do it twice.
325 result |= getMaxJavaInRegistry(
326 "Java Runtime Environment", KEY_WOW64_64KEY, outJavaPath, &version);
327 result |= getMaxJavaInRegistry(
328 "Java Development Kit", KEY_WOW64_64KEY, outJavaPath, &version);
329 }
330 }
331
332 return result ? version : 0;
333 }
334
335 // --------------
336
checkProgramFiles(CPath * outJavaPath,int * inOutVersion)337 static bool checkProgramFiles(CPath *outJavaPath, int *inOutVersion) {
338
339 char programFilesPath[MAX_PATH + 1];
340 HRESULT result = SHGetFolderPathA(
341 NULL, // hwndOwner
342 CSIDL_PROGRAM_FILES, // nFolder
343 NULL, // hToken
344 SHGFP_TYPE_CURRENT, // dwFlags
345 programFilesPath); // pszPath
346 if (FAILED(result)) return false;
347
348 CPath path(programFilesPath);
349 path.addPath("Java");
350
351 // Do we have a C:\\Program Files\\Java directory?
352 if (!path.dirExists()) return false;
353
354 CPath glob(path);
355 glob.addPath("j*");
356
357 bool found = false;
358 WIN32_FIND_DATAA findData;
359 HANDLE findH = FindFirstFileA(glob.cstr(), &findData);
360 if (findH == INVALID_HANDLE_VALUE) return false;
361 do {
362 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
363 CPath temp(path);
364 temp.addPath(findData.cFileName);
365 // Check C:\\Program Files[x86]\\Java\\j*\\bin\\java.exe
366 int v = checkBinPath(&temp);
367 if (v > *inOutVersion) {
368 found = true;
369 *inOutVersion = v;
370 *outJavaPath = temp;
371 }
372 }
373 } while (!found && FindNextFileA(findH, &findData) != 0);
374 FindClose(findH);
375
376 return found;
377 }
378
findJavaInProgramFiles(CPath * outJavaPath)379 int findJavaInProgramFiles(CPath *outJavaPath) {
380
381 // Check the C:\\Program Files (x86) directory
382 // With WOW64 fs redirection in place by default, we should get the x86
383 // version on a 64-bit OS since this app is a 32-bit itself.
384 bool result = false;
385 int version = MIN_JAVA_VERSION - 1;
386 result |= checkProgramFiles(outJavaPath, &version);
387
388 // Check the real sysinfo state (not the one hidden by WOW64) for x86
389 SYSTEM_INFO sysInfo;
390 GetNativeSystemInfo(&sysInfo);
391
392 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
393 // On a 64-bit OS, try again by disabling the fs redirection so
394 // that we can try the real C:\\Program Files directory.
395 PVOID oldWow64Value = disableWow64FsRedirection();
396 result |= checkProgramFiles(outJavaPath, &version);
397 revertWow64FsRedirection(oldWow64Value);
398 }
399
400 return result ? version : 0;
401 }
402
403 // --------------
404
405
406 // Tries to invoke the java.exe at the given path and extract it's
407 // version number.
408 // - outVersionStr: if not null, will capture version as a string (e.g. "1.6")
409 // - outVersionInt: if not null, will capture version as an int (major * 1000 + minor, e.g. 1006).
getJavaVersion(CPath & javaPath,CString * outVersionStr,int * outVersionInt)410 bool getJavaVersion(CPath &javaPath, CString *outVersionStr, int *outVersionInt) {
411 bool result = false;
412
413 // Run "java -version", which outputs something like to *STDERR*:
414 //
415 // java version "1.6.0_29"
416 // Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
417 // Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
418 //
419 // We want to capture the first line, and more exactly the "1.6" part.
420
421
422 CString cmd;
423 cmd.setf("\"%s\" -version", javaPath.cstr());
424
425 SECURITY_ATTRIBUTES saAttr;
426 STARTUPINFO startup;
427 PROCESS_INFORMATION pinfo;
428
429 // Want to inherit pipe handle
430 ZeroMemory(&saAttr, sizeof(saAttr));
431 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
432 saAttr.bInheritHandle = TRUE;
433 saAttr.lpSecurityDescriptor = NULL;
434
435 // Create pipe for stdout
436 HANDLE stdoutPipeRd, stdoutPipeWt;
437 if (!CreatePipe(
438 &stdoutPipeRd, // hReadPipe,
439 &stdoutPipeWt, // hWritePipe,
440 &saAttr, // lpPipeAttributes,
441 0)) { // nSize (0=default buffer size)
442 if (gIsConsole || gIsDebug) displayLastError("CreatePipe failed: ");
443 return false;
444 }
445 if (!SetHandleInformation(stdoutPipeRd, HANDLE_FLAG_INHERIT, 0)) {
446 if (gIsConsole || gIsDebug) displayLastError("SetHandleInformation failed: ");
447 return false;
448 }
449
450 ZeroMemory(&pinfo, sizeof(pinfo));
451
452 ZeroMemory(&startup, sizeof(startup));
453 startup.cb = sizeof(startup);
454 startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
455 startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
456 // Capture both stderr and stdout
457 startup.hStdError = stdoutPipeWt;
458 startup.hStdOutput = stdoutPipeWt;
459 startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
460
461 BOOL ok = CreateProcessA(
462 NULL, // program path
463 (LPSTR) cmd.cstr(), // command-line
464 NULL, // process handle is not inheritable
465 NULL, // thread handle is not inheritable
466 TRUE, // yes, inherit some handles
467 0, // process creation flags
468 NULL, // use parent's environment block
469 NULL, // use parent's starting directory
470 &startup, // startup info, i.e. std handles
471 &pinfo);
472
473 if ((gIsConsole || gIsDebug) && !ok) displayLastError("CreateProcess failed: ");
474
475 // Close the write-end of the output pipe (we're only reading from it)
476 CloseHandle(stdoutPipeWt);
477
478 // Read from the output pipe. We don't need to read everything,
479 // the first line should be 'Java version "1.2.3_45"\r\n'
480 // so reading about 32 chars is all we need.
481 char first32[32 + 1];
482 int index = 0;
483 first32[0] = 0;
484
485 if (ok) {
486
487 #define SIZE 1024
488 char buffer[SIZE];
489 DWORD sizeRead = 0;
490
491 while (ok) {
492 // Keep reading in the same buffer location
493 ok = ReadFile(stdoutPipeRd, // hFile
494 buffer, // lpBuffer
495 SIZE, // DWORD buffer size to read
496 &sizeRead, // DWORD buffer size read
497 NULL); // overlapped
498 if (!ok || sizeRead == 0 || sizeRead > SIZE) break;
499
500 // Copy up to the first 32 characters
501 if (index < 32) {
502 DWORD n = 32 - index;
503 if (n > sizeRead) n = sizeRead;
504 // copy as lowercase to simplify checks later
505 for (char *b = buffer; n > 0; n--, b++, index++) {
506 char c = *b;
507 if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
508 first32[index] = c;
509 }
510 first32[index] = 0;
511 }
512 }
513
514 WaitForSingleObject(pinfo.hProcess, INFINITE);
515
516 DWORD exitCode;
517 if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {
518 // this should not return STILL_ACTIVE (259)
519 result = exitCode == 0;
520 }
521
522 CloseHandle(pinfo.hProcess);
523 CloseHandle(pinfo.hThread);
524 }
525 CloseHandle(stdoutPipeRd);
526
527 if (result && index > 0) {
528 // Look for a few keywords in the output however we don't
529 // care about specific ordering or case-senstiviness.
530 // We only captures roughtly the first line in lower case.
531 char *j = strstr(first32, "java");
532 char *v = strstr(first32, "version");
533 if ((gIsConsole || gIsDebug) && (!j || !v)) {
534 fprintf(stderr, "Error: keywords 'java version' not found in '%s'\n", first32);
535 }
536 if (j != NULL && v != NULL) {
537 result = extractJavaVersion(first32, index, outVersionStr, outVersionInt);
538 }
539 }
540
541 return result;
542 }
543
544
545 #endif /* _WIN32 */
546