From 4ab938fde82b3216739b3d063d4ca4d782ae7d16 Mon Sep 17 00:00:00 2001 From: CUBELinux-2 Date: Tue, 11 Aug 2026 18:33:28 -0400 Subject: [PATCH] tools/cubec: fix CLI so ./check stress works; separate client flags from command cubec forwarded its OWN leading options (--socket/--tenant/...) verbatim into the command payload sent to the daemon, so 'cubec --socket SOCK "prog ..."' made the server reject '--socket' as an unknown command. stress.sh therefore produced 'error: unknown command: --socket' on every sample and measured nothing. Split cubec arg parsing into client-option vs command-payload so flags are consumed locally and only the command reaches the daemon. The canonical './check stress' stage now drives a real daemon and samples stats. --- cubesys/src/bin/cubec.rs | 83 +++++++++++++++++++++++++++++++--------- tools/stress.sh | 3 ++ 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/cubesys/src/bin/cubec.rs b/cubesys/src/bin/cubec.rs index 7c24237..4d75fdd 100644 --- a/cubesys/src/bin/cubec.rs +++ b/cubesys/src/bin/cubec.rs @@ -13,29 +13,74 @@ use cubesys::net::{read_stream_frame, write_frame}; use std::io::{BufRead, Write}; use std::os::unix::net::UnixStream; -/// Resolve `--key VALUE` from argv, or None. -fn arg_value(args: &[String], key: &str) -> Option { - args.iter() - .position(|a| a == key) - .and_then(|i| args.get(i + 1).cloned()) -} - +/// `cubec` — a tiny Unix-domain-socket client for the `cube-server` daemon. +/// +/// Separates cubec's OWN options (`--socket`, `--tenant`, `--owner`, +/// `--auth-key*`) from the command it forwards to the daemon. Conflating the +/// two made `cubec --socket SOCK "prog ..."` send `--socket SOCK prog ...` to +/// the server, which rejected it as an unknown command. fn main() { - let args: Vec = std::env::args().skip(1).collect(); - let socket = arg_value(&args, "--socket") - .or_else(|| std::env::var("CUBE_SOCKET").ok()) - .unwrap_or_else(|| "/run/cube/demo.sock".to_string()); - let tenant = arg_value(&args, "--tenant").unwrap_or_else(|| "default".to_string()); - let owner = arg_value(&args, "--owner") - .or_else(|| std::env::var("USER").ok()) - .unwrap_or_else(|| "cubec".to_string()); + let raw: Vec = std::env::args().skip(1).collect(); + + // Defaults from the environment (kept so legacy invocations still work). + let mut socket = + std::env::var("CUBE_SOCKET").unwrap_or_else(|_| "/run/cube/demo.sock".to_string()); + let mut tenant = "default".to_string(); + let mut owner = std::env::var("USER").unwrap_or_else(|_| "cubec".to_string()); + let mut psk_path: Option = None; + let mut psk_env: Option = None; + + // Everything that is not one of cubec's options becomes the command text + // forwarded to the daemon. + let mut cmd_args: Vec = Vec::new(); + let mut i = 0; + while i < raw.len() { + match raw[i].as_str() { + "--socket" => { + if let Some(v) = raw.get(i + 1) { + socket = v.clone(); + } + i += 2; + } + "--tenant" => { + if let Some(v) = raw.get(i + 1) { + tenant = v.clone(); + } + i += 2; + } + "--owner" => { + if let Some(v) = raw.get(i + 1) { + owner = v.clone(); + } + i += 2; + } + "--auth-key" => { + if let Some(v) = raw.get(i + 1) { + psk_path = Some(v.clone()); + } + i += 2; + } + "--auth-key-env" => { + if let Some(v) = raw.get(i + 1) { + psk_env = Some(v.clone()); + } + i += 2; + } + // Not a cubec option: part of the command payload. + other => { + cmd_args.push(other.to_string()); + i += 1; + } + } + } + // Pre-shared key for the signed-HELLO handshake (plan R4). `--auth-key PATH` // reads a file; `--auth-key-env VAR` reads an env var; otherwise `CUBE_AUTH_KEY`. - let psk: Option> = if let Some(path) = arg_value(&args, "--auth-key") { + let psk: Option> = if let Some(path) = psk_path { std::fs::read_to_string(&path) .map(|s| s.trim().as_bytes().to_vec()) .ok() - } else if let Some(var) = arg_value(&args, "--auth-key-env") { + } else if let Some(var) = psk_env { std::env::var(&var) .ok() .map(|s| s.trim().as_bytes().to_vec()) @@ -79,7 +124,7 @@ fn main() { } } - if args.is_empty() { + if cmd_args.is_empty() { // REPL mode (legacy behaviour, now after an optional handshake). let stdin = std::io::stdin(); for line in stdin.lock().lines().map_while(Result::ok) { @@ -101,7 +146,7 @@ fn main() { } } else { // One-shot: join the remaining args as the command line, send, print. - let cmd = args.join(" "); + let cmd = cmd_args.join(" "); if write_frame(&mut stream, &cmd).is_err() { eprintln!("cubec: write failed"); std::process::exit(1); diff --git a/tools/stress.sh b/tools/stress.sh index bec2e07..51de3ef 100755 --- a/tools/stress.sh +++ b/tools/stress.sh @@ -47,6 +47,9 @@ PAIRS=0 SAMPLE_EVERY=25 # sample stats every 25 pairs (~ once a second at load) while [ "$(date +%s)" -lt "$END" ]; do X=$((PAIRS % 256)) + # Flags MUST precede the command: `cubec` parses --socket/--tenant as leading + # options, so `"$CLI" --socket SOCK "prog ..."` would turn the trailing + # --socket into an unknown subcommand. Pass flags first. "$CLI" --socket "$SOCK" "prog /c077/z001/y001/x$X const $((PAIRS % 97)) halt" >/dev/null 2>&1 "$CLI" --socket "$SOCK" "run /c077/z001/y001/x$X" >/dev/null 2>&1 PAIRS=$((PAIRS + 1))