Home | History | Annotate | Download | only in jni
      1 //
      2 //  Copyright (C) 2017 Google, Inc.
      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 #include "com_googlecode_android_scripting_Exec.h"
     18 
     19 #include <errno.h>
     20 #include <fcntl.h>
     21 #include <stdlib.h>
     22 #include <sys/ioctl.h>
     23 #include <sys/types.h>
     24 #include <sys/wait.h>
     25 #include <termios.h>
     26 #include <unistd.h>
     27 #include <stdio.h>
     28 #include <string.h>
     29 
     30 #include "android/log.h"
     31 
     32 #define LOG_TAG "Exec"
     33 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
     34 
     35 int CreateSubprocess(const char* cmd, char* args[], char* vars[], char *wkdir, pid_t* pid) {
     36   char* devname;
     37   int ptm = open("/dev/ptmx", O_RDWR);
     38   if(ptm < 0){
     39     LOGE("Cannot open /dev/ptmx: %s\n", strerror(errno));
     40     return -1;
     41   }
     42   fcntl(ptm, F_SETFD, FD_CLOEXEC);
     43 
     44   if (grantpt(ptm) || unlockpt(ptm) ||
     45       ((devname = (char*) ptsname(ptm)) == 0)) {
     46     LOGE("Trouble with /dev/ptmx: %s\n", strerror(errno));
     47     return -1;
     48   }
     49 
     50   *pid = fork();
     51   if(*pid < 0) {
     52     LOGE("Fork failed: %s\n", strerror(errno));
     53     return -1;
     54   }
     55 
     56   if(*pid == 0){
     57     int pts;
     58     setsid();
     59     pts = open(devname, O_RDWR);
     60     if(pts < 0) {
     61       exit(-1);
     62     }
     63     dup2(pts, 0);
     64     dup2(pts, 1);
     65     dup2(pts, 2);
     66     close(ptm);
     67     if (wkdir) chdir(wkdir);
     68     execve(cmd, args, vars);
     69     exit(-1);
     70   } else {
     71     return ptm;
     72   }
     73 }
     74 
     75 void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) {
     76   jclass clazz = env->FindClass(name);
     77   if (clazz != NULL) {
     78     env->ThrowNew(clazz, msg);
     79   }
     80   env->DeleteLocalRef(clazz);
     81 }
     82 
     83 char* JNU_GetStringNativeChars(JNIEnv* env, jstring jstr) {
     84   if (jstr == NULL) {
     85     return NULL;
     86   }
     87   jbyteArray bytes = 0;
     88   jthrowable exc;
     89   char* result = 0;
     90   if (env->EnsureLocalCapacity(2) < 0) {
     91     return 0; /* out of memory error */
     92   }
     93   jclass Class_java_lang_String = env->FindClass("java/lang/String");
     94   jmethodID MID_String_getBytes = env->GetMethodID(
     95       Class_java_lang_String, "getBytes", "()[B");
     96   bytes = (jbyteArray) env->CallObjectMethod(jstr, MID_String_getBytes);
     97   exc = env->ExceptionOccurred();
     98   if (!exc) {
     99     jint len = env->GetArrayLength(bytes);
    100     result = (char*) malloc(len + 1);
    101     if (result == 0) {
    102       JNU_ThrowByName(env, "java/lang/OutOfMemoryError", 0);
    103       env->DeleteLocalRef(bytes);
    104       return 0;
    105     }
    106     env->GetByteArrayRegion(bytes, 0, len, (jbyte*) result);
    107     result[len] = 0; /* NULL-terminate */
    108   } else {
    109     env->DeleteLocalRef(exc);
    110   }
    111   env->DeleteLocalRef(bytes);
    112   return result;
    113 }
    114 
    115 int JNU_GetFdFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
    116   jclass Class_java_io_FileDescriptor = env->FindClass("java/io/FileDescriptor");
    117   jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, "descriptor", "I");
    118   return env->GetIntField(fileDescriptor, descriptor);
    119 }
    120 
    121 JNIEXPORT jobject JNICALL Java_com_googlecode_android_1scripting_Exec_createSubprocess(
    122     JNIEnv* env, jclass clazz, jstring cmd, jobjectArray argArray, jobjectArray varArray,
    123     jstring workingDirectory,
    124     jintArray processIdArray) {
    125   char* cmd_native = JNU_GetStringNativeChars(env, cmd);
    126   char* wkdir_native = JNU_GetStringNativeChars(env, workingDirectory);
    127   pid_t pid;
    128   jsize len = 0;
    129   if (argArray) {
    130     len = env->GetArrayLength(argArray);
    131   }
    132   char* args[len + 2];
    133   args[0] = cmd_native;
    134   for (int i = 0; i < len; i++) {
    135     jstring arg = (jstring) env->GetObjectArrayElement(argArray, i);
    136     char* arg_native = JNU_GetStringNativeChars(env, arg);
    137     args[i + 1] = arg_native;
    138   }
    139   args[len + 1] = NULL;
    140 
    141   len = 0;
    142   if (varArray) {
    143     len = env->GetArrayLength(varArray);
    144   }
    145   char* vars[len + 1];
    146   for (int i = 0; i < len; i++) {
    147     jstring var = (jstring) env->GetObjectArrayElement(varArray, i);
    148     char* var_native = JNU_GetStringNativeChars(env, var);
    149     vars[i] = var_native;
    150   }
    151   vars[len] = NULL;
    152 
    153   int ptm = CreateSubprocess(cmd_native, args, vars, wkdir_native, &pid);
    154   if (processIdArray) {
    155     if (env->GetArrayLength(processIdArray) > 0) {
    156       jboolean isCopy;
    157       int* proccessId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy);
    158       if (proccessId) {
    159         *proccessId = (int) pid;
    160         env->ReleasePrimitiveArrayCritical(processIdArray, proccessId, 0);
    161       }
    162     }
    163   }
    164 
    165   jclass Class_java_io_FileDescriptor =
    166       env->FindClass("java/io/FileDescriptor");
    167   jmethodID init = env->GetMethodID(Class_java_io_FileDescriptor, "<init>", "()V");
    168   jobject result = env->NewObject(Class_java_io_FileDescriptor, init);
    169 
    170   if (!result) {
    171     LOGE("Couldn't create a FileDescriptor.");
    172   } else {
    173     jfieldID descriptor = env->GetFieldID(Class_java_io_FileDescriptor, "descriptor", "I");
    174     env->SetIntField(result, descriptor, ptm);
    175   }
    176   return result;
    177 }
    178 
    179 JNIEXPORT void JNICALL Java_com_googlecode_android_1scripting_Exec_setPtyWindowSize(
    180     JNIEnv* env, jclass clazz, jobject fileDescriptor, jint row, jint col, jint xpixel,
    181     jint ypixel) {
    182   struct winsize sz;
    183   int fd = JNU_GetFdFromFileDescriptor(env, fileDescriptor);
    184   if (env->ExceptionOccurred() != NULL) {
    185     return;
    186   }
    187   sz.ws_row = row;
    188   sz.ws_col = col;
    189   sz.ws_xpixel = xpixel;
    190   sz.ws_ypixel = ypixel;
    191   ioctl(fd, TIOCSWINSZ, &sz);
    192 }
    193 
    194 JNIEXPORT jint JNICALL Java_com_googlecode_android_1scripting_Exec_waitFor(JNIEnv* env, jclass clazz, jint procId) {
    195   int status;
    196   waitpid(procId, &status, 0);
    197   int result = 0;
    198   if (WIFEXITED(status)) {
    199     result = WEXITSTATUS(status);
    200   }
    201   return result;
    202 }
    203