#define _POSIX_C_SOURCE 200809L
#include "core/poll.h"
#include "core/ipc.h"
#include "core/pool.h"
#include "core/profile.h"
#include "core/qlog.h"
#include "core/crash.h"
#include "app/repl.h"
#include "core/runtime.h"
#include "store/journal.h"
#include "ops/internal.h"
#include "ops/idxop.h"
#include "mem/heap.h"
#include <rayforce.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
static int64_t parse_size_arg(const char* s) {
if (!s || !*s) return -1;
char* end = NULL;
errno = 0;
double v = strtod(s, &end);
if (errno != 0 || end == s || v < 0) return -1;
int64_t mult = 1;
if (*end) {
switch (*end) {
case 'k': case 'K': mult = 1LL << 10; break;
case 'm': case 'M': mult = 1LL << 20; break;
case 'g': case 'G': mult = 1LL << 30; break;
case 't': case 'T': mult = 1LL << 40; break;
default: return -1;
}
if (end[1] != '\0') return -1;
}
return (int64_t)(v * (double)mult);
}
int main(int argc, char** argv) {
ray_crash_install();
ray_expr_stats_init();
ray_idx_stats_init();
ray_runtime_t* rt = ray_runtime_create(argc, argv);
if (!rt) { fprintf(stderr, "failed to create runtime\n"); return 1; }
int rc = 0;
int interactive = 0;
const char* file = NULL;
uint16_t port = 0;
char bind_host[64] = "";
int n_cores = -1;
int timeit_init = 0;
int qlog_init = 0;
const char* auth_pw = NULL;
bool auth_restricted = false;
const char* log_base = NULL;
ray_journal_mode_t log_mode = RAY_JOURNAL_OFF;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--interactive") == 0)
interactive = 1;
else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) {
const char* parg = argv[++i];
const char* colon = strchr(parg, ':');
const char* pstr = parg;
if (colon) {
size_t hlen = (size_t)(colon - parg);
if (hlen == 0 || hlen >= sizeof(bind_host)) {
fprintf(stderr, "bad -p bind address: %s\n", parg);
return 2;
}
memcpy(bind_host, parg, hlen);
bind_host[hlen] = '\0';
pstr = colon + 1;
}
char* endp;
errno = 0;
long pv = strtol(pstr, &endp, 10);
if (endp == pstr || *endp != '\0' || errno == ERANGE || pv <= 0 || pv > 65535) {
fprintf(stderr, "bad -p port (must be 1..65535): %s\n", parg);
return 2;
}
port = (uint16_t)pv;
}
else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--cores") == 0) && i + 1 < argc) {
int v = atoi(argv[++i]);
if (v < 0) v = 0;
n_cores = v;
}
else if ((strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timeit") == 0) && i + 1 < argc) {
int v = atoi(argv[++i]);
timeit_init = (v != 0);
}
else if ((strcmp(argv[i], "-Q") == 0 || strcmp(argv[i], "--querylog") == 0) && i + 1 < argc) {
int v = atoi(argv[++i]);
qlog_init = (v != 0);
}
else if ((strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--file") == 0) && i + 1 < argc) {
file = argv[++i];
}
else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) {
auth_pw = argv[++i];
auth_restricted = false;
}
else if (strcmp(argv[i], "-U") == 0 && i + 1 < argc) {
auth_pw = argv[++i];
auth_restricted = true;
}
else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
log_base = argv[++i];
log_mode = RAY_JOURNAL_ASYNC;
}
else if (strcmp(argv[i], "-L") == 0 && i + 1 < argc) {
log_base = argv[++i];
log_mode = RAY_JOURNAL_SYNC;
}
else if ((strcmp(argv[i], "-m") == 0 || strcmp(argv[i], "--mem") == 0) && i + 1 < argc) {
int64_t wm = parse_size_arg(argv[++i]);
if (wm <= 0) {
fprintf(stderr, "error: -m/--mem expects a size like 4G, 512M, or a byte count\n");
ray_runtime_destroy(rt);
return 1;
}
ray_heap_set_anon_watermark(wm);
}
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
fprintf(stdout,
"usage: %s [-f file] [-p port] [-c cores] [-t 0|1] [-i]"
" [-u PW | -U PW] [-l BASE | -L BASE] [-m SIZE] [file.rfl] [-- app args]\n"
" -f, --file FILE run script file (or pass as a positional arg)\n"
" -p, --port PORT listen for IPC clients on PORT (all interfaces)\n"
" --port HOST:PORT bind the listener to HOST only (e.g. 127.0.0.1:7701)\n"
" -c, --cores N total execution cores, main included (0 = auto)\n"
" -t, --timeit N enable profiler at startup (N != 0)\n"
" -i, --interactive start the REPL even after running a file\n"
" -u PW set plain auth password\n"
" -U PW set restricted auth password\n"
" -l BASE enable journal logging (BASE.log + BASE.qdb)\n"
" -L BASE enable journal logging with fsync per write\n"
" -m, --mem SIZE RAM budget (e.g. 4G, 512M); allocations past it\n"
" spill to disk instead of OOM (default: physical RAM)\n"
" -h, --help show this message\n"
" -- pass following args to the app; read\n"
" them in Rayfall via (.sys.args)\n"
"\nFor a remote REPL, start rayforce locally and call\n"
"(.repl.connect \"host:port\") inside the REPL.\n"
"\nRunning as a service:\n"
" When stdin and stdout are both not a terminal (systemd\n"
" Type=simple, Docker, nohup &, etc.), rayforce skips the\n"
" REPL and runs only the IPC poll loop. Redirect stdout\n"
" and stderr to a file/journal — anything (println ...)\n"
" prints from the server side goes there. Example:\n"
" nohup rayforce -p 5000 > rayforce.log 2>&1 &\n"
" systemd: StandardOutput=journal StandardError=journal\n"
" With -p and a script, a script that fails to parse or\n"
" evaluate is fatal: the listener closes and the process\n"
" exits non-zero (with -i the REPL stays up instead).\n",
argv[0]);
ray_runtime_destroy(rt);
return 0;
}
else if (strcmp(argv[i], "--") == 0)
break;
else
file = argv[i];
}
ray_runtime_set_sys_args(ray_build_sys_args(argc, argv));
if (n_cores >= 0) {
ray_err_t err = ray_pool_init_total((uint32_t)n_cores);
if (err != RAY_OK)
fprintf(stderr, "warning: ray_pool_init_total(%d) failed (%d)\n",
n_cores, (int)err);
}
if (timeit_init)
g_ray_profile.active = true;
if (qlog_init)
ray_qlog_set_enabled(true);
ray_poll_t* poll = ray_poll_create();
if (poll) ray_runtime_set_poll(poll);
if (poll && auth_pw) {
size_t pw_len = strlen(auth_pw);
if (pw_len >= sizeof(poll->auth_secret))
pw_len = sizeof(poll->auth_secret) - 1;
memcpy(poll->auth_secret, auth_pw, pw_len);
poll->auth_secret[pw_len] = '\0';
ray_poll_set_restricted(poll, auth_restricted);
}
if (log_base) {
ray_err_t le = ray_journal_open(log_base, log_mode);
if (le != RAY_OK) {
fprintf(stderr, "log: open failed for base=%s (rc=%d)\n",
log_base, (int)le);
if (poll) ray_poll_destroy(poll);
ray_runtime_destroy(rt);
return 2;
}
}
if (port > 0) {
const char* bh = bind_host[0] ? bind_host : NULL;
if (poll && ray_ipc_listen_at(poll, bh, port) >= 0) {
fprintf(stderr, "listening on %s:%u\n", bh ? bh : "*", port);
} else {
fprintf(stderr, "failed to listen on %s:%u: %s\n",
bh ? bh : "*", port, strerror(errno));
rc = 1;
goto done;
}
}
if (file) {
rc = ray_repl_run_file(file);
if (rc != 0 && !interactive && port > 0) {
fprintf(stderr, "startup script %s failed; closing listener on %s:%u and exiting\n",
file, bind_host[0] ? bind_host : "*", port);
goto done;
}
if (!interactive && !(port > 0)) {
if (poll) ray_poll_drain_timers(poll);
goto done;
}
}
bool stdin_tty = isatty(STDIN_FILENO);
bool stdout_tty = isatty(STDOUT_FILENO);
bool server_only = poll && port > 0 && !file && !interactive
&& !stdin_tty && !stdout_tty;
if (server_only) {
#ifndef RAY_OS_WINDOWS
signal(SIGPIPE, SIG_IGN);
#endif
fprintf(stderr, "no terminal — running in server-only mode\n");
ray_poll_run(poll);
} else {
ray_repl_t* repl = ray_repl_create(poll);
if (repl) {
ray_repl_run(repl);
ray_repl_destroy(repl);
} else if (poll && port > 0) {
ray_poll_run(poll);
}
}
done:
ray_journal_close();
if (poll) ray_poll_destroy(poll);
ray_runtime_destroy(rt);
return rc;
}