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>
192 lines
6.4 KiB
C++
192 lines
6.4 KiB
C++
// DragonX Wallet - ImGui Edition
|
||
// Copyright 2024-2026 The Hush Developers
|
||
// Released under the GPLv3
|
||
|
||
#pragma once
|
||
|
||
#include <string>
|
||
#include <cstdint>
|
||
|
||
namespace dragonx {
|
||
namespace util {
|
||
|
||
/**
|
||
* @brief Platform-specific utilities
|
||
*/
|
||
class Platform {
|
||
public:
|
||
/**
|
||
* @brief Open a URL in the default browser
|
||
* @param url The URL to open
|
||
* @return true if successful
|
||
*/
|
||
static bool openUrl(const std::string& url);
|
||
|
||
/**
|
||
* @brief Open a folder in the system file manager
|
||
* @param path Path to the folder
|
||
* @param createIfMissing Create the folder if it doesn't exist
|
||
* @return true if successful
|
||
*/
|
||
static bool openFolder(const std::string& path, bool createIfMissing = true);
|
||
|
||
/**
|
||
* @brief Get the size of a file in bytes
|
||
* @param path Path to the file
|
||
* @return File size in bytes, or 0 if file doesn't exist
|
||
*/
|
||
static uint64_t getFileSize(const std::string& path);
|
||
|
||
/**
|
||
* @brief Format a file size as human-readable string
|
||
* @param bytes Size in bytes
|
||
* @return Formatted string (e.g., "5.23 MB")
|
||
*/
|
||
static std::string formatFileSize(uint64_t bytes);
|
||
|
||
/**
|
||
* @brief Get the user's home directory
|
||
* @return Home directory path
|
||
*/
|
||
static std::string getHomeDir();
|
||
|
||
/**
|
||
* @brief Get the DragonX data directory
|
||
* @return Path like ~/.hush/DRAGONX/ or %APPDATA%\Hush\DRAGONX\
|
||
*/
|
||
static std::string getDragonXDataDir();
|
||
|
||
/**
|
||
* @brief Get the wallet data directory (alias for getDragonXDataDir)
|
||
* @return Path like ~/.hush/DRAGONX/ or %APPDATA%\Hush\DRAGONX\
|
||
*/
|
||
static std::string getDataDir();
|
||
|
||
/**
|
||
* @brief Get the LITE wallet data directory (where the SilentDragonXLite backend stores
|
||
* silentdragonxlite-wallet.dat). Mirrors the backend's get_zcash_data_path() for the
|
||
* "main" chain: dirs::data_dir()/silentdragonxlite on Windows/macOS, ~/.silentdragonxlite
|
||
* on Linux. Distinct from getDragonXDataDir() (the full-node blocks/wallet dir).
|
||
* @return Path like ~/.silentdragonxlite/ or %APPDATA%\silentdragonxlite\
|
||
*/
|
||
static std::string getLiteWalletDataDir();
|
||
|
||
/**
|
||
* @brief Get the config directory for storing wallet exports/backups
|
||
* @return Path like ~/.config/ObsidianDragon/ or %APPDATA%\ObsidianDragon\
|
||
*/
|
||
static std::string getConfigDir();
|
||
|
||
/**
|
||
* @brief Delete a file
|
||
* @param path Path to the file
|
||
* @return true if successful or file didn't exist
|
||
*/
|
||
static bool deleteFile(const std::string& path);
|
||
|
||
/**
|
||
* @brief Write a file atomically and durably.
|
||
*
|
||
* Writes @p content to `<path>.tmp`, flushes it to stable storage
|
||
* (fsync / FlushFileBuffers), then atomically renames it over @p path
|
||
* (POSIX rename / Win32 MoveFileEx with REPLACE_EXISTING). A crash or power
|
||
* loss at any point leaves either the old file intact or the fully-written
|
||
* new one — never a truncated/corrupt file. The parent directory is created
|
||
* if missing, and on POSIX it is fsync'd so the rename itself is durable.
|
||
*
|
||
* @param path Destination file path.
|
||
* @param content Bytes to write.
|
||
* @param restrictPermissions When true, create the file owner-only (0600 on
|
||
* POSIX) before the rename so it is never briefly world-readable.
|
||
* @return true on success; on failure any pre-existing file is left untouched.
|
||
*/
|
||
static bool writeFileAtomically(const std::string& path,
|
||
const std::string& content,
|
||
bool restrictPermissions = false);
|
||
|
||
/**
|
||
* @brief Get the directory containing the executable
|
||
* @return Path to executable's directory
|
||
*/
|
||
static std::string getExecutableDirectory();
|
||
|
||
/**
|
||
* @brief Get the ObsidianDragon config directory
|
||
* @return Path like ~/.config/ObsidianDragon/ or %APPDATA%\ObsidianDragon\
|
||
*/
|
||
static std::string getObsidianDragonDir();
|
||
|
||
/**
|
||
* @brief Create ObsidianDragon folder structure and template files on first run
|
||
*
|
||
* Creates:
|
||
* ObsidianDragon/
|
||
* ObsidianDragon/themes/
|
||
* ObsidianDragon/themes/my_theme.toml (template)
|
||
*
|
||
* Only writes template files if they don't already exist.
|
||
*/
|
||
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
|
||
*/
|
||
static double getTotalSystemRAM_MB();
|
||
|
||
/**
|
||
* @brief Get currently used system RAM in megabytes
|
||
* @return Used physical RAM in MB (total - available), or 0 on failure
|
||
*/
|
||
static double getUsedSystemRAM_MB();
|
||
|
||
/**
|
||
* @brief Get this process's own RSS (resident set size) in megabytes
|
||
* @return Self process RSS in MB, or 0 on failure
|
||
*/
|
||
static double getSelfMemoryUsageMB();
|
||
|
||
/**
|
||
* @brief Get total RSS of all dragonxd daemon processes in megabytes
|
||
* Scans for any running dragonxd process by name, regardless of how it was launched.
|
||
* @return Combined daemon RSS in MB, or 0 if no daemon found
|
||
*/
|
||
static double getDaemonMemoryUsageMB();
|
||
|
||
/**
|
||
* @brief Get system-wide idle time in seconds
|
||
* Uses platform-specific APIs: GetLastInputInfo (Windows),
|
||
* XScreenSaverQueryInfo via dlopen (Linux), IOKit (macOS).
|
||
* @return Seconds since last user input, or 0 on failure
|
||
*/
|
||
static int getSystemIdleSeconds();
|
||
|
||
/**
|
||
* @brief Get GPU utilization percentage (0–100).
|
||
* Linux: reads sysfs for AMD, /proc for NVIDIA.
|
||
* Windows: queries PDH GPU engine counters.
|
||
* @return GPU busy percent, or -1 if unavailable.
|
||
*/
|
||
static int getGpuUtilization();
|
||
};
|
||
|
||
/**
|
||
* @brief Get the directory containing the executable (free function)
|
||
* @return Path to executable's directory
|
||
*/
|
||
std::string getExecutableDirectory();
|
||
|
||
} // namespace util
|
||
} // namespace dragonx
|