diff --git a/Makefile b/Makefile index 464fc1e..1786723 100644 --- a/Makefile +++ b/Makefile @@ -70,8 +70,8 @@ rayforce: pull_core @mkdir -p $(CORE)/src/q @cp q.c q.h q_server.c q_server.h embed/rayforce_q.c $(CORE)/src/q/ @sed -e 's@ray_runtime_t\* rt = ray_runtime_create(argc, argv);@& extern void q_env_register(void); if (rt) q_env_register();@' \ - -e 's@ else if (strcmp(argv\[i\], "--") == 0)@ else if ((strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) \&\& i + 1 < argc) { i++; } &@' \ - -e 's@if (poll) ray_runtime_set_poll(poll);@if (poll) { ray_runtime_set_poll(poll); extern int64_t q_serve_from_args(ray_poll_t*, int, char**); q_serve_from_args(poll, argc, argv); }@' \ + -e 's@ else if (strcmp(argv\[i\], "--") == 0)@ else if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) { if (i + 1 < argc) i++; } &@' \ + -e 's@if (poll) ray_runtime_set_poll(poll);@if (poll) { ray_runtime_set_poll(poll); extern int64_t q_serve_from_args(ray_poll_t*, int, char**); if (q_serve_from_args(poll, argc, argv) < 0) { rc = 2; goto done; } }@' \ $(CORE)/src/app/main.c > $(CORE)/src/app/main.c.q && \ mv $(CORE)/src/app/main.c.q $(CORE)/src/app/main.c @$(MAKE) -C $(CORE) release diff --git a/README.md b/README.md index e27550d..714d2fe 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,12 @@ The server is exposed via the `-q PORT` flag — it shares the REPL's event loop ./rayforce -q 25565 # serve Rayfall over the Q wire on port 25565 ``` +Both `-q` and `--q-serve` require a decimal TCP port in `1..65535`. A +missing or invalid port prints a diagnostic and exits with status `2`, so a +service supervisor does not mistake a failed Q listener for a healthy +process. Arguments after `--` are passed to the Rayfall application and are +not interpreted as Q-server options. + # API See **[INTEGRATING.md](./docs/INTEGRATING.md)** for more details. diff --git a/embed/rayforce_q.c b/embed/rayforce_q.c index ec886da..d757e4e 100644 --- a/embed/rayforce_q.c +++ b/embed/rayforce_q.c @@ -212,12 +212,18 @@ void q_env_register(void) { ray_release(f); } -/* Folded entry point, called from the rayforce binary's main() - * Starts the Q server when `-q PORT` / `--q-serve PORT` is present */ +/* Folded entry point, called from the rayforce binary's main(). + * Returns the listener id on success, 0 when no Q-server flag is present, + * or -1 when a Q-server flag is malformed or cannot be started. */ int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv) { for (int i = 1; i < argc; i++) { - if ((strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) && - i + 1 < argc) { + if (strcmp(argv[i], "--") == 0) + break; + if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--q-serve") == 0) { + if (i + 1 >= argc) { + fprintf(stderr, "q: missing port after %s (expected 1..65535)\n", argv[i]); + return -1; + } int port = 0; if (!q_parse_port(argv[++i], &port)) { fprintf(stderr, "q: invalid port %s (expected 1..65535)\n", argv[i]); @@ -226,5 +232,5 @@ int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv) { return q_serve(poll, port); } } - return -1; + return 0; } diff --git a/test/driver.c b/test/driver.c index 8bdf204..fa2a7b7 100644 --- a/test/driver.c +++ b/test/driver.c @@ -37,6 +37,7 @@ /* Registers `.q.connect` / `.q.send` / `.q.close` */ void q_env_register(void); +int64_t q_serve_from_args(ray_poll_t *poll, int argc, char **argv); typedef struct { uint8_t endianness; @@ -72,6 +73,42 @@ static int run_server(int port) { return 0; } +static int run_q_cli_selftest(void) { + ray_runtime_t *rt = ray_runtime_create(0, NULL); + if (rt == NULL) { + fprintf(stderr, "q CLI selftest: failed to create runtime\n"); + return 1; + } + ray_poll_t *poll = ray_poll_create(); + if (poll == NULL) { + fprintf(stderr, "q CLI selftest: failed to create poll\n"); + ray_runtime_destroy(rt); + return 1; + } + + char *no_flag[] = {"rayforce", "--file", "app.rfl"}; + char *bad_port[] = {"rayforce", "-q", "70000"}; + char *junk_port[] = {"rayforce", "--q-serve", "1abc"}; + char *missing_port[] = {"rayforce", "-q"}; + char *app_args[] = {"rayforce", "--", "-q", "70000"}; + int failures = 0; + if (q_serve_from_args(poll, 3, no_flag) != 0) + failures++; + if (q_serve_from_args(poll, 3, bad_port) >= 0) + failures++; + if (q_serve_from_args(poll, 3, junk_port) >= 0) + failures++; + if (q_serve_from_args(poll, 2, missing_port) >= 0) + failures++; + if (q_serve_from_args(poll, 4, app_args) != 0) + failures++; + + ray_poll_destroy(poll); + ray_runtime_destroy(rt); + printf("q CLI selftest: %s\n", failures ? "FAIL" : "ok"); + return failures ? 1 : 0; +} + static int fmt_eq(ray_t *a, ray_t *b) { if (a == NULL && b == NULL) return 1; @@ -426,6 +463,9 @@ int main(int argc, char **argv) { if (argc >= 2 && strcmp(argv[1], "--exchange-selftest") == 0) return run_exchange_selftest(); + if (argc >= 2 && strcmp(argv[1], "--q-cli-selftest") == 0) + return run_q_cli_selftest(); + /* Server role: `driver --serve PORT`. */ if (argc >= 3 && strcmp(argv[1], "--serve") == 0) return run_server(atoi(argv[2])); diff --git a/test/run.sh b/test/run.sh index 9cd1912..5e71288 100755 --- a/test/run.sh +++ b/test/run.sh @@ -91,6 +91,9 @@ echo "running codec selftest..." echo "running exchange selftest..." "$DRIVER" --exchange-selftest +echo "running Q CLI argument selftest..." +"$DRIVER" --q-cli-selftest + # ---- Leg 1: Rayforce server SERVERPORT="${SERVERPORT:-$(free_port)}" "$DRIVER" --serve "$SERVERPORT" &