mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
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:
@@ -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 {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user