source: trunk/PrefsPane/proxyApp/proxy.c @ 1046

Revision 1041, 16.7 KB checked in by speck, 4 months ago (diff)

make using jvisualvm easier

Line 
1/* Copyright (C) 2009 Peter Speck
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <CoreFoundation/CoreFoundation.h>
18#include <JavaVM/jni.h>
19#include <mach-o/arch.h>
20#include <spawn.h>
21#include <sys/resource.h>
22#include <sys/stat.h>
23#include <sys/sysctl.h>
24#include <unistd.h>
25
26static int debugFlag = 0;
27static int hasUsrLibexecJavaHome = 0;
28
29static int hasVM(const char *jvmVersion)
30{
31    if (debugFlag)
32        fprintf(stderr, "hasVM(%s) runs.\n", jvmVersion);
33    // Look for the JavaVM bundle using its identifier
34    CFBundleRef vmBundleR = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.JavaVM"));
35    if (!vmBundleR) {
36        if (debugFlag)
37            fprintf(stderr, "hasVM(%s): Missing com.apple.JavaVM bundle.\n", jvmVersion);
38        return 0;
39    }
40    CFURLRef frameworkUrl = CFBundleCopyBundleURL(vmBundleR);
41     // ->  /System/Library/Frameworks/JavaVM.framework
42    if (!frameworkUrl) {
43        if (debugFlag)
44            fprintf(stderr, "hasVM(%s): Can't create frameworkUrl.\n", jvmVersion);
45        return 0;
46    }
47    CFURLRef versionDirUrl = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, frameworkUrl, CFSTR("Versions"), true);
48    // ->  /System/Library/Frameworks/JavaVM.framework/Versions
49    CFRelease(frameworkUrl);
50    if (!versionDirUrl) {
51        if (debugFlag)
52            fprintf(stderr, "hasVM(%s): Can't create versionDirUrl.\n", jvmVersion);
53        return 0;
54    }
55    CFStringRef vers = CFStringCreateWithCString(kCFAllocatorDefault, jvmVersion, kCFStringEncodingUTF8);
56    CFURLRef targetUrl = vers ? CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault, versionDirUrl, vers, true) : NULL;
57    // ->  /System/Library/Frameworks/JavaVM.framework/Versions/1.6
58    if (vers)
59        CFRelease(vers);
60    CFRelease(versionDirUrl);
61    if (!targetUrl) {
62        if (debugFlag)
63            fprintf(stderr, "hasVM(%s): Can't create targetUrl.\n", jvmVersion);
64        return 0;
65    }
66    char path[PATH_MAX] = "\0";
67    if (!CFURLGetFileSystemRepresentation(targetUrl, true, (UInt8*)path, sizeof(path))) {
68        if (debugFlag)
69            fprintf(stderr, "hasVM(%s): Can't convert targetUrl to file system representation.\n", jvmVersion);
70        CFRelease(targetUrl);
71        return 0;
72    }
73    CFRelease(targetUrl);
74    struct stat statbuf;
75    if (stat(path, &statbuf)) {
76        if (debugFlag)
77            fprintf(stderr, "hasVM(%s): Missing JavaVM directory: '%s'\n", jvmVersion, path);
78        return 0;
79    }
80    if (debugFlag)
81        fprintf(stderr, "hasVM(%s): Success.\n", jvmVersion);
82    return 1;
83}
84
85#define JARDIR "/Library/PreferencePanes/GlimmerBlocker.prefPane/Contents/GlimmerBlockerProxy.app/Contents/Resources/Java"
86#define CLASSPATH JARDIR "/glimmerblocker-server.jar" \
87/**/                     ":" JARDIR "/annotations.jar" \
88/**/                     ":" JARDIR "/dnsjava.jar" \
89/**/                     ":" JARDIR "/smalljs.jar"
90
91static JavaVMOption javaOptions[25];
92
93static void addJavaOption(const char* s)
94{
95    JavaVMOption* jvo = javaOptions;
96    while (jvo->optionString)
97        jvo++;
98    jvo->optionString = (char*)s;
99    jvo->extraInfo = NULL;
100    jvo++;
101    jvo->optionString = NULL;
102    jvo->extraInfo = NULL;
103}
104
105static int startVM(const char *jvmVersion, int tryLibExecTool)
106{
107    if (!hasUsrLibexecJavaHome && strcmp(jvmVersion, "*") && !hasVM(jvmVersion))
108        return 0;
109    if (debugFlag)
110        fprintf(stderr, "Tries starting java version %s\n", jvmVersion);
111    javaOptions[0].optionString = NULL;
112    addJavaOption("-Xms25m");
113    addJavaOption("-Xmx50m");
114    if (strcmp(jvmVersion, "1.5"))
115        addJavaOption("-XX:+UseCompressedOops"); // seems to make java 1.5 to fail launch on PPC 10.5
116    addJavaOption("-Djava.awt.headless=true");
117    addJavaOption("-Dfile.encoding=UTF-8");
118    struct stat statbuf;
119    if (getenv("GB_JAVA_DEBUGGER") || !stat("/Library/GlimmerBlocker/enable-java-debugging.txt", &statbuf)) {
120        fprintf(stderr, "Enables java debugging.  Pid = %d\n", getpid());
121        addJavaOption("-Xint");
122        addJavaOption("-Xdebug");
123        addJavaOption("-Xshare:off");
124        addJavaOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005");
125    }
126    if (!stat("/Library/GlimmerBlocker/enable-jmxremote.txt", &statbuf)) {
127        fprintf(stderr, "Enables jmxremote for e.g. jvisualvm.  Pid = %d\n", getpid());
128        fprintf(stderr, " use 'Add JMXConnection' to 127.0.0.1:9494\n");
129        addJavaOption("-Dcom.sun.management.jmxremote.port=9494");
130        addJavaOption("-Dcom.sun.management.jmxremote.ssl=false");
131        addJavaOption("-Dcom.sun.management.jmxremote.authenticate=false");
132    }
133    addJavaOption("-Djava.class.path=" CLASSPATH);
134    if (!stat("/Library/GlimmerBlocker/preferIPv4Stack.txt", &statbuf)) {
135        addJavaOption("-Djava.net.preferIPv4Stack=1");
136        fprintf(stderr, "Launches GB using java.net.preferIPv4Stack\n");
137    }
138    if (debugFlag) {
139        fprintf(stderr, "Java VM options:\n");
140        JavaVMOption* jvo = javaOptions;
141        while (jvo->optionString) {
142            fprintf(stderr, "   %s\n", jvo->optionString);
143            jvo++;
144        }
145    }
146    if (hasUsrLibexecJavaHome && tryLibExecTool) {
147        // The bad thing about using /usr/libexec/java_home -exec java
148        // is that the process is called "java" in Activity Monitor
149        // and by the firewall.
150        long len = 500;
151        for (JavaVMOption* jvo = javaOptions; jvo->optionString; jvo++)
152            len += strlen(jvo->optionString) + 1;
153        char* buf = malloc(len);
154        if (!buf) {
155            fprintf(stderr, "Couldn't malloc %ld bytes.\n", len);
156            return 0;
157        }
158        char* p = buf + sprintf(buf, "/usr/libexec/java_home");
159        if (strcmp(jvmVersion, "*"))
160            p += sprintf(p, " -F -v'%s+'", jvmVersion);
161        p += sprintf(p, " -exec java");
162        for (JavaVMOption* jvo = javaOptions; jvo->optionString; jvo++)
163            p += sprintf(p, " '%s'", jvo->optionString);
164        p += sprintf(p, " org.glimmerblocker.proxy.GlimmerBlocker");
165        if (debugFlag) {
166            p += sprintf(p, " -stderr");
167            fprintf(stderr, "cmdline (%ld/%ld): %s\n", (long)(p - buf) + 1, len, buf);
168        }
169        if (system(buf)) {
170            fprintf(stderr, "GB proxy java failed to launch.\n");
171            return 0;
172        } else {
173            fprintf(stderr, "GB proxy java launched ok.\n");
174            exit(0);
175        }
176    }
177    setenv("JAVA_JVM_VERSION", jvmVersion, true);
178    JavaVMInitArgs vm_args;
179    vm_args.version = JNI_VERSION_1_4;
180    vm_args.options = javaOptions;
181    vm_args.nOptions = 0;
182    for (JavaVMOption* opt = vm_args.options; opt->optionString; opt++, vm_args.nOptions++)
183        ;
184    vm_args.ignoreUnrecognized  = JNI_TRUE;
185    //
186#define CHECK_EXCEPTION(MSG) \
187    if ((*env)->ExceptionOccurred(env)) { \
188        fprintf(stderr, "Java %s: Got exception from " MSG ".\n", jvmVersion); \
189        (*env)->ExceptionDescribe(env); \
190        goto done; \
191    }
192    JNIEnv *env;
193    JavaVM *vm;
194    int r = JNI_CreateJavaVM(&vm, (void**)&env, &vm_args);
195    if (r) {
196        if (debugFlag)
197            fprintf(stderr, "Java %s: JNI_CreateJavaVM failed with error code %d\n", jvmVersion, r);
198        return 0;
199    }
200    if (debugFlag)
201        fprintf(stderr, "Java %s: Did create JVM using JNI_CreateJavaVM\n", jvmVersion);
202    jclass mainClass = (*env)->FindClass(env, "org/glimmerblocker/proxy/GlimmerBlocker");
203    if (!mainClass) {
204        fprintf(stderr, "Java %s: Can't load main class.\n", jvmVersion);
205        CHECK_EXCEPTION("Loading main class");
206        goto done;
207    }
208    jmethodID mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");
209    if (!mainID) {
210        fprintf(stderr, "Java %s: Can't find 'public void main(...)' in main class.\n", jvmVersion);
211        CHECK_EXCEPTION("Finding main() method");
212        goto done;
213    }
214    jobjectArray mainArgs = (*env)->NewObjectArray(env, debugFlag ? 1 : 0, (*env)->FindClass(env, "java/lang/String"), NULL);
215    if (mainArgs == nil) {
216        fprintf(stderr, "Java %s: Can't create String[] array.\n", jvmVersion);
217        CHECK_EXCEPTION("Creating String[] array");
218        goto done;
219    }
220    if (debugFlag) {
221        jstring s = (*env)->NewStringUTF(env, "-stderr");
222        if (!s) {
223            fprintf(stderr, "Java %s: Can't create String constant.\n", jvmVersion);
224            CHECK_EXCEPTION("Creating String constant");
225            goto done;
226        }
227        (*env)->SetObjectArrayElement(env, mainArgs, 0, s);
228        CHECK_EXCEPTION("Setting -stderr argument");
229    }
230    if (debugFlag)
231        fprintf(stderr, "Java %s: Calls main()\n", jvmVersion);
232    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
233    CHECK_EXCEPTION("Executing main method");
234    if (debugFlag)
235        fprintf(stderr, "Java %s: main() returned.\n", jvmVersion);
236    return 1; // don't destroy successful vm.
237done:
238    if (debugFlag)
239        fprintf(stderr, "Java %s: DestroyJavaVM.\n", jvmVersion);
240    (*vm)->DestroyJavaVM(vm);
241    if (debugFlag)
242        fprintf(stderr, "Java %s: DestroyJavaVM done.\n", jvmVersion);
243    return 0;
244}
245
246static void check(const char* cmd, int rc)
247{
248    if (!rc)
249        return;
250    fprintf(stderr, "GlimmerBlocker PROXY LAUNCHER FAILED (%d = %s): %s\n", rc, strerror(rc), cmd);
251    exit(rc);
252}
253
254static void spawn(cpu_type_t cpu)
255{
256    posix_spawnattr_t attr;
257    check("posix_spawnattr_init", posix_spawnattr_init(&attr));
258    /* do the equivalent of exec, rather than creating a separate process */
259    check("posix_spawnattr_setflags", posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETEXEC));
260    size_t copied;
261    check("posix_spawnattr_setbinpref_np", posix_spawnattr_setbinpref_np(&attr, 1, &cpu, &copied));
262    if (copied != 1)
263        check("posix_spawnattr_setbinpref_np didn't set cpu type.", -1);
264    const char* cmd = "/Library/PreferencePanes/GlimmerBlocker.prefPane/Contents/GlimmerBlockerProxy.app/Contents/MacOS/GlimmerBlocker";
265    const char* argv[] = { cmd, NULL };
266    pid_t pid;
267    extern char **environ;
268    check("posix_spawn", posix_spawn(&pid, cmd, NULL, &attr, (char**)argv, environ));
269    fprintf(stderr, "posix_spawn returned!!!\n");
270}
271
272static void* startBestVM()
273{
274    if (startVM("1.6", 0))
275        return NULL;
276    if (startVM("1.5", 0))
277        return NULL;
278    if (startVM("1.6", 1))
279        return NULL;
280    if (startVM("1.5", 1))
281        return NULL;
282    if (startVM("*", 1))
283        return NULL;
284    //
285    // Did not find any java which works on this architecture.
286    // This happends for PPC on 10.5 in 64-bit mode as it has no java 1.6 and java 1.5 is 32-bit only (on PPC).
287    // Only x64 can do 32 and 64 bit java 1.5 in 10.5
288    int mib[CTL_MAXNAME];
289    size_t len = CTL_MAXNAME;
290    check("sysctlnametomib", sysctlnametomib("sysctl.proc_cputype", mib, &len));
291    mib[len++] = getpid();
292    cpu_type_t cputype;
293    size_t cpusz = sizeof(cputype);
294    if (sysctl(mib, (u_int)len, &cputype, &cpusz, NULL, 0)) {
295        // prefer sysctlbyname_with_pid because NXGetLocalArchInfo is buggy:
296        // Mac OS X 10.5.6, Core 2 Duo, in 64-bit mode, arch->cputype returns 7 == CPU_TYPE_X86 and not CPU_TYPE_X86_64 as expected!
297        cputype = NXGetLocalArchInfo()->cputype; // failback if sysctl fails.
298    }
299    if (debugFlag)
300        fprintf(stderr, "startBestVM: 0x%lX, sz = %d\n", (long)cputype, (int)sizeof(void*));
301    if (cputype == CPU_TYPE_POWERPC64) {
302        if (debugFlag)
303            fprintf(stderr, "Running on ppc64, has no java, tries to relaunch in 32-bit mode.\n");
304        spawn(CPU_TYPE_POWERPC);
305    }
306    if (cputype == CPU_TYPE_X86_64) {
307        if (debugFlag)
308            fprintf(stderr, "Running on x86_64, has no java, tries to relaunch in 32-bit mode.\n");
309        spawn(CPU_TYPE_I386);
310    }
311    fprintf(stderr, "Can't find any usable java version. Tries to exec java as cmd line.\n");
312    //fprintf(stderr, "CLASSPATH: %s\n\n", CLASSPATH);
313    // argv[0] needs to be repeated in execlp
314    int rc = execlp("java", "java", "-cp", CLASSPATH, "-Xmx100m", "-Djava.awt.headless=true", "-Dfile.encoding=UTF-8", "org.glimmerblocker.proxy.GlimmerBlocker", NULL);
315    fprintf(stderr, "exec java returned with error = %d\n", rc);
316    exit(rc ? rc : -1);
317}
318
319static void sourceCallBack(void *info) // dummy for CFRunLoop so it doesn't exit.
320{
321}
322
323static int sys(const char *x)
324{
325    fprintf(stderr, "Executes: %s\n", x);
326    int r = system(x);
327    if (r)
328        fprintf(stderr, "Got returncode = %d from %s\n", r, x);
329    fprintf(stderr, "\n");
330    return r;
331}
332
333int checkCodesign(const char *x)
334{
335    fprintf(stderr, "Executes: %s\n", x);
336    int r = system(x);
337    if (r) {
338        fprintf(stderr, "\n");
339        fprintf(stderr, "\n");
340        fprintf(stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
341        fprintf(stderr, "@@@@  Codesign verification failed with returncode = %3d @@@@\n", r);
342        fprintf(stderr, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
343        fprintf(stderr, "\n");
344        fprintf(stderr, "\n");
345    }
346    fprintf(stderr, "\n");
347    return r;
348}
349
350int main(int argc, const char **argv)
351{
352    unsetenv("IFS");
353    setenv("SHELL", "/bin/sh", 1);
354    setenv("PATH", "/bin:/usr/bin:/usr/sbin", 1);
355    unsetenv("CDPATH");
356    int r = chdir("/tmp");
357    if (r)
358        fprintf(stderr, "GlimmerBlockerProxy: failed chdir('/tmp'): %d\n", r);
359    int displayedInTextEdit = 0;
360    int makeDebugReport = 0;
361    for (int i = 1; i < argc; i++) {
362        if (!strcmp(argv[i], "--make-report"))
363            makeDebugReport = debugFlag = 1;
364        else if (!strcmp(argv[i], "--textedit"))
365            displayedInTextEdit = 1;
366        else if (!strcmp(argv[i], "-d"))
367            debugFlag = 1;
368    }
369    struct rlimit rlp;
370    struct rlimit rlpOld;
371    getrlimit(RLIMIT_NOFILE, &rlpOld);
372    getrlimit(RLIMIT_NOFILE, &rlp);
373    if (rlp.rlim_cur < rlp.rlim_max) {
374        rlp.rlim_cur = MAX(rlp.rlim_max, 10240);
375        setrlimit(RLIMIT_NOFILE, &rlp);
376    }
377    struct stat statbuf;
378    // java_home in 10.6.0 doesn't support the -exec parameter.
379    hasUsrLibexecJavaHome = !stat("/usr/libexec/java_home", &statbuf) && (statbuf.st_mode & 0111)
380        && !stat("/System/Library/Java/JavaVirtualMachines", &statbuf);
381    if (makeDebugReport) {
382        if (displayedInTextEdit) {
383            fprintf(stderr, "#\n");
384            fprintf(stderr, "#\n");
385            fprintf(stderr, "# Please copy/paste the content of this window into a mail to\n");
386            fprintf(stderr, "#     feedback@glimmerblocker.org\n");
387            fprintf(stderr, "#\n");
388            fprintf(stderr, "#\n");
389        } else {
390            fprintf(stderr, "GlimmerBlockerProxy: debug log enabled.\n");
391        }
392        fprintf(stderr, "rlimit.files: %lld -> %lld, hard = %lld\n", 
393                (long long)rlpOld.rlim_cur, (long long)rlp.rlim_cur, (long long)rlp.rlim_max);
394        fprintf(stderr, "hasUsrLibexecJavaHome = %s\n", hasUsrLibexecJavaHome ? "yes" : "no");
395        sys("printenv");
396        sys("id");
397        sys("ls -l /Library/Logs/GlimmerBlocker");
398        sys("ls -l /Library/GlimmerBlocker");
399        sys("codesign --verify  --verbose /Library/PreferencePanes/GlimmerBlocker.prefPane/Contents/GlimmerBlockerProxy.app");
400        sys("dscl . -read /Users/_glimmerblocker");
401        sys("java -version");
402        if (hasUsrLibexecJavaHome) {
403            r = sys("/usr/libexec/java_home -v'1.6*' java -version");
404            if (r)
405                sys("/usr/libexec/java_home -v'1.5*' java -version");
406        }
407        sys("perl -MIO -e 'print \"perl is ok\\n\";'"); // http://www.theregister.co.uk/2009/02/16/apple_update_perl_breakage/
408        sys("system_profiler SPSoftwareDataType | egrep -i 'version|kernel'");
409        sys("system_profiler SPHardwareDataType | perl -ne 'print unless /Serial Number|Hardware UUID/i;' # omits serial + hardwareid.");
410#define PPANE "/Library/PreferencePanes/GlimmerBlocker.prefPane"
411#define UPDATER PPANE "/Contents/GlimmerBlockerUpdater.app"
412#define SPARKLE PPANE "/Contents/GlimmerBlockerUpdater.app/Contents/Frameworks/Sparkle.framework/Versions/A"
413#define SIGNATURE "c9d85c0c3b7a8d1cb8060aa34df69713cfff637c"
414        int a = checkCodesign("codesign -vv '-R=identifier org.glimmerblocker.prefsPane and certificate leaf = H\"" SIGNATURE "\"' " PPANE);
415        int b = checkCodesign("codesign -vv '-R=identifier org.glimmerblocker.upgradeApp and certificate leaf = H\"" SIGNATURE "\"' " UPDATER);
416        int c = checkCodesign("codesign -vv '-R=identifier org.andymatuschak.Sparkle and certificate leaf = H\"" SIGNATURE "\"' " SPARKLE);
417        if (a || b || c) {
418            sys("codesign -vddd -r- " PPANE);
419            sys("codesign -vddd -r- " UPDATER);
420            sys("codesign -vddd -r- " SPARKLE);
421        }
422        if (c)
423            sys("file " SPARKLE "/Resources/relaunch");
424        sys("ulimit -a");
425    }
426    // posix_spawn doesn't work on thread, so launch JVM in main thread before run loop.
427    // TN2147 says it's ok for non-AWT apps.
428    startBestVM();
429    if (makeDebugReport) {
430        sleep(2);
431        fprintf(stderr, "#\n");
432        fprintf(stderr, "# Debug-mode launch of GlimmerBlocker proxy now done.\n");
433        fprintf(stderr, "#\n");
434        fprintf(stderr, "# Please copy/paste the content of this window into a mail to\n");
435        fprintf(stderr, "#     feedback@glimmerblocker.org\n");
436        if (!displayedInTextEdit)
437            fprintf(stderr, "# and then close this window and quit Terminal.\n");
438        fprintf(stderr, "#\n");
439        exit(0);
440    }
441    //
442    // Create a a sourceContext to be used by our source that makes
443    // sure the CFRunLoop doesn't exit right away
444    CFRunLoopSourceContext sourceContext;
445    sourceContext.version = 0;
446    sourceContext.info = NULL;
447    sourceContext.retain = NULL;
448    sourceContext.release = NULL;
449    sourceContext.copyDescription = NULL;
450    sourceContext.equal = NULL;
451    sourceContext.hash = NULL;
452    sourceContext.schedule = NULL;
453    sourceContext.cancel = NULL;
454    sourceContext.perform = &sourceCallBack;
455    //
456    CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
457    CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes); 
458    CFRunLoopRun();
459    CFRelease(sourceRef);
460    return 0;
461}
Note: See TracBrowser for help on using the repository browser.