fix(windows): handle zfs volume paths (#3157)

* fix(windows): handle zfs volume paths

* fix(windows): address volume path review

* refactor(windows): share fallback path resolution

---------

Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
cxymds
2026-06-01 21:04:40 +08:00
committed by GitHub
parent e91e513ab3
commit 39a283fbce
4 changed files with 128 additions and 15 deletions
+17
View File
@@ -19,6 +19,23 @@ use std::{fmt::Display, path::Path};
use tracing::debug;
use url::{ParseError, Url};
#[cfg(windows)]
pub(crate) fn windows_fallback_local_path(
path: &str,
canonicalize_error: &std::io::Error,
context: &'static str,
) -> std::io::Result<std::path::PathBuf> {
let absolute = Path::new(path).absolutize()?.to_path_buf();
tracing::warn!(
path = %path,
canonicalize_error = %canonicalize_error,
resolved = ?absolute,
context = context,
"using windows fallback path resolution for local endpoint"
);
Ok(absolute)
}
/// enum for endpoint type.
#[derive(PartialEq, Eq, Debug)]
pub enum EndpointType {
+51 -11
View File
@@ -356,20 +356,60 @@ impl Debug for LocalDisk {
}
}
/// Resolve the local disk root path from an endpoint path.
///
/// Tries `canonicalize` first (fast path). On Windows, if canonicalization reports
/// `NotFound` for paths that may still be valid mount roots, falls back to
/// `absolutize` + metadata check to accept valid local directory roots that
/// don't support full canonicalization.
fn resolve_local_disk_root(ep_path: &str) -> Result<PathBuf> {
match rustfs_utils::canonicalize(ep_path) {
Ok(path) => Ok(path),
Err(err) => {
if err.kind() != ErrorKind::NotFound {
return Err(to_file_error(err).into());
}
#[cfg(windows)]
{
// On Windows, canonicalize can fail for ZFS volumes, junction points,
// subst drives, and other non-standard filesystem mounts. Try a fallback
// path resolution using absolutize + metadata check.
let absolute = match crate::disk::endpoint::windows_fallback_local_path(ep_path, &err, "local disk root") {
Ok(path) => path,
Err(_) => {
return Err(DiskError::VolumeNotFound);
}
};
match std::fs::metadata(&absolute) {
Ok(metadata) => {
if !metadata.is_dir() {
return Err(DiskError::DiskNotDir);
}
return Ok(absolute);
}
Err(meta_err) => {
if meta_err.kind() == ErrorKind::NotFound {
return Err(DiskError::VolumeNotFound);
}
return Err(to_file_error(meta_err).into());
}
}
}
#[cfg(not(windows))]
{
Err(DiskError::VolumeNotFound)
}
}
}
}
impl LocalDisk {
pub async fn new(ep: &Endpoint, cleanup: bool) -> Result<Self> {
debug!("Creating local disk");
// Use optimized path resolution instead of absolutize() for better performance
// Use dunce::canonicalize instead of std::fs::canonicalize to avoid UNC paths on Windows
let root = match rustfs_utils::canonicalize(ep.get_file_path()) {
Ok(path) => path,
Err(e) => {
if e.kind() == ErrorKind::NotFound {
return Err(DiskError::VolumeNotFound);
}
return Err(to_file_error(e).into());
}
};
let root = resolve_local_disk_root(&ep.get_file_path())?;
ensure_data_usage_layout(&root).await.map_err(DiskError::from)?;
+33 -2
View File
@@ -689,8 +689,39 @@ fn validate_local_physical_disk_independence(pools: &[Endpoints]) -> Result<()>
let canonical = match rustfs_utils::canonicalize(path) {
Ok(path) => path,
Err(err) if err.kind() == ErrorKind::NotFound => {
missing_paths.push(path.clone());
continue;
// On Windows, canonicalize can fail for ZFS volumes, junction points,
// subst drives, and other non-standard mounts. Try absolutize as fallback.
#[cfg(windows)]
{
match crate::disk::endpoint::windows_fallback_local_path(path, &err, "disk independence validation") {
Ok(absolute) => {
let abs_path = absolute.to_string_lossy().into_owned();
match rustfs_utils::os::get_physical_device_ids(&abs_path) {
Ok(ids) => {
for device_id in ids {
device_paths.entry(device_id).or_default().insert(abs_path.clone());
}
}
Err(device_err) => {
return Err(Error::other(format!(
"failed to inspect physical disk for local endpoint '{abs_path}' after fallback path resolution: {device_err}"
)));
}
}
continue;
}
Err(fallback_err) => {
return Err(Error::other(format!(
"failed to resolve local endpoint path '{path}' for disk validation: {err}; fallback resolution failed: {fallback_err}"
)));
}
}
}
#[cfg(not(windows))]
{
missing_paths.push(path.clone());
continue;
}
}
Err(err) => {
return Err(Error::other(format!(
+27 -2
View File
@@ -423,9 +423,30 @@ pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
/// ```
///
pub fn has_ellipses<T: AsRef<str>>(s: &[T]) -> bool {
let pattern = [ELLIPSES, OPEN_BRACES, CLOSE_BRACES];
s.iter().any(|v| {
let v = v.as_ref();
s.iter().any(|v| pattern.iter().any(|p| v.as_ref().contains(p)))
let mut remaining = v;
while let Some(start) = remaining.find(OPEN_BRACES) {
let after_start = &remaining[start + OPEN_BRACES.len()..];
let Some(end) = after_start.find(CLOSE_BRACES) else {
break;
};
// Dotted brace content covers both valid ellipsis patterns like
// {1...64} and invalid attempts like {1..64}, while excluding
// Windows Volume{GUID} paths (GUIDs use hyphens, not dots).
if after_start[..end].contains('.') {
return true;
}
remaining = &after_start[end + CLOSE_BRACES.len()..];
}
// Bare `...` without braces (e.g. "1...64") is still an ellipsis attempt.
if v.contains(ELLIPSES) {
return true;
}
false
})
}
/// Parses an ellipses range pattern of following style
@@ -569,6 +590,10 @@ mod tests {
),
(15, vec!["mydisk-{a...z}{1...20}"], true),
(16, vec!["mydisk-{1...4}{1..2.}"], true),
// Windows Volume{GUID} paths must not match; GUIDs use hyphens, not dots.
(17, vec![r"\\?\Volume{9425a190-4bb3-11f1-86de-803253b6a8b8}\"], false),
(18, vec![r"//./Volume{9425a190-4bb3-11f1-86de-803253b6a8b8}/"], false),
(19, vec![r"\\?\Volume{9425a190-4bb3-11f1-86de-803253b6a8b8}\disk-{1..4}"], true),
];
for (i, args, expected) in test_cases {