use super::open_flags::OpenFlags; use super::util; use super::{VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsStat}; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::{Path, PathBuf}; /// 本地文件系统实现(直接包装 std::fs,不做路径解析) /// 路径解析由上层(SftpHandler)负责 pub struct LocalFs; impl Default for LocalFs { fn default() -> Self { Self::new() } } impl LocalFs { pub fn new() -> Self { Self } } struct LocalFile { file: File, } impl VfsFile for LocalFile { fn read(&mut self, buf: &mut [u8]) -> Result { self.file.read(buf).map_err(|e| VfsError::Io(e.to_string())) } fn write(&mut self, buf: &[u8]) -> Result { self.file .write(buf) .map_err(|e| VfsError::Io(e.to_string())) } fn seek(&mut self, pos: SeekFrom) -> Result { self.file.seek(pos).map_err(|e| VfsError::Io(e.to_string())) } fn flush(&mut self) -> Result<(), VfsError> { self.file.flush().map_err(|e| VfsError::Io(e.to_string())) } fn stat(&mut self) -> Result { let meta = self .file .metadata() .map_err(|e| VfsError::Io(e.to_string()))?; Ok(util::stat_from_metadata(&meta, false)) } fn set_len(&mut self, size: u64) -> Result<(), VfsError> { self.file .set_len(size) .map_err(|e| VfsError::Io(e.to_string())) } } impl VfsBackend for LocalFs { fn clone_boxed(&self) -> Box { Box::new(Self {}) } fn read_dir(&self, path: &Path) -> Result, VfsError> { let dir = fs::read_dir(path).map_err(|e| util::map_io_error(path, e))?; let mut entries = Vec::new(); for entry in dir { let entry = entry.map_err(|e| util::map_io_error(path, e))?; let name = entry.file_name().to_string_lossy().to_string(); let file_type = entry.file_type().map_err(|e| util::map_io_error(path, e))?; let meta = entry.metadata().map_err(|e| util::map_io_error(path, e))?; let stat = util::stat_from_metadata(&meta, file_type.is_symlink()); let long_name = util::build_long_name(&stat, &name); entries.push(VfsDirEntry { name, long_name, stat, }); } entries.sort_by(|a, b| a.name.cmp(&b.name)); Ok(entries) } fn open_file(&self, path: &Path, flags: &OpenFlags) -> Result, VfsError> { let mut opts = OpenOptions::new(); opts.read(flags.read); opts.write(flags.write); opts.append(flags.append); opts.create(flags.create); opts.truncate(flags.truncate); opts.create_new(flags.exclusive); let file = opts.open(path).map_err(|e| util::map_io_error(path, e))?; #[cfg(unix)] if flags.create && !flags.exclusive { if let Ok(meta) = file.metadata() { if flags.mode != 0 && meta.permissions().mode() != flags.mode { fs::set_permissions(path, std::fs::Permissions::from_mode(flags.mode)).ok(); } } } Ok(Box::new(LocalFile { file })) } fn stat(&self, path: &Path) -> Result { let meta = fs::metadata(path).map_err(|e| util::map_io_error(path, e))?; Ok(util::stat_from_metadata(&meta, false)) } fn lstat(&self, path: &Path) -> Result { let meta = fs::symlink_metadata(path).map_err(|e| util::map_io_error(path, e))?; let is_symlink = path.is_symlink() || meta.file_type().is_symlink(); Ok(util::stat_from_metadata(&meta, is_symlink)) } fn create_dir(&self, path: &Path, mode: u32) -> Result<(), VfsError> { fs::create_dir(path).map_err(|e| util::map_io_error(path, e))?; #[cfg(unix)] { fs::set_permissions(path, std::fs::Permissions::from_mode(mode)) .map_err(|e| util::map_io_error(path, e))?; } Ok(()) } fn create_dir_all(&self, path: &Path, mode: u32) -> Result<(), VfsError> { fs::create_dir_all(path).map_err(|e| util::map_io_error(path, e))?; #[cfg(unix)] { if mode != 0 { fs::set_permissions(path, std::fs::Permissions::from_mode(mode)) .map_err(|e| util::map_io_error(path, e))?; } } Ok(()) } fn remove_dir(&self, path: &Path) -> Result<(), VfsError> { fs::remove_dir(path).map_err(|e| util::map_io_error(path, e)) } fn remove_file(&self, path: &Path) -> Result<(), VfsError> { fs::remove_file(path).map_err(|e| util::map_io_error(path, e)) } fn rename(&self, from: &Path, to: &Path) -> Result<(), VfsError> { fs::rename(from, to).map_err(|e| util::map_io_error(from, e)) } fn set_stat(&self, path: &Path, stat: &VfsStat) -> Result<(), VfsError> { #[cfg(unix)] { if stat.mode != 0 { fs::set_permissions(path, std::fs::Permissions::from_mode(stat.mode)) .map_err(|e| util::map_io_error(path, e))?; } } if let (Some(atime), Some(mtime)) = ( stat.atime.duration_since(std::time::UNIX_EPOCH).ok(), stat.mtime.duration_since(std::time::UNIX_EPOCH).ok(), ) { filetime::set_file_times( path, filetime::FileTime::from_unix_time(atime.as_secs() as i64, 0), filetime::FileTime::from_unix_time(mtime.as_secs() as i64, 0), ) .map_err(|e| util::map_io_error(path, e))?; } Ok(()) } fn read_link(&self, path: &Path) -> Result { let target = fs::read_link(path).map_err(|e| util::map_io_error(path, e))?; Ok(target) } fn create_symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> { #[cfg(unix)] { std::os::unix::fs::symlink(target, link).map_err(|e| util::map_io_error(link, e))?; } #[cfg(not(unix))] { std::os::windows::fs::symlink_file(target, link) .map_err(|e| util::map_io_error(link, e))?; } Ok(()) } fn real_path(&self, path: &Path) -> Result { let canonical = path .canonicalize() .map_err(|e| util::map_io_error(path, e))?; Ok(canonical) } fn exists(&self, path: &Path) -> bool { path.exists() } fn hard_link(&self, original: &Path, link: &Path) -> Result<(), VfsError> { #[cfg(unix)] { fs::hard_link(original, link).map_err(|e| util::map_io_error(original, e))?; } #[cfg(not(unix))] { return Err(VfsError::Unsupported( "hard_link not supported on non-Unix systems".to_string(), )); } Ok(()) } }