ramensoftware/windhawk · error · SettingsError

GetPrivateProfileString

Error message

GetPrivateProfileString

What it means

IniTree::get_string wraps the Win32 GetPrivateProfileString call; when that call fails, the OS error is mapped into a SettingsError with the operation tag "get" and message "GetPrivateProfileString". It indicates the underlying Windows API could not read the requested value from the INI file (file access, path, or format problem).

Solutions

  1. Inspect the embedded OS error (os) in the SettingsError to find the root cause (path not found, access denied, etc.).
  2. Verify the INI file exists at self.file and the process has read permission to it.
  3. Check the file is not exclusively locked by another process (editor, antivirus, sync client).
  4. Fall back to defaults or the registry backend if the file is unreadable.
  5. Re-create a valid INI file if it is corrupt.

Example fix

// before: propagate raw failure
let v = ini.get_string("key")?;

// after: degrade gracefully
let v = match ini.get_string("key") {
    Ok(v) => v,
    Err(e) => { log::warn!("INI read failed: {e}"); None }
};
Defensive patterns

Strategy: fallback

Validate before calling

let ini_path = Path::new(&file);
if !ini_path.exists() { log::warn!("INI file missing: {}", ini_path.display()); }
// optionally check readability
std::fs::File::open(ini_path).map_err(|e| format!("INI unreadable: {e}"))?;

Try / catch

let value = match ini.get_string("key") {
    Ok(v) => v,
    Err(e) => { log::warn!("GetPrivateProfileString failed: {e}"); default_value.into() }
};

Prevention

When it happens

Trigger: Any read via get_string on an IniTree — also reached indirectly through get_int, get_binary, and enum_values — where get_profile_string returns an OS error: the INI file cannot be opened/read, the path is invalid, or access is denied.

Common situations: INI file moved or deleted while the app runs; file locked by another process; insufficient permissions in Program Files-like locations; a redirected or UNC path that GetPrivateProfileString cannot resolve.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of ramensoftware/windhawk@61d99ed8e1 (2026-09-12). Data as JSON: /api/errors/2f3d028486594741. Report an issue: GitHub.

Appendix: source

Thrown at src/windhawk-core/windows/src/ini.rs:174

    /// A value name reaches the profile API as a `PCWSTR`, which ends at its
    /// first NUL, so a name carrying one addresses a DIFFERENT value - the
    /// prefix - with the call reporting success. The read and remove paths take
    /// a name straight to Win32, so they guard it here; the write path refuses
    /// the same name as one [`unrepresentable_name`] rejects, so one name gets
    /// one answer whichever operation carries it, in both storage modes.
    fn check_name(&self, op: &'static str, name: &str) -> Result<(), SettingsError> {
        if name.contains('\0') {
            return Err(self.err(op, 0, &format!("value name {name:?} contains a NUL")));
        }
        Ok(())
    }
}

impl SettingsTree for IniTree {
    fn get_string(&self, name: &str) -> Result<Option<String>, SettingsError> {
        self.check_name("get", name)?;
        get_profile_string(&self.file, &self.section, name)
            .map_err(|os| self.err("get", os, "GetPrivateProfileString"))
    }

    fn set_string(&mut self, name: &str, value: &str) -> Result<(), SettingsError> {
        if let Some(why) = unrepresentable_name(name) {
            return Err(self.err("set", 0, &format!("value name {name:?} {why}")));
        }
        // `WritePrivateProfileStringW` takes the value as a NUL-terminated
        // string, so an embedded NUL ends it: everything after it is dropped and
        // the call still reports success. `escape_ini_value` cannot rescue that
        // (the INI line format has no encoding for a NUL), so refuse the write
        // rather than store a silently truncated value.
        if value.contains('\0') {
            return Err(self.err("set", 0, "value contains a NUL character"));
        }
        // An INI entry ends at the line break, so a value carrying one cannot
        // be stored either: it would read back cut at the break, with the rest
        // of it parsed as further lines of a file that also holds the mod's
        // `[Mod]` config. Refused for the same reason as a NUL - the registry

View on GitHub (pinned to 61d99ed8e1)