From 2bff501a62a3bba1aeb39f49f7c785d541ca209e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cochard-Labb=C3=A9?= Date: Fri, 21 Aug 2026 08:14:09 +0200 Subject: [PATCH] fix(freebsd): treat ${LOCALBASE}/bin as pkg-managed; resolve self via sysctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two FreeBSD gaps remain after the runtime rework of install/update (#1566) and the KERN_PROC_PATHNAME self-path fix landed on main. 1. cli_external_manager_name() matches only positive evidence (mise, Homebrew, nix, asdf, cargo). ${LOCALBASE}/bin was not among them, so a ports/pkg install of cbm was still copied into ~/.local/bin and a shell rc was edited -- exactly what externally-managed detection is meant to prevent. Recognise ${LOCALBASE}/bin and /sbin on FreeBSD so install skips the copy and update defers to `pkg upgrade`. LOCALBASE is configurable, so the FreeBSD port passes its real PREFIX via -DCBM_PKG_PREFIX and we honour it, falling back to the documented /usr/local default for a plain upstream build. --force-binary remains the escape hatch for anyone self-managing that prefix. 2. resolve_self_executable() in the HTTP server still read /proc/self/exe, which FreeBSD does not mount by default. Add a KERN_PROC_PATHNAME sysctl branch, mirroring cbm_detect_self_path(). Test updated: on FreeBSD ${LOCALBASE}/bin/cbm is now externally managed; a non-LOCALBASE prefix (/opt/local) is not. Signed-off-by: Olivier Cochard-Labbé --- src/cli/cli.c | 23 +++++++++++++++++++++++ src/ui/http_server.c | 13 +++++++++++++ tests/test_cli.c | 10 +++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/cli/cli.c b/src/cli/cli.c index d981af266..e90d8b885 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -9699,6 +9699,27 @@ static const char *cli_external_manager_name(const char *self_path) { if (strstr(self_path, "/.cargo/bin/")) { return "cargo"; } +#ifdef __FreeBSD__ + /* FreeBSD ports/pkg install under ${LOCALBASE} (default /usr/local). pkg owns + * that file, so install must not drop a second copy in ~/.local/bin or edit a + * shell rc, and update must defer to pkg(8). Anchor at the start so a manual + * `install --dir=/opt/...` elsewhere is still treated as ours; --force-binary + * is the escape hatch for anyone who really does self-manage that prefix. + * + * LOCALBASE is configurable, so the port passes its real PREFIX via + * -DCBM_PKG_PREFIX; when it is absent we fall back to the documented default. */ +#ifdef CBM_PKG_PREFIX + if (strncmp(self_path, CBM_PKG_PREFIX "/bin/", sizeof(CBM_PKG_PREFIX "/bin/") - 1) == 0 || + strncmp(self_path, CBM_PKG_PREFIX "/sbin/", sizeof(CBM_PKG_PREFIX "/sbin/") - 1) == 0) { + return "FreeBSD pkg"; + } +#else + if (strncmp(self_path, "/usr/local/bin/", 15) == 0 || + strncmp(self_path, "/usr/local/sbin/", 16) == 0) { + return "FreeBSD pkg"; + } +#endif +#endif return NULL; } @@ -12227,6 +12248,8 @@ int cbm_cmd_update(int argc, char **argv) { (void)fprintf(stderr, " update it with: mise upgrade codebase-memory-mcp\n"); } else if (manager && strcmp(manager, "Homebrew") == 0) { (void)fprintf(stderr, " update it with: brew upgrade codebase-memory-mcp\n"); + } else if (manager && strcmp(manager, "FreeBSD pkg") == 0) { + (void)fprintf(stderr, " update it with: pkg upgrade codebase-memory-mcp\n"); } else { (void)fprintf(stderr, " update it through whichever tool installed it.\n"); } diff --git a/src/ui/http_server.c b/src/ui/http_server.c index 18589d8d8..409267358 100644 --- a/src/ui/http_server.c +++ b/src/ui/http_server.c @@ -63,6 +63,10 @@ #ifdef __APPLE__ #include #endif +#ifdef __FreeBSD__ +#include +#include +#endif /* ── Constants ────────────────────────────────────────────────── */ @@ -1003,6 +1007,15 @@ static bool resolve_self_executable(char *out, size_t outsz) { return copy_path(out, outsz, buf); } return false; +#elif defined(__FreeBSD__) + /* No /proc by default on FreeBSD; ask the kernel for our own path. */ + char buf[1024]; + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + size_t cb = sizeof(buf); + if (sysctl(mib, 4, buf, &cb, NULL, 0) == 0 && cb > 0) { + return copy_path(out, outsz, buf); + } + return false; #else char buf[1024]; ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf) - 1); diff --git a/tests/test_cli.c b/tests/test_cli.c index 95fff9d76..f9f2ed99d 100644 --- a/tests/test_cli.c +++ b/tests/test_cli.c @@ -12752,8 +12752,16 @@ TEST(cli_external_manager_detection_needs_positive_evidence_issue1566) { ASSERT_NULL( cbm_cli_external_manager_name_for_testing("/Users/x/.local/bin/codebase-memory-mcp")); ASSERT_NULL(cbm_cli_external_manager_name_for_testing("/opt/cbm/codebase-memory-mcp")); - ASSERT_NULL(cbm_cli_external_manager_name_for_testing("/usr/local/bin/codebase-memory-mcp")); ASSERT_NULL(cbm_cli_external_manager_name_for_testing("build/c/test-runner")); +#ifdef __FreeBSD__ + /* On FreeBSD the port/pkg owns ${LOCALBASE}/bin, so a binary there IS + * externally managed; elsewhere the same path is unremarkable. */ + ASSERT_NOT_NULL( + cbm_cli_external_manager_name_for_testing("/usr/local/bin/codebase-memory-mcp")); + ASSERT_NULL(cbm_cli_external_manager_name_for_testing("/opt/local/bin/codebase-memory-mcp")); +#else + ASSERT_NULL(cbm_cli_external_manager_name_for_testing("/usr/local/bin/codebase-memory-mcp")); +#endif ASSERT_NULL(cbm_cli_external_manager_name_for_testing("")); ASSERT_NULL(cbm_cli_external_manager_name_for_testing(NULL)); PASS();