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
35typedef 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.
42static 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.
78static 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.
97static int checkBinPath(CPath *inOutPath) {
98    inOutPath->addPath("bin");
99    return checkPath(inOutPath);
100}
101
102// Search java.exe in the environment
103int 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
150static 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.
199static 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
280static 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
291int 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
337static 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
379int 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).
410bool 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