diff --git a/src/android/AndroidManifest.xml b/src/android/AndroidManifest.xml index c3a6ef34..d6693e56 100644 --- a/src/android/AndroidManifest.xml +++ b/src/android/AndroidManifest.xml @@ -20,5 +20,10 @@ + diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java b/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java index 1c7b1cb4..a117460f 100644 --- a/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java +++ b/src/android/com/unprompted/tildefriends/TildeFriendsActivity.java @@ -3,15 +3,18 @@ package com.unprompted.tildefriends; import android.app.Activity; import android.app.AlertDialog; import android.app.DownloadManager; +import android.content.ComponentName; import android.content.DialogInterface; import android.content.Intent; +import android.content.ServiceConnection; import android.net.Uri; import android.os.Bundle; -import android.os.CountDownTimer; import android.os.Environment; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.StrictMode; -import android.os.SystemClock; -import android.os.strictmode.Violation; import android.util.Base64; import android.util.Log; import android.view.KeyEvent; @@ -29,19 +32,13 @@ import android.webkit.WebChromeClient; import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.widget.Button; import android.widget.TextView; import android.widget.Toast; -import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.OutputStream; import java.io.FileReader; -import java.io.InputStream; -import java.lang.Process; -import java.lang.Thread; +import java.io.OutputStream; import java.nio.file.FileSystems; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKinds; @@ -51,12 +48,13 @@ import java.nio.file.WatchService; import java.util.concurrent.TimeUnit; public class TildeFriendsActivity extends Activity { + static TildeFriendsActivity s_activity; TildeFriendsWebView web_view; String base_url; String port_file_path; - Process process; Thread thread; Thread server_thread; + ServiceConnection service_connection; private ValueCallback upload_message; private final static int FILECHOOSER_RESULT = 1; @@ -68,10 +66,12 @@ public class TildeFriendsActivity extends Activity { Log.w("tildefriends", "system.loadLibrary() completed."); } - static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path); + public static native int tf_server_main(String files_dir, String apk_path, String out_port_file_path); + public static native int tf_sandbox_main(int pipe_fd); @Override protected void onCreate(Bundle savedInstanceState) { + s_activity = this; StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedClosableObjects() .penaltyLog() @@ -274,13 +274,8 @@ public class TildeFriendsActivity extends Activity { @Override protected void onDestroy() { - if (process != null) { - Log.w("tildefriends", "Killing process."); - process.destroyForcibly(); - Log.w("tildefriends", "Process killed."); - process = null; - } super.onDestroy(); + s_activity = null; } @Override @@ -382,4 +377,49 @@ public class TildeFriendsActivity extends Activity { web_view.setVisibility(View.VISIBLE); text_view.setVisibility(View.GONE); } + + public static void start_sandbox(int pipe_fd) { + Log.w("tildefriends", "starting service with fd: " + pipe_fd); + Intent intent = new Intent(s_activity, TildeFriendsSandboxService.class); + s_activity.service_connection = new ServiceConnection() { + @Override + public void onBindingDied(ComponentName name) { + Log.w("tildefriends", "onBindingDied"); + } + + @Override + public void onNullBinding(ComponentName name) { + Log.w("tildefriends", "onNullBinding"); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + Log.w("tildefriends", "onServiceConnected"); + Parcel data = Parcel.obtain(); + ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(pipe_fd); + data.writeParcelable(pfd, 0); + try { + binder.transact(TildeFriendsSandboxService.START_CALL, data, null, IBinder.FLAG_ONEWAY); + } catch (RemoteException e) { + Log.w("tildefriends", "RemoteException"); + } finally { + data.recycle(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.w("tildefriends", "onServiceDisconnected"); + } + }; + s_activity.bindService(intent, s_activity.service_connection, BIND_AUTO_CREATE); + } + + public static void stop_sandbox() { + Log.w("tildefriends", "stop_sandbox"); + if (s_activity.service_connection != null) { + s_activity.unbindService(s_activity.service_connection); + s_activity.service_connection = null; + } + } } diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java b/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java new file mode 100644 index 00000000..4626f705 --- /dev/null +++ b/src/android/com/unprompted/tildefriends/TildeFriendsSandboxService.java @@ -0,0 +1,59 @@ +package com.unprompted.tildefriends; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +public class TildeFriendsSandboxService extends Service { + public static final int START_CALL = IBinder.FIRST_CALL_TRANSACTION; + + Thread thread; + + public int onStartCommand(Intent intent, int flags, int start_id) { + Log.w("tildefriends", "TildeFriendsSandboxService: onStartCommand"); + return super.onStartCommand(intent, flags, start_id); + } + + public void onDestroy() { + Log.w("tildefriends", "TildeFriendsSandboxService: onDestroy"); + super.onDestroy(); + } + + private void start_thread(int pipe_fd) { + thread = new Thread(new Runnable() { + @Override + public void run() { + Log.w("tildefriends", "Calling tf_sandbox_main."); + int result = TildeFriendsActivity.tf_sandbox_main(pipe_fd); + Log.w("tildefriends", "tf_sandbox_main returned " + result + "."); + } + }); + thread.start(); + } + + @Override + public IBinder onBind(Intent intent) { + return new Binder() { + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) { + if (code == START_CALL) { + ParcelFileDescriptor pfd = data.readParcelable(ParcelFileDescriptor.class.getClassLoader(), ParcelFileDescriptor.class); + if (pfd != null) { + Log.w("tildefriends", "fd is " + pfd.getFd()); + start_thread(pfd.detachFd()); + try { + pfd.close(); + } catch (java.io.IOException e) { + } + } + return true; + } + return false; + } + }; + } +} diff --git a/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java b/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java index e56bee09..340de874 100644 --- a/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java +++ b/src/android/com/unprompted/tildefriends/TildeFriendsWebView.java @@ -2,7 +2,6 @@ package com.unprompted.tildefriends; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; public class TildeFriendsWebView extends android.webkit.WebView { boolean overscrolledY = false; diff --git a/src/main.c b/src/main.c index e834383b..37b203d1 100644 --- a/src/main.c +++ b/src/main.c @@ -607,14 +607,16 @@ static int _tf_command_run(const char* file, int argc, char* argv[]) static int _tf_command_sandbox(const char* file, int argc, char* argv[]) { bool show_usage = false; + int fd = STDIN_FILENO; while (!show_usage) { static const struct option k_options[] = { + { "fd", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; - int c = getopt_long(argc, argv, "h", k_options, NULL); + int c = getopt_long(argc, argv, "f:h", k_options, NULL); if (c == -1) { break; @@ -626,6 +628,10 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[]) default: show_usage = true; break; + case 'f': + tf_printf("got -f %s\n", optarg); + fd = atoi(optarg); + break; } } @@ -634,6 +640,7 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[]) tf_printf("\nUsage: %s sandbox [options]\n\n", file); tf_printf("options:\n"); tf_printf(" -h, --help Show this usage information.\n"); + tf_printf(" -f, --fd File descriptor with which to communicate with parent process.\n"); return EXIT_FAILURE; } @@ -641,7 +648,7 @@ static int _tf_command_sandbox(const char* file, int argc, char* argv[]) prctl(PR_SET_PDEATHSIG, SIGHUP); #endif tf_task_t* task = tf_task_create(); - tf_task_configure_from_fd(task, STDIN_FILENO); + tf_task_configure_from_fd(task, fd); _shed_privileges(); /* The caller will trigger tf_task_activate with a message. */ tf_task_run(task); @@ -689,10 +696,6 @@ static void _startup(int argc, char* argv[]) } } -#if defined(__ANDROID__) - setenv("UV_USE_IO_URING", "0", 1); -#endif - tf_mem_startup(tracking); g_backtrace_state = backtrace_create_state(argv[0], 0, _backtrace_error, NULL); @@ -720,7 +723,7 @@ static void _startup(int argc, char* argv[]) { if ( #if !defined(_WIN32) - signal(SIGSYS, _error_handler) == SIG_ERR || + signal(SIGSYS, _error_handler) == SIG_ERR || signal(SIGABRT, _error_handler) == SIG_ERR || #endif signal(SIGSEGV, _error_handler) == SIG_ERR) { @@ -730,8 +733,28 @@ static void _startup(int argc, char* argv[]) } #if defined(__ANDROID__) +static JNIEnv* s_jni_env; + +static void _tf_service_start(int pipe_fd) +{ + tf_printf("_tf_service_start\n"); + jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity"); + jmethodID start_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "start_sandbox", "(I)V"); + (*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, start_sandbox, pipe_fd); +} + +static void _tf_service_stop() +{ + tf_printf("_tf_service_stop\n"); + jclass c = (*s_jni_env)->FindClass(s_jni_env, "com/unprompted/tildefriends/TildeFriendsActivity"); + jmethodID stop_sandbox = (*s_jni_env)->GetStaticMethodID(s_jni_env, c, "stop_sandbox", "()V"); + (*s_jni_env)->CallStaticVoidMethod(s_jni_env, c, stop_sandbox); +} + static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir, jstring apk_path, jstring out_port_file_path) { + s_jni_env = env; + tf_printf("This is tf_server_main main.\n"); _startup(0, (char*[]) { NULL }); tf_printf("That was startup.\n"); @@ -754,24 +777,51 @@ static jint _tf_server_main(JNIEnv* env, jobject this_object, jstring files_dir, char* port_file_arg = alloca(port_file_arg_length); snprintf(port_file_arg, port_file_arg_length, "out_http_port_file=%s", out_port_file); - const char* args[] = - { + const char* args[] = { + "run", "-z", apk, "-a", port_file_arg, "-p", "0", - "-o", /* HACK! FIXME! */ }; + tf_task_set_android_service_callbacks(_tf_service_start, _tf_service_stop); result = _tf_command_run(apk, _countof(args), (char**)args); + tf_task_set_android_service_callbacks(NULL, NULL); (*env)->ReleaseStringUTFChars(env, files_dir, files); (*env)->ReleaseStringUTFChars(env, apk_path, apk); (*env)->ReleaseStringUTFChars(env, out_port_file_path, out_port_file); tf_printf("tf_server_main finished with %d.", result); + + s_jni_env = NULL; + return result; +} + +static jint _tf_sandbox_main(JNIEnv* env, jobject this_object, int pipe_fd) +{ + s_jni_env = env; + + tf_printf("This is tf_sandbox_main main (fd=%d).\n", pipe_fd); + _startup(0, (char*[]) { NULL }); + tf_printf("That was startup.\n"); + + char fd[32] = { 0 }; + snprintf(fd, sizeof(fd), "%d", pipe_fd); + const char* args[] = { + "sandbox", + "-f", + fd, + }; + + int result = _tf_command_sandbox(NULL, _countof(args), (char**)args); + + tf_printf("tf_sandbox_main finished with %d.", result); + + s_jni_env = NULL; return result; } @@ -796,6 +846,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) tf_printf("Registering method.\n"); static const JNINativeMethod methods[] = { { "tf_server_main", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", _tf_server_main }, + { "tf_sandbox_main", "(I)I", _tf_sandbox_main }, }; int result = (*env)->RegisterNatives(env, c, methods, (int)_countof(methods)); if (result != JNI_OK) diff --git a/src/task.c b/src/task.c index dbf32710..b0d54f95 100644 --- a/src/task.c +++ b/src/task.c @@ -50,6 +50,9 @@ static JSClassID _import_class_id; static int _count; +static tf_android_start_service_t* s_android_start_service; +static tf_android_stop_service_t* s_android_stop_service; + extern struct backtrace_state* g_backtrace_state; typedef struct _export_record_t export_record_t; @@ -2160,3 +2163,19 @@ static JSValue _tf_task_pokeSandbox(JSContext* context, JSValueConst this_val, i #endif return JS_NewInt32(context, WEXITSTATUS(result)); } + +void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service) +{ + s_android_start_service = start_service; + s_android_stop_service = stop_service; +} + +tf_android_start_service_t* tf_task_get_android_start_service() +{ + return s_android_start_service; +} + +tf_android_stop_service_t* tf_task_get_android_stop_service() +{ + return s_android_stop_service; +} diff --git a/src/task.h b/src/task.h index 61a2eb09..7d81d2c6 100644 --- a/src/task.h +++ b/src/task.h @@ -333,4 +333,35 @@ char* tf_task_get_debug(tf_task_t* task); */ char* tf_task_get_hitches(tf_task_t* task); +/** +** A callback used to start an Android service. +** @param pipe_fd A file descriptor with which to communicate with the invoking +** task. +*/ +typedef void(tf_android_start_service_t)(int pipe_fd); + +/** +** A callback used to stop an Android service. +*/ +typedef void(tf_android_stop_service_t)(); + +/** +** Set Android service callbacks. +** @param start_service Start service callback. +** @param stop_service Stop service callback. +*/ +void tf_task_set_android_service_callbacks(tf_android_start_service_t* start_service, tf_android_stop_service_t* stop_service); + +/** +** Get the callback registered for starting an Android service. +** @return the callback. +*/ +tf_android_start_service_t* tf_task_get_android_start_service(); + +/** +** Get the callback registered for stopping an Android service. +** @return the callback. +*/ +tf_android_stop_service_t* tf_task_get_android_stop_service(); + /** @} */ diff --git a/src/taskstub.js.c b/src/taskstub.js.c index 5fdb3414..e2071e12 100644 --- a/src/taskstub.js.c +++ b/src/taskstub.js.c @@ -154,41 +154,74 @@ static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int a } else { - uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream); - memset(pipe, 0, sizeof(*pipe)); - if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0) + tf_android_start_service_t* start_service = tf_task_get_android_start_service(); + if (start_service) { - tf_printf("uv_pipe_init failed\n"); - } + uv_os_sock_t fds[2]; + int socketpair_result = uv_socketpair(SOCK_STREAM, 0, fds, 0, 0); + if (socketpair_result) + { + tf_printf("uv_socketpair: %s\n", uv_strerror(socketpair_result)); + } - uv_stdio_container_t io[3]; - io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE; - io[0].data.stream = (uv_stream_t*)pipe; - io[1].flags = UV_INHERIT_FD; - io[1].data.fd = STDOUT_FILENO; - io[2].flags = UV_INHERIT_FD; - io[2].data.fd = STDERR_FILENO; + uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream); + *pipe = (uv_pipe_t) { 0 }; + if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0) + { + tf_printf("uv_pipe_init failed\n"); + } - uv_process_options_t options = { 0 }; - options.args = command_argv; - options.exit_cb = _taskstub_on_process_exit; - options.stdio = io; - options.stdio_count = sizeof(io) / sizeof(*io); - options.file = command_argv[0]; - options.env = (char*[]) { "ASAN_OPTIONS=detect_leaks=0", NULL }; + int pipe_result = uv_pipe_open(pipe, fds[0]); + if (pipe_result) + { + tf_printf("uv_pipe_open: %s\n", uv_strerror(pipe_result)); + } - stub->_process.data = stub; - int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options); - if (spawn_result == 0) - { + start_service(fds[1]); + + stub->_process.data = stub; tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub); tf_packetstream_start(stub->_stream); result = taskObject; } else { - tf_printf("uv_spawn failed: %s\n", uv_strerror(spawn_result)); - JS_FreeValue(context, taskObject); + uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream); + memset(pipe, 0, sizeof(*pipe)); + if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0) + { + tf_printf("uv_pipe_init failed\n"); + } + + uv_stdio_container_t io[3]; + io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE; + io[0].data.stream = (uv_stream_t*)pipe; + io[1].flags = UV_INHERIT_FD; + io[1].data.fd = STDOUT_FILENO; + io[2].flags = UV_INHERIT_FD; + io[2].data.fd = STDERR_FILENO; + + uv_process_options_t options = { 0 }; + options.args = command_argv; + options.exit_cb = _taskstub_on_process_exit; + options.stdio = io; + options.stdio_count = sizeof(io) / sizeof(*io); + options.file = command_argv[0]; + options.env = (char*[]) { "ASAN_OPTIONS=detect_leaks=0", NULL }; + + stub->_process.data = stub; + int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options); + if (spawn_result == 0) + { + tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub); + tf_packetstream_start(stub->_stream); + result = taskObject; + } + else + { + tf_printf("uv_spawn failed: %s\n", uv_strerror(spawn_result)); + JS_FreeValue(context, taskObject); + } } } return JS_DupValue(context, result); @@ -443,7 +476,15 @@ JSValue tf_taskstub_kill(tf_taskstub_t* stub) JSValue result = JS_UNDEFINED; if (!tf_task_get_one_proc(stub->_owner)) { - uv_process_kill(&stub->_process, SIGKILL); + tf_android_stop_service_t* stop_service = tf_task_get_android_stop_service(); + if (stop_service) + { + stop_service(); + } + else + { + uv_process_kill(&stub->_process, SIGKILL); + } } else {