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, &currentVersion)) {
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