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:
160
markbase-core/src/vfs/mod.rs
Normal file
160
markbase-core/src/vfs/mod.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
pub mod open_flags;
|
||||
pub mod local_fs;
|
||||
pub mod util;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::SystemTime;
|
||||
|
||||
/// VFS 错误类型
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum VfsError {
|
||||
NotFound(String),
|
||||
PermissionDenied(String),
|
||||
AlreadyExists(String),
|
||||
NotEmpty(String),
|
||||
NotADirectory(String),
|
||||
IsADirectory(String),
|
||||
Unsupported(String),
|
||||
Io(String),
|
||||
UnexpectedEof,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VfsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VfsError::NotFound(p) => write!(f, "No such file or directory: {}", p),
|
||||
VfsError::PermissionDenied(p) => write!(f, "Permission denied: {}", p),
|
||||
VfsError::AlreadyExists(p) => write!(f, "File already exists: {}", p),
|
||||
VfsError::NotEmpty(p) => write!(f, "Directory not empty: {}", p),
|
||||
VfsError::NotADirectory(p) => write!(f, "Not a directory: {}", p),
|
||||
VfsError::IsADirectory(p) => write!(f, "Is a directory: {}", p),
|
||||
VfsError::Unsupported(msg) => write!(f, "Unsupported: {}", msg),
|
||||
VfsError::Io(msg) => write!(f, "IO error: {}", msg),
|
||||
VfsError::UnexpectedEof => write!(f, "Unexpected end of file"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for VfsError {}
|
||||
|
||||
/// 文件统计信息(类似 libc::stat)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VfsStat {
|
||||
pub size: u64,
|
||||
pub mode: u32,
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub atime: SystemTime,
|
||||
pub mtime: SystemTime,
|
||||
pub is_dir: bool,
|
||||
pub is_symlink: bool,
|
||||
}
|
||||
|
||||
impl VfsStat {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
size: 0,
|
||||
mode: 0,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
atime: SystemTime::UNIX_EPOCH,
|
||||
mtime: SystemTime::UNIX_EPOCH,
|
||||
is_dir: false,
|
||||
is_symlink: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VfsStat {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// 目录条目
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VfsDirEntry {
|
||||
pub name: String,
|
||||
pub long_name: String,
|
||||
pub stat: VfsStat,
|
||||
}
|
||||
|
||||
/// 打开文件的抽象
|
||||
pub trait VfsFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, VfsError>;
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, VfsError>;
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> Result<u64, VfsError>;
|
||||
fn flush(&mut self) -> Result<(), VfsError>;
|
||||
fn stat(&mut self) -> Result<VfsStat, VfsError>;
|
||||
fn set_len(&mut self, size: u64) -> Result<(), VfsError>;
|
||||
|
||||
/// Write all bytes (convenience, default loops write() until done)
|
||||
fn write_all(&mut self, mut buf: &[u8]) -> Result<(), VfsError> {
|
||||
while !buf.is_empty() {
|
||||
let n = self.write(buf)?;
|
||||
if n == 0 {
|
||||
return Err(VfsError::Io("write returned 0".to_string()));
|
||||
}
|
||||
buf = &buf[n..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read exactly `buf.len()` bytes (convenience, loops read() until done)
|
||||
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), VfsError> {
|
||||
while !buf.is_empty() {
|
||||
let n = self.read(buf)?;
|
||||
if n == 0 {
|
||||
return Err(VfsError::UnexpectedEof);
|
||||
}
|
||||
buf = &mut buf[n..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// VFS 后端 trait(所有文件系统操作)
|
||||
pub trait VfsBackend: Send {
|
||||
/// 读取目录内容
|
||||
fn read_dir(&self, path: &Path) -> Result<Vec<VfsDirEntry>, VfsError>;
|
||||
|
||||
/// 打开文件(读/写)
|
||||
fn open_file(&self, path: &Path, flags: &open_flags::OpenFlags) -> Result<Box<dyn VfsFile>, VfsError>;
|
||||
|
||||
/// 获取文件/目录元数据
|
||||
fn stat(&self, path: &Path) -> Result<VfsStat, VfsError>;
|
||||
fn lstat(&self, path: &Path) -> Result<VfsStat, VfsError>;
|
||||
|
||||
/// 创建目录
|
||||
fn create_dir(&self, path: &Path, mode: u32) -> Result<(), VfsError>;
|
||||
|
||||
/// 递归创建目录
|
||||
fn create_dir_all(&self, path: &Path, mode: u32) -> Result<(), VfsError>;
|
||||
|
||||
/// 删除空目录
|
||||
fn remove_dir(&self, path: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 删除文件
|
||||
fn remove_file(&self, path: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 重命名
|
||||
fn rename(&self, from: &Path, to: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 设置文件属性
|
||||
fn set_stat(&self, path: &Path, stat: &VfsStat) -> Result<(), VfsError>;
|
||||
|
||||
/// 读取符号链接目标
|
||||
fn read_link(&self, path: &Path) -> Result<PathBuf, VfsError>;
|
||||
|
||||
/// 创建符号链接
|
||||
fn create_symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError>;
|
||||
|
||||
/// 规范化路径
|
||||
fn real_path(&self, path: &Path) -> Result<PathBuf, VfsError>;
|
||||
|
||||
/// 检查路径是否存在
|
||||
fn exists(&self, path: &Path) -> bool;
|
||||
|
||||
/// 创建硬链接
|
||||
fn hard_link(&self, original: &Path, link: &Path) -> Result<(), VfsError>;
|
||||
}
|
||||
Reference in New Issue
Block a user