From 29100d525011c8e458edfb87af30c3d160a1d039 Mon Sep 17 00:00:00 2001 From: weisd Date: Tue, 24 Sep 2024 00:00:44 +0800 Subject: [PATCH] review disk --- ecstore/src/disk/error.rs | 58 +++++++++++++++++++++ ecstore/src/disk/local.rs | 101 ++++++++++++++++++++++++------------- ecstore/src/disk/mod.rs | 5 +- ecstore/src/disk/os.rs | 93 ++++++++++++++++++++++++++++++++++ ecstore/src/disk/remote.rs | 2 +- ecstore/src/quorum.rs | 20 ++++---- ecstore/src/utils/fs.rs | 33 +++++++++++- ecstore/src/utils/path.rs | 2 +- 8 files changed, 265 insertions(+), 49 deletions(-) create mode 100644 ecstore/src/disk/os.rs diff --git a/ecstore/src/disk/error.rs b/ecstore/src/disk/error.rs index de6079616..7b69463ac 100644 --- a/ecstore/src/disk/error.rs +++ b/ecstore/src/disk/error.rs @@ -1,3 +1,5 @@ +use std::io::{self, ErrorKind}; + use crate::{ error::{Error, Result}, quorum::CheckErrorFn, @@ -98,6 +100,9 @@ pub enum DiskError { #[error("more data was sent than what was advertised")] MoreData, + + #[error("other io err {0}")] + IoError(io::Error), } impl DiskError { @@ -201,8 +206,61 @@ pub fn clone_err(err: &Error) -> Error { DiskError::CrossDeviceLink => Error::new(DiskError::CrossDeviceLink), DiskError::LessData => Error::new(DiskError::LessData), DiskError::MoreData => Error::new(DiskError::MoreData), + DiskError::IoError(ioerr) => Error::msg(ioerr.to_string()), } } else { Error::msg(err.to_string()) } } + + +pub fn ioerr_to_diskerr(e:io::Error)->DiskError{ +match e.kind(){ + io::ErrorKind::NotFound => DiskError::FileNotFound, + io::ErrorKind::PermissionDenied => DiskError::FileAccessDenied, + // io::ErrorKind::ConnectionRefused => todo!(), + // io::ErrorKind::ConnectionReset => todo!(), + // io::ErrorKind::HostUnreachable => todo!(), + // io::ErrorKind::NetworkUnreachable => todo!(), + // io::ErrorKind::ConnectionAborted => todo!(), + // io::ErrorKind::NotConnected => todo!(), + // io::ErrorKind::AddrInUse => todo!(), + // io::ErrorKind::AddrNotAvailable => todo!(), + // io::ErrorKind::NetworkDown => todo!(), + // io::ErrorKind::BrokenPipe => todo!(), + // io::ErrorKind::AlreadyExists => todo!(), + // io::ErrorKind::WouldBlock => todo!(), + // io::ErrorKind::NotADirectory => DiskError::FileNotFound, + // io::ErrorKind::IsADirectory => DiskError::FileNotFound, + // io::ErrorKind::DirectoryNotEmpty => DiskError::VolumeNotEmpty, + // io::ErrorKind::ReadOnlyFilesystem => todo!(), + // io::ErrorKind::FilesystemLoop => todo!(), + // io::ErrorKind::StaleNetworkFileHandle => todo!(), + // io::ErrorKind::InvalidInput => todo!(), + // io::ErrorKind::InvalidData => todo!(), + // io::ErrorKind::TimedOut => todo!(), + // io::ErrorKind::WriteZero => todo!(), + // io::ErrorKind::StorageFull => DiskError::DiskFull, + // io::ErrorKind::NotSeekable => todo!(), + // io::ErrorKind::FilesystemQuotaExceeded => todo!(), + // io::ErrorKind::FileTooLarge => todo!(), + // io::ErrorKind::ResourceBusy => todo!(), + // io::ErrorKind::ExecutableFileBusy => todo!(), + // io::ErrorKind::Deadlock => todo!(), + // io::ErrorKind::CrossesDevices => todo!(), + // io::ErrorKind::TooManyLinks =>DiskError::TooManyOpenFiles, + // io::ErrorKind::InvalidFilename => todo!(), + // io::ErrorKind::ArgumentListTooLong => todo!(), + // io::ErrorKind::Interrupted => todo!(), + // io::ErrorKind::Unsupported => todo!(), + // io::ErrorKind::UnexpectedEof => todo!(), + // io::ErrorKind::OutOfMemory => todo!(), + // io::ErrorKind::Other => todo!(), + // TODO: 把不支持的king用字符串处理 + _ => DiskError::IoError(e), +} +} + +pub fn os_is_not_exist(e:io::Error) -> bool{ + e.kind() == ErrorKind::NotFound +} \ No newline at end of file diff --git a/ecstore/src/disk/local.rs b/ecstore/src/disk/local.rs index 586ed0772..fd0e5d388 100644 --- a/ecstore/src/disk/local.rs +++ b/ecstore/src/disk/local.rs @@ -1,9 +1,11 @@ +use super::error::{ioerr_to_diskerr, os_is_not_exist}; use super::{endpoint::Endpoint, error::DiskError, format::FormatV3}; use super::{ - DeleteOptions, DiskAPI, DiskLocation, FileInfoVersions, FileReader, FileWriter, MetaCacheEntry, ReadMultipleReq, + os, DeleteOptions, DiskAPI, DiskLocation, FileInfoVersions, FileReader, FileWriter, MetaCacheEntry, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions, }; use crate::disk::{LocalFileReader, LocalFileWriter, STORAGE_FORMAT_FILE}; +use crate::utils::path::SLASH_SEPARATOR; use crate::{ error::{Error, Result}, file_meta::FileMeta, @@ -93,6 +95,8 @@ impl LocalDisk { last_check: format_last_check, }; + // TODO: DIRECT suport + // TODD: DiskInfo let disk = Self { root, endpoint: ep.clone(), @@ -109,6 +113,36 @@ impl LocalDisk { Ok(disk) } + fn check_path_length(path_name: &str) -> Result<()> { + unimplemented!() + } + + fn is_valid_volname(volname: &str) -> bool { + if volname.len() < 3 { + return false; + } + + if cfg!(target_os = "windows") { + // 在 Windows 上,卷名不应该包含保留字符。 + // 这个正则表达式匹配了不允许的字符。 + if volname.contains('|') + || volname.contains('<') + || volname.contains('>') + || volname.contains('?') + || volname.contains('*') + || volname.contains(':') + || volname.contains('"') + || volname.contains('\\') + { + return false; + } + } else { + // 对于非 Windows 系统,可能需要其他的验证逻辑。 + } + + true + } + async fn check_format_json(&self) -> Result { let md = fs::metadata(&self.format_path).await.map_err(|e| match e.kind() { ErrorKind::NotFound => DiskError::DiskNotFound, @@ -475,7 +509,7 @@ impl DiskAPI for LocalDisk { self.root.clone() } - fn get_location(&self) -> DiskLocation { + fn get_disk_location(&self) -> DiskLocation { DiskLocation { pool_idx: self.endpoint.pool_idx, set_idx: self.endpoint.set_idx, @@ -900,54 +934,51 @@ impl DiskAPI for LocalDisk { async fn make_volumes(&self, volumes: Vec<&str>) -> Result<()> { for vol in volumes { if let Err(e) = self.make_volume(vol).await { - match &e.downcast_ref::() { - Some(DiskError::VolumeExists) => Ok(()), - Some(_) => Err(e), - None => Err(e), - }?; + if !DiskError::VolumeExists.is(&e) { + return Err(e); + } } // TODO: health check } Ok(()) } async fn make_volume(&self, volume: &str) -> Result<()> { + if !Self::is_valid_volname(volume) { + return Err(Error::msg("Invalid arguments specified")); + } + let p = self.get_bucket_path(volume)?; - match File::open(&p).await { - Ok(_) => (), - Err(e) => match e.kind() { - ErrorKind::NotFound => { - fs::create_dir_all(&p).await?; - return Ok(()); - } - _ => return Err(Error::from(e)), - }, + + if let Err(err) = utils::fs::access(&p).await { + if os_is_not_exist(err) { + os::make_dir_all(&p).await?; + } } Err(Error::from(DiskError::VolumeExists)) } async fn list_volumes(&self) -> Result> { - let mut entries = fs::read_dir(&self.root).await?; - let mut volumes = Vec::new(); - while let Some(entry) = entries.next_entry().await? { - if let Ok(metadata) = entry.metadata().await { - // if !metadata.is_dir() { - // continue; - // } - - let name = entry.file_name().to_string_lossy().to_string(); - - let created = match metadata.created() { - Ok(md) => Some(OffsetDateTime::from(md)), - Err(_) => { - warn!("Not supported created on this platform"); - None - } - }; - - volumes.push(VolumeInfo { name, created }); + let entries = os::read_dir(&self.root, 0).await.map_err(|e| { + if DiskError::FileAccessDenied.is(&e) { + Error::new(DiskError::DiskAccessDenied) + } else if DiskError::FileNotFound.is(&e) { + Error::new(DiskError::DiskAccessDenied) + } else { + e } + })?; + + for entry in entries { + if utils::path::has_suffix(&entry, SLASH_SEPARATOR) || !Self::is_valid_volname(&entry) { + continue; + } + + volumes.push(VolumeInfo { + name: entry, + created: None, + }); } Ok(volumes) diff --git a/ecstore/src/disk/mod.rs b/ecstore/src/disk/mod.rs index fd46396c0..3ec7f52df 100644 --- a/ecstore/src/disk/mod.rs +++ b/ecstore/src/disk/mod.rs @@ -3,6 +3,7 @@ pub mod error; pub mod format; mod local; mod remote; +pub mod os; pub const RUSTFS_META_BUCKET: &str = ".rustfs.sys"; pub const RUSTFS_META_MULTIPART_BUCKET: &str = ".rustfs.sys/multipart"; @@ -56,7 +57,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static { async fn set_disk_id(&self, id: Option) -> Result<()>; fn path(&self) -> PathBuf; - fn get_location(&self) -> DiskLocation; + fn get_disk_location(&self) -> DiskLocation; // Healing // DiskInfo @@ -126,7 +127,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static { // CleanAbandonedData async fn write_all(&self, volume: &str, path: &str, data: Vec) -> Result<()>; async fn read_all(&self, volume: &str, path: &str) -> Result; - // GetDiskLoc + } pub struct UpdateMetadataOpts { diff --git a/ecstore/src/disk/os.rs b/ecstore/src/disk/os.rs new file mode 100644 index 000000000..4fa9336b3 --- /dev/null +++ b/ecstore/src/disk/os.rs @@ -0,0 +1,93 @@ +use std::path::Path; + +use futures::TryFutureExt; +use tokio::fs; + +use crate::{ + error::{Error, Result}, + utils, +}; + +use super::error::{ioerr_to_diskerr, DiskError}; + +fn check_path_length(path_name: &str) -> Result<()> { + // Apple OS X path length is limited to 1016 + if cfg!(target_os = "macos") && path_name.len() > 1016 { + return Err(Error::new(DiskError::FileNameTooLong)); + } + + // Disallow more than 1024 characters on windows, there + // are no known name_max limits on Windows. + if cfg!(target_os = "windows") && path_name.len() > 1024 { + return Err(Error::new(DiskError::FileNameTooLong)); + } + + // On Unix we reject paths if they are just '.', '..' or '/' + let invalid_paths = [".", "..", "/"]; + if invalid_paths.contains(&path_name) { + return Err(Error::new(DiskError::FileAccessDenied)); + } + + // Check each path segment length is > 255 on all Unix + // platforms, look for this value as NAME_MAX in + // /usr/include/linux/limits.h + let mut count = 0usize; + for c in path_name.chars() { + match c { + '/' | '\\' if cfg!(target_os = "windows") => count = 0, // Reset + _ => { + count += 1; + if count > 255 { + return Err(Error::new(DiskError::FileNameTooLong)); + } + } + } + } + + // Success. + Ok(()) +} + +pub async fn make_dir_all(path: impl AsRef) -> Result<()> { + check_path_length(path.as_ref().to_string_lossy().to_string().as_str())?; + + utils::fs::make_dir_all(path.as_ref()).map_err(ioerr_to_diskerr).await?; + + Ok(()) +} + +// read_dir count read limit. when count == 0 unlimit. +pub async fn read_dir(path: impl AsRef, count: usize) -> Result> { + let mut entries = fs::read_dir(path.as_ref()).await?; + + let mut volumes = Vec::new(); + + let mut count: i32 = { + if count == 0 { + -1 + } else { + count as i32 + } + }; + + while let Some(entry) = entries.next_entry().await? { + let name = entry.file_name().to_string_lossy().to_string(); + + if name == "" || name == "." || name == ".." { + continue; + } + + let file_type = entry.file_type().await?; + + if file_type.is_dir() { + count -= 1; + volumes.push(format!("{}{}", name, utils::path::SLASH_SEPARATOR)); + + if count == 0 { + break; + } + } + } + + Ok(volumes) +} diff --git a/ecstore/src/disk/remote.rs b/ecstore/src/disk/remote.rs index 5eed152c2..0034235f8 100644 --- a/ecstore/src/disk/remote.rs +++ b/ecstore/src/disk/remote.rs @@ -125,7 +125,7 @@ impl DiskAPI for RemoteDisk { self.root.clone() } - fn get_location(&self) -> DiskLocation { + fn get_disk_location(&self) -> DiskLocation { DiskLocation { pool_idx: self.endpoint.pool_idx, set_idx: self.endpoint.set_idx, diff --git a/ecstore/src/quorum.rs b/ecstore/src/quorum.rs index aabfb7994..13544632f 100644 --- a/ecstore/src/quorum.rs +++ b/ecstore/src/quorum.rs @@ -15,10 +15,6 @@ enum QuorumError { Write, } -pub fn is_file_not_found(e: &Error) -> bool { - DiskError::FileNotFound.is(e) -} - pub fn base_ignored_errs() -> Vec> { vec![ Box::new(DiskError::DiskNotFound), @@ -29,14 +25,20 @@ pub fn base_ignored_errs() -> Vec> { // object_op_ignored_errs pub fn object_op_ignored_errs() -> Vec> { - vec![ - Box::new(DiskError::DiskNotFound), - Box::new(DiskError::FaultyDisk), - Box::new(DiskError::FaultyRemoteDisk), + let mut base = base_ignored_errs(); + + + let ext:Vec> = vec![ + // Box::new(DiskError::DiskNotFound), + // Box::new(DiskError::FaultyDisk), + // Box::new(DiskError::FaultyRemoteDisk), Box::new(DiskError::DiskAccessDenied), Box::new(DiskError::UnformattedDisk), Box::new(DiskError::DiskOngoingReq), - ] + ]; + + base.extend(ext); + base } // 用于检查错误是否被忽略的函数 diff --git a/ecstore/src/utils/fs.rs b/ecstore/src/utils/fs.rs index 730047855..67405d4d8 100644 --- a/ecstore/src/utils/fs.rs +++ b/ecstore/src/utils/fs.rs @@ -1,6 +1,11 @@ -use std::{fs::Metadata, os::unix::fs::MetadataExt}; +use std::{fs::Metadata, path::Path}; +use tokio::{fs, io}; + +#[cfg(target_os = "linux")] pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool { + use os::unix::fs::MetadataExt; + if f1.dev() != f2.dev() { return false; } @@ -22,3 +27,29 @@ pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool { true } + +#[cfg(target_os = "windows")] +pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool { + if f1.permissions() != f2.permissions() { + return false; + } + + + if f1.file_type() != f2.file_type() { + return false; + } + + if f1.len() != f2.len() { + return false; + } + true +} + +pub async fn access(path: impl AsRef) -> io::Result<()>{ + fs::metadata(path).await?; + Ok(()) +} + +pub async fn make_dir_all(path: impl AsRef) -> io::Result<()>{ + fs::create_dir_all(path.as_ref()).await +} \ No newline at end of file diff --git a/ecstore/src/utils/path.rs b/ecstore/src/utils/path.rs index 0f799b41e..79bfc6ced 100644 --- a/ecstore/src/utils/path.rs +++ b/ecstore/src/utils/path.rs @@ -1,6 +1,6 @@ const GLOBAL_DIR_SUFFIX: &str = "__XLDIR__"; -const SLASH_SEPARATOR: &str = "/"; +pub const SLASH_SEPARATOR: &str = "/"; pub fn has_suffix(s: &str, suffix: &str) -> bool { if cfg!(target_os = "windows") {