VFS/DataProvider/Config refactoring + SSH public key authentication
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

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:
Warren
2026-06-18 23:35:18 +08:00
parent 83fb0de78a
commit f90e4f496c
25 changed files with 2039 additions and 612 deletions

View 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>;
}