1/*
2 * Copyright (C) 2008 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 * Command-line invocation of the Dalvik VM.
18 */
19#include "jni.h"
20
21#include <stdlib.h>
22#include <stdio.h>
23#include <string.h>
24#include <signal.h>
25#include <assert.h>
26
27
28/*
29 * We want failed write() calls to just return with an error.
30 */
31static void blockSigpipe()
32{
33    sigset_t mask;
34
35    sigemptyset(&mask);
36    sigaddset(&mask, SIGPIPE);
37    if (sigprocmask(SIG_BLOCK, &mask, NULL) != 0)
38        fprintf(stderr, "WARNING: SIGPIPE not blocked\n");
39}
40
41/*
42 * Create a String[] and populate it with the contents of argv.
43 */
44static jobjectArray createStringArray(JNIEnv* env, char* const argv[], int argc)
45{
46    jclass stringClass = NULL;
47    jobjectArray strArray = NULL;
48    jobjectArray result = NULL;
49    int i;
50
51    stringClass = env->FindClass("java/lang/String");
52    if (env->ExceptionCheck()) {
53        fprintf(stderr, "Got exception while finding class String\n");
54        goto bail;
55    }
56    assert(stringClass != NULL);
57    strArray = env->NewObjectArray(argc, stringClass, NULL);
58    if (env->ExceptionCheck()) {
59        fprintf(stderr, "Got exception while creating String array\n");
60        goto bail;
61    }
62    assert(strArray != NULL);
63
64    for (i = 0; i < argc; i++) {
65        jstring argStr;
66
67        argStr = env->NewStringUTF(argv[i]);
68        if (env->ExceptionCheck()) {
69            fprintf(stderr, "Got exception while allocating Strings\n");
70            goto bail;
71        }
72        assert(argStr != NULL);
73        env->SetObjectArrayElement(strArray, i, argStr);
74        env->DeleteLocalRef(argStr);
75    }
76
77    /* return the array, and ensure we don't delete the local ref to it */
78    result = strArray;
79    strArray = NULL;
80
81bail:
82    env->DeleteLocalRef(stringClass);
83    env->DeleteLocalRef(strArray);
84    return result;
85}
86
87/*
88 * Determine whether or not the specified method is public.
89 *
90 * Returns JNI_TRUE on success, JNI_FALSE on failure.
91 */
92static int methodIsPublic(JNIEnv* env, jclass clazz, jmethodID methodId)
93{
94    static const int PUBLIC = 0x0001;   // java.lang.reflect.Modifiers.PUBLIC
95    jobject refMethod = NULL;
96    jclass methodClass = NULL;
97    jmethodID getModifiersId;
98    int modifiers;
99    int result = JNI_FALSE;
100
101    refMethod = env->ToReflectedMethod(clazz, methodId, JNI_FALSE);
102    if (refMethod == NULL) {
103        fprintf(stderr, "Dalvik VM unable to get reflected method\n");
104        goto bail;
105    }
106
107    /*
108     * We now have a Method instance.  We need to call
109     * its getModifiers() method.
110     */
111    methodClass = env->FindClass("java/lang/reflect/Method");
112    if (methodClass == NULL) {
113        fprintf(stderr, "Dalvik VM unable to find class Method\n");
114        goto bail;
115    }
116    getModifiersId = env->GetMethodID(methodClass,
117                        "getModifiers", "()I");
118    if (getModifiersId == NULL) {
119        fprintf(stderr, "Dalvik VM unable to find reflect.Method.getModifiers\n");
120        goto bail;
121    }
122
123    modifiers = env->CallIntMethod(refMethod, getModifiersId);
124    if ((modifiers & PUBLIC) == 0) {
125        fprintf(stderr, "Dalvik VM: main() is not public\n");
126        goto bail;
127    }
128
129    result = JNI_TRUE;
130
131bail:
132    env->DeleteLocalRef(refMethod);
133    env->DeleteLocalRef(methodClass);
134    return result;
135}
136
137/*
138 * Parse arguments.  Most of it just gets passed through to the VM.  The
139 * JNI spec defines a handful of standard arguments.
140 */
141int main(int argc, char* const argv[])
142{
143    JavaVM* vm = NULL;
144    JNIEnv* env = NULL;
145    JavaVMInitArgs initArgs;
146    JavaVMOption* options = NULL;
147    char* slashClass = NULL;
148    int optionCount, curOpt, i, argIdx;
149    int needExtra = JNI_FALSE;
150    int result = 1;
151
152    setvbuf(stdout, NULL, _IONBF, 0);
153
154    /* ignore argv[0] */
155    argv++;
156    argc--;
157
158    /*
159     * If we're adding any additional stuff, e.g. function hook specifiers,
160     * add them to the count here.
161     *
162     * We're over-allocating, because this includes the options to the VM
163     * plus the options to the program.
164     */
165    optionCount = argc;
166
167    options = (JavaVMOption*) malloc(sizeof(JavaVMOption) * optionCount);
168    memset(options, 0, sizeof(JavaVMOption) * optionCount);
169
170    /*
171     * Copy options over.  Everything up to the name of the class starts
172     * with a '-' (the function hook stuff is strictly internal).
173     *
174     * [Do we need to catch & handle "-jar" here?]
175     */
176    for (curOpt = argIdx = 0; argIdx < argc; argIdx++) {
177        if (argv[argIdx][0] != '-' && !needExtra)
178            break;
179        options[curOpt++].optionString = strdup(argv[argIdx]);
180
181        /* some options require an additional arg */
182        needExtra = JNI_FALSE;
183        if (strcmp(argv[argIdx], "-classpath") == 0 ||
184            strcmp(argv[argIdx], "-cp") == 0)
185            /* others? */
186        {
187            needExtra = JNI_TRUE;
188        }
189    }
190
191    if (needExtra) {
192        fprintf(stderr, "Dalvik VM requires value after last option flag\n");
193        goto bail;
194    }
195
196    /* insert additional internal options here */
197
198    assert(curOpt <= optionCount);
199
200    initArgs.version = JNI_VERSION_1_4;
201    initArgs.options = options;
202    initArgs.nOptions = curOpt;
203    initArgs.ignoreUnrecognized = JNI_FALSE;
204
205    //printf("nOptions = %d\n", initArgs.nOptions);
206
207    blockSigpipe();
208
209    /*
210     * Start VM.  The current thread becomes the main thread of the VM.
211     */
212    if (JNI_CreateJavaVM(&vm, &env, &initArgs) < 0) {
213        fprintf(stderr, "Dalvik VM init failed (check log file)\n");
214        goto bail;
215    }
216
217    /*
218     * Make sure they provided a class name.  We do this after VM init
219     * so that things like "-Xrunjdwp:help" have the opportunity to emit
220     * a usage statement.
221     */
222    if (argIdx == argc) {
223        fprintf(stderr, "Dalvik VM requires a class name\n");
224        goto bail;
225    }
226
227    /*
228     * We want to call main() with a String array with our arguments in it.
229     * Create an array and populate it.  Note argv[0] is not included.
230     */
231    jobjectArray strArray;
232    strArray = createStringArray(env, &argv[argIdx+1], argc-argIdx-1);
233    if (strArray == NULL)
234        goto bail;
235
236    /*
237     * Find [class].main(String[]).
238     */
239    jclass startClass;
240    jmethodID startMeth;
241    char* cp;
242
243    /* convert "com.android.Blah" to "com/android/Blah" */
244    slashClass = strdup(argv[argIdx]);
245    for (cp = slashClass; *cp != '\0'; cp++)
246        if (*cp == '.')
247            *cp = '/';
248
249    startClass = env->FindClass(slashClass);
250    if (startClass == NULL) {
251        fprintf(stderr, "Dalvik VM unable to locate class '%s'\n", slashClass);
252        goto bail;
253    }
254
255    startMeth = env->GetStaticMethodID(startClass,
256                    "main", "([Ljava/lang/String;)V");
257    if (startMeth == NULL) {
258        fprintf(stderr, "Dalvik VM unable to find static main(String[]) in '%s'\n",
259            slashClass);
260        goto bail;
261    }
262
263    /*
264     * Make sure the method is public.  JNI doesn't prevent us from calling
265     * a private method, so we have to check it explicitly.
266     */
267    if (!methodIsPublic(env, startClass, startMeth))
268        goto bail;
269
270    /*
271     * Invoke main().
272     */
273    env->CallStaticVoidMethod(startClass, startMeth, strArray);
274
275    if (!env->ExceptionCheck())
276        result = 0;
277
278bail:
279    /*printf("Shutting down Dalvik VM\n");*/
280    if (vm != NULL) {
281        /*
282         * This allows join() and isAlive() on the main thread to work
283         * correctly, and also provides uncaught exception handling.
284         */
285        if (vm->DetachCurrentThread() != JNI_OK) {
286            fprintf(stderr, "Warning: unable to detach main thread\n");
287            result = 1;
288        }
289
290        if (vm->DestroyJavaVM() != 0)
291            fprintf(stderr, "Warning: Dalvik VM did not shut down cleanly\n");
292        /*printf("\nDalvik VM has exited\n");*/
293    }
294
295    for (i = 0; i < optionCount; i++)
296        free((char*) options[i].optionString);
297    free(options);
298    free(slashClass);
299    /*printf("--- VM is down, process exiting\n");*/
300    return result;
301}
302