// swarmd-ctl — talk to a running daemon instead of hunting for its process.
//
// swarmd-ctl --socket <path> status
// swarmd-ctl --socket <path> join <words>
// swarmd-ctl --socket <path> leave <words>
// swarmd-ctl --socket <path> stop
// swarmd-ctl --socket <path> watch [--seconds N]
//
// ⛔ THIS EXISTS BECAUSE `pkill -f` IS NOT A CONTROL PLANE. A pattern that
// matches a daemon's argv also matches the SHELL that typed it: `pkill -f
// "session-daemon --topic mountain"` run over ssh killed the ssh session's own
// remote shell — twice in this project's history, once in the session that wrote
// this file. It is also indiscriminate: two daemons on two topics share every
// substring worth matching on.
//
// `stop` reads the pidfile the daemon writes beside its socket and signals THAT
// pid. One process, named by the daemon itself, never a pattern.
//
// ⛔ JOIN AND LEAVE WORK ON A RUNNING DAEMON. The topic was a start-up argument
// and nothing else, so changing session meant killing a process — which is why
// killing processes was the interface. The RPCs have existed since the control
// plane was written; nothing exposed them.
//
// The topic is BLAKE2b(words), the same derivation session-daemon and
// swarmd-session use, so the words you type here are the words you type in
// Blender.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <csignal>
#include <unistd.h>
#include <sodium.h>
#include "client.h"
static void usage() {
fprintf(stderr,
"usage: swarmd-ctl --socket <path> <command>\n"
" status what the daemon says about itself\n"
" join <words> join a topic, on a daemon that is already running\n"
" leave <words> leave one\n"
" stop terminate it by the pid in its pidfile\n"
" watch [--seconds N] status, repeatedly\n");
}
static void topic_of(const char *words, uint8_t out[32]) {
crypto_generichash(out, 32, (const uint8_t *)words, strlen(words), nullptr, 0);
}
static int do_stop(const std::string &sock) {
// ⛔ BY PID, FROM THE DAEMON'S OWN PIDFILE. Never by pattern.
const std::string pidfile = sock + ".pid";
FILE *f = fopen(pidfile.c_str(), "r");
if (!f) {
fprintf(stderr, "no pidfile at %s — is this a swarmd socket?\n", pidfile.c_str());
return 1;
}
long pid = 0;
const int got = fscanf(f, "%ld", &pid);
fclose(f);
if (got != 1 || pid <= 0) {
fprintf(stderr, "%s does not contain a pid\n", pidfile.c_str());
return 1;
}
if (kill((pid_t)pid, SIGTERM) != 0) {
fprintf(stderr, "could not signal %ld (already gone?)\n", pid);
return 1;
}
printf("stopped %ld\n", pid);
return 0;
}
static void print_status(const swarmd::Status &st, int local) {
const char *dht = st.dht_state == 2 ? "up" : (st.dht_state == 1 ? "connecting" : "starting");
printf("dht=%s peers=%llu known=%llu connecting=%llu queued=%llu local=%d\n",
dht, (unsigned long long)st.peers_connected,
(unsigned long long)st.peers_known, (unsigned long long)st.connecting,
(unsigned long long)st.queued, local);
fflush(stdout);
}
int main(int argc, char **argv) {
if (sodium_init() < 0) { fprintf(stderr, "sodium_init failed\n"); return 1; }
const char *sock = nullptr;
int seconds = 0;
std::string cmd, words;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--socket") && i + 1 < argc) sock = argv[++i];
else if (!strcmp(argv[i], "--seconds") && i + 1 < argc) seconds = atoi(argv[++i]);
else if (cmd.empty()) cmd = argv[i];
else if (words.empty()) words = argv[i];
}
if (!sock || cmd.empty()) { usage(); return 2; }
if (cmd == "stop") return do_stop(sock);
swarmd::Client c;
if (!c.connect(sock, "swarmd-ctl")) {
fprintf(stderr, "%s\n", c.last_error().c_str());
return 1;
}
if (cmd == "status" || cmd == "watch") {
const int rounds = (cmd == "watch") ? (seconds > 0 ? seconds : 3600) : 1;
for (int i = 0; i < rounds; i++) {
swarmd::Status st;
if (!c.status(st)) { fprintf(stderr, "%s\n", c.last_error().c_str()); return 1; }
print_status(st, (int)st.local_clients);
if (cmd == "status") break;
if (!c.poll(1000)) break;
}
return 0;
}
if (cmd == "join" || cmd == "leave") {
if (words.empty()) { usage(); return 2; }
uint8_t topic[32];
topic_of(words.c_str(), topic);
const bool ok = (cmd == "join") ? c.join(topic) : c.leave(topic);
if (!ok) { fprintf(stderr, "%s\n", c.last_error().c_str()); return 1; }
// ⚠ Say what was done to WHICH topic: the words are hashed, and two
// similar names are indistinguishable in a log otherwise.
char hex[17];
for (int i = 0; i < 8; i++) sprintf(hex + i * 2, "%02x", topic[i]);
hex[16] = 0;
printf("%s %s (topic %s…)\n", cmd == "join" ? "joined" : "left",
words.c_str(), hex);
return 0;
}
usage();
return 2;
}