fix(startup): surface filesystem failures and verify Sapling param integrity
Three verified daemon-startup edge-case fixes centered on the config/params filesystem path: - F7: new non-throwing Platform::ensureDirectory(dir, outError) with one consistent "Cannot create <dir>: <reason>. Check permissions / free space." message. Replaces the unchecked/throwing create_directories sites at main.cpp (pre-init: log + Windows MessageBox + return 1), connection.cpp's autoDetectConfig (was the *throwing* overload -- could raise an uncaught filesystem_error through its callers; now sets the new ConnectionConfig::dir_error), and both app.cpp daemon-dir sites (surface via daemon_status_ + return false). The primary connect path (app_network.cpp) checks dir_error and shows it instead of mislabelling it "waiting for config". embedded_resources.cpp already checked its error_code, so it is left as-is. - F6: verifySaplingParams() now hash-verifies each param against its pinned canonical SHA-256 (source of truth: scripts/build-lite-backend-artifact.sh) instead of only checking existence, so a truncated / corrupt-but-present param is rejected up front rather than failing later on a shielded operation. A <params_dir>/.sapling_verified marker keyed on size:mtime avoids re-hashing ~48MB on every startup. Logic extracted to the injectable, unit-testable verifySaplingParamsIn(dir, digests); reuses util::sha256Hex (no new hash impl). - F5: startEmbeddedDaemon() now checks extractEmbeddedResources()'s return and the previously-dropped copy_file error_code in the daemon-binary fallback loop, aborting with a clear status (sb_daemon_extract_failed / sb_daemon_files_failed) instead of failing opaquely at spawn. An absent source file stays non-fatal. Adds testPlatformEnsureDirectory and testVerifySaplingParams to test_phase4.cpp. i18n keys added to i18n.cpp (English source of truth); the res/lang/*.json back-fill via add_missing_translations.py is deferred to a single run at the end of the batch. Progress tracked in docs/daemon-startup-hardening.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1316,6 +1316,8 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["sb_extracting_sapling"] = "Extracting Sapling parameters...";
|
||||
strings_["sb_sapling_failed"] = "Failed to extract Sapling parameters.";
|
||||
strings_["sb_sapling_not_found"] = "Sapling parameters not found.";
|
||||
strings_["sb_daemon_extract_failed"] = "Failed to write daemon files — check free disk space and permissions.";
|
||||
strings_["sb_daemon_files_failed"] = "Failed to write daemon files to %s — check free disk space and permissions.";
|
||||
strings_["sb_dragonxd_running"] = "dragonxd running";
|
||||
strings_["sb_dragonxd_stopping"] = "Stopping dragonxd...";
|
||||
strings_["sb_dragonxd_stopped"] = "dragonxd stopped";
|
||||
|
||||
@@ -126,6 +126,27 @@ bool Platform::openUrl(const std::string& url)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Platform::ensureDirectory(const std::string& dir, std::string* outError)
|
||||
{
|
||||
if (dir.empty()) {
|
||||
if (outError) *outError = "Cannot create directory: empty path.";
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (std::filesystem::is_directory(dir, ec)) return true;
|
||||
ec.clear();
|
||||
std::filesystem::create_directories(dir, ec);
|
||||
if (ec) {
|
||||
if (outError) {
|
||||
*outError = "Cannot create " + dir + ": " + ec.message() +
|
||||
". Check permissions / free space.";
|
||||
}
|
||||
DEBUG_LOGF("[ERROR] ensureDirectory failed for %s: %s\n", dir.c_str(), ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Platform::openFolder(const std::string& path, bool createIfMissing)
|
||||
{
|
||||
if (path.empty()) return false;
|
||||
|
||||
@@ -128,6 +128,17 @@ public:
|
||||
*/
|
||||
static void ensureObsidianDragonSetup();
|
||||
|
||||
/**
|
||||
* @brief Create a directory (and parents) if missing, with a clear error on failure.
|
||||
*
|
||||
* Uses the non-throwing std::error_code overload internally. On failure sets *outError
|
||||
* (when non-null) to one consistent, user-facing message:
|
||||
* "Cannot create <dir>: <reason>. Check permissions / free space."
|
||||
*
|
||||
* @return true if the directory exists (already did, or was just created).
|
||||
*/
|
||||
static bool ensureDirectory(const std::string& dir, std::string* outError = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get total system RAM in megabytes
|
||||
* @return Total physical RAM in MB, or 0 on failure
|
||||
|
||||
Reference in New Issue
Block a user