Files
ObsidianDragon/src/util/platform.h
T
DanSandClaude Opus 4.8 2a535c844d fix(security): overwrite key/PIN files with fsync before deleting them
Two secure-delete paths overwrote a sensitive file and then unlinked it,
but neither reliably forced the overwrite to stable storage. The
plaintext key export used a buffered stream with only a flush, and the
PIN vault removal opened a second descriptor to fsync only on non-
Windows. On a crash, or on Windows, the zeroing write could stay in the
OS cache and the original bytes could survive on disk.

Both now go through one shared best-effort secure-delete helper:

- It overwrites the file in place in chunks, never truncating first, so
  the original blocks are the ones rewritten.
- It flushes the data to the device before unlinking (full fsync on
  macOS, write-through on Windows), then removes the file.
- It is safe on a missing file and reports whether the file is gone.

This is still best-effort: SSD wear-levelling and copy-on-write or
journaling filesystems may retain copies.

Tests exercise the overwrite-and-remove helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 19:24:12 -05:00

210 lines
7.5 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 Best-effort secure delete: overwrite the file in place, flush, then unlink.
*
* Never truncates first (that would free the original blocks). Always attempts the unlink.
* SSD wear-levelling and CoW/journaling filesystems may retain copies.
* @return true when the file no longer exists
*/
static bool secureWipeAndRemove(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();
// Run a command line and capture its stdout WITHOUT ever popping a console window: Windows uses
// CreateProcess + CREATE_NO_WINDOW (a plain popen()/_popen() flashes a cmd.exe console), POSIX uses
// popen(). Use this instead of _popen for anything run while the GUI is up. `mergeStderr` folds the
// child's stderr into the result (like "2>&1"); otherwise stderr is discarded. `exitCode`, if given,
// receives the child's exit status (raw pclose() status on POSIX, GetExitCodeProcess on Windows; -1
// if the process could not be launched).
static std::string runHiddenCapture(const std::string& cmdLine, bool mergeStderr = false,
int* exitCode = nullptr);
};
/**
* @brief Get the directory containing the executable (free function)
* @return Path to executable's directory
*/
std::string getExecutableDirectory();
} // namespace util
} // namespace dragonx