mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-20 05:49:37 +08:00
fix(daemon): keep one separator spelling for the session root on Windows
On Windows cbm_canonical_path answers in the platform's native backslash form, and the daemon stored that spelling verbatim for the client's session_root and allowed_root (cbm_mcp_server_set_session_context) and for the auto-index job's repo_path (application.c canonicalizes with the bare call at three sites). Every tool handler, however, normalizes separators after canonicalizing. One directory therefore had two names, and the exact comparison in application_index_args_equal never matched: an explicit index_repository request for the session root was refused as OPTIONS_CONFLICT instead of joining the auto-index job already running for it. Normalize the separator spelling of both session roots in set_session_context, and canonicalize plus normalize at the three application.c sites through one helper, application_canonical_root(). Separators fold on every platform, so the new test daemon_session_context_keeps_one_spelling_of_a_root binds wherever the suite runs; it fails on main with "C:\repos\cbm" != "C:/repos/cbm". Distilled from #1726 with co-author credit. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com> Co-authored-by: 刘冲 <mail@liuchong.dev>
This commit is contained in:
@@ -379,6 +379,20 @@ static bool application_canonical_directory_exists(const char *path) {
|
||||
return cbm_path_info_utf8(path, &info) == 0 && info.is_directory;
|
||||
}
|
||||
|
||||
/* The daemon's one spelling of a repository root. cbm_canonical_path answers in
|
||||
* the platform's native form (backslashes on Windows) while every tool handler
|
||||
* normalizes the separators of the paths it canonicalizes. A root kept native
|
||||
* is a second name for one directory, and the exact comparison downstream -
|
||||
* index-args equality when a request joins the job already running for that
|
||||
* root - never matches it. */
|
||||
static bool application_canonical_root(const char *path, char *out, size_t out_size) {
|
||||
if (!cbm_canonical_path(path, out, out_size)) {
|
||||
return false;
|
||||
}
|
||||
cbm_normalize_path_sep(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
static cbm_daemon_application_watch_t *application_find_watch_locked(
|
||||
cbm_daemon_application_t *application, const char *project) {
|
||||
for (cbm_daemon_application_watch_t *watch = application->watches; watch; watch = watch->next) {
|
||||
@@ -2355,9 +2369,10 @@ static cbm_daemon_runtime_application_status_t application_set_context(
|
||||
}
|
||||
char canonical_root[APPLICATION_PATH_CAP] = {0};
|
||||
char canonical_allowed[APPLICATION_PATH_CAP] = {0};
|
||||
bool canonical = cbm_canonical_path(root, canonical_root, sizeof(canonical_root));
|
||||
bool canonical = application_canonical_root(root, canonical_root, sizeof(canonical_root));
|
||||
if (canonical && allowed_present) {
|
||||
canonical = cbm_canonical_path(allowed, canonical_allowed, sizeof(canonical_allowed));
|
||||
canonical =
|
||||
application_canonical_root(allowed, canonical_allowed, sizeof(canonical_allowed));
|
||||
}
|
||||
canonical = canonical && application_canonical_directory_exists(canonical_root);
|
||||
bool set =
|
||||
@@ -3372,7 +3387,7 @@ static int application_background_index(cbm_daemon_application_t *application,
|
||||
return -1;
|
||||
}
|
||||
char canonical_root[APPLICATION_PATH_CAP];
|
||||
if (!cbm_canonical_path(root_path, canonical_root, sizeof(canonical_root)) ||
|
||||
if (!application_canonical_root(root_path, canonical_root, sizeof(canonical_root)) ||
|
||||
!application_canonical_directory_exists(canonical_root)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
+14
-2
@@ -1765,7 +1765,18 @@ bool cbm_mcp_server_set_session_context(cbm_mcp_server_t *srv, const char *sessi
|
||||
return false;
|
||||
}
|
||||
|
||||
char *project = cbm_project_name_from_path(session_root);
|
||||
/* Keep both roots in the one spelling every tool handler produces. Each
|
||||
* handler normalizes separators after canonicalizing, so a root stored in
|
||||
* the platform's native form (backslashes on Windows) is a second name for
|
||||
* the same directory - and the daemon compares roots exactly. An explicit
|
||||
* index_repository request for this root was refused as an options
|
||||
* conflict instead of joining the auto-index job already running for it,
|
||||
* because the job's repo_path never matched the request's. */
|
||||
char root[sizeof(srv->session_root)];
|
||||
snprintf(root, sizeof(root), "%s", session_root);
|
||||
cbm_normalize_path_sep(root);
|
||||
|
||||
char *project = cbm_project_name_from_path(root);
|
||||
if (!project || project[0] == '\0' || strlen(project) >= sizeof(srv->session_project)) {
|
||||
free(project);
|
||||
return false;
|
||||
@@ -1776,8 +1787,9 @@ bool cbm_mcp_server_set_session_context(cbm_mcp_server_t *srv, const char *sessi
|
||||
free(project);
|
||||
return false;
|
||||
}
|
||||
cbm_normalize_path_sep(allowed_copy);
|
||||
|
||||
snprintf(srv->session_root, sizeof(srv->session_root), "%s", session_root);
|
||||
snprintf(srv->session_root, sizeof(srv->session_root), "%s", root);
|
||||
snprintf(srv->session_project, sizeof(srv->session_project), "%s", project);
|
||||
free(project);
|
||||
|
||||
|
||||
@@ -467,6 +467,26 @@ TEST(daemon_sessions_keep_distinct_roots_and_allowed_root_policy) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The daemon canonicalizes a client's root through the platform API, which on
|
||||
* Windows answers in backslash form, while every tool handler normalizes the
|
||||
* separators of the paths it canonicalizes. Stored in its native form, one
|
||||
* directory had two names, and an explicit index_repository request was
|
||||
* refused as an options conflict instead of joining the auto-index job already
|
||||
* running for that root. Separators fold on every platform, so the check binds
|
||||
* wherever the suite runs. */
|
||||
TEST(daemon_session_context_keeps_one_spelling_of_a_root) {
|
||||
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
|
||||
ASSERT_NOT_NULL(srv);
|
||||
cbm_mcp_server_set_background_tasks(srv, false);
|
||||
|
||||
ASSERT_TRUE(cbm_mcp_server_set_session_context(srv, "C:\\repos\\cbm", "C:\\repos"));
|
||||
ASSERT_STR_EQ(cbm_mcp_server_session_root(srv), "C:/repos/cbm");
|
||||
ASSERT_STR_EQ(cbm_mcp_server_allowed_root(srv), "C:/repos");
|
||||
|
||||
cbm_mcp_server_free(srv);
|
||||
PASS();
|
||||
}
|
||||
|
||||
SUITE(daemon) {
|
||||
RUN_TEST(daemon_client_ids_are_connection_bound);
|
||||
RUN_TEST(daemon_shared_job_survives_until_final_subscriber_disconnects);
|
||||
@@ -480,4 +500,5 @@ SUITE(daemon) {
|
||||
RUN_TEST(daemon_bridge_rejects_embedded_nul_body);
|
||||
RUN_TEST(daemon_bridge_rejects_oversized_headers);
|
||||
RUN_TEST(daemon_sessions_keep_distinct_roots_and_allowed_root_policy);
|
||||
RUN_TEST(daemon_session_context_keeps_one_spelling_of_a_root);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user