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