@@ -258,17 +258,34 @@ int cbm_copy_file(const char *src, const char *dst) {
258258 return rc == 0 ? 0 : -1 ;
259259}
260260
261- /* Replace a binary file: unlink first (handles read-only existing files),
262- * then create with the given data and permissions. */
261+ /* Replace a binary file. Unlinks the old file first (handles read-only and
262+ * running binaries on Unix where unlink succeeds on open files). On all
263+ * platforms, the caller should tell the user to restart after update. */
263264int cbm_replace_binary (const char * path , const unsigned char * data , int len , int mode ) {
264265 if (!path || !data || len <= 0 ) {
265266 return -1 ;
266267 }
267268
268- /* Remove existing file first — handles the case where the old binary
269- * has no write permission (e.g., 0500). unlink() only requires write
270- * permission on the parent directory, not the file itself. */
271- (void )cbm_unlink (path );
269+ /* Remove existing file if it exists. On Unix, unlink works even if the
270+ * binary is running (inode stays alive until the process exits). On Windows,
271+ * unlink fails on running .exe — rename it aside as fallback. */
272+ struct stat st_check ;
273+ if (stat (path , & st_check ) == 0 ) {
274+ /* File exists — remove or rename it */
275+ if (cbm_unlink (path ) != 0 ) {
276+ #ifdef _WIN32
277+ /* Windows: can't unlink running .exe — rename aside */
278+ char old_path [1024 ];
279+ snprintf (old_path , sizeof (old_path ), "%s.old" , path );
280+ (void )cbm_unlink (old_path );
281+ if (rename (path , old_path ) != 0 ) {
282+ return -1 ;
283+ }
284+ #else
285+ return -1 ;
286+ #endif
287+ }
288+ }
272289
273290#ifndef _WIN32
274291 int fd = open (path , O_WRONLY | O_CREAT | O_TRUNC , (mode_t )mode );
@@ -281,6 +298,7 @@ int cbm_replace_binary(const char *path, const unsigned char *data, int len, int
281298 return -1 ;
282299 }
283300#else
301+ (void )mode ;
284302 FILE * f = fopen (path , "wb" );
285303 if (!f ) {
286304 return -1 ;
@@ -2705,6 +2723,18 @@ int cbm_cmd_uninstall(int argc, char **argv) {
27052723int cbm_cmd_update (int argc , char * * argv ) {
27062724 parse_auto_answer (argc , argv );
27072725
2726+ bool dry_run = false;
2727+ int variant_flag = 0 ; /* 0 = ask, 1 = standard, 2 = ui */
2728+ for (int i = 0 ; i < argc ; i ++ ) {
2729+ if (strcmp (argv [i ], "--dry-run" ) == 0 ) {
2730+ dry_run = true;
2731+ } else if (strcmp (argv [i ], "--standard" ) == 0 ) {
2732+ variant_flag = 1 ;
2733+ } else if (strcmp (argv [i ], "--ui" ) == 0 ) {
2734+ variant_flag = 2 ;
2735+ }
2736+ }
2737+
27082738 const char * home = cbm_get_home_dir ();
27092739 if (!home ) {
27102740 fprintf (stderr , "error: HOME not set (use USERPROFILE on Windows)\n" );
@@ -2734,29 +2764,39 @@ int cbm_cmd_update(int argc, char **argv) {
27342764 printf ("Found %d existing index(es) that must be rebuilt after update:\n" , index_count );
27352765 cbm_list_indexes (home );
27362766 printf ("\n" );
2737- if (!prompt_yn ("Delete these indexes and continue with update?" )) {
2738- printf ("Update cancelled.\n" );
2739- return 1 ;
2767+ if (!dry_run ) {
2768+ if (!prompt_yn ("Delete these indexes and continue with update?" )) {
2769+ printf ("Update cancelled.\n" );
2770+ return 1 ;
2771+ }
2772+ int removed = cbm_remove_indexes (home );
2773+ printf ("Removed %d index(es).\n\n" , removed );
2774+ } else {
2775+ printf ("(dry-run — indexes would be deleted)\n\n" );
27402776 }
2741- int removed = cbm_remove_indexes (home );
2742- printf ("Removed %d index(es).\n\n" , removed );
27432777 }
27442778
2745- /* Step 2: Ask for UI variant */
2746- printf ("Which binary variant do you want?\n" );
2747- printf (" 1) standard — MCP server only\n" );
2748- printf (" 2) ui — MCP server + embedded graph visualization\n" );
2749- printf ("Choose (1/2): " );
2750- (void )fflush (stdout );
2751-
2752- char choice [16 ];
2753- if (!fgets (choice , sizeof (choice ), stdin )) {
2754- fprintf (stderr , "error: failed to read input\n" );
2755- return 1 ;
2779+ /* Step 2: Determine variant (--standard / --ui flags skip interactive prompt) */
2780+ bool want_ui = false;
2781+ if (variant_flag == 1 ) {
2782+ want_ui = false;
2783+ } else if (variant_flag == 2 ) {
2784+ want_ui = true;
2785+ } else {
2786+ printf ("Which binary variant do you want?\n" );
2787+ printf (" 1) standard — MCP server only\n" );
2788+ printf (" 2) ui — MCP server + embedded graph visualization\n" );
2789+ printf ("Choose (1/2): " );
2790+ (void )fflush (stdout );
2791+
2792+ char choice [16 ];
2793+ if (!fgets (choice , sizeof (choice ), stdin )) {
2794+ fprintf (stderr , "error: failed to read input\n" );
2795+ return 1 ;
2796+ }
2797+ want_ui = (choice [0 ] == '2' );
27562798 }
27572799 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
2758- bool want_ui = (choice [0 ] == '2' ) ? true : false;
2759- // NOLINTNEXTLINE(readability-implicit-bool-conversion)
27602800 const char * variant = want_ui ? "ui-" : "" ;
27612801 // NOLINTNEXTLINE(readability-implicit-bool-conversion)
27622802 const char * variant_label = want_ui ? "ui" : "standard" ;
@@ -2779,9 +2819,23 @@ int cbm_cmd_update(int argc, char **argv) {
27792819 os , arch , ext );
27802820 }
27812821
2782- printf ("\nDownloading %s binary for %s/%s ...\n" , variant_label , os , arch );
2822+ if (dry_run ) {
2823+ printf ("\nWould download %s binary for %s/%s ...\n" , variant_label , os , arch );
2824+ } else {
2825+ printf ("\nDownloading %s binary for %s/%s ...\n" , variant_label , os , arch );
2826+ }
27832827 printf (" %s\n" , url );
27842828
2829+ if (dry_run ) {
2830+ printf ("\n(dry-run — skipping download, extraction, and binary replacement)\n" );
2831+ printf (" target: %s/.local/bin/codebase-memory-mcp\n" , home );
2832+ printf (" variant: %s\n" , variant_label );
2833+ printf (" os/arch: %s/%s\n" , os , arch );
2834+ printf ("\nUpdate dry-run complete.\n" );
2835+ (void )variant ;
2836+ return 0 ;
2837+ }
2838+
27852839 /* Step 4: Download using curl */
27862840 char tmp_archive [256 ];
27872841 snprintf (tmp_archive , sizeof (tmp_archive ), "%s/cbm-update.%s" , cbm_tmpdir (), ext );
@@ -2897,6 +2951,7 @@ int cbm_cmd_update(int argc, char **argv) {
28972951
28982952 printf ("\nAll project indexes were cleared. They will be rebuilt\n" );
28992953 printf ("automatically when you next use the MCP server.\n" );
2954+ printf ("\nPlease restart your MCP client to use the new binary.\n" );
29002955 (void )variant ;
29012956 return 0 ;
29022957}
0 commit comments