• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &currentVersion)) {
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