Files
codebase-memory-mcp/src/main.c
T
Andrew Hundt 3efb1cd95d fix(cli): handle cli help before tool dispatch
Handle codebase-memory-mcp cli -h/--help before memory initialization and MCP tool dispatch so the subcommand prints intentional help, exits 0, and emits no memory-init diagnostic or unknown-tool JSON error.\n\nValidation:\n- make -j8 -f Makefile.cbm cbm\n- git diff --check && bash scripts/check-source-safety.sh\n- isolated cli --help/root --help smokes\n- make -j8 -f Makefile.cbm build/c/test-runner && CBM_ONLY_SUITE=cli ./build/c/test-runner

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-01 06:05:30 -04:00

640 lines
24 KiB
C

/*
* main.c — Entry point for codebase-memory-mcp.
*
* Modes:
* (default) Run as MCP server on stdin/stdout (JSON-RPC 2.0)
* cli <tool> <json> Run a single tool call and print result
* --version Print version and exit
* --help Print usage and exit
* --ui=true/false Enable/disable HTTP UI server (persisted)
* --port=N Set HTTP UI port (persisted, default 9749)
*
* Signal handling: SIGTERM/SIGINT trigger graceful shutdown.
* Watcher runs in a background thread, polling for git changes.
* HTTP UI server (optional) runs in a background thread on localhost.
*/
#include "cbm.h" // cbm_alloc_init — bind 3rd-party allocators to mimalloc before any sqlite/git init
#include "mcp/mcp.h"
#include "watcher/watcher.h"
#include "pipeline/pipeline.h"
#include "store/store.h"
#include "depindex/depindex.h"
#include "pagerank/pagerank.h"
#include "cli/cli.h"
#include "cli/progress_sink.h"
#include "foundation/constants.h"
enum {
MAIN_MIN_ARGC = 1,
MAIN_CLI_ARGC = 2,
MAIN_FLAG_OFF = 5, /* strlen("--ui=") */
MAIN_PORT_OFF = 7, /* strlen("--port=") */
MAIN_MAX_PORT = 65536,
PARENT_WATCHDOG_STACK_SIZE = 64 * CBM_SZ_1K, /* watchdog only polls — tiny stack suffices */
PARENT_WATCHDOG_POLL_US = CBM_USEC_PER_SEC / 2,
};
#define MAIN_RAM_FRACTION 0.5
#define SLEN(s) (sizeof(s) - 1)
#include "foundation/log.h"
#include "foundation/diagnostics.h"
#include "foundation/platform.h"
#include "foundation/compat.h"
#include "foundation/compat_thread.h"
#include "foundation/mem.h"
#include "foundation/profile.h"
#include "ui/config.h"
#include "ui/http_server.h"
#include "ui/embedded_assets.h"
#include <yyjson/yyjson.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdatomic.h>
#ifndef CBM_VERSION
#define CBM_VERSION "dev"
#endif
/* ── Globals for signal handling ────────────────────────────────── */
static cbm_watcher_t *g_watcher = NULL;
static cbm_mcp_server_t *g_server = NULL;
static cbm_http_server_t *g_http_server = NULL;
static atomic_int g_shutdown = 0;
/* Idempotent shutdown: cancels the active pipeline, stops background servers,
* and closes stdin to unblock the MCP read loop. Invoked from the signal
* handler and from the parent-death watchdog, hence the atomic_exchange guard
* so the body runs at most once. Body is async-signal-safe (only atomic stores
* and stop calls that themselves only set atomics). */
static void request_shutdown(void) {
if (atomic_exchange(&g_shutdown, 1)) {
return; /* already shutting down */
}
/* Cancel any in-progress pipeline (async-signal-safe: only does atomic_store) */
if (g_server) {
cbm_pipeline_t *p = cbm_mcp_server_active_pipeline(g_server);
if (p) {
cbm_pipeline_cancel(p);
}
}
/* Release pipeline lock to prevent stale lock on restart */
cbm_pipeline_unlock();
if (g_watcher) {
cbm_watcher_stop(g_watcher);
}
if (g_http_server) {
cbm_http_server_stop(g_http_server);
}
/* Close stdin to unblock getline in the MCP server loop */
(void)fclose(stdin);
}
static void signal_handler(int sig) {
(void)sig;
request_shutdown();
}
/* ── Parent-process watchdog ────────────────────────────────────── */
/* parent-death watchdog — distilled from #407 (fixes #406, thanks @nvt-pankajsharma).
*
* When this stdio MCP server is launched by an agent that later dies without a
* clean SIGTERM (e.g. the editor is force-killed), the orphaned server would
* otherwise linger forever blocked on stdin. POSIX has no portable "notify on
* parent death" primitive (PR_SET_PDEATHSIG is Linux-only), so we poll getppid:
* once the parent dies the process is reparented (ppid changes, typically to 1)
* and we shut down. Windows is unaffected (job objects handle this) — #ifndef. */
#ifndef _WIN32
static void *parent_watchdog_thread(void *arg) {
pid_t initial_ppid = *(pid_t *)arg;
while (!atomic_load(&g_shutdown)) {
cbm_usleep(PARENT_WATCHDOG_POLL_US);
if (atomic_load(&g_shutdown)) {
break;
}
/* initial_ppid > 1 guards against an already-orphaned start (ppid==1),
* where a changing ppid carries no signal. */
if (initial_ppid > 1 && getppid() != initial_ppid) {
cbm_log_warn("parent.exited", "reason", "ppid_changed");
request_shutdown();
exit(0);
}
}
return NULL;
}
#endif
/* ── Watcher background thread ──────────────────────────────────── */
typedef struct {
cbm_watcher_t *w;
int base_ms;
int max_ms;
} watcher_thread_args_t;
static watcher_thread_args_t g_watcher_args; /* lifetime: static, no free needed */
static void *watcher_thread(void *arg) {
/* Fork: configurable base/max poll intervals passed via watcher_thread_args_t
* (read from config "watcher_poll_base_ms"/"watcher_poll_max_ms" at start_watcher).
* Upstream used a hardcoded WATCHER_BASE_INTERVAL_MS 5000; the fork's args struct
* is the superset and is what start_watcher() now passes. */
watcher_thread_args_t *a = arg;
cbm_watcher_run(a->w, a->base_ms, a->max_ms);
return NULL;
}
/* ── HTTP UI background thread ──────────────────────────────────── */
static void *http_thread(void *arg) {
cbm_http_server_t *srv = arg;
cbm_http_server_run(srv);
return NULL;
}
/* ── Index callback for watcher ─────────────────────────────────── */
static int watcher_index_fn(const char *project_name, const char *root_path, void *user_data) {
(void)user_data;
/* Skip indexing if shutdown is in progress */
if (atomic_load(&g_shutdown)) {
return 0;
}
/* Non-blocking: skip if another pipeline is already running.
* Watcher will retry on next poll cycle (5-60s). */
if (!cbm_pipeline_try_lock()) {
cbm_log_info("watcher.skip", "project", project_name, "reason", "pipeline_busy");
return 0;
}
cbm_log_info("watcher.reindex", "project", project_name, "path", root_path);
cbm_pipeline_t *p = cbm_pipeline_new(root_path, NULL, CBM_MODE_FULL);
if (!p) {
cbm_pipeline_unlock();
return CBM_NOT_FOUND;
}
int rc = cbm_pipeline_run(p);
cbm_pipeline_free(p);
/* Re-index dependencies after fresh dump. Uses cbm_project_name_from_path
* for consistent naming (matches pipeline's project_name derivation). */
if (rc == 0) {
char *pname = cbm_project_name_from_path(root_path);
cbm_store_t *store = cbm_store_open(pname);
if (store) {
cbm_dep_auto_index(pname, root_path, store, CBM_DEFAULT_AUTO_DEP_LIMIT, NULL);
cbm_pagerank_compute_default(store, pname);
cbm_store_close(store);
}
free(pname);
}
cbm_mem_collect();
cbm_pipeline_unlock();
return rc;
}
/* ── CLI mode ───────────────────────────────────────────────────── */
#define CLI_USAGE "Usage: codebase-memory-mcp cli [--progress] [--json] <tool_name> [json_args]\n"
static bool cli_args_request_help(int argc, char **argv) {
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
return true;
}
}
return false;
}
static void print_cli_help(void) {
fputs(CLI_USAGE, stdout);
fputs("\nOptions:\n", stdout);
fputs(" --json Print the raw MCP tool-result JSON envelope\n", stdout);
fputs(" --progress Print progress diagnostics to stderr during tool execution\n", stdout);
fputs("\nExamples:\n", stdout);
fputs(" codebase-memory-mcp cli search_graph '{\"query\":\"handler\"}'\n", stdout);
fputs(" codebase-memory-mcp cli --json trace_path '{\"function_name\":\"main\"}'\n", stdout);
fputs("\nRun `codebase-memory-mcp --help` for the default and advanced tool lists.\n", stdout);
}
/* Extract text content from MCP tool result envelope and print it.
* MCP results: {"content":[{"type":"text","text":"..."}],"isError":...}
* Returns 1 if the result was an error, 0 otherwise. */
static int cli_print_mcp_result(const char *result) {
yyjson_doc *doc = yyjson_read(result, strlen(result), 0);
if (!doc) {
printf("%s\n", result);
return 0;
}
yyjson_val *root = yyjson_doc_get_root(doc);
yyjson_val *err_val = yyjson_obj_get(root, "isError");
bool is_error = err_val && yyjson_get_bool(err_val);
const char *text = NULL;
yyjson_val *content = yyjson_obj_get(root, "content");
if (yyjson_is_arr(content) && yyjson_arr_size(content) > 0) {
yyjson_val *tv = yyjson_obj_get(yyjson_arr_get_first(content), "text");
text = tv ? yyjson_get_str(tv) : NULL;
}
if (text) {
(void)fprintf(is_error ? stderr : stdout, "%s\n", text);
} else {
printf("%s\n", result);
}
yyjson_doc_free(doc);
return is_error ? SKIP_ONE : 0;
}
/* Strip a flag from argv, returning true if found. */
static bool cli_strip_flag(int *argc, char **argv, const char *flag) {
for (int i = 0; i < *argc; i++) {
if (strcmp(argv[i], flag) != 0) {
continue;
}
for (int j = i; j < *argc - SKIP_ONE; j++) {
argv[j] = argv[j + SKIP_ONE];
}
(*argc)--;
return true;
}
return false;
}
static int run_cli(int argc, char **argv) {
if (argc < MAIN_MIN_ARGC) {
(void)fprintf(stderr, CLI_USAGE);
return SKIP_ONE;
}
bool progress = cli_strip_flag(&argc, argv, "--progress");
bool raw_json = cli_strip_flag(&argc, argv, "--json");
if (argc < MAIN_MIN_ARGC) {
(void)fprintf(stderr, CLI_USAGE);
return SKIP_ONE;
}
const char *tool_name = argv[0];
const char *args_json = argc >= MAIN_CLI_ARGC ? argv[SKIP_ONE] : "{}";
if (progress) {
cbm_progress_sink_init(stderr);
}
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
if (!srv) {
(void)fprintf(stderr, "error: failed to create server\n");
if (progress) {
cbm_progress_sink_fini();
}
return SKIP_ONE;
}
/* Match the stdio MCP server: one-shot CLI tools should honor registry
* defaults and config/env overrides such as auto_index and search_limit. */
cbm_config_t *runtime_config = NULL;
const char *cfg_home = cbm_get_home_dir();
if (cfg_home) {
runtime_config = cbm_config_open(cbm_resolve_cache_dir());
cbm_mcp_server_set_config(srv, runtime_config);
}
char *result = cbm_mcp_handle_tool(srv, tool_name, args_json);
int exit_code = 0;
if (result) {
if (raw_json) {
printf("%s\n", result);
} else {
exit_code = cli_print_mcp_result(result);
}
free(result);
}
cbm_mcp_server_free(srv);
cbm_config_close(runtime_config);
/* Union: fork's global pipeline cleanup (CLI mode: no background threads, safe
* to release process-lifetime state now) + upstream's progress-sink teardown and
* correct exit_code propagation. */
if (progress) {
cbm_progress_sink_fini();
}
cbm_pipeline_global_cleanup();
return exit_code;
}
/* ── Help ───────────────────────────────────────────────────────── */
static void print_help(void) {
printf("codebase-memory-mcp %s\n\n", CBM_VERSION);
printf("Usage:\n");
printf(" codebase-memory-mcp Run MCP server on stdio\n");
printf(" codebase-memory-mcp cli <tool> [json] Run a single tool\n");
printf(" codebase-memory-mcp install [-y|-n] [--force] [--dry-run]\n");
printf(" codebase-memory-mcp uninstall [-y|-n] [--dry-run]\n");
printf(" codebase-memory-mcp update [-y|-n]\n");
printf(" codebase-memory-mcp config <list|get|set|reset>\n");
printf(" codebase-memory-mcp --version Print version\n");
printf(" codebase-memory-mcp --help Print this help\n");
printf("\nUI options:\n");
printf(" --ui=true Enable HTTP graph visualization (persisted)\n");
printf(" --ui=false Disable HTTP graph visualization (persisted)\n");
printf(" --port=N Set UI port (default 9749, persisted)\n");
printf("\nSupported agents (auto-detected):\n");
printf(" Claude Code, Codex CLI, Gemini CLI, Zed, OpenCode,\n");
printf(" Antigravity, Aider, KiloCode, Kiro\n");
printf("\nDefault MCP tools: search_graph, query_graph, trace_path,\n");
printf(" search_code, get_code, _hidden_tools\n");
printf("\nAdvanced and CLI-callable tools: index_repository, get_code_snippet,\n");
printf(" get_graph_schema, get_architecture, list_projects, delete_project,\n");
printf(" index_status, detect_changes, manage_adr, ingest_traces,\n");
printf(" index_dependencies\n");
}
/* ── Main ───────────────────────────────────────────────────────── */
/* Try to handle a subcommand (cli/install/uninstall/update/config/--version/--help).
* Returns -1 if no subcommand matched, otherwise the exit code. */
static int handle_subcommand(int argc, char **argv) {
/* First scan: global flags */
for (int i = SKIP_ONE; i < argc; i++) {
if (strcmp(argv[i], "--profile") == 0) {
cbm_profile_enable();
}
}
for (int i = SKIP_ONE; i < argc; i++) {
if (strcmp(argv[i], "--version") == 0) {
printf("codebase-memory-mcp %s\n", CBM_VERSION);
return 0;
}
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
print_help();
return 0;
}
if (strcmp(argv[i], "cli") == 0) {
int cli_argc = argc - i - SKIP_ONE;
char **cli_argv = argv + i + SKIP_ONE;
if (cli_args_request_help(cli_argc, cli_argv)) {
print_cli_help();
return 0;
}
cbm_mem_init(MAIN_RAM_FRACTION);
return run_cli(cli_argc, cli_argv);
}
if (strcmp(argv[i], "hook-augment") == 0) {
cbm_mem_init(MAIN_RAM_FRACTION);
return cbm_cmd_hook_augment();
}
if (strcmp(argv[i], "install") == 0) {
return cbm_cmd_install(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
if (strcmp(argv[i], "uninstall") == 0) {
return cbm_cmd_uninstall(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
if (strcmp(argv[i], "update") == 0) {
return cbm_cmd_update(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
if (strcmp(argv[i], "config") == 0) {
return cbm_cmd_config(argc - i - SKIP_ONE, argv + i + SKIP_ONE);
}
}
return CBM_NOT_FOUND;
}
/* Parse --ui= and --port= flags. Returns true if config was modified. */
static bool parse_ui_flags(int argc, char **argv, cbm_ui_config_t *cfg, bool *explicit_enable) {
bool changed = false;
for (int i = SKIP_ONE; i < argc; i++) {
if (strncmp(argv[i], "--ui=", SLEN("--ui=")) == 0) {
cfg->ui_enabled = (strcmp(argv[i] + MAIN_FLAG_OFF, "true") == 0);
if (explicit_enable && cfg->ui_enabled) {
*explicit_enable = true;
}
changed = true;
}
if (strncmp(argv[i], "--port=", SLEN("--port=")) == 0) {
int p = (int)strtol(argv[i] + MAIN_PORT_OFF, NULL, CBM_DECIMAL_BASE);
if (p > 0 && p < MAIN_MAX_PORT) {
cfg->ui_port = p;
changed = true;
}
}
}
return changed;
}
/* Install platform-specific signal handlers. */
static void setup_signal_handlers(void) {
#ifdef _WIN32
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
#else
struct sigaction sa = {0};
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
#endif
}
int main(int argc, char **argv) {
/* Defense-in-depth: bind tree-sitter, sqlite3, and libgit2 to mimalloc so a
* correct binary does not rely on the fragile MI_OVERRIDE symbol override
* (#424). MUST be the VERY FIRST statement: SQLITE_CONFIG_MALLOC has to run
* before the first sqlite3_open* (cbm_mcp_server_new → cbm_store_open_memory
* below opens sqlite early), else sqlite3_config returns SQLITE_MISUSE and
* the bind is silently ignored. No-op in the test build. */
cbm_alloc_init();
cbm_profile_init(); /* reads CBM_PROFILE env var, gates all prof macros */
/* CBM_LOG_LEVEL support — distilled from #414 (closes #413). Apply before
* the first log statement so the configured level governs all output. */
cbm_log_init_from_env();
int subcmd = handle_subcommand(argc, argv);
if (subcmd >= 0) {
return subcmd;
}
/* parent-death watchdog — distilled from #407 (fixes #406). Start it early so
* an orphaned server exits even if it dies before reaching the MCP loop. A
* thread-create failure (or ppid<=1) is non-fatal: the server still runs, it
* just won't auto-exit on parent death — same policy as the watcher/HTTP
* threads below. We deliberately do NOT exit at startup when ppid<=1 (the PR's
* original behaviour): a legitimately-launched server can transiently show
* ppid==1 (early reparent races, double-fork/container launchers), and the
* watchdog already no-ops safely in that case via its initial_ppid>1 guard. */
#ifndef _WIN32
/* main() outlives the watchdog (it joins before returning), so a stack
* local is a valid lifetime for the thread's argument. */
pid_t initial_ppid = getppid();
cbm_thread_t parent_watchdog_tid;
bool parent_watchdog_started = false;
if (cbm_thread_create(&parent_watchdog_tid, PARENT_WATCHDOG_STACK_SIZE, parent_watchdog_thread,
&initial_ppid) == 0) {
parent_watchdog_started = true;
} else {
cbm_log_warn("parent.watchdog.unavailable", "reason", "thread_create_failed");
}
#endif
/* Default: MCP server on stdio */
cbm_mem_init(MAIN_RAM_FRACTION); /* 50% of RAM — safe now because mimalloc tracks ALL
* memory (C + C++ allocations) via global override.
* No more untracked heap blind spots. */
/* Store binary path for subprocess spawning + hook log sink */
cbm_http_server_set_binary_path(argv[0]);
cbm_log_set_sink(cbm_ui_log_append);
cbm_log_info("server.start", "version", CBM_VERSION);
cbm_diag_start(); /* starts if CBM_DIAGNOSTICS=1 */
/* Parse --ui and --port flags (persisted config) */
cbm_ui_config_t ui_cfg;
cbm_ui_config_load(&ui_cfg);
bool explicit_ui_enable = false;
if (parse_ui_flags(argc, argv, &ui_cfg, &explicit_ui_enable)) {
cbm_ui_config_save(&ui_cfg);
}
/* If the user explicitly asked for the UI but this binary has no embedded
* frontend, the HTTP server can never start (see below). The warning that
* covers this goes to the log sink, which a user running `--ui=true` on a
* terminal won't see — so tell them plainly on stderr why nothing happens
* and which build to use (#350). */
if (explicit_ui_enable && CBM_EMBEDDED_FILE_COUNT == 0) {
(void)fprintf(stderr,
"codebase-memory-mcp: --ui requested, but this binary was built without the "
"embedded UI, so the HTTP server will not start.\n"
"Use the UI release asset (codebase-memory-mcp-ui) or rebuild with: "
"make -f Makefile.cbm cbm-with-ui\n");
}
setup_signal_handlers();
/* Open config store for runtime settings */
char config_dir[CBM_SZ_1K];
const char *cfg_home = cbm_get_home_dir();
cbm_config_t *runtime_config = NULL;
if (cfg_home) {
snprintf(config_dir, sizeof(config_dir), "%s", cbm_resolve_cache_dir());
runtime_config = cbm_config_open(config_dir);
}
/* Create MCP server */
g_server = cbm_mcp_server_new(NULL);
if (!g_server) {
cbm_log_error("server.err", "msg", "failed to create server");
cbm_config_close(runtime_config);
#ifndef _WIN32
if (parent_watchdog_started) {
atomic_store(&g_shutdown, 1);
cbm_thread_join(&parent_watchdog_tid);
}
#endif
return SKIP_ONE;
}
/* Create and start watcher in background thread */
/* Initialize log mutex before any threads are created */
cbm_ui_log_init();
cbm_store_t *watch_store = cbm_store_open_memory();
g_watcher = cbm_watcher_new(watch_store, watcher_index_fn, NULL);
/* Wire watcher + config into MCP server for session auto-index */
cbm_mcp_server_set_watcher(g_server, g_watcher);
cbm_mcp_server_set_config(g_server, runtime_config);
cbm_thread_t watcher_tid;
bool watcher_started = false;
if (g_watcher) {
g_watcher_args.w = g_watcher;
g_watcher_args.base_ms = runtime_config
? cbm_config_get_int(runtime_config, "watcher_poll_base_ms", 5000)
: 5000;
g_watcher_args.max_ms = runtime_config
? cbm_config_get_int(runtime_config, "watcher_poll_max_ms", 60000)
: 60000;
if (cbm_thread_create(&watcher_tid, 0, watcher_thread, &g_watcher_args) == 0) {
watcher_started = true;
}
}
/* Optionally start HTTP UI server in background thread */
cbm_thread_t http_tid;
bool http_started = false;
if (ui_cfg.ui_enabled && CBM_EMBEDDED_FILE_COUNT > 0) {
g_http_server = cbm_http_server_new(ui_cfg.ui_port);
if (g_http_server) {
if (cbm_thread_create(&http_tid, 0, http_thread, g_http_server) == 0) {
http_started = true;
}
}
} else if (ui_cfg.ui_enabled && CBM_EMBEDDED_FILE_COUNT == 0) {
cbm_log_warn("ui.no_assets", "hint", "rebuild with: make -f Makefile.cbm cbm-with-ui");
}
/* MCP stdio: force unbuffered I/O so responses are sent immediately.
* C defaults to fully-buffered when stdout is piped (as MCP clients do).
* fflush() is already called after each write, but this is defense-in-depth. */
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
/* Run MCP event loop (blocks until EOF or signal) */
int rc = cbm_mcp_server_run(g_server, stdin, stdout);
atomic_store(&g_shutdown, 1); /* unblock the watchdog poll loop */
/* Shutdown */
cbm_log_info("server.shutdown");
#ifndef _WIN32
if (parent_watchdog_started) {
cbm_thread_join(&parent_watchdog_tid);
}
#endif
if (http_started) {
cbm_http_server_stop(g_http_server);
cbm_thread_join(&http_tid);
cbm_http_server_free(g_http_server);
g_http_server = NULL;
}
/* Join autoindex thread first — it may reference watcher and store.
* cbm_mcp_server_free joins the autoindex thread internally. */
cbm_mcp_server_free(g_server);
/* Release pipeline-level global state (compiled regex patterns etc.).
* Called here — after ALL server threads are joined — to avoid a race between
* the stdio server's autoindex thread (joined above) and the HTTP server's
* cleanup (which ran earlier in cbm_http_server_free). */
cbm_pipeline_global_cleanup();
if (watcher_started) {
cbm_watcher_stop(g_watcher);
cbm_thread_join(&watcher_tid);
}
cbm_watcher_free(g_watcher);
cbm_store_close(watch_store);
cbm_config_close(runtime_config);
g_watcher = NULL;
g_server = NULL;
cbm_diag_stop();
return rc;
}