mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-17 02:15:28 +00:00
f17ea7f146
* fix(heal): gate auto replacement formatting Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): require replacement target outcomes Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): bind resumes to replacement targets Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): fence healing marker ownership Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): cover replacement target completion Co-Authored-By: heihutu <heihutu@gmail.com> * docs(heal): clarify replacement recovery status Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): canonicalize replacement target checks Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): satisfy marker test module lint Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): scope automatic replacement format Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): require a mounted replacement target Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): avoid cloned ref slice in test Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): revalidate replacement before scanning Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): reset stale resume checkpoints Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): release scanner disk map before probing Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): persist replacement intent before format Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): fail closed on mountinfo read errors Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): fence replacement target identity Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): order replacement completion cleanup Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): atomically seal replacement completion Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): census replacement target shards Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): fence replacement recovery ownership Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): preserve replacement recovery anchors Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): satisfy replacement recovery lint gates Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): bind replacement identity to mount lease Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): cover durable replacement recovery states Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): validate persisted resume task identifiers Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): avoid blocking replacement marker CAS Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): report failed marker rollback Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): pin replacement resume schema compatibility Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): preserve durable recovery anchors Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): preserve public disk path semantics Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): use canonical replacement task ids Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): cover automatic replacement in 3x4 cluster Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): verify replacement target commits Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): persist replacement completion proof Co-Authored-By: heihutu <heihutu@gmail.com> * feat(heal): expose durable replacement status Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): bound durable replacement discovery Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): remove replacement readiness bypass Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): retry terminal replacement cleanup Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): isolate replacement intents from legacy resume Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): migrate legacy replacement intents at startup Co-Authored-By: heihutu <heihutu@gmail.com> * style(heal): apply strict clippy fix Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): prioritize active replacement recovery state Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): bind readiness to the admitted mount lease Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): atomically publish replacement intents Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): isolate replacement recovery directory Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): tolerate an empty recovery directory Co-Authored-By: heihutu <heihutu@gmail.com> * style(heal): remove redundant disk bytes conversion Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): reconcile proof-first replacement recovery Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): fence torn intent recovery Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): cover replacement migration conflicts Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): fence replacement lease mount identity Co-Authored-By: heihutu <heihutu@gmail.com> * test(heal): cover missing replacement path admission Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): reject conflicting legacy completion proof Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): fall back to proc mount identity Co-Authored-By: heihutu <heihutu@gmail.com> * feat(admin): expose replacement recovery status Surface the local durable replacement recovery snapshot in the background heal status response so operators can tell whether replacement cleanup is definitive or still pending. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): keep replacement status compatible Keep the existing background heal status response wire-compatible while retaining the Linux mount lease cleanup needed for the replacement recovery branch. Co-Authored-By: heihutu <heihutu@gmail.com> * style(ecstore): match linux mount lease formatting Keep Linux rustfmt output stable for the replacement mount lease comparison. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): qualify mount lease test constant Use the disk module path for the format config constant in the Linux mount lease regression test. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): keep procfd mount roots directory-safe Use a procfd path with an explicit directory component so Unix directory guards can open the replacement mount lease root with O_NOFOLLOW while preserving handle-relative I/O semantics. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): delete empty leased buckets via dirfd Use the held mount lease fd as the parent for non-force empty bucket deletion on Linux so procfd-rooted paths do not get rejected as BucketNotEmpty. Also make the download-part OpenOptions truncate behavior explicit and keep fsync test recording stable across procfd canonicalization. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): scan leased bucket paths for emptiness Use the local disk I/O root for bucket emptiness probes before non-force bucket deletion and table-bucket metadata checks. This keeps validation on the same mount instance as the subsequent local disk delete path. Co-Authored-By: heihutu <heihutu@gmail.com> * test(ecstore): align lease path test probes Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): block unsafe replacement recovery restarts Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): defer blocked replacement candidates Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): retry transient replacement discovery Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): keep transient recovery errors retryable Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): block corrupt legacy replacement state Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): classify flat replacement intent corruption Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): keep transient resume loads retryable Classify malformed legacy replacement state as blocking corruption while preserving disk and transient load failures for retry. This avoids permanently blocking replacement recovery on temporary storage errors. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): avoid latching transient legacy publishes Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): retry blocked legacy migrations Co-Authored-By: heihutu <heihutu@gmail.com> * fix(heal): defer blocked startup recoveries Co-Authored-By: heihutu <heihutu@gmail.com> * fix(ecstore): preserve disk sync limiter across lease roots Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com> Co-authored-by: zhi22915 <qiuzgang@gmail.com>
217 lines
8.0 KiB
Rust
217 lines
8.0 KiB
Rust
// Copyright 2024 RustFS Team
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
use crate::os::{DiskInfo, IOStats};
|
|
use std::io::Error;
|
|
use std::path::Path;
|
|
use windows::Win32::Foundation::MAX_PATH;
|
|
use windows::Win32::Storage::FileSystem::{GetDiskFreeSpaceExW, GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW};
|
|
|
|
/// Returns total and free bytes available in a directory, e.g. `C:\`.
|
|
// SAFETY: Windows API calls receive null-terminated UTF-16 paths and valid
|
|
// pointers to initialized stack output variables.
|
|
#[allow(unsafe_code)]
|
|
pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<DiskInfo> {
|
|
let path_wide = to_wide_path(p.as_ref());
|
|
|
|
let mut free_bytes_available = 0u64;
|
|
let mut total_number_of_bytes = 0u64;
|
|
let mut total_number_of_free_bytes = 0u64;
|
|
|
|
// SAFETY:
|
|
// 1. `path_wide` is a valid null-terminated UTF-16 string.
|
|
// 2. Pointers to `u64` variables are valid and point to initialized stack memory.
|
|
unsafe {
|
|
GetDiskFreeSpaceExW(
|
|
windows::core::PCWSTR::from_raw(path_wide.as_ptr()),
|
|
Some(&mut free_bytes_available),
|
|
Some(&mut total_number_of_bytes),
|
|
Some(&mut total_number_of_free_bytes),
|
|
)
|
|
.map_err(|e| Error::from_raw_os_error(e.code().0))?;
|
|
}
|
|
|
|
let total = total_number_of_bytes;
|
|
let free = free_bytes_available;
|
|
|
|
if free > total {
|
|
return Err(Error::other(format!(
|
|
"detected free space ({free}) > total drive space ({total}), fs corruption at ({}). please run 'fsck'",
|
|
p.as_ref().display()
|
|
)));
|
|
}
|
|
|
|
let mut sectors_per_cluster = 0u32;
|
|
let mut bytes_per_sector = 0u32;
|
|
let mut number_of_free_clusters = 0u32;
|
|
let mut total_number_of_clusters = 0u32;
|
|
|
|
// SAFETY:
|
|
// 1. `path_wide` is a valid null-terminated UTF-16 string.
|
|
// 2. Pointers to `u32` variables are valid and point to initialized stack memory.
|
|
unsafe {
|
|
GetDiskFreeSpaceW(
|
|
windows::core::PCWSTR::from_raw(path_wide.as_ptr()),
|
|
Some(&mut sectors_per_cluster),
|
|
Some(&mut bytes_per_sector),
|
|
Some(&mut number_of_free_clusters),
|
|
Some(&mut total_number_of_clusters),
|
|
)
|
|
.map_err(|e| Error::from_raw_os_error(e.code().0))?;
|
|
}
|
|
|
|
Ok(DiskInfo {
|
|
total,
|
|
free,
|
|
used: total - free,
|
|
files: total_number_of_clusters as u64,
|
|
ffree: number_of_free_clusters as u64,
|
|
fstype: get_windows_fs_type(&path_wide).unwrap_or_default(),
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
// SAFETY: Windows volume APIs receive null-terminated UTF-16 paths and fixed
|
|
// stack buffers sized for the documented MAX_PATH outputs used here.
|
|
#[allow(unsafe_code)]
|
|
fn get_windows_fs_type(p: &[u16]) -> std::io::Result<String> {
|
|
let path = get_volume_name(p)?;
|
|
|
|
let mut volume_serial_number = 0u32;
|
|
let mut maximum_component_length = 0u32;
|
|
let mut file_system_flags = 0u32;
|
|
let mut volume_name_buffer = [0u16; MAX_PATH as usize];
|
|
let mut file_system_name_buffer = [0u16; MAX_PATH as usize];
|
|
|
|
// SAFETY:
|
|
// 1. `path` is a valid null-terminated UTF-16 string (volume root path).
|
|
// 2. Buffers are allocated with `MAX_PATH` size, which is sufficient for standard Windows paths.
|
|
// 3. Pointers to output variables are valid.
|
|
unsafe {
|
|
GetVolumeInformationW(
|
|
windows::core::PCWSTR::from_raw(path.as_ptr()),
|
|
Some(&mut volume_name_buffer),
|
|
Some(&mut volume_serial_number),
|
|
Some(&mut maximum_component_length),
|
|
Some(&mut file_system_flags),
|
|
Some(&mut file_system_name_buffer),
|
|
)
|
|
.map_err(|e| Error::from_raw_os_error(e.code().0))?;
|
|
}
|
|
|
|
Ok(utf16_to_string(&file_system_name_buffer))
|
|
}
|
|
|
|
// SAFETY: `v` is a null-terminated UTF-16 path and `volume_name_buffer` is a
|
|
// writable MAX_PATH-sized stack buffer for the returned volume path.
|
|
#[allow(unsafe_code)]
|
|
fn get_volume_name(v: &[u16]) -> std::io::Result<Vec<u16>> {
|
|
let mut volume_name_buffer = [0u16; MAX_PATH as usize];
|
|
|
|
// SAFETY:
|
|
// 1. `v` is a valid null-terminated UTF-16 string.
|
|
// 2. `volume_name_buffer` is allocated with `MAX_PATH` size.
|
|
// 3. `GetVolumePathNameW` writes to the buffer and respects the buffer size (implicitly MAX_PATH for this API context usually, though explicit length param isn't present, it expects a buffer large enough).
|
|
// Note: GetVolumePathNameW documentation says "The buffer should be large enough to hold the path". MAX_PATH is generally safe for volume roots.
|
|
unsafe {
|
|
GetVolumePathNameW(windows::core::PCWSTR::from_raw(v.as_ptr()), &mut volume_name_buffer)
|
|
.map_err(|e| Error::from_raw_os_error(e.code().0))?;
|
|
}
|
|
|
|
let len = volume_name_buffer
|
|
.iter()
|
|
.position(|&x| x == 0)
|
|
.unwrap_or(volume_name_buffer.len());
|
|
Ok(volume_name_buffer[..len].to_vec())
|
|
}
|
|
|
|
fn utf16_to_string(v: &[u16]) -> String {
|
|
let len = v.iter().position(|&x| x == 0).unwrap_or(v.len());
|
|
String::from_utf16_lossy(&v[..len])
|
|
}
|
|
|
|
fn to_wide_path(path: &Path) -> Vec<u16> {
|
|
path.as_os_str().encode_wide().chain(std::iter::once(0)).collect()
|
|
}
|
|
|
|
// Helper trait to access encode_wide which is only available on Windows
|
|
use std::os::windows::ffi::OsStrExt;
|
|
|
|
pub fn same_disk(disk1: &str, disk2: &str) -> std::io::Result<bool> {
|
|
let path1_wide = to_wide_path(Path::new(disk1));
|
|
let path2_wide = to_wide_path(Path::new(disk2));
|
|
|
|
let volume1 = get_volume_name(&path1_wide)?;
|
|
let volume2 = get_volume_name(&path2_wide)?;
|
|
|
|
Ok(volume1 == volume2)
|
|
}
|
|
|
|
/// Automatic replacement formatting is disabled until Windows has an
|
|
/// equivalent mount identity probe.
|
|
pub fn is_mount_point(_path: &std::path::Path) -> std::io::Result<bool> {
|
|
Ok(false)
|
|
}
|
|
|
|
pub fn get_physical_device_ids(disk: &str) -> std::io::Result<Vec<String>> {
|
|
let path_wide = to_wide_path(Path::new(disk));
|
|
let volume = get_volume_name(&path_wide)?;
|
|
|
|
Ok(vec![String::from_utf16_lossy(&volume)])
|
|
}
|
|
|
|
/// Returns the volume serial number for the filesystem backing `path`.
|
|
///
|
|
/// This is the Windows equivalent of Linux's `st_dev` major:minor pair and is
|
|
/// useful for diagnostic purposes when validating physical disk independence.
|
|
// SAFETY: `path_wide` is a valid null-terminated UTF-16 string and all output
|
|
// SAFETY: pointers target initialized stack variables with documented MAX_PATH sizing.
|
|
#[allow(unsafe_code)]
|
|
pub fn get_volume_serial_number(path: &str) -> std::io::Result<u32> {
|
|
let path_wide = to_wide_path(Path::new(path));
|
|
let volume = get_volume_name(&path_wide)?;
|
|
|
|
let mut volume_serial_number = 0u32;
|
|
let mut maximum_component_length = 0u32;
|
|
let mut file_system_flags = 0u32;
|
|
let mut volume_name_buffer = [0u16; MAX_PATH as usize];
|
|
let mut file_system_name_buffer = [0u16; MAX_PATH as usize];
|
|
|
|
// SAFETY:
|
|
// 1. `volume` is a valid null-terminated UTF-16 string (volume root path).
|
|
// 2. Buffers are allocated with `MAX_PATH` size.
|
|
// 3. Pointers to output variables are valid.
|
|
unsafe {
|
|
GetVolumeInformationW(
|
|
windows::core::PCWSTR::from_raw(volume.as_ptr()),
|
|
Some(&mut volume_name_buffer),
|
|
Some(&mut volume_serial_number),
|
|
Some(&mut maximum_component_length),
|
|
Some(&mut file_system_flags),
|
|
Some(&mut file_system_name_buffer),
|
|
)
|
|
.map_err(|e| Error::from_raw_os_error(e.code().0))?;
|
|
}
|
|
|
|
Ok(volume_serial_number)
|
|
}
|
|
|
|
pub fn check_cross_device_mounts(_paths: &[String]) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_drive_stats(_major: u32, _minor: u32) -> std::io::Result<IOStats> {
|
|
Ok(IOStats::default())
|
|
}
|