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