#include "log.h" #include "mem.h" #include "ssb.db.h" #include "ssb.export.h" #include "ssb.h" #include "ssb.import.h" #include "task.h" #include "taskstub.js.h" #include "tests.h" #include "util.js.h" #include "ares.h" #include "backtrace.h" #include "sqlite3.h" #include "unzip.h" #include #include #include #if !defined(_WIN32) && !defined(__APPLE__) && !defined(__HAIKU__) #include #include #endif #if defined(__linux__) #include #include #endif #if defined(__APPLE__) #include #endif #if !defined(_WIN32) #include #endif #if defined(__ANDROID__) #include "jni.h" #endif struct backtrace_state* g_backtrace_state; #if !TARGET_OS_IPHONE static const char* _get_db_path() { const char* k_db_path_default = "db.sqlite"; #if defined(__linux__) if (stat(k_db_path_default, &(struct stat) { 0 }) == 0) { return tf_strdup(k_db_path_default); } else { char buffer[32]; char* data_home = NULL; size_t size = sizeof(buffer); int r = uv_os_getenv("XDG_DATA_HOME", buffer, &size); if (r == 0 || r == UV_ENOBUFS) { size++; data_home = alloca(size); if (uv_os_getenv("XDG_DATA_HOME", data_home, &size) != 0) { data_home = NULL; } } if (!data_home) { size = sizeof(buffer); r = uv_os_getenv("HOME", buffer, &size); if (r == 0 || r == UV_ENOBUFS) { size++; char* home = alloca(size); r = uv_os_getenv("HOME", home, &size); if (r == 0) { size = snprintf(NULL, 0, "%s/.local/share", home) + 1; data_home = alloca(size); snprintf(data_home, size, "%s/.local/share", home); } } } if (data_home) { size = snprintf(NULL, 0, "%s/tildefriends/db.sqlite", data_home) + 1; char* path = alloca(size); snprintf(path, size, "%s/tildefriends/db.sqlite", data_home); return tf_strdup(path); } } #endif return tf_strdup(k_db_path_default); } static void _create_directories_for_file(const char* path, int mode) { if (stat(path, &(struct stat) { 0 }) == 0) { /* It already exists. OK. */ return; } size_t length = strlen(path) + 1; char* copy = alloca(length); memcpy(copy, path, length); #if defined(_WIN32) for (char* c = copy; *c; c++) { if (*c == '\\') { *c = '/'; } } #endif char* slash = copy; while (slash) { slash = strchr(slash + 1, '/'); if (slash) { *slash = '\0'; #if defined(_WIN32) if (mkdir(copy) == 0) #else if (mkdir(copy, mode) == 0) #endif { tf_printf("Created directory %s.\n", copy); } *slash = '/'; } } } #endif #if !TARGET_OS_IPHONE && !defined(__ANDROID__) static int _tf_command_export(const char* file, int argc, char* argv[]); static int _tf_command_import(const char* file, int argc, char* argv[]); static int _tf_command_publish(const char* file, int argc, char* argv[]); static int _tf_command_run(const char* file, int argc, char* argv[]); static int _tf_command_sandbox(const char* file, int argc, char* argv[]); static int _tf_command_store_blob(const char* file, int argc, char* argv[]); static int _tf_command_test(const char* file, int argc, char* argv[]); static int _tf_command_verify(const char* file, int argc, char* argv[]); static int _tf_command_usage(const char* file); typedef struct _command_t { const char* name; int (*callback)(const char* file, int argc, char* argv[]); const char* description; } command_t; const command_t k_commands[] = { { "run", _tf_command_run, "Run tildefriends (default)." }, { "sandbox", _tf_command_sandbox, "Run a sandboxed tildefriends sandbox process (used internally)." }, { "import", _tf_command_import, "Import apps to SSB." }, { "export", _tf_command_export, "Export apps from SSB." }, { "publish", _tf_command_publish, "Append a message to a feed." }, { "store_blob", _tf_command_store_blob, "Write a file to the blob store." }, { "verify", _tf_command_verify, "Verify a feed." }, { "test", _tf_command_test, "Test SSB." }, }; static int _tf_command_test(const char* file, int argc, char* argv[]) { #if !defined(__ANDROID__) tf_test_options_t test_options = { .exe_path = file, }; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "tests", required_argument, NULL, 't' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "t:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 't': test_options.tests = optarg; break; } } for (int i = optind; i < argc; i++) { tf_printf("Unexpected argument: %s\n", argv[i]); show_usage = true; } if (show_usage) { tf_printf("\n%s test [options]\n\n", file); tf_printf("options\n"); tf_printf(" -t, --tests tests Comma-separated list of tests to run. (default: all)\n"); tf_printf(" -h, --help Show this usage information.\n"); return EXIT_FAILURE; } tf_tests(&test_options); return EXIT_SUCCESS; #else return EXIT_FAILURE; #endif } static int _tf_command_import(const char* file, int argc, char* argv[]) { const char* user = "import"; const char* default_db_path = _get_db_path(); const char* db_path = default_db_path; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "user", required_argument, NULL, 'u' }, { "db-path", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "u:d:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'u': user = optarg; break; case 'd': db_path = optarg; break; } } if (show_usage) { tf_printf("\n%s import [options] [paths...]\n\n", file); tf_printf("options:\n"); tf_printf(" -u, --user user User into whose account apps will be imported (default: \"import\").\n"); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -h, --help Show this usage information.\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } _create_directories_for_file(db_path, 0700); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); if (optind < argc) { for (int i = optind; i < argc; i++) { tf_printf("Importing %s...\n", argv[i]); tf_ssb_import(ssb, user, argv[i]); } } else { tf_printf("Importing %s...\n", "apps"); tf_ssb_import(ssb, user, "apps"); } tf_ssb_destroy(ssb); tf_free((void*)default_db_path); return EXIT_SUCCESS; } static int _tf_command_export(const char* file, int argc, char* argv[]) { const char* user = "core"; const char* default_db_path = _get_db_path(); const char* db_path = default_db_path; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "user", required_argument, NULL, 'u' }, { "db-path", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "u:d:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'u': user = optarg; break; case 'd': db_path = optarg; break; } } if (show_usage) { tf_printf("\n%s export [options] [paths...]\n\n", file); tf_printf("options:\n"); tf_printf(" -u, --user user User from whose account apps will be exported (default: \"core\").\n"); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -h, --help Show this usage information.\n"); tf_printf("\n"); tf_printf("paths Paths of apps to export (example: /~core/ssb /~user/app).\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); if (optind < argc) { for (int i = optind; i < argc; i++) { tf_printf("Exporting %s...\n", argv[i]); tf_ssb_export(ssb, argv[i]); } } else { const char* k_export[] = { "admin", "api", "apps", "appstore", "db", "docs", "follow", "ssb", "todo", }; for (int i = 0; i < tf_countof(k_export); i++) { char buffer[256]; snprintf(buffer, sizeof(buffer), "/~%s/%s", user, k_export[i]); tf_printf("Exporting %s...\n", buffer); tf_ssb_export(ssb, buffer); } } tf_ssb_destroy(ssb); tf_free((void*)default_db_path); return EXIT_SUCCESS; } static void _tf_published_callback(const char* id, bool verified, bool stored, void* user_data) { if (verified) { if (stored) { tf_printf("Message %s stored.\n", id); } else { tf_printf("Unable to store the message.\n"); } } else { tf_printf("Failed to verify the message.\n"); } *(int*)user_data = stored ? EXIT_SUCCESS : EXIT_FAILURE; } static int _tf_command_publish(const char* file, int argc, char* argv[]) { const char* user = NULL; const char* identity = NULL; const char* default_db_path = _get_db_path(); const char* db_path = default_db_path; const char* content = NULL; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "user", required_argument, NULL, 'u' }, { "id", required_argument, NULL, 'i' }, { "db-path", required_argument, NULL, 'd' }, { "content", required_argument, NULL, 'c' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "u:i:d:c:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'u': user = optarg; break; case 'i': identity = optarg; break; case 'd': db_path = optarg; break; case 'c': content = optarg; break; } } if (show_usage || !user || !identity || !content) { tf_printf("\n%s publish [options]\n\n", file); tf_printf("options:\n"); tf_printf(" -u, --user user User owning identity with which to publish.\n"); tf_printf(" -i, --id identity Identity with which to publish message.\n"); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -c, --content json JSON content of message to publish.\n"); tf_printf(" -h, --help Show this usage information.\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } int result = EXIT_FAILURE; tf_printf("Posting %s as account %s belonging to %s...\n", content, identity, user); _create_directories_for_file(db_path, 0700); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); uint8_t private_key[512] = { 0 }; if (tf_ssb_db_identity_get_private_key(ssb, user, identity, private_key, sizeof(private_key))) { JSContext* context = tf_ssb_get_context(ssb); int64_t sequence = 0; char previous[k_id_base64_len] = { 0 }; tf_ssb_db_get_latest_message_by_author(ssb, identity, &sequence, previous, sizeof(previous)); JSValue content_value = JS_ParseJSON(context, content, strlen(content), NULL); if (!JS_IsException(content_value)) { JSValue message = tf_ssb_sign_message(ssb, identity, private_key, content_value, previous, sequence); JSValue message_value = JS_JSONStringify(context, message, JS_NULL, JS_NULL); const char* message_str = JS_ToCString(context, message_value); tf_printf("Posting: %s.\n", message_str); tf_ssb_verify_strip_and_store_message(ssb, message, _tf_published_callback, &result); JS_FreeCString(context, message_str); JS_FreeValue(context, message_value); JS_FreeValue(context, message); } else { tf_printf("Unable to parse content as JSON: "); tf_util_report_error(context, content_value); } JS_FreeValue(context, content_value); } else { tf_printf("Did not find private key for identity %s belonging to %s.\n", identity, user); } tf_ssb_destroy(ssb); tf_free((void*)default_db_path); return result; } static int _tf_command_store_blob(const char* file, int argc, char* argv[]) { const char* default_db_path = _get_db_path(); const char* db_path = default_db_path; const char* file_path = NULL; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "db-path", required_argument, NULL, 'd' }, { "file", required_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "d:f:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'd': db_path = optarg; break; case 'f': file_path = optarg; break; } } if (show_usage || !file_path) { tf_printf("\n%s store_blob [options]\n\n", file); tf_printf("options:\n"); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -f, --file file_path Path to file to add to the blob store.\n"); tf_printf(" -h, --help Show this usage information.\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } char* data = NULL; size_t size = 0; FILE* blob_file = fopen(file_path, "rb"); if (!blob_file) { tf_printf("Failed to open %s: %s.\n", file_path, strerror(errno)); tf_free((void*)default_db_path); return EXIT_FAILURE; } char buffer[16 * 1024]; while (true) { size_t bytes = fread(buffer, 1, sizeof(buffer), blob_file); if (bytes > 0) { data = tf_resize_vec(data, size + bytes); memcpy(data + size, buffer, bytes); size += bytes; } else { break; } } if (ferror(blob_file)) { tf_printf("Failed to read %s: %s.\n", file_path, strerror(errno)); fclose(blob_file); tf_free(data); tf_free((void*)default_db_path); return EXIT_FAILURE; } fclose(blob_file); char id[256]; _create_directories_for_file(db_path, 0700); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); if (tf_ssb_db_blob_store(ssb, (const uint8_t*)data, size, id, sizeof(id), NULL)) { tf_printf("%s\n", id); } tf_ssb_destroy(ssb); tf_free(data); tf_free((void*)default_db_path); return EXIT_SUCCESS; } static int _tf_command_verify(const char* file, int argc, char* argv[]) { const char* identity = NULL; const char* default_db_path = _get_db_path(); const char* db_path = default_db_path; bool show_usage = false; while (!show_usage) { static const struct option k_options[] = { { "id", required_argument, NULL, 'i' }, { "db-path", required_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { 0 }, }; int c = getopt_long(argc, argv, "i:d:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'i': identity = optarg; break; case 'd': db_path = optarg; break; } } if (show_usage) { tf_printf("\n%s import [options] [paths...]\n\n", file); tf_printf("options:\n"); tf_printf(" -i, --identity identity Identity to verify.\n"); tf_printf(" -d, --db-path db_path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -h, --help Show this usage information.\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } tf_printf("Verifying %s...\n", identity); tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db_path, NULL); bool verified = tf_ssb_db_verify(ssb, identity); tf_ssb_destroy(ssb); tf_free((void*)default_db_path); return verified ? EXIT_SUCCESS : EXIT_FAILURE; } #endif typedef struct tf_run_args_t { const char* script; const char* network_key; int ssb_port; int http_port; int https_port; const char* db_path; int count; const char* args; const char* zip; bool one_proc; bool verbose; bool help; } tf_run_args_t; typedef struct _tf_run_thread_data_t { tf_run_args_t args; int index; int result; } tf_run_thread_data_t; static int _tf_run_task(const tf_run_args_t* args, int index) { int result = -1; tf_task_t* task = tf_task_create(); tf_task_set_trusted(task, true); tf_printf("setting zip path to %s\n", args->zip); tf_task_set_zip_path(task, args->zip); tf_task_set_ssb_network_key(task, args->network_key); tf_task_set_ssb_port(task, args->ssb_port ? args->ssb_port + index : 0); tf_task_set_http_port(task, args->http_port ? args->http_port + index : 0); tf_task_set_https_port(task, args->https_port ? args->https_port + index : 0); tf_task_set_args(task, args->args); tf_task_set_one_proc(task, args->one_proc); const char* db_path = args->db_path; char db_path_buffer[256]; if (index) { snprintf(db_path_buffer, sizeof(db_path_buffer), "%s.%d", args->db_path, index); db_path = db_path_buffer; } tf_task_set_db_path(task, db_path); tf_task_activate(task); tf_ssb_set_verbose(tf_task_get_ssb(task), args->verbose); tf_ssb_start_periodic(tf_task_get_ssb(task)); if (args->http_port || args->https_port) { if (args->zip) { tf_ssb_import_from_zip(tf_task_get_ssb(task), args->zip, "core", "apps"); } else { tf_ssb_import(tf_task_get_ssb(task), "core", "apps"); } } tf_ssb_set_main_thread(tf_task_get_ssb(task), true); if (tf_task_execute(task, args->script)) { tf_task_run(task); result = 0; } tf_task_destroy(task); tf_printf("_tf_run_task is done. Goodbye.\n"); return result; } static void _tf_run_task_thread(void* data) { tf_run_thread_data_t* info = data; info->result = _tf_run_task(&info->args, info->index); } #if !TARGET_OS_IPHONE static void _shed_privileges() { #if !defined(_WIN32) && !defined(__HAIKU__) struct rlimit zeroLimit; zeroLimit.rlim_cur = 0; zeroLimit.rlim_max = 0; // RLIMIT_AS // RLIMIT_CORE // RLIMIT_CPU // RLIMIT_DATA // RLIMIT_FSIZE // RLIMIT_RSS // RLIMIT_RTPRIO // RLIMIT_RTTIME // RLIMIT_SIGPENDING // RLIMIT_STACK if (setrlimit(RLIMIT_FSIZE, &zeroLimit) != 0) { perror("setrlimit(RLIMIT_FSIZE, {0, 0})"); exit(-1); } if (setrlimit(RLIMIT_NOFILE, &zeroLimit) != 0) { perror("setrlimit(RLIMIT_NOFILE, {0, 0})"); exit(-1); } if (setrlimit(RLIMIT_NPROC, &zeroLimit) != 0) { perror("setrlimit(RLIMIT_NPROC, {0, 0})"); exit(-1); } #if !defined(__MACH__) && !defined(__OpenBSD__) if (setrlimit(RLIMIT_LOCKS, &zeroLimit) != 0) { perror("setrlimit(RLIMIT_LOCKS, {0, 0})"); exit(-1); } if (setrlimit(RLIMIT_MSGQUEUE, &zeroLimit) != 0) { perror("setrlimit(RLIMIT_MSGQUEUE, {0, 0})"); exit(-1); } #endif #endif #if defined(__OpenBSD__) /* How do I unveil nothing? */ if (unveil("/dev/null", "r") || unveil(NULL, NULL)) { perror("unveil"); exit(-1); } if (pledge("stdio unveil", NULL)) { perror("pledge"); exit(-1); } #endif } static int _tf_command_run(const char* file, int argc, char* argv[]) { const char* default_db_path = _get_db_path(); tf_run_args_t args = { .count = 1, .script = "core/core.js", .http_port = 12345, .https_port = 12346, .ssb_port = 8008, .db_path = default_db_path, }; bool show_usage = false; /* Check if the executable has data attached. */ unzFile zip = unzOpen(file); if (zip) { args.zip = file; unzClose(zip); } while (!show_usage) { static const struct option k_options[] = { { "script", required_argument, NULL, 's' }, { "ssb-port", required_argument, NULL, 'b' }, { "ssb-network-key", required_argument, NULL, 'k' }, { "http-port", required_argument, NULL, 'p' }, { "https-port", required_argument, NULL, 'q' }, { "db-path", required_argument, NULL, 'd' }, { "count", required_argument, NULL, 'n' }, { "args", required_argument, NULL, 'a' }, { "one-proc", no_argument, NULL, 'o' }, { "zip", required_argument, NULL, 'z' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, }; int c = getopt_long(argc, argv, "s:b:k:p:q:d:n:a:oz:vh", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 's': args.script = optarg; break; case 'k': args.network_key = optarg; break; case 'b': args.ssb_port = atoi(optarg); break; case 'p': args.http_port = atoi(optarg); break; case 'q': args.https_port = atoi(optarg); break; case 'd': args.db_path = optarg; break; case 'n': args.count = atoi(optarg); break; case 'a': args.args = optarg; break; case 'o': args.one_proc = true; break; case 'z': args.zip = optarg; break; case 'v': args.verbose = true; break; } } if (show_usage) { tf_printf("\n%s run [options]\n\n", file); tf_printf("options\n"); tf_printf(" -s, --script script Script to run (default: core/core.js).\n"); tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n"); tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n"); tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n"); tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", default_db_path); tf_printf(" -k, --ssb-network-key key SSB network key to use.\n"); tf_printf(" -n, --count count Number of instances to run.\n"); tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n"); tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n"); tf_printf(" -z, --zip path Zip archive from which to load files.\n"); tf_printf(" -v, --verbose Log raw messages.\n"); tf_printf(" -h, --help Show this usage information.\n"); tf_free((void*)default_db_path); return EXIT_FAILURE; } int result = 0; #if !defined(_WIN32) && !defined(__MACH__) setpgid(0, 0); #endif _create_directories_for_file(args.db_path, 0700); if (args.count == 1) { result = _tf_run_task(&args, 0); } if (args.count > 1) { uv_thread_t* threads = tf_malloc(sizeof(uv_thread_t) * args.count); tf_run_thread_data_t* data = tf_malloc(sizeof(tf_run_thread_data_t) * args.count); for (int i = 0; i < args.count; i++) { data[i] = (tf_run_thread_data_t) { .args = args, .index = i, }; uv_thread_create(&threads[i], _tf_run_task_thread, &data[i]); } for (int i = 0; i < args.count; i++) { uv_thread_join(&threads[i]); if (data[i].result != 0) { result = data[i].result; } } tf_free(data); tf_free(threads); } tf_free((void*)default_db_path); return result; } 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, "f:h", k_options, NULL); if (c == -1) { break; } switch (c) { case '?': case 'h': default: show_usage = true; break; case 'f': fd = atoi(optarg); break; } } if (show_usage) { 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; } #if defined(__linux__) prctl(PR_SET_PDEATHSIG, SIGHUP); #endif tf_task_t* task = tf_task_create(); tf_task_configure_from_fd(task, fd); _shed_privileges(); /* The caller will trigger tf_task_activate with a message. */ tf_task_run(task); tf_task_destroy(task); return EXIT_SUCCESS; } #if !defined(__ANDROID__) static int _tf_command_usage(const char* file) { tf_printf("Usage: %s command [command-options]\n", file); tf_printf("commands:\n"); for (int i = 0; i < tf_countof(k_commands); i++) { tf_printf(" %s - %s\n", k_commands[i].name, k_commands[i].description); } return -1; } #endif #endif static void _backtrace_error(void* data, const char* message, int errnum) { tf_printf("libbacktrace error %d: %s\n", errnum, message); } static void _error_handler(int sig) { const char* stack = tf_util_backtrace_string(); tf_printf("ERROR:\n%s\n", stack); tf_free((void*)stack); _exit(1); } #if defined(_WIN32) static LONG WINAPI _win32_exception_handler(EXCEPTION_POINTERS* info) { if (info->ExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION || info->ExceptionRecord->ExceptionCode == STATUS_ILLEGAL_INSTRUCTION || info->ExceptionRecord->ExceptionCode == STATUS_STACK_OVERFLOW || info->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION) { const char* stack = tf_util_backtrace_string(); tf_printf("ERROR:\n%s\n", stack); tf_free((void*)stack); _exit(1); } return EXCEPTION_CONTINUE_SEARCH; } #endif static void _startup(int argc, char* argv[]) { char buffer[8] = { 0 }; size_t buffer_size = sizeof(buffer); bool tracking = uv_os_getenv("TF_MEM_TRACKING", buffer, &buffer_size) == 0 && strcmp(buffer, "1") == 0; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "sandbox") == 0) { tracking = false; } } tf_mem_startup(tracking); g_backtrace_state = backtrace_create_state(argv[0], 0, _backtrace_error, NULL); #if defined(__linux__) prctl(PR_SET_PDEATHSIG, SIGKILL); #endif tf_mem_replace_uv_allocator(); tf_mem_replace_tls_allocator(); tf_mem_replace_sqlite_allocator(); uv_setup_args(argc, argv); tf_taskstub_startup(); #if !defined(_WIN32) if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { perror("signal"); } #endif bool use_error_handler = false; #if defined(__ANDROID__) || defined(_WIN32) use_error_handler = true; #endif if (use_error_handler) { if ( #if !defined(_WIN32) signal(SIGSYS, _error_handler) == SIG_ERR || #endif signal(SIGABRT, _error_handler) == SIG_ERR || signal(SIGSEGV, _error_handler) == SIG_ERR) { perror("signal"); } } #if defined(_WIN32) AddVectoredExceptionHandler(0, _win32_exception_handler); #endif } #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, jobject connectivity_manager) { s_jni_env = env; tf_printf("This is tf_server_main main.\n"); _startup(0, (char*[]) { NULL }); tf_printf("That was startup.\n"); ares_library_init_android(connectivity_manager); ares_library_init(0); const char* files = (*env)->GetStringUTFChars(env, files_dir, NULL); const char* apk = (*env)->GetStringUTFChars(env, apk_path, NULL); const char* out_port_file = (*env)->GetStringUTFChars(env, out_port_file_path, NULL); tf_printf("FILES = %s\n", files); tf_printf("APK = %s\n", apk); tf_printf("OUT_PORT = %s\n", out_port_file); int result = uv_chdir(files); if (result) { tf_printf("uv_chdir: %s\n", uv_strerror(result)); } size_t port_file_arg_length = strlen(out_port_file) + strlen("out_http_port_file=") + 1; 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[] = { "run", "-z", apk, "-a", port_file_arg, "-p", "0", }; tf_task_set_android_service_callbacks(_tf_service_start, _tf_service_stop); result = _tf_command_run(apk, tf_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); ares_library_cleanup(); tf_mem_shutdown(); 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, tf_countof(args), (char**)args); tf_mem_shutdown(); tf_printf("tf_sandbox_main finished with %d.", result); s_jni_env = NULL; return result; } JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { tf_printf("JNI_Onload called.\n"); JNIEnv* env; if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) { tf_printf("Failed to get JNI environment.\n"); return JNI_ERR; } tf_printf("Finding class.\n"); jclass c = (*env)->FindClass(env, "com/unprompted/tildefriends/TildeFriendsActivity"); if (!c) { tf_printf("Failed to find TildeFriendsActivity class.\n"); return JNI_ERR; } tf_printf("Registering method.\n"); static const JNINativeMethod methods[] = { { "tf_server_main", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Landroid/net/ConnectivityManager;)I", _tf_server_main }, { "tf_sandbox_main", "(I)I", _tf_sandbox_main }, }; int result = (*env)->RegisterNatives(env, c, methods, tf_countof(methods)); if (result != JNI_OK) { return result; } ares_library_init_jvm(vm); tf_printf("Done.\n"); return JNI_VERSION_1_6; } int main(int argc, char* argv[]) { tf_printf("Welcome to Tilde Friends. This is not the way to run on Android.\n"); return EXIT_FAILURE; } #elif TARGET_OS_IPHONE void tf_run_thread_start(const char* zip_path) { _startup(0, NULL); uv_thread_t* thread = tf_malloc(sizeof(uv_thread_t)); tf_run_thread_data_t* data = tf_malloc(sizeof(tf_run_thread_data_t)); tf_run_args_t args = { .count = 1, .script = "core/core.js", .http_port = 12345, .https_port = 12346, .ssb_port = 8008, .db_path = "db.sqlite", .one_proc = true, .zip = zip_path, }; *data = (tf_run_thread_data_t) { .args = args, }; uv_thread_create(thread, _tf_run_task_thread, data); } #else int main(int argc, char* argv[]) { _startup(argc, argv); ares_library_init(0); int result = 0; if (argc >= 2) { for (int i = 0; i < tf_countof(k_commands); i++) { const command_t* command = &k_commands[i]; if (strcmp(argv[1], command->name) == 0) { result = command->callback(argv[0], argc - 1, argv + 1); goto done; } } result = _tf_command_usage(argv[0]); } else { result = _tf_command_run(argv[0], argc, argv); } done: ares_library_cleanup(); tf_mem_shutdown(); return result; } #endif