VFS/DataProvider/Config refactoring + SSH public key authentication
Phase 1-6 of refactoring plan: - VFS abstraction (VfsBackend trait + LocalFs + OpenFlags builder) - DataProvider trait (SqliteProvider + PgProvider, SFTPGo-compatible) - Config refactoring (AppConfig unified sections, env overrides) - SSH handlers (sftp/scp/rsync) migrated to VFS + DataProvider - SSH public key authentication (Ed25519 signature verification) - SSH stderr → CHANNEL_EXTENDED_DATA support - Web auth uses DataProvider instead of direct SQL - User home directory from provider (per-user isolation) - PostgreSQL auth provider for SFTPGo compatibility
This commit is contained in:
212
markbase-core/src/vfs/local_fs.rs
Normal file
212
markbase-core/src/vfs/local_fs.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
use super::util;
|
||||
use super::open_flags::OpenFlags;
|
||||
use super::{VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsStat};
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
|
||||
/// 本地文件系统实现(直接包装 std::fs,不做路径解析)
|
||||
/// 路径解析由上层(SftpHandler)负责
|
||||
pub struct LocalFs;
|
||||
|
||||
impl LocalFs {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
struct LocalFile {
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl VfsFile for LocalFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, VfsError> {
|
||||
self.file.read(buf).map_err(|e| VfsError::Io(e.to_string()))
|
||||
}
|
||||
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, VfsError> {
|
||||
self.file.write(buf).map_err(|e| VfsError::Io(e.to_string()))
|
||||
}
|
||||
|
||||
fn seek(&mut self, pos: SeekFrom) -> Result<u64, VfsError> {
|
||||
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<VfsStat, VfsError> {
|
||||
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 read_dir(&self, path: &Path) -> Result<Vec<VfsDirEntry>, 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<Box<dyn VfsFile>, 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<VfsStat, VfsError> {
|
||||
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<VfsStat, VfsError> {
|
||||
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<PathBuf, VfsError> {
|
||||
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<PathBuf, VfsError> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user