diff --git a/Cargo.lock b/Cargo.lock index aba59a7fe..4a670527a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -518,6 +518,7 @@ dependencies = [ "transform-stream", "url", "uuid", + "winapi", "xxhash-rust", ] diff --git a/ecstore/Cargo.toml b/ecstore/Cargo.toml index 89eef4670..b66875f03 100644 --- a/ecstore/Cargo.toml +++ b/ecstore/Cargo.toml @@ -31,7 +31,6 @@ lazy_static.workspace = true lock.workspace = true regex = "1.11.0" netif = "0.1.6" -nix = { version = "0.29.0", features = ["fs"] } path-absolutize = "3.1.1" protos.workspace = true rmp-serde = "1.3.0" @@ -53,7 +52,11 @@ num_cpus = "1.16" s3s-policy.workspace = true [target.'cfg(not(windows))'.dependencies] +nix = { version = "0.29.0", features = ["fs"] } openssl = "0.10.66" +[target.'cfg(windows)'.dependencies] +winapi = "0.3.9" + [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/ecstore/src/disk/error.rs b/ecstore/src/disk/error.rs index 2a5414302..da8c7063e 100644 --- a/ecstore/src/disk/error.rs +++ b/ecstore/src/disk/error.rs @@ -401,3 +401,20 @@ pub fn convert_access_error(e: io::Error, per_err: DiskError) -> Error { Error::new(e) } + +pub fn is_all_not_found(errs: &[Option]) -> bool { + for err in errs.iter() { + if let Some(err) = err { + if let Some(err) = err.downcast_ref::() { + match err { + DiskError::FileNotFound | DiskError::VolumeNotFound | &DiskError::FileVersionNotFound =>{ + continue; + }, + _ => return false, + } + } + } + } + + !errs.is_empty() +} diff --git a/ecstore/src/disk/local.rs b/ecstore/src/disk/local.rs index 6772cc72f..272b87276 100644 --- a/ecstore/src/disk/local.rs +++ b/ecstore/src/disk/local.rs @@ -17,7 +17,7 @@ use crate::error::{Error, Result}; use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold}; use crate::utils::fs::{lstat, O_APPEND, O_CREATE, O_RDONLY, O_WRONLY}; use crate::utils::path::{clean, has_suffix, SLASH_SEPARATOR}; -use crate::utils::stat_linux::get_info; +use crate::utils::os::get_info; use crate::{ file_meta::FileMeta, store_api::{FileInfo, RawFileInfo}, diff --git a/ecstore/src/disk/os.rs b/ecstore/src/disk/os.rs index 75f43166e..033cbb9e9 100644 --- a/ecstore/src/disk/os.rs +++ b/ecstore/src/disk/os.rs @@ -8,7 +8,7 @@ use tokio::fs; use crate::{ disk::error::{is_sys_err_not_dir, is_sys_err_path_not_found, os_is_not_exist}, error::{Error, Result}, - utils::{self, stat_linux::same_disk}, + utils::{self, os::same_disk}, }; use super::error::{os_err_to_file_err, os_is_exist, DiskError}; diff --git a/ecstore/src/heal/heal_commands.rs b/ecstore/src/heal/heal_commands.rs index 8725e66ea..1c1724ecf 100644 --- a/ecstore/src/heal/heal_commands.rs +++ b/ecstore/src/heal/heal_commands.rs @@ -27,6 +27,16 @@ pub const HEAL_ITEM_BUCKET: &str = "bucket"; pub const HEAL_ITEM_BUCKET_METADATA: &str = "bucket-metadata"; pub const HEAL_ITEM_OBJECT: &str = "object"; +pub const DRIVE_STATE_OK: &str = "ok"; +pub const DRIVE_STATE_OFFLINE: &str = "offline"; +pub const DRIVE_STATE_CORRUPT: &str = "corrupt"; +pub const DRIVE_STATE_MISSING: &str = "missing"; +pub const DRIVE_STATE_PERMISSION: &str = "permission-denied"; +pub const DRIVE_STATE_FAULTY: &str = "faulty"; +pub const DRIVE_STATE_ROOT_MOUNT: &str = "root-mount"; +pub const DRIVE_STATE_UNKNOWN: &str = "unknown"; +pub const DRIVE_STATE_UNFORMATTED: &str = "unformatted"; // only returned by disk + #[derive(Clone, Copy, Debug, Default)] pub struct HealOpts { pub recursive: bool, @@ -41,10 +51,10 @@ pub struct HealOpts { } #[derive(Clone, Debug)] -struct HealDriveInfo { - uuid: String, - endpoint: String, - state: String, +pub struct HealDriveInfo { + pub uuid: String, + pub endpoint: String, + pub state: String, } #[derive(Clone, Debug, Default)] diff --git a/ecstore/src/utils/mod.rs b/ecstore/src/utils/mod.rs index bf3b6e4fd..b12bf8048 100644 --- a/ecstore/src/utils/mod.rs +++ b/ecstore/src/utils/mod.rs @@ -4,5 +4,5 @@ pub mod fs; pub mod hash; pub mod net; pub mod path; -pub mod stat_linux; +pub mod os; pub mod wildcard; diff --git a/ecstore/src/utils/stat_linux.rs b/ecstore/src/utils/os/linux.rs similarity index 100% rename from ecstore/src/utils/stat_linux.rs rename to ecstore/src/utils/os/linux.rs diff --git a/ecstore/src/utils/os/mod.rs b/ecstore/src/utils/os/mod.rs new file mode 100644 index 000000000..7524d2ecb --- /dev/null +++ b/ecstore/src/utils/os/mod.rs @@ -0,0 +1,14 @@ +#[cfg(target_os = "linux")] +mod linux; +#[cfg(all(unix, not(target_os = "linux")))] +mod unix; +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "linux")] +pub use linux::get_info; +pub use linux::same_disk; +#[cfg(all(unix, not(target_os = "linux")))] +pub use unix::get_info; +#[cfg(target_os = "windows")] +pub use windows::get_info; \ No newline at end of file diff --git a/ecstore/src/utils/os/unix.rs b/ecstore/src/utils/os/unix.rs new file mode 100644 index 000000000..b60d8290f --- /dev/null +++ b/ecstore/src/utils/os/unix.rs @@ -0,0 +1,77 @@ +use crate::disk::Info; +use nix::sys::{statfs::statfs, stat::stat}; +use std::io::{Error, ErrorKind}; +use std::path::Path; + +/// returns total and free bytes available in a directory, e.g. `/`. +pub fn get_info(p: impl AsRef) -> std::io::Result { + let stat = statfs(p.as_ref())?; + + let bsize = stat.block_size() as u64; + let bfree = stat.blocks_free() as u64; + let bavail = stat.blocks_available() as u64; + let blocks = stat.blocks() as u64; + + let reserved = match bfree.checked_sub(bavail) { + Some(reserved) => reserved, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected f_bavail space ({}) > f_bfree space ({}), fs corruption at ({}). please run 'fsck'", + bavail, + bfree, + p.as_ref().display() + ), + )) + } + }; + + let total = match blocks.checked_sub(reserved) { + Some(total) => total * bsize, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected reserved space ({}) > blocks space ({}), fs corruption at ({}). please run 'fsck'", + reserved, + blocks, + p.as_ref().display() + ), + )) + } + }; + + let free = bavail * bsize; + let used = match total.checked_sub(free) { + Some(used) => used, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'", + free, + total, + p.as_ref().display() + ), + )) + } + }; + + Ok(Info { + total, + free, + used, + files: stat.files(), + ffree: stat.files_free(), + fstype: stat.filesystem_type_name().to_string(), + ..Default::default() + }) +} + +pub fn same_disk(disk1: &str, disk2: &str) -> Result { + let stat1 = stat(disk1)?; + let stat2 = stat(disk2)?; + + Ok(stat1.st_dev == stat2.st_dev) +} \ No newline at end of file diff --git a/ecstore/src/utils/os/windows.rs b/ecstore/src/utils/os/windows.rs new file mode 100644 index 000000000..65a9d0022 --- /dev/null +++ b/ecstore/src/utils/os/windows.rs @@ -0,0 +1,136 @@ +use crate::disk::Info; +use std::io::{Error, ErrorKind, Result}; +use std::mem; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use winapi::shared::minwindef::{DWORD, MAX_PATH}; +use winapi::shared::ntdef::ULARGE_INTEGER; +use winapi::um::fileapi::{GetDiskFreeSpaceExW, GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW}; +use winapi::um::winnt::{LPCWSTR, WCHAR}; + +/// returns total and free bytes available in a directory, e.g. `C:\`. +pub fn get_info(p: impl AsRef) -> Result { + let path_wide: Vec = p + .as_ref() + .canonicalize()? + .into_os_string() + .encode_wide() + .chain(std::iter::once(0)) // Null-terminate the string + .collect(); + + let mut lp_free_bytes_available: ULARGE_INTEGER = unsafe { mem::zeroed() }; + let mut lp_total_number_of_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() }; + let mut lp_total_number_of_free_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() }; + + let success = unsafe { + GetDiskFreeSpaceExW( + path_wide.as_ptr(), + &mut lp_free_bytes_available, + &mut lp_total_number_of_bytes, + &mut lp_total_number_of_free_bytes, + ) + }; + if success == 0 { + return Err(Error::last_os_error()); + } + + let total = unsafe { *lp_total_number_of_bytes.QuadPart() }; + let free = unsafe { *lp_total_number_of_free_bytes.QuadPart() }; + + if free > total { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'", + free, + total, + p.as_ref().display() + ), + )); + } + + let mut lp_sectors_per_cluster: DWORD = 0; + let mut lp_bytes_per_sector: DWORD = 0; + let mut lp_number_of_free_clusters: DWORD = 0; + let mut lp_total_number_of_clusters: DWORD = 0; + + let success = unsafe { + GetDiskFreeSpaceW( + path_wide.as_ptr(), + &mut lp_sectors_per_cluster, + &mut lp_bytes_per_sector, + &mut lp_number_of_free_clusters, + &mut lp_total_number_of_clusters, + ) + }; + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(Info { + total, + free, + used: total - free, + files: lp_total_number_of_clusters as u64, + ffree: lp_number_of_free_clusters as u64, + fstype: get_fs_type(&path_wide)?, + ..Default::default() + }) +} + +/// returns leading volume name. +fn get_volume_name(v: &[WCHAR]) -> Result { + let volume_name_size: DWORD = MAX_PATH as _; + let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + + let success = unsafe { GetVolumePathNameW(v.as_ptr(), lp_volume_name_buffer.as_mut_ptr(), volume_name_size) }; + + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(lp_volume_name_buffer.as_ptr()) +} + +fn utf16_to_string(v: &[WCHAR]) -> String { + let len = v.iter().position(|&x| x == 0).unwrap_or(v.len()); + String::from_utf16_lossy(&v[..len]) +} + +/// returns the filesystem type of the underlying mounted filesystem +fn get_fs_type(p: &[WCHAR]) -> Result { + let path = get_volume_name(p)?; + + let volume_name_size: DWORD = MAX_PATH as _; + let n_file_system_name_size: DWORD = MAX_PATH as _; + + let mut lp_volume_serial_number: DWORD = 0; + let mut lp_maximum_component_length: DWORD = 0; + let mut lp_file_system_flags: DWORD = 0; + + let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + let mut lp_file_system_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + + let success = unsafe { + GetVolumeInformationW( + path, + lp_volume_name_buffer.as_mut_ptr(), + volume_name_size, + &mut lp_volume_serial_number, + &mut lp_maximum_component_length, + &mut lp_file_system_flags, + lp_file_system_name_buffer.as_mut_ptr(), + n_file_system_name_size, + ) + }; + + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(utf16_to_string(&lp_file_system_name_buffer)) +} + +pub fn same_disk(disk1: &str, disk2: &str) -> Result { + Ok(false) +} \ No newline at end of file diff --git a/rustfs/src/grpc.rs b/rustfs/src/grpc.rs index 9df7f9788..e809a885f 100644 --- a/rustfs/src/grpc.rs +++ b/rustfs/src/grpc.rs @@ -1,7 +1,10 @@ use std::{error::Error, io::ErrorKind, pin::Pin}; use ecstore::{ - disk::{DeleteOptions, DiskInfoOptions, DiskStore, FileInfoVersions, ReadMultipleReq, ReadOptions, UpdateMetadataOpts, WalkDirOptions}, + disk::{ + DeleteOptions, DiskInfoOptions, DiskStore, FileInfoVersions, ReadMultipleReq, ReadOptions, UpdateMetadataOpts, + WalkDirOptions, + }, erasure::{ReadAt, Write}, peer::{LocalPeerS3Client, PeerS3Client}, store::{all_local_disk_path, find_local_disk}, @@ -13,7 +16,17 @@ use lock::{lock_args::LockArgs, Locker, GLOBAL_LOCAL_SERVER}; use protos::{ models::{PingBody, PingBodyBuilder}, proto_gen::node_service::{ - node_service_server::NodeService as Node, DeleteBucketRequest, DeleteBucketResponse, DeletePathsRequest, DeletePathsResponse, DeleteRequest, DeleteResponse, DeleteVersionRequest, DeleteVersionResponse, DeleteVersionsRequest, DeleteVersionsResponse, DeleteVolumeRequest, DeleteVolumeResponse, DiskInfoRequest, DiskInfoResponse, GenerallyLockRequest, GenerallyLockResponse, GetBucketInfoRequest, GetBucketInfoResponse, ListBucketRequest, ListBucketResponse, ListDirRequest, ListDirResponse, ListVolumesRequest, ListVolumesResponse, MakeBucketRequest, MakeBucketResponse, MakeVolumeRequest, MakeVolumeResponse, MakeVolumesRequest, MakeVolumesResponse, PingRequest, PingResponse, ReadAllRequest, ReadAllResponse, ReadAtRequest, ReadAtResponse, ReadMultipleRequest, ReadMultipleResponse, ReadVersionRequest, ReadVersionResponse, ReadXlRequest, ReadXlResponse, RenameDataRequest, RenameDataResponse, RenameFileRequst, RenameFileResponse, RenamePartRequst, RenamePartResponse, StatVolumeRequest, StatVolumeResponse, UpdateMetadataRequest, UpdateMetadataResponse, WalkDirRequest, WalkDirResponse, WriteAllRequest, WriteAllResponse, WriteMetadataRequest, WriteMetadataResponse, WriteRequest, WriteResponse + node_service_server::NodeService as Node, DeleteBucketRequest, DeleteBucketResponse, DeletePathsRequest, + DeletePathsResponse, DeleteRequest, DeleteResponse, DeleteVersionRequest, DeleteVersionResponse, DeleteVersionsRequest, + DeleteVersionsResponse, DeleteVolumeRequest, DeleteVolumeResponse, DiskInfoRequest, DiskInfoResponse, + GenerallyLockRequest, GenerallyLockResponse, GetBucketInfoRequest, GetBucketInfoResponse, ListBucketRequest, + ListBucketResponse, ListDirRequest, ListDirResponse, ListVolumesRequest, ListVolumesResponse, MakeBucketRequest, + MakeBucketResponse, MakeVolumeRequest, MakeVolumeResponse, MakeVolumesRequest, MakeVolumesResponse, PingRequest, + PingResponse, ReadAllRequest, ReadAllResponse, ReadAtRequest, ReadAtResponse, ReadMultipleRequest, ReadMultipleResponse, + ReadVersionRequest, ReadVersionResponse, ReadXlRequest, ReadXlResponse, RenameDataRequest, RenameDataResponse, + RenameFileRequst, RenameFileResponse, RenamePartRequst, RenamePartResponse, StatVolumeRequest, StatVolumeResponse, + UpdateMetadataRequest, UpdateMetadataResponse, WalkDirRequest, WalkDirResponse, WriteAllRequest, WriteAllResponse, + WriteMetadataRequest, WriteMetadataResponse, WriteRequest, WriteResponse, }, }; use tokio::sync::mpsc;