| 2104 | } store_recovery_status_t; |
| 2105 | |
| 2106 | static cbm_store_t *resolve_store_internal(cbm_mcp_server_t *srv, const char *project, |
| 2107 | bool mutation_already_held, bool nonblocking_recovery, |
| 2108 | store_recovery_status_t *recovery_status) { |
| 2109 | if (recovery_status) { |
| 2110 | *recovery_status = STORE_RECOVERY_NONE; |
| 2111 | } |
| 2112 | if (!project) { |
| 2113 | return NULL; /* project is required — no implicit fallback */ |
| 2114 | } |
| 2115 | |
| 2116 | srv->store_last_used = time(NULL); |
| 2117 | |
| 2118 | /* Already open for this project? */ |
| 2119 | if (srv->current_project && strcmp(srv->current_project, project) == 0 && srv->store) { |
| 2120 | return srv->store; |
| 2121 | } |
| 2122 | |
| 2123 | /* Close old store */ |
| 2124 | if (srv->owns_store && srv->store) { |
| 2125 | cbm_store_close(srv->store); |
| 2126 | srv->store = NULL; |
| 2127 | } |
| 2128 | |
| 2129 | /* Open project's .db file — query-only open (no SQLITE_OPEN_CREATE) to |
| 2130 | * prevent ghost .db file creation for unknown/unindexed projects. |
| 2131 | * #1425: an invalid project name yields an empty path. SQLite opens "" |
| 2132 | * as an anonymous temp db, which then fails the integrity check and |
| 2133 | * quarantines a db that never existed — as a RELATIVE .corrupt.<hex> |
| 2134 | * file in the daemon's cwd. Skip the direct open entirely; the fallback |
| 2135 | * scan below still resolves legacy dbs whose internal name predates |
| 2136 | * validation. */ |
| 2137 | char path[CBM_SZ_1K]; |
| 2138 | project_db_path(project, path, sizeof(path)); |
| 2139 | srv->store = path[0] ? cbm_store_open_path_query(path) : NULL; |
| 2140 | if (srv->store) { |
| 2141 | /* Check DB integrity — back up (never silently delete) a corrupt DB */ |
| 2142 | if (!cbm_store_check_integrity(srv->store)) { |
| 2143 | cbm_store_close(srv->store); |
| 2144 | srv->store = NULL; |
| 2145 | bool mutation_acquired = mutation_already_held; |
| 2146 | if (!mutation_acquired) { |
| 2147 | mutation_acquired = nonblocking_recovery |
| 2148 | ? mcp_project_mutation_try_begin(srv, project) |
| 2149 | : mcp_project_mutation_begin(srv, project); |
| 2150 | } |
| 2151 | if (!mutation_acquired) { |
| 2152 | if (nonblocking_recovery && recovery_status) { |
| 2153 | *recovery_status = srv->mutation_try_begin |
| 2154 | ? STORE_RECOVERY_BUSY |
| 2155 | : STORE_RECOVERY_TRY_GUARD_UNAVAILABLE; |
| 2156 | } |
| 2157 | return NULL; |
| 2158 | } |
| 2159 | |
| 2160 | /* The lease may have waited behind a publisher. Re-open and trust |
| 2161 | * only the current generation, never the stale pre-wait verdict. |
| 2162 | * Use the verdict API here — this is the point that decides whether |
| 2163 | * a healthy DB gets quarantined. The plain bool check cannot tell |
no test coverage detected