ClementTsang/bottom · error · anyhow::Error
Expects a directory to be passed in.
Error message
Expects a directory to be passed in.
What it means
volume_io on Windows opens a volume path (e.g. \\.\C:\) with CreateFile to query DISK_PERFORMANCE via DeviceIoControl. The function expects the input to be a volume directory-style path; if volume.is_file() returns true, the input is rejected as it cannot be a volume root.
Solutions
- Ensure the path passed is a volume root ending with a path separator (e.g. C:\)
- Only pass paths obtained from GetLogicalDrives / volume enumeration, not arbitrary files
- Normalize the path to its root (path.ancestors().last() or Path::new("C:\\"))
- Filter enumerated paths with is_dir() checks before calling
Example fix
// before
let perf = volume_io(&Path::new("C:"))?;
// after
let root = Path::new("C:\");
assert!(root.is_dir());
let perf = volume_io(root)?; Defensive patterns
Strategy: validation
Validate before calling
// Ensure the path is a volume root directory with a trailing separator
let p = std::path::Path::new("C:\");
if p.is_file() { panic!("not a volume root"); }
let ends_with_sep = p.as_os_str().to_string_lossy().ends_with('\\'); Type guard
fn is_volume_root(p: &std::path::Path) -> bool {
p.is_dir() && p.parent().is_none() && p.as_os_str().to_string_lossy().ends_with('\\')
} Try / catch
match all_volume_io() {
Ok(map) => map,
Err(e) if e.to_string().contains("Expects a directory") => {
log::warn!("bad volume path given: {e}"); Default::default()
},
Err(e) => return Err(e),
} Prevention
- Always pass volume roots like C:\\ with a trailing backslash
- Derive volume paths from GetLogicalDrives/GetVolumePathNamesForVolumeName, not user input
- Check is_dir() before invoking volume IO queries
- Normalize drive letters to their root via path ancestors
When it happens
Trigger: Calling volume_io (via all_volume_io) with a path that the system classifies as a regular file rather than a directory/volume root, e.g. passing a drive path without trailing separator or an actual file path.
Common situations: Passing 'C:' without a trailing backslash, passing mounted folder paths or file paths instead of volume roots, or misconfigured volume path lists.
Related errors
- Unsupported OS
- statvfs had an issue getting info from
- IOServiceGetMatchingServices failed, error code
- IORegistryEntryCreateCFProperties failed, error code
- IORegistryEntryGetParentEntry failed, error code
AI-assisted analysis of ClementTsang/bottom@b77d317502 (2026-09-07).
Data as JSON: /api/errors/bfe87d094d3d3e23.
Report an issue: GitHub.
Appendix: source
Thrown at src/collection/disks/windows/bindings.rs:31
Storage::FileSystem::{
CreateFileW, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_READ, FILE_SHARE_WRITE,
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetVolumeNameForVolumeMountPointW,
OPEN_EXISTING,
},
System::{
IO::DeviceIoControl,
Ioctl::{DISK_PERFORMANCE, IOCTL_DISK_PERFORMANCE},
},
};
/// Returns the I/O for a given volume.
///
/// Based on [psutil's implementation](https://github.com/giampaolo/psutil/blob/52fe5517f716dedf9c9918e56325e49a49146130/psutil/arch/windows/disk.c#L78-L83)
/// and [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/windows/bindings/perf.rs).
fn volume_io(volume: &Path) -> anyhow::Result<DISK_PERFORMANCE> {
if volume.is_file() {
// We assume the volume is a directory, so bail ASAP if it isn't.
bail!("Expects a directory to be passed in.");
}
let volume = {
let mut wide_path = volume.as_os_str().encode_wide().collect::<Vec<_>>();
// We replace the trailing backslash and replace it with a \0.
wide_path.pop();
wide_path.push(0x0000);
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,View on GitHub (pinned to b77d317502)