Files
ObsidianDragon/src/util/platform.h
DanS 2675b8ab93 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>
2026-08-02 11:07:32 -05:00

192 lines
6.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 (0100).
* 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