From 8d1f2da1f075957a1d87d409a8345d29c590b648 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 18:44:31 +0800 Subject: [PATCH] Implement SIGTERM in jni-helper --- .../com/github/shadowsocks/JniHelper.java | 25 +++++--- mobile/src/main/jni/jni-helper.cpp | 63 +++++++++++-------- .../github/shadowsocks/GuardedProcess.scala | 15 ++++- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java index a042a9f2..dfce9080 100755 --- a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -38,12 +38,23 @@ package com.github.shadowsocks; -public class JniHelper { - static { - System.loadLibrary("jni-helper"); - } +import android.os.Build; +import android.system.ErrnoException; - public static native int exec(String cmd); - public static native int sendFd(int fd, String path); - public static native void close(int fd); +public class JniHelper { + static { + System.loadLibrary("jni-helper"); + } + + @Deprecated // Use Process.destroy() since API 24 + public static void sigtermCompat(Process process) throws Exception { + if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); + int errno = sigterm(process); + if (errno != 0) throw Build.VERSION.SDK_INT >= 21 + ? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno); + } + + private static native int sigterm(Process process); + public static native int sendFd(int fd, String path); + public static native void close(int fd); } diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index 30b118f5..cbba6dbc 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -1,11 +1,14 @@ -#define LOG_TAG "Shadowsocks" +#define LOG_TAG "JniHelper" #include "jni.h" #include +#include + #include #include #include #include +#include #include #include @@ -16,28 +19,26 @@ #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) +#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0) -jint Java_com_github_shadowsocks_jnihelper_exec(JNIEnv *env, jobject thiz, jstring cmd) { - const char *cmd_str = env->GetStringUTFChars(cmd, 0); +static jclass ProcessImpl; +static jfieldID ProcessImpl_pid; - pid_t pid; +static int sdk_version() { + char version[PROP_VALUE_MAX + 1]; + __system_property_get("ro.build.version.sdk", version); + return atoi(version); +} - /* Fork off the parent process */ - pid = fork(); - if (pid < 0) { - env->ReleaseStringUTFChars(cmd, cmd_str); +jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jobject process) { + if (!env->IsInstanceOf(process, ProcessImpl)) { + THROW(env, "java/lang/ClassCastException", + "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); return -1; } - - if (pid > 0) { - env->ReleaseStringUTFChars(cmd, cmd_str); - return pid; - } - - execl("/system/bin/sh", "sh", "-c", cmd_str, NULL); - env->ReleaseStringUTFChars(cmd, cmd_str); - - return 1; + jint pid = env->GetIntField(process, ProcessImpl_pid); + // Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed. + return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; } void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { @@ -82,8 +83,8 @@ static JNINativeMethod method_table[] = { (void*) Java_com_github_shadowsocks_jnihelper_close }, { "sendFd", "(ILjava/lang/String;)I", (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, - { "exec", "(Ljava/lang/String;)I", - (void*) Java_com_github_shadowsocks_jnihelper_exec } + { "sigterm", "(Ljava/lang/Process;)I", + (void*) Java_com_github_shadowsocks_jnihelper_sigterm } }; @@ -139,20 +140,30 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint result = -1; JNIEnv* env = NULL; - LOGI("JNI_OnLoad"); - - if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { - LOGE("ERROR: GetEnv failed"); + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) { + THROW(env, "java/lang/RuntimeException", "GetEnv failed"); goto bail; } env = uenv.env; if (registerNatives(env) != JNI_TRUE) { - LOGE("ERROR: registerNatives failed"); + THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed"); goto bail; } - result = JNI_VERSION_1_4; + if (sdk_version() < 24) { + if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); + goto bail; + } + ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl); + if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); + goto bail; + } + } + + result = JNI_VERSION_1_6; bail: return result; diff --git a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala index b995c8a7..7ff41694 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala @@ -24,6 +24,7 @@ import java.io._ import java.lang.System.currentTimeMillis import java.util.concurrent.Semaphore +import android.os.Build import android.util.Log import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.Commandline @@ -89,7 +90,7 @@ class GuardedProcess(cmd: String*) { } catch { case _: InterruptedException => if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) - process.destroy() + destroyProcess() case e: IOException => ioException = e } finally semaphore.release() }, "GuardThread-" + cmd.head) @@ -105,16 +106,24 @@ class GuardedProcess(cmd: String*) { def destroy() { isDestroyed = true guardThread.interrupt() - process.destroy() + destroyProcess() try guardThread.join() catch { case _: InterruptedException => } } + private def destroyProcess() { + if (Build.VERSION.SDK_INT < 24) { + JniHelper.sigtermCompat(process) + process.waitFor() + } + process.destroy() + } + def restart() { this.synchronized { isRestart = true - process.destroy() + destroyProcess() } }