ClementTsang/bottom · error · anyhow::Error

Invalid handle value

Error message

Invalid handle value: {:?}

What it means

In volume_io, CreateFileW opened the volume path (\\.\C:) but returned a handle that fails is_invalid() (INVALID_HANDLE_VALUE), so the code bails with the last OS error. The library treats an invalid volume handle as fatal because the subsequent DeviceIoControl(IOCTL_DISK_PERFORMANCE) call needs a valid handle. It typically means the volume path could not be opened even though the Win32 call itself reported success-shaped behavior.

Solutions

  1. Check the logged io::Error::last_os_error() for the underlying Win32 error code (e.g. ERROR_ACCESS_DENIED, ERROR_FILE_NOT_FOUND) and fix that condition
  2. Re-run the collection; transient cases (volume removed mid-iteration) resolve on the next pass
  3. Ensure the process runs with sufficient privileges (Administrator) to open volume devices
  4. Filter out volumes that fail to open instead of failing the entire all_volume_io scan

Example fix

// before
if h_device.is_invalid() {
    bail!("Invalid handle value: {:?}", io::Error::last_os_error());
}
// after
if h_device.is_invalid() {
    eprintln!("skipping volume {}: {:?}", volume.display(), io::Error::last_os_error());
    return Ok(None); // or continue to the next volume
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Windows: check the volume path is a \\?\Volume{...}\ device path before querying
fn is_volume_guid_path(p: &str) -> bool {
    p.starts_with("\\\\?\\Volume{") && p.ends_with("\\")
}

Type guard

fn handle_valid(h: HANDLE) -> bool { !h.is_invalid() }

Try / catch

match all_volume_io() {
    Ok(results) => results,
    Err(e) => { log::warn!("volume io unavailable: {e:?}"); Vec::new() }
}

Prevention

When it happens

Trigger: CreateFileW on a volume GUID path returns INVALID_HANDLE_VALUE — e.g. the volume has been removed/dismounted between FindFirstVolumeW enumeration and the open, the path lacks the correct \\?\ volume GUID prefix, or access to the device is denied in a way surfaced as an invalid handle.

Common situations: Enumerating disk performance stats on machines where a USB/network volume disappears mid-scan; running in environments (services, containers, restricted tokens) without rights to open volume devices; calling volume_io with a hand-crafted path missing the trailing-backslash-to-NUL normalization the function performs.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07). Data as JSON: /api/errors/158f12fc9230be8f. Report an issue: GitHub.

Appendix: source

Thrown at src/collection/disks/windows/bindings.rs:59

        wide_path
    };

    // SAFETY: API call, arguments should be correct. We must also check after
    // the call to ensure it is valid.
    let h_device = unsafe {
        CreateFileW(
            windows::core::PCWSTR(volume.as_ptr()),
            0,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            None,
            OPEN_EXISTING,
            FILE_FLAGS_AND_ATTRIBUTES(0),
            Some(Foundation::HANDLE::default()),
        )?
    };

    if h_device.is_invalid() {
        bail!("Invalid handle value: {:?}", io::Error::last_os_error());
    }

    let mut disk_performance = DISK_PERFORMANCE::default();
    let mut bytes_returned = 0;

    // SAFETY: This should be safe, we'll manually check the results and the
    // arguments should be valid.
    let ret = unsafe {
        DeviceIoControl(
            h_device,
            IOCTL_DISK_PERFORMANCE,
            None,
            0,
            Some(&mut disk_performance as *mut _ as _),
            mem::size_of::<DISK_PERFORMANCE>() as u32,
            Some(&mut bytes_returned),
            None,
        )

View on GitHub (pinned to b77d317502)