From dc2a5ca19c93df11e0658624cb517fe0de2fad3f Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 18 Nov 2017 01:47:44 -0800 Subject: [PATCH] Use SIGKILL to kill old processes Fix #1455. Note that in later Android versions, only processes under same uid will be listed and this will be quite efficient. --- .../com/github/shadowsocks/JniHelper.java | 1 + mobile/src/main/jni/jni-helper.cpp | 74 ++++--------------- .../github/shadowsocks/bg/Executable.scala | 25 +++++-- 3 files changed, 32 insertions(+), 68 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java index c2299e58..2cc41a34 100755 --- a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -64,6 +64,7 @@ public class JniHelper { } } + public static native int sigkill(int pid); private static native int sigterm(Process process); private static native Integer getExitValue(Process process); private static native Object getExitValueMutex(Process process); diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index bf017acd..6c56cb52 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -31,7 +31,13 @@ static int sdk_version() { return atoi(version); } -jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jobject process) { +extern "C" { +JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) { + // Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed. + return kill(pid, SIGKILL) == -1 && errno != ESRCH ? errno : 0; +} + +JNIEXPORT jint JNICALL 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."); @@ -42,7 +48,8 @@ jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jo return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; } -jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) { +JNIEXPORT jobject JNICALL + Java_com_github_shadowsocks_JniHelper_getExitValue(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."); @@ -51,7 +58,8 @@ jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject return env->GetObjectField(process, ProcessImpl_exitValue); } -jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) { +JNIEXPORT jobject JNICALL + Java_com_github_shadowsocks_JniHelper_getExitValueMutex(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."); @@ -60,11 +68,12 @@ jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, job return env->GetObjectField(process, ProcessImpl_exitValueMutex); } -void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { +JNIEXPORT void JNICALL Java_com_github_shadowsocks_JniHelper_close(JNIEnv *env, jobject thiz, jint fd) { close(fd); } -jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { +JNIEXPORT jint JNICALL + Java_com_github_shadowsocks_JniHelper_sendFd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { int fd; struct sockaddr_un addr; const char *sock_str = env->GetStringUTFChars(path, 0); @@ -94,56 +103,6 @@ jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jin env->ReleaseStringUTFChars(path, sock_str); return 0; } - -static const char *classPathName = "com/github/shadowsocks/JniHelper"; - -static JNINativeMethod method_table[] = { - { "close", "(I)V", - (void*) Java_com_github_shadowsocks_jnihelper_close }, - { "sendFd", "(ILjava/lang/String;)I", - (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, - { "sigterm", "(Ljava/lang/Process;)I", - (void*) Java_com_github_shadowsocks_jnihelper_sigterm }, - { "getExitValue", "(Ljava/lang/Process;)Ljava/lang/Integer;", - (void*) Java_com_github_shadowsocks_jnihelper_getExitValue }, - { "getExitValueMutex", "(Ljava/lang/Process;)Ljava/lang/Object;", - (void*) Java_com_github_shadowsocks_jnihelper_getExitValueMutex } -}; - -/* - * Register several native methods for one class. - */ -static int registerNativeMethods(JNIEnv* env, const char* className, - JNINativeMethod* gMethods, int numMethods) -{ - jclass clazz; - - clazz = env->FindClass(className); - if (clazz == NULL) { - LOGE("Native registration unable to find class '%s'", className); - return JNI_FALSE; - } - if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { - LOGE("RegisterNatives failed for '%s'", className); - return JNI_FALSE; - } - - return JNI_TRUE; -} - -/* - * Register native methods for all classes we know about. - * - * returns JNI_TRUE on success. - */ -static int registerNatives(JNIEnv* env) -{ - if (!registerNativeMethods(env, classPathName, method_table, - sizeof(method_table) / sizeof(method_table[0]))) { - return JNI_FALSE; - } - - return JNI_TRUE; } /* @@ -167,11 +126,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { } env = uenv.env; - if (registerNatives(env) != JNI_TRUE) { - THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed"); - goto bail; - } - if (sdk_version() < 24) { if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); diff --git a/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala index 7d2a1e38..d024b747 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/bg/Executable.scala @@ -1,9 +1,14 @@ package com.github.shadowsocks.bg +import java.io.File import java.util.Locale -import java.util.concurrent.TimeUnit +import android.text.TextUtils import android.util.Log +import com.github.shadowsocks.JniHelper +import com.github.shadowsocks.ShadowsocksApplication.app + +import scala.io.Source /** * @author Mygod @@ -16,12 +21,16 @@ object Executable { val TUN2SOCKS = "libtun2socks.so" val OVERTURE = "liboverture.so" - val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE) + val EXECUTABLES = Set(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE) - def killAll() { - val killer = new ProcessBuilder("killall" +: EXECUTABLES: _*).start() - if (!killer.waitFor(1, TimeUnit.SECONDS)) - Log.w("killall", "%s didn't exit within 1s. Post-crash clean-up may have failed." - .formatLocal(Locale.ENGLISH, killer.toString)) - } + def killAll(): Unit = + for (process <- new File("/proc").listFiles((_, name) => TextUtils.isDigitsOnly(name))) { + val exe = new File(Source.fromFile(new File(process, "cmdline")).mkString.split("\0", 2).head) + if (exe.getParent == app.getApplicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.getName)) + JniHelper.sigkill(process.getName.toInt) match { + case 0 => + case errno => Log.w("kill", "SIGKILL %s (%d) failed with %d" + .formatLocal(Locale.ENGLISH, exe.getAbsolutePath, process.getName, errno)) + } + } }