@@ -13202,6 +13202,45 @@ static int cli_update_activate_binary(void *opaque) {
1320213202 return CLI_OK;
1320313203}
1320413204
13205+ #ifdef __APPLE__
13206+ /* The first step of the signed-candidate preparation that failed. One boolean
13207+ * used to cover all of them, so a full disk read exactly like a broken
13208+ * codesign: "candidate preparation failed", and nothing else. */
13209+ typedef struct {
13210+ const char *step;
13211+ int os_error; /* errno left by an OS-level step; 0 when none applies */
13212+ const char *detail; /* the reason when no OS error applies, or NULL */
13213+ } cli_update_prep_failure_t;
13214+
13215+ /* Records the first failing step. An OS-level step keeps the errno it left; a
13216+ * step that succeeds clears errno so the next one starts clean. Returns ok, so
13217+ * the steps chain with &&. */
13218+ static bool cli_update_prep_step(bool ok, const char *step, bool os_level, const char *detail,
13219+ cli_update_prep_failure_t *failure) {
13220+ if (ok) {
13221+ errno = 0;
13222+ return true;
13223+ }
13224+ if (!failure->step) {
13225+ failure->step = step;
13226+ failure->os_error = os_level ? errno : 0;
13227+ failure->detail = detail;
13228+ }
13229+ return false;
13230+ }
13231+
13232+ static void cli_update_prep_report(const cli_update_prep_failure_t *failure) {
13233+ const char *step = failure->step ? failure->step : "an unrecorded step";
13234+ const char *reason = failure->os_error != 0 ? strerror(failure->os_error) : failure->detail;
13235+ if (reason) {
13236+ (void)fprintf(stderr, "error: signed update candidate preparation failed while %s: %s\n",
13237+ step, reason);
13238+ } else {
13239+ (void)fprintf(stderr, "error: signed update candidate preparation failed while %s\n", step);
13240+ }
13241+ }
13242+ #endif
13243+
1320513244static int extract_and_install_binary(extract_install_args_t args) {
1320613245 const char *tmp_archive = args.tmp_archive;
1320713246 const char *ext = args.ext;
@@ -13259,42 +13298,79 @@ static int extract_and_install_binary(extract_install_args_t args) {
1325913298#ifdef __APPLE__
1326013299 /* codesign replaces the signed file on current macOS releases. Publish and
1326113300 * sign a disposable private copy first, then stage the resulting immutable
13262- * bytes into the final transaction. */
13301+ * bytes into the final transaction. Every step records why it failed, so a
13302+ * full disk is reported as one and not as a signing failure. */
13303+ cli_update_prep_failure_t failure = {0};
1326313304 char prepared_dir[CLI_BUF_1K];
1326413305 char prepared_candidate[CLI_BUF_1K];
1326513306 int prepared_dir_length =
1326613307 snprintf(prepared_dir, sizeof(prepared_dir), "%s/cbm-update-sign-XXXXXX", cbm_tmpdir());
13267- bool prepared = prepared_dir_length > 0 && (size_t)prepared_dir_length < sizeof(prepared_dir) &&
13268- cbm_mkdtemp(prepared_dir) != NULL;
13308+ errno = 0;
13309+ bool prepared = cli_update_prep_step(prepared_dir_length > 0 &&
13310+ (size_t)prepared_dir_length < sizeof(prepared_dir) &&
13311+ cbm_mkdtemp(prepared_dir) != NULL,
13312+ "creating its private directory", true, NULL, &failure);
1326913313 int prepared_candidate_length = prepared
1327013314 ? snprintf(prepared_candidate, sizeof(prepared_candidate),
1327113315 "%s/codebase-memory-mcp", prepared_dir)
1327213316 : CLI_ERR;
13273- prepared = prepared && prepared_candidate_length > 0 &&
13274- (size_t)prepared_candidate_length < sizeof(prepared_candidate);
13317+ prepared = prepared && cli_update_prep_step(prepared_candidate_length > 0 &&
13318+ (size_t)prepared_candidate_length <
13319+ sizeof(prepared_candidate),
13320+ "naming the candidate file", false, NULL, &failure);
1327513321 cbm_activation_transaction_t *preparation = NULL;
1327613322 stage_status = prepared ? cbm_activation_transaction_stage_bytes(prepared_candidate, bin_data,
1327713323 (size_t)bin_len, &preparation)
1327813324 : CBM_ACTIVATION_TRANSACTION_IO;
13325+ prepared = prepared &&
13326+ cli_update_prep_step(
13327+ stage_status == CBM_ACTIVATION_TRANSACTION_OK && preparation,
13328+ "writing the unsigned candidate", stage_status == CBM_ACTIVATION_TRANSACTION_IO,
13329+ cbm_activation_transaction_status_message(stage_status), &failure);
1327913330 free(bin_data);
1328013331 cli_binary_validator_t unsigned_validator = {{0}};
13281- prepared = stage_status == CBM_ACTIVATION_TRANSACTION_OK && preparation &&
13282- cli_activation_transaction_expected_build(preparation, &unsigned_validator) &&
13283- cli_activation_transaction_commit_validated(preparation, &unsigned_validator,
13284- CLI_OCTAL_PERM) == CLI_OK &&
13285- cli_activation_transaction_finalize_close(&preparation) == CLI_OK &&
13286- cbm_macos_adhoc_sign(prepared_candidate) == CLI_OK &&
13287- cbm_daemon_build_fingerprint_file(prepared_candidate, validator.fingerprint);
13332+ prepared = prepared &&
13333+ cli_update_prep_step(
13334+ cli_activation_transaction_expected_build(preparation, &unsigned_validator),
13335+ "fingerprinting the unsigned candidate", true, NULL, &failure) &&
13336+ cli_update_prep_step(cli_activation_transaction_commit_validated(
13337+ preparation, &unsigned_validator, CLI_OCTAL_PERM) == CLI_OK,
13338+ "publishing the unsigned candidate", false, NULL, &failure) &&
13339+ cli_update_prep_step(
13340+ cli_activation_transaction_finalize_close(&preparation) == CLI_OK,
13341+ "closing the unsigned candidate's transaction", false, NULL, &failure) &&
13342+ cli_update_prep_step(cbm_macos_adhoc_sign(prepared_candidate) == CLI_OK,
13343+ "signing the candidate ad hoc", false, NULL, &failure) &&
13344+ cli_update_prep_step(
13345+ cbm_daemon_build_fingerprint_file(prepared_candidate, validator.fingerprint),
13346+ "fingerprinting the signed candidate", true, NULL, &failure);
1328813347 const char *prepared_argv[] = {prepared_candidate, "--version", NULL};
13289- prepared = prepared && cbm_exec_no_shell(prepared_argv) == CLI_OK;
13348+ int version_rc = prepared ? cbm_exec_no_shell(prepared_argv) : CLI_OK;
13349+ char version_detail[80];
13350+ if (version_rc == CBM_NOT_FOUND) {
13351+ snprintf(version_detail, sizeof(version_detail), "it could not run or was killed");
13352+ } else {
13353+ snprintf(version_detail, sizeof(version_detail), "--version exited with status %d",
13354+ version_rc);
13355+ }
13356+ prepared =
13357+ prepared && cli_update_prep_step(version_rc == CLI_OK, "running the signed candidate",
13358+ false, version_detail, &failure);
1329013359 if (prepared) {
1329113360 stage_status = cbm_activation_transaction_stage_file(bin_dest, prepared_candidate,
1329213361 &binary_transaction);
1329313362 cli_binary_validator_t staged_validator = {{0}};
1329413363 prepared =
13295- stage_status == CBM_ACTIVATION_TRANSACTION_OK && binary_transaction &&
13296- cli_activation_transaction_expected_build(binary_transaction, &staged_validator) &&
13297- strcmp(staged_validator.fingerprint, validator.fingerprint) == 0;
13364+ cli_update_prep_step(
13365+ stage_status == CBM_ACTIVATION_TRANSACTION_OK && binary_transaction,
13366+ "staging the signed candidate", stage_status == CBM_ACTIVATION_TRANSACTION_IO,
13367+ cbm_activation_transaction_status_message(stage_status), &failure) &&
13368+ cli_update_prep_step(
13369+ cli_activation_transaction_expected_build(binary_transaction, &staged_validator),
13370+ "fingerprinting the staged candidate", true, NULL, &failure) &&
13371+ cli_update_prep_step(strcmp(staged_validator.fingerprint, validator.fingerprint) == 0,
13372+ "comparing the staged and signed candidates", false,
13373+ "their fingerprints differ", &failure);
1329813374 if (prepared) {
1329913375 validator = staged_validator;
1330013376 }
@@ -13308,18 +13384,35 @@ static int extract_and_install_binary(extract_install_args_t args) {
1330813384 (void)cbm_rmdir(prepared_dir);
1330913385 }
1331013386 if (!prepared) {
13311- (void)fprintf(stderr, "error: signed update candidate preparation failed\n" );
13387+ cli_update_prep_report(&failure );
1331213388 (void)cli_activation_transaction_abort(&binary_transaction);
1331313389 return CLI_TRUE;
1331413390 }
1331513391#else
13392+ errno = 0;
1331613393 stage_status = cbm_activation_transaction_stage_bytes(bin_dest, bin_data, (size_t)bin_len,
1331713394 &binary_transaction);
13395+ /* POSIX only: Windows staging reports through GetLastError, not errno. */
13396+ #ifdef _WIN32
13397+ int stage_error = 0;
13398+ #else
13399+ int stage_error = stage_status == CBM_ACTIVATION_TRANSACTION_IO ? errno : 0;
13400+ #endif
1331813401 free(bin_data);
13319- if (stage_status != CBM_ACTIVATION_TRANSACTION_OK || !binary_transaction ||
13320- !cli_activation_transaction_expected_build(binary_transaction, &validator)) {
13321- (void)fprintf(stderr, "error: failed to stage verified update: %s\n",
13322- cbm_activation_transaction_status_message(stage_status));
13402+ if (stage_status != CBM_ACTIVATION_TRANSACTION_OK || !binary_transaction) {
13403+ if (stage_error != 0) {
13404+ (void)fprintf(stderr, "error: failed to stage verified update: %s: %s\n",
13405+ cbm_activation_transaction_status_message(stage_status),
13406+ strerror(stage_error));
13407+ } else {
13408+ (void)fprintf(stderr, "error: failed to stage verified update: %s\n",
13409+ cbm_activation_transaction_status_message(stage_status));
13410+ }
13411+ (void)cli_activation_transaction_abort(&binary_transaction);
13412+ return CLI_TRUE;
13413+ }
13414+ if (!cli_activation_transaction_expected_build(binary_transaction, &validator)) {
13415+ (void)fprintf(stderr, "error: failed to fingerprint the staged update\n");
1332313416 (void)cli_activation_transaction_abort(&binary_transaction);
1332413417 return CLI_TRUE;
1332513418 }
0 commit comments