diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs
index 3ee55e4f5f1..9b544007a10 100644
--- a/src/uu/df/src/filesystem.rs
+++ b/src/uu/df/src/filesystem.rs
@@ -8,7 +8,9 @@
//! filesystem mounted at a particular directory. It also includes
//! information on amount of space available and amount of space used.
// spell-checker:ignore canonicalized
-use std::{ffi::OsString, path::Path};
+use std::ffi::OsString;
+#[cfg(unix)]
+use std::path::Path;
use uucore::fsext::{FsUsage, MountInfo};
@@ -59,6 +61,7 @@ pub(crate) enum FsError {
///
/// * [`Path::canonicalize`]
/// * [`MountInfo::mount_dir`]
+#[cfg(unix)]
fn mount_info_from_path
(
mounts: &[MountInfo],
path: P,
@@ -128,6 +131,7 @@ impl Filesystem {
/// * [`Path::canonicalize`]
/// * [`MountInfo::mount_dir`]
///
+ #[cfg(unix)]
pub(crate) fn from_path
(mounts: &[MountInfo], path: P) -> Result
where
P: AsRef,
@@ -141,7 +145,7 @@ impl Filesystem {
}
}
-#[cfg(test)]
+#[cfg(all(test, unix))]
mod tests {
mod mount_info_from_path {
diff --git a/src/uu/df/src/platform/windows.rs b/src/uu/df/src/platform/windows.rs
index 400d37ba8c2..5f694721498 100644
--- a/src/uu/df/src/platform/windows.rs
+++ b/src/uu/df/src/platform/windows.rs
@@ -3,7 +3,9 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
-//! Windows backend of `df`: volume usage probes and the `-i` notice.
+// spell-checker:ignore SUBST
+
+//! Windows backend of `df`: volume usage probes, path resolution and the `-i` notice.
use std::ffi::OsString;
use std::path::Path;
@@ -21,13 +23,7 @@ pub(crate) fn sync() {}
/// Usage of the filesystem at `mount_info`, `None` if it cannot be queried.
pub(crate) fn fs_usage(mount_info: &MountInfo) -> Option {
- let stat_path = if mount_info.mount_dir.is_empty() {
- // On windows, we expect the volume id
- mount_info.dev_id.as_ref()
- } else {
- mount_info.mount_dir.as_os_str()
- };
- FsUsage::new(Path::new(stat_path)).ok()
+ FsUsage::new(Path::new(&mount_info.mount_dir)).ok()
}
/// Find and create the filesystem from the given mount.
@@ -39,7 +35,9 @@ pub(crate) fn filesystem_from_mount(
Filesystem::new(mount.clone(), file).ok_or(FsError::MountMissing)
}
-/// Find and create the filesystem that contains `path` through the mount table.
+/// Find and create the filesystem that contains `path`: the mount with the
+/// longest directory prefixing it, or, when the mount table does not list it
+/// (UNC paths, unreadable table), the volume root the OS reports for it.
pub(crate) fn filesystem_for_path(
mounts: &[MountInfo],
_use_fallback: bool,
@@ -48,7 +46,23 @@ pub(crate) fn filesystem_for_path
(
where
P: AsRef,
{
- Filesystem::from_path(mounts, path)
+ let path = path.as_ref();
+ let file = path.as_os_str().to_owned();
+ // Not `canonicalize`: it resolves SUBST drives and junctions away and
+ // yields `\\?\` prefixes that never match a mount directory.
+ let absolute = std::path::absolute(path).map_err(|_| FsError::InvalidPath)?;
+ absolute.metadata().map_err(|_| FsError::InvalidPath)?;
+ let longest = mounts
+ .iter()
+ .filter(|m| absolute.starts_with(&m.mount_dir))
+ .max_by_key(|m| m.mount_dir.len());
+ let mount_info = if let Some(mount_info) = longest {
+ mount_info.clone()
+ } else {
+ let root = uucore::fs::volume_path_name(&absolute).map_err(|_| FsError::MountMissing)?;
+ MountInfo::from_mount_dir(root.into_os_string())
+ };
+ Filesystem::new(mount_info, Some(file)).ok_or(FsError::MountMissing)
}
/// `-i` is not supported: say so and stop successfully.
diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml
index 88a6ef9a2ba..05b9b0b156e 100644
--- a/src/uucore/Cargo.toml
+++ b/src/uucore/Cargo.toml
@@ -119,7 +119,9 @@ windows-sys = { workspace = true, optional = true, default-features = false, fea
"Win32_Storage_FileSystem",
"Win32_Foundation",
"Win32_Globalization",
+ "Win32_NetworkManagement_WNet",
"Win32_System_Console",
+ "Win32_System_Diagnostics_Debug",
"Win32_System_IO",
"Win32_System_Ioctl",
"Win32_System_JobObjects",
@@ -144,7 +146,7 @@ entries = ["libc", "rustix/fs", "rustix/process"]
extendedbigdecimal = ["bigdecimal", "num-traits"]
fast-inc = []
fs = ["dunce", "libc", "rustix/fs", "windows-sys"]
-fsext = ["libc", "windows-sys", "bstr"]
+fsext = ["libc", "windows-sys", "bstr", "wide"]
fsxattr = ["xattr", "itertools", "libc"]
hardware = []
lines = []
diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext/mod.rs
similarity index 82%
rename from src/uucore/src/lib/features/fsext.rs
rename to src/uucore/src/lib/features/fsext/mod.rs
index 79d34644f76..b676936b130 100644
--- a/src/uucore/src/lib/features/fsext.rs
+++ b/src/uucore/src/lib/features/fsext/mod.rs
@@ -7,16 +7,15 @@
// spell-checker:ignore DATETIME getmntinfo subsecond (fs) cifs smbfs
+#[cfg(windows)]
+mod windows;
+
#[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))]
const LINUX_MTAB: &str = "/etc/mtab";
#[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))]
const LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))]
static MOUNT_OPT_BIND: &str = "bind";
-#[cfg(windows)]
-const MAX_PATH: usize = 266;
-#[cfg(windows)]
-static EXIT_ERR: i32 = 1;
#[cfg(any(
target_vendor = "apple",
@@ -25,39 +24,11 @@ static EXIT_ERR: i32 = 1;
target_os = "openbsd"
))]
use crate::os_str_from_bytes;
-#[cfg(windows)]
-use crate::show_warning;
-#[cfg(not(target_os = "wasi"))]
+#[cfg(unix)]
use std::ffi::OsStr;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
-#[cfg(windows)]
-use std::os::windows::ffi::OsStrExt;
-#[cfg(windows)]
-use windows_sys::Win32::{
- Foundation::{ERROR_NO_MORE_FILES, INVALID_HANDLE_VALUE},
- Storage::FileSystem::{
- FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceW, GetDriveTypeW,
- GetVolumeInformationW, GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
- },
- System::WindowsProgramming::DRIVE_REMOTE,
-};
-
-#[cfg(windows)]
-#[allow(non_snake_case)]
-fn LPWSTR2String(buf: &[u16]) -> String {
- let len = buf.iter().position(|&n| n == 0).unwrap();
- String::from_utf16(&buf[..len]).unwrap()
-}
-
-#[cfg(windows)]
-fn to_nul_terminated_wide_string(s: impl AsRef) -> Vec {
- s.as_ref()
- .encode_wide()
- .chain(Some(0))
- .collect::>()
-}
#[cfg(unix)]
use core::ffi::CStr;
@@ -67,12 +38,10 @@ use libc::{
};
#[cfg(unix)]
use std::ffi::CString;
-#[cfg(not(target_os = "wasi"))]
+#[cfg(unix)]
use std::io::Error as IOError;
#[cfg(unix)]
use std::mem;
-#[cfg(windows)]
-use std::path::Path;
use std::time::SystemTime;
#[cfg(unix)]
use std::time::UNIX_EPOCH;
@@ -187,7 +156,7 @@ pub fn metadata_get_time(md: &Metadata, md_time: MetadataTimeField) -> Option Option {
- let mut dev_name_buf = [0u16; MAX_PATH];
- volume_name.pop();
- unsafe {
- QueryDosDeviceW(
- OsStr::new(&volume_name)
- .encode_wide()
- .chain(Some(0))
- .skip(4)
- .collect::>()
- .as_ptr(),
- dev_name_buf.as_mut_ptr(),
- dev_name_buf.len() as u32,
- )
- };
- volume_name.push('\\');
- let dev_name = LPWSTR2String(&dev_name_buf);
-
- let mut mount_root_buf = [0u16; MAX_PATH];
- let success = unsafe {
- let volume_name = to_nul_terminated_wide_string(&volume_name);
- GetVolumePathNamesForVolumeNameW(
- volume_name.as_ptr(),
- mount_root_buf.as_mut_ptr(),
- mount_root_buf.len() as u32,
- ptr::null_mut(),
- )
- };
- if 0 == success {
- // TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
- return None;
- }
- // TODO: This should probably call `OsString::from_wide`, but unclear if
- // terminating zeros need to be striped first.
- let mount_root = LPWSTR2String(&mount_root_buf);
-
- let mut fs_type_buf = [0u16; MAX_PATH];
- let success = unsafe {
- let mount_root = to_nul_terminated_wide_string(&mount_root);
- GetVolumeInformationW(
- mount_root.as_ptr(),
- ptr::null_mut(),
- 0,
- ptr::null_mut(),
- ptr::null_mut(),
- ptr::null_mut(),
- fs_type_buf.as_mut_ptr(),
- fs_type_buf.len() as u32,
- )
- };
- let fs_type = if 0 == success {
- None
- } else {
- Some(LPWSTR2String(&fs_type_buf))
- };
- let remote = DRIVE_REMOTE
- == unsafe {
- let mount_root = to_nul_terminated_wide_string(&mount_root);
- GetDriveTypeW(mount_root.as_ptr())
- };
- Some(Self {
- dev_id: volume_name,
- dev_name,
- fs_type: fs_type.unwrap_or_default(),
- mount_root: mount_root.into(), // TODO: We should figure out how to keep an OsString here.
- mount_dir: OsString::new(),
- mount_option: String::new(),
- remote,
- dummy: false,
- })
- }
}
#[cfg(any(
@@ -431,8 +327,7 @@ use crate::error::UResult;
target_vendor = "apple",
target_os = "freebsd",
target_os = "netbsd",
- target_os = "openbsd",
- windows
+ target_os = "openbsd"
))]
use crate::error::USimpleError;
#[cfg(any(target_os = "linux", target_os = "android", target_os = "cygwin"))]
@@ -443,8 +338,7 @@ use std::io::{BufRead, BufReader};
target_vendor = "apple",
target_os = "freebsd",
target_os = "netbsd",
- target_os = "openbsd",
- windows
+ target_os = "openbsd"
))]
use std::ptr;
#[cfg(any(
@@ -493,44 +387,7 @@ pub fn read_fs_list() -> UResult> {
}
#[cfg(windows)]
{
- let mut volume_name_buf = [0u16; MAX_PATH];
- // As recommended in the MS documentation, retrieve the first volume before the others
- let find_handle =
- unsafe { FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as u32) };
- if INVALID_HANDLE_VALUE == find_handle {
- let os_err = IOError::last_os_error();
- let msg = format!("FindFirstVolumeW failed: {os_err}");
- return Err(USimpleError::new(EXIT_ERR, msg));
- }
- let mut mounts = Vec::::new();
- loop {
- let volume_name = LPWSTR2String(&volume_name_buf);
- if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') {
- show_warning!("A bad path was skipped: {volume_name}");
- continue;
- }
- if let Some(m) = MountInfo::new(volume_name) {
- mounts.push(m);
- }
- if 0 == unsafe {
- FindNextVolumeW(
- find_handle,
- volume_name_buf.as_mut_ptr(),
- volume_name_buf.len() as u32,
- )
- } {
- let err = IOError::last_os_error();
- if err.raw_os_error() != Some(ERROR_NO_MORE_FILES as i32) {
- let msg = format!("FindNextVolumeW failed: {err}");
- return Err(USimpleError::new(EXIT_ERR, msg));
- }
- break;
- }
- }
- unsafe {
- FindVolumeClose(find_handle);
- }
- Ok(mounts)
+ windows::read_fs_list()
}
#[cfg(any(
target_os = "aix",
@@ -615,61 +472,6 @@ impl FsUsage {
};
}
}
- #[cfg(windows)]
- pub fn new(path: &Path) -> UResult {
- let mut root_path = [0u16; MAX_PATH];
- let success = unsafe {
- let path = to_nul_terminated_wide_string(path);
- GetVolumePathNamesForVolumeNameW(
- //path_utf8.as_ptr(),
- path.as_ptr(),
- root_path.as_mut_ptr(),
- root_path.len() as u32,
- ptr::null_mut(),
- )
- };
- if 0 == success {
- let msg = format!(
- "GetVolumePathNamesForVolumeNameW failed: {}",
- IOError::last_os_error()
- );
- return Err(USimpleError::new(EXIT_ERR, msg));
- }
-
- let mut sectors_per_cluster = 0;
- let mut bytes_per_sector = 0;
- let mut number_of_free_clusters = 0;
- let mut total_number_of_clusters = 0;
-
- unsafe {
- let path = to_nul_terminated_wide_string(path);
- GetDiskFreeSpaceW(
- path.as_ptr(),
- &raw mut sectors_per_cluster,
- &raw mut bytes_per_sector,
- &raw mut number_of_free_clusters,
- &raw mut total_number_of_clusters,
- );
- }
-
- let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
- Ok(Self {
- // f_bsize File system block size.
- blocksize: bytes_per_cluster,
- // f_blocks - Total number of blocks on the file system, in units of f_frsize.
- // frsize = Fundamental file system block size (fragment size).
- blocks: total_number_of_clusters as u64,
- // Total number of free blocks.
- bfree: number_of_free_clusters as u64,
- // Total number of free blocks available to non-privileged processes.
- bavail: 0,
- bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0,
- // Total number of file nodes (inodes) on the file system.
- files: 0, // Not available on windows
- // Total number of free file nodes (inodes).
- ffree: 0, // Meaningless on Windows
- })
- }
}
#[cfg(unix)]
diff --git a/src/uucore/src/lib/features/fsext/windows.rs b/src/uucore/src/lib/features/fsext/windows.rs
new file mode 100644
index 00000000000..dd3eb15b077
--- /dev/null
+++ b/src/uucore/src/lib/features/fsext/windows.rs
@@ -0,0 +1,316 @@
+// This file is part of the uutils coreutils package.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+// spell-checker:ignore WNet FAILCRITICALERRORS SUBST
+
+//! Windows backend of `fsext`: volume enumeration and usage probes.
+
+use std::ffi::OsString;
+use std::io;
+use std::path::Path;
+
+use super::{FsUsage, MountInfo};
+use crate::error::UResult;
+
+/// Every mount path of every volume, then the drive letters that are not
+/// volume mount points (mapped network drives, SUBST drives).
+pub(super) fn read_fs_list() -> UResult> {
+ let _quiet = sys::ErrorMode::fail_critical_errors();
+ let mut mounts = Vec::new();
+ for volume in sys::volumes()? {
+ for mount_dir in sys::volume_mount_paths(&volume).unwrap_or_default() {
+ mounts.push(MountInfo::from_mount_dir(mount_dir));
+ }
+ }
+ for drive in sys::logical_drives() {
+ if !mounts.iter().any(|m| m.mount_dir == drive) {
+ mounts.push(MountInfo::from_mount_dir(drive));
+ }
+ }
+ Ok(mounts)
+}
+
+impl MountInfo {
+ /// The filesystem mounted at `mount_dir` (`C:\`, `C:\mount\`, `\\server\share\`).
+ pub fn from_mount_dir(mount_dir: OsString) -> Self {
+ let remote = sys::is_remote_drive(&mount_dir);
+ let dev_name = remote
+ .then(|| sys::remote_name(&mount_dir))
+ .flatten()
+ .unwrap_or_else(|| mount_dir.to_string_lossy().into_owned());
+ let (dev_id, fs_type) = match sys::volume_information(&mount_dir) {
+ Ok(info) => (info.serial.to_string(), info.fs_type),
+ Err(_) => (dev_name.clone(), String::new()),
+ };
+ Self {
+ dev_id,
+ dev_name,
+ fs_type,
+ mount_root: OsString::new(),
+ mount_dir,
+ mount_option: String::new(),
+ remote,
+ dummy: false,
+ }
+ }
+}
+
+impl FsUsage {
+ /// Usage of the volume mounted at `root`; Windows reports no inode counts.
+ pub fn new(root: &Path) -> io::Result {
+ let _quiet = sys::ErrorMode::fail_critical_errors();
+ let root = root.as_os_str();
+ let space = sys::disk_space(root)?;
+ let blocksize = sys::cluster_size(root).unwrap_or(1).max(1);
+ Ok(Self {
+ blocksize,
+ blocks: space.total / blocksize,
+ bfree: space.free / blocksize,
+ bavail: space.available / blocksize,
+ bavail_top_bit_set: false,
+ files: 0,
+ ffree: 0,
+ })
+ }
+}
+
+/// Safe wrappers around the Win32 calls; every `unsafe` lives here.
+mod sys {
+ use std::ffi::{OsStr, OsString};
+ use std::io;
+ use std::os::windows::ffi::OsStringExt;
+ use std::ptr;
+
+ use crate::wide::{FromWide, ToWide};
+ use windows_sys::Win32::Foundation::{
+ ERROR_MORE_DATA, ERROR_NO_MORE_FILES, HANDLE, INVALID_HANDLE_VALUE, MAX_PATH, NO_ERROR,
+ };
+ use windows_sys::Win32::NetworkManagement::WNet::WNetGetConnectionW;
+ use windows_sys::Win32::Storage::FileSystem::{
+ FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDiskFreeSpaceExW, GetDiskFreeSpaceW,
+ GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, GetVolumePathNamesForVolumeNameW,
+ };
+ use windows_sys::Win32::System::Diagnostics::Debug::{
+ SEM_FAILCRITICALERRORS, SetThreadErrorMode,
+ };
+ use windows_sys::Win32::System::WindowsProgramming::DRIVE_REMOTE;
+ use windows_sys::core::BOOL;
+
+ const BUF_LEN: usize = MAX_PATH as usize + 1;
+
+ fn cvt(result: BOOL) -> io::Result<()> {
+ if result == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Keeps the "no disk in drive" dialog away while probing drives without
+ /// media; the previous mode is restored on drop.
+ pub struct ErrorMode(u32);
+
+ impl ErrorMode {
+ pub fn fail_critical_errors() -> Self {
+ let mut previous = 0;
+ // SAFETY: `previous` is a valid out-pointer.
+ unsafe { SetThreadErrorMode(SEM_FAILCRITICALERRORS, &raw mut previous) };
+ Self(previous)
+ }
+ }
+
+ impl Drop for ErrorMode {
+ fn drop(&mut self) {
+ // SAFETY: a null out-pointer is allowed.
+ unsafe { SetThreadErrorMode(self.0, ptr::null_mut()) };
+ }
+ }
+
+ struct FindVolume(HANDLE);
+
+ impl Drop for FindVolume {
+ fn drop(&mut self) {
+ // SAFETY: the handle came from `FindFirstVolumeW` and is closed once.
+ unsafe { FindVolumeClose(self.0) };
+ }
+ }
+
+ /// The `\\?\Volume{...}\` name of every volume.
+ pub fn volumes() -> io::Result> {
+ let mut name = [0u16; BUF_LEN];
+ // SAFETY: `name` is a valid buffer of `name.len()` units.
+ let handle = unsafe { FindFirstVolumeW(name.as_mut_ptr(), name.len() as u32) };
+ if handle == INVALID_HANDLE_VALUE {
+ return Err(io::Error::last_os_error());
+ }
+ let handle = FindVolume(handle);
+ let mut volumes = vec![String::from_wide_null(&name)];
+ loop {
+ // SAFETY: `handle` is open; `name` is a valid buffer of `name.len()` units.
+ if unsafe { FindNextVolumeW(handle.0, name.as_mut_ptr(), name.len() as u32) } == 0 {
+ let err = io::Error::last_os_error();
+ return if err.raw_os_error() == Some(ERROR_NO_MORE_FILES as i32) {
+ Ok(volumes)
+ } else {
+ Err(err)
+ };
+ }
+ volumes.push(String::from_wide_null(&name));
+ }
+ }
+
+ /// The drive letters and mounted folders `volume` is reachable at, each
+ /// with a trailing `\`.
+ pub fn volume_mount_paths(volume: &str) -> io::Result> {
+ let volume = volume.to_wide_null();
+ let mut paths = vec![0u16; BUF_LEN];
+ loop {
+ let mut len = 0;
+ // SAFETY: `volume` is NUL-terminated; `paths` is a valid buffer of
+ // `paths.len()` units and `len` a valid out-pointer.
+ let ok = unsafe {
+ GetVolumePathNamesForVolumeNameW(
+ volume.as_ptr(),
+ paths.as_mut_ptr(),
+ paths.len() as u32,
+ &raw mut len,
+ )
+ };
+ if ok != 0 {
+ return Ok(paths[..len as usize]
+ .split(|&c| c == 0)
+ .filter(|p| !p.is_empty())
+ .map(OsString::from_wide)
+ .collect());
+ }
+ let err = io::Error::last_os_error();
+ if err.raw_os_error() != Some(ERROR_MORE_DATA as i32) {
+ return Err(err);
+ }
+ paths.resize(len as usize, 0);
+ }
+ }
+
+ /// The root of every drive letter in use, `A:\` to `Z:\`.
+ pub fn logical_drives() -> impl Iterator- {
+ // SAFETY: no preconditions.
+ let mask = unsafe { GetLogicalDrives() };
+ (b'A'..=b'Z')
+ .filter(move |letter| mask & (1 << (letter - b'A')) != 0)
+ .map(|letter| OsString::from(format!("{}:\\", letter as char)))
+ }
+
+ pub struct VolumeInformation {
+ pub fs_type: String,
+ pub serial: u32,
+ }
+
+ pub fn volume_information(root: &OsStr) -> io::Result {
+ let root = root.to_wide_null();
+ let mut serial = 0;
+ let mut fs_type = [0u16; BUF_LEN];
+ // SAFETY: `root` is NUL-terminated; `serial` and `fs_type` are valid
+ // out-buffers and the remaining out-pointers may be null.
+ cvt(unsafe {
+ GetVolumeInformationW(
+ root.as_ptr(),
+ ptr::null_mut(),
+ 0,
+ &raw mut serial,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ fs_type.as_mut_ptr(),
+ fs_type.len() as u32,
+ )
+ })?;
+ Ok(VolumeInformation {
+ fs_type: String::from_wide_null(&fs_type),
+ serial,
+ })
+ }
+
+ pub fn is_remote_drive(root: &OsStr) -> bool {
+ let root = root.to_wide_null();
+ // SAFETY: `root` is NUL-terminated.
+ unsafe { GetDriveTypeW(root.as_ptr()) == DRIVE_REMOTE }
+ }
+
+ /// The UNC name a drive letter is mapped to, `\\server\share`.
+ pub fn remote_name(root: &OsStr) -> Option {
+ // `X:` only; the API rejects the trailing separator.
+ let local: Vec = root.to_wide().into_iter().take(2).chain([0]).collect();
+ let mut remote = [0u16; BUF_LEN];
+ let mut len = remote.len() as u32;
+ // SAFETY: `local` is NUL-terminated; `remote` is a valid buffer of `len` units.
+ let status =
+ unsafe { WNetGetConnectionW(local.as_ptr(), remote.as_mut_ptr(), &raw mut len) };
+ (status == NO_ERROR).then(|| String::from_wide_null(&remote))
+ }
+
+ pub struct DiskSpace {
+ pub total: u64,
+ pub free: u64,
+ pub available: u64,
+ }
+
+ /// Byte counts of the volume at `root`; `available` honours quotas.
+ pub fn disk_space(root: &OsStr) -> io::Result {
+ let root = root.to_wide_null();
+ let mut space = DiskSpace {
+ total: 0,
+ free: 0,
+ available: 0,
+ };
+ // SAFETY: `root` is NUL-terminated; the three out-pointers are valid.
+ cvt(unsafe {
+ GetDiskFreeSpaceExW(
+ root.as_ptr(),
+ &raw mut space.available,
+ &raw mut space.total,
+ &raw mut space.free,
+ )
+ })?;
+ Ok(space)
+ }
+
+ /// Bytes per allocation unit of the volume at `root`.
+ pub fn cluster_size(root: &OsStr) -> io::Result {
+ let root = root.to_wide_null();
+ let mut sectors_per_cluster = 0;
+ let mut bytes_per_sector = 0;
+ // SAFETY: `root` is NUL-terminated; the two out-pointers are valid and
+ // the remaining ones may be null.
+ cvt(unsafe {
+ GetDiskFreeSpaceW(
+ root.as_ptr(),
+ &raw mut sectors_per_cluster,
+ &raw mut bytes_per_sector,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ })?;
+ Ok(u64::from(sectors_per_cluster) * u64::from(bytes_per_sector))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::read_fs_list;
+ use std::ffi::OsString;
+
+ #[test]
+ fn test_read_fs_list_has_system_drive() {
+ let system_drive = OsString::from(std::env::var("SystemDrive").unwrap() + "\\");
+ let mounts = read_fs_list().unwrap();
+ assert!(
+ mounts
+ .iter()
+ .all(|m| m.mount_dir.to_string_lossy().ends_with('\\'))
+ );
+ let system = mounts.iter().find(|m| m.mount_dir == system_drive).unwrap();
+ assert!(!system.fs_type.is_empty());
+ assert_eq!(system.dev_name, system_drive.to_string_lossy());
+ }
+}