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