* fix(heal): gate auto replacement formatting Co-Authored-By: heihutu <[email protected]> * fix(heal): require replacement target outcomes Co-Authored-By: heihutu <[email protected]> * fix(heal): bind resumes to replacement targets Co-Authored-By: heihutu <[email protected]> * fix(heal): fence healing marker ownership Co-Authored-By: heihutu <[email protected]> * test(heal): cover replacement target completion Co-Authored-By: heihutu <[email protected]> * docs(heal): clarify replacement recovery status Co-Authored-By: heihutu <[email protected]> * fix(heal): canonicalize replacement target checks Co-Authored-By: heihutu <[email protected]> * fix(heal): satisfy marker test module lint Co-Authored-By: heihutu <[email protected]> * fix(heal): scope automatic replacement format Co-Authored-By: heihutu <[email protected]> * fix(heal): require a mounted replacement target Co-Authored-By: heihutu <[email protected]> * fix(ecstore): avoid cloned ref slice in test Co-Authored-By: heihutu <[email protected]> * fix(heal): revalidate replacement before scanning Co-Authored-By: heihutu <[email protected]> * fix(heal): reset stale resume checkpoints Co-Authored-By: heihutu <[email protected]> * fix(heal): release scanner disk map before probing Co-Authored-By: heihutu <[email protected]> * fix(heal): persist replacement intent before format Co-Authored-By: heihutu <[email protected]> * fix(heal): fail closed on mountinfo read errors Co-Authored-By: heihutu <[email protected]> * fix(heal): fence replacement target identity Co-Authored-By: heihutu <[email protected]> * fix(heal): order replacement completion cleanup Co-Authored-By: heihutu <[email protected]> * fix(heal): atomically seal replacement completion Co-Authored-By: heihutu <[email protected]> * test(heal): census replacement target shards Co-Authored-By: heihutu <[email protected]> * fix(heal): fence replacement recovery ownership Co-Authored-By: heihutu <[email protected]> * fix(heal): preserve replacement recovery anchors Co-Authored-By: heihutu <[email protected]> * fix(heal): satisfy replacement recovery lint gates Co-Authored-By: heihutu <[email protected]> * fix(ecstore): bind replacement identity to mount lease Co-Authored-By: heihutu <[email protected]> * test(heal): cover durable replacement recovery states Co-Authored-By: heihutu <[email protected]> * fix(heal): validate persisted resume task identifiers Co-Authored-By: heihutu <[email protected]> * fix(ecstore): avoid blocking replacement marker CAS Co-Authored-By: heihutu <[email protected]> * fix(heal): report failed marker rollback Co-Authored-By: heihutu <[email protected]> * test(heal): pin replacement resume schema compatibility Co-Authored-By: heihutu <[email protected]> * fix(heal): preserve durable recovery anchors Co-Authored-By: heihutu <[email protected]> * fix(ecstore): preserve public disk path semantics Co-Authored-By: heihutu <[email protected]> * test(heal): use canonical replacement task ids Co-Authored-By: heihutu <[email protected]> * test(heal): cover automatic replacement in 3x4 cluster Co-Authored-By: heihutu <[email protected]> * fix(heal): verify replacement target commits Co-Authored-By: heihutu <[email protected]> * fix(heal): persist replacement completion proof Co-Authored-By: heihutu <[email protected]> * feat(heal): expose durable replacement status Co-Authored-By: heihutu <[email protected]> * fix(heal): bound durable replacement discovery Co-Authored-By: heihutu <[email protected]> * fix(heal): remove replacement readiness bypass Co-Authored-By: heihutu <[email protected]> * fix(heal): retry terminal replacement cleanup Co-Authored-By: heihutu <[email protected]> * fix(heal): isolate replacement intents from legacy resume Co-Authored-By: heihutu <[email protected]> * fix(heal): migrate legacy replacement intents at startup Co-Authored-By: heihutu <[email protected]> * style(heal): apply strict clippy fix Co-Authored-By: heihutu <[email protected]> * fix(heal): prioritize active replacement recovery state Co-Authored-By: heihutu <[email protected]> * fix(heal): bind readiness to the admitted mount lease Co-Authored-By: heihutu <[email protected]> * fix(heal): atomically publish replacement intents Co-Authored-By: heihutu <[email protected]> * fix(heal): isolate replacement recovery directory Co-Authored-By: heihutu <[email protected]> * fix(heal): tolerate an empty recovery directory Co-Authored-By: heihutu <[email protected]> * style(heal): remove redundant disk bytes conversion Co-Authored-By: heihutu <[email protected]> * fix(heal): reconcile proof-first replacement recovery Co-Authored-By: heihutu <[email protected]> * fix(heal): fence torn intent recovery Co-Authored-By: heihutu <[email protected]> * test(heal): cover replacement migration conflicts Co-Authored-By: heihutu <[email protected]> * fix(ecstore): fence replacement lease mount identity Co-Authored-By: heihutu <[email protected]> * test(heal): cover missing replacement path admission Co-Authored-By: heihutu <[email protected]> * fix(heal): reject conflicting legacy completion proof Co-Authored-By: heihutu <[email protected]> * fix(ecstore): fall back to proc mount identity Co-Authored-By: heihutu <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * style(ecstore): match linux mount lease formatting Keep Linux rustfmt output stable for the replacement mount lease comparison. Co-Authored-By: heihutu <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * test(ecstore): align lease path test probes Co-Authored-By: heihutu <[email protected]> * fix(heal): block unsafe replacement recovery restarts Co-Authored-By: heihutu <[email protected]> * fix(heal): defer blocked replacement candidates Co-Authored-By: heihutu <[email protected]> * fix(heal): retry transient replacement discovery Co-Authored-By: heihutu <[email protected]> * fix(heal): keep transient recovery errors retryable Co-Authored-By: heihutu <[email protected]> * fix(heal): block corrupt legacy replacement state Co-Authored-By: heihutu <[email protected]> * fix(heal): classify flat replacement intent corruption Co-Authored-By: heihutu <[email protected]> * 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 <[email protected]> * fix(heal): avoid latching transient legacy publishes Co-Authored-By: heihutu <[email protected]> * fix(heal): retry blocked legacy migrations Co-Authored-By: heihutu <[email protected]> * fix(heal): defer blocked startup recoveries Co-Authored-By: heihutu <[email protected]> * fix(ecstore): preserve disk sync limiter across lease roots Co-Authored-By: heihutu <[email protected]> --------- Co-authored-by: heihutu <[email protected]> Co-authored-by: zhi22915 <[email protected]>
126 lines
4.2 KiB
Rust
126 lines
4.2 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 rustix::fs::{StatVfs, statvfs};
|
|
use std::io::Error;
|
|
use std::path::Path;
|
|
|
|
/// Returns total and free bytes available in a directory, e.g. `/`.
|
|
pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<DiskInfo> {
|
|
let path_display = p.as_ref().display();
|
|
let stat = statvfs(p.as_ref())?;
|
|
|
|
// According to POSIX statvfs definition:
|
|
// f_bsize: File system block size.
|
|
// f_frsize: Fundamental file system block size.
|
|
// f_blocks: Total number of blocks on file system in units of f_frsize.
|
|
//
|
|
// We should use f_frsize to calculate the size in bytes.
|
|
// If f_frsize is 0 (which shouldn't happen on compliant systems), fallback to f_bsize.
|
|
let bsize = if stat.f_frsize > 0 {
|
|
stat.f_frsize as u64
|
|
} else {
|
|
stat.f_bsize as u64
|
|
};
|
|
|
|
let bfree = stat.f_bfree as u64;
|
|
let bavail = stat.f_bavail as u64;
|
|
let blocks = stat.f_blocks as u64;
|
|
|
|
let reserved = match bfree.checked_sub(bavail) {
|
|
Some(reserved) => reserved,
|
|
None => {
|
|
return Err(Error::other(format!(
|
|
"detected f_bavail space ({bavail}) > f_bfree space ({bfree}), fs corruption at ({path_display}). please run 'fsck'",
|
|
)));
|
|
}
|
|
};
|
|
|
|
let total = match blocks.checked_sub(reserved) {
|
|
Some(total) => total * bsize,
|
|
None => {
|
|
return Err(Error::other(format!(
|
|
"detected reserved space ({reserved}) > blocks space ({blocks}), fs corruption at ({path_display}). please run 'fsck'",
|
|
)));
|
|
}
|
|
};
|
|
|
|
let free = bavail * bsize;
|
|
let used = match total.checked_sub(free) {
|
|
Some(used) => used,
|
|
None => {
|
|
return Err(Error::other(format!(
|
|
"detected free space ({free}) > total drive space ({total}), fs corruption at ({path_display}). please run 'fsck'"
|
|
)));
|
|
}
|
|
};
|
|
|
|
let st = rustix::fs::stat(p.as_ref())?;
|
|
|
|
Ok(DiskInfo {
|
|
total,
|
|
free,
|
|
used,
|
|
files: stat.f_files,
|
|
ffree: stat.f_ffree,
|
|
fstype: get_fs_type(&stat).to_string(),
|
|
major: rustix::fs::major(st.st_dev) as u64,
|
|
minor: rustix::fs::minor(st.st_dev) as u64,
|
|
..Default::default()
|
|
})
|
|
}
|
|
|
|
fn get_fs_type(_stat: &StatVfs) -> &'static str {
|
|
"UNKNOWN"
|
|
}
|
|
|
|
pub fn same_disk(disk1: &str, disk2: &str) -> std::io::Result<bool> {
|
|
let stat1 = rustix::fs::stat(disk1)?;
|
|
let stat2 = rustix::fs::stat(disk2)?;
|
|
|
|
Ok(stat1.st_dev == stat2.st_dev)
|
|
}
|
|
|
|
/// Return whether `path` is mounted separately from its parent directory.
|
|
///
|
|
/// This is intentionally conservative on non-Linux Unix platforms. Linux
|
|
/// uses mountinfo to recognize bind mounts; elsewhere a shared device number
|
|
/// cannot distinguish a bind mount from a normal directory safely.
|
|
pub fn is_mount_point(path: &Path) -> std::io::Result<bool> {
|
|
let metadata = std::fs::symlink_metadata(path)?;
|
|
if !metadata.file_type().is_dir() || metadata.file_type().is_symlink() {
|
|
return Ok(false);
|
|
}
|
|
let parent = path.parent().ok_or_else(|| Error::other("mount point has no parent"))?;
|
|
Ok(rustix::fs::stat(path)?.st_dev != rustix::fs::stat(parent)?.st_dev)
|
|
}
|
|
|
|
pub fn get_physical_device_ids(disk: &str) -> std::io::Result<Vec<String>> {
|
|
let stat = rustix::fs::stat(disk)?;
|
|
let major = rustix::fs::major(stat.st_dev);
|
|
let minor = rustix::fs::minor(stat.st_dev);
|
|
|
|
Ok(vec![format!("{major}:{minor}")])
|
|
}
|
|
|
|
pub fn check_cross_device_mounts(_paths: &[String]) -> std::io::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
pub fn get_drive_stats(_major: u32, _minor: u32) -> std::io::Result<IOStats> {
|
|
Ok(IOStats::default())
|
|
}
|