tildefriends/src/main.c
Cory McWilliams 7d9e0f4080
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 22m44s
macos: Fix build.
2025-01-02 14:20:58 -05:00

1269 lines
30 KiB
C

#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 <getopt.h>
#include <stdlib.h>
#include <string.h>
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__HAIKU__)
#include <signal.h>
#include <sys/resource.h>
#endif
#if defined(__linux__)
#include <sys/prctl.h>
#include <sys/stat.h>
#endif
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#if !defined(_WIN32)
#include <unistd.h>
#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