- Add VfsFile: Send supertrait for Mutex compatibility - Fix SmbServerCommand: struct → Subcommand enum with Start variant - Fix tracing_subscriber::init() → try_init() to avoid panic when logger already initialized - Fix CLI subcommand name: smb-server → smb-start (flatten naming) - Add #[command(name = "smb-start")] for CLI disambiguation - Fix unused variable warnings (smb_fs.rs, smb_server_backend.rs) - Remove unused VfsFile imports (webdav.rs, scp_handler.rs) - Integration test: Docker smbclient verified (list, upload, read)
310 lines
8.9 KiB
Rust
310 lines
8.9 KiB
Rust
use crate::vfs::open_flags::OpenFlags;
|
|
use crate::vfs::{VfsBackend, VfsDirEntry, VfsStat};
|
|
use crate::ssh_server::upload_hook::UploadHook;
|
|
use bytes::{Buf, Bytes};
|
|
use dav_server::davpath::DavPath;
|
|
use dav_server::fs::{
|
|
DavDirEntry, DavFile, DavFileSystem, DavMetaData, FsError, FsFuture, FsStream, OpenOptions,
|
|
};
|
|
use dav_server::fakels::FakeLs;
|
|
use futures_util::stream;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::time::SystemTime;
|
|
|
|
pub struct VfsDavFs {
|
|
vfs: Box<dyn VfsBackend>,
|
|
root: PathBuf,
|
|
upload_hook: Option<Arc<UploadHook>>,
|
|
user_uuid: String,
|
|
}
|
|
|
|
impl Clone for VfsDavFs {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
vfs: self.vfs.clone_boxed(),
|
|
root: self.root.clone(),
|
|
upload_hook: self.upload_hook.clone(),
|
|
user_uuid: self.user_uuid.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VfsDavFs {
|
|
pub fn new(
|
|
vfs: Box<dyn VfsBackend>,
|
|
root: PathBuf,
|
|
upload_hook: Option<Arc<UploadHook>>,
|
|
user_uuid: String,
|
|
) -> Box<Self> {
|
|
Box::new(Self {
|
|
vfs,
|
|
root,
|
|
upload_hook,
|
|
user_uuid,
|
|
})
|
|
}
|
|
|
|
fn resolve_path(&self, path: &DavPath) -> PathBuf {
|
|
let relative = path.as_pathbuf();
|
|
self.root.join(relative)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VfsDavMetaData {
|
|
len: u64,
|
|
is_dir: bool,
|
|
modified: SystemTime,
|
|
}
|
|
|
|
impl VfsDavMetaData {
|
|
pub fn new(len: u64, is_dir: bool) -> Self {
|
|
Self {
|
|
len,
|
|
is_dir,
|
|
modified: SystemTime::now(),
|
|
}
|
|
}
|
|
|
|
pub fn from_stat(stat: &VfsStat) -> Self {
|
|
Self {
|
|
len: stat.size,
|
|
is_dir: stat.is_dir,
|
|
modified: stat.mtime,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DavMetaData for VfsDavMetaData {
|
|
fn len(&self) -> u64 {
|
|
self.len
|
|
}
|
|
|
|
fn modified(&self) -> Result<SystemTime, FsError> {
|
|
Ok(self.modified)
|
|
}
|
|
|
|
fn is_dir(&self) -> bool {
|
|
self.is_dir
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct VfsDavDirEntry {
|
|
name: String,
|
|
meta: VfsDavMetaData,
|
|
}
|
|
|
|
impl VfsDavDirEntry {
|
|
pub fn new(name: String, meta: VfsDavMetaData) -> Self {
|
|
Self { name, meta }
|
|
}
|
|
}
|
|
|
|
impl DavDirEntry for VfsDavDirEntry {
|
|
fn name(&self) -> Vec<u8> {
|
|
self.name.as_bytes().to_vec()
|
|
}
|
|
|
|
fn metadata(&self) -> FsFuture<'_, Box<dyn DavMetaData>> {
|
|
Box::pin(std::future::ready(Ok(Box::new(self.meta.clone()) as Box<dyn DavMetaData>)))
|
|
}
|
|
}
|
|
|
|
pub struct VfsDavFile {
|
|
data: Vec<u8>,
|
|
position: u64,
|
|
path: Option<PathBuf>,
|
|
upload_hook: Option<Arc<UploadHook>>,
|
|
user_uuid: String,
|
|
is_write: bool,
|
|
}
|
|
|
|
impl std::fmt::Debug for VfsDavFile {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("VfsDavFile")
|
|
.field("is_write", &self.is_write)
|
|
.field("path", &self.path)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl VfsDavFile {
|
|
pub fn new_read(data: Vec<u8>) -> Self {
|
|
Self {
|
|
data,
|
|
position: 0,
|
|
path: None,
|
|
upload_hook: None,
|
|
user_uuid: String::new(),
|
|
is_write: false,
|
|
}
|
|
}
|
|
|
|
pub fn new_write(
|
|
path: PathBuf,
|
|
upload_hook: Option<Arc<UploadHook>>,
|
|
user_uuid: String,
|
|
) -> Self {
|
|
Self {
|
|
data: Vec::new(),
|
|
position: 0,
|
|
path: Some(path),
|
|
upload_hook,
|
|
user_uuid,
|
|
is_write: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DavFile for VfsDavFile {
|
|
fn metadata(&'_ mut self) -> FsFuture<'_, Box<dyn DavMetaData>> {
|
|
let len = self.data.len() as u64;
|
|
Box::pin(std::future::ready(Ok(
|
|
Box::new(VfsDavMetaData::new(len, false)) as Box<dyn DavMetaData>,
|
|
)))
|
|
}
|
|
|
|
fn write_buf(&'_ mut self, mut buf: Box<dyn Buf + Send>) -> FsFuture<'_, ()> {
|
|
while buf.has_remaining() {
|
|
let chunk = buf.chunk();
|
|
self.data.extend_from_slice(chunk);
|
|
buf.advance(chunk.len());
|
|
}
|
|
Box::pin(std::future::ready(Ok(())))
|
|
}
|
|
|
|
fn write_bytes(&'_ mut self, buf: Bytes) -> FsFuture<'_, ()> {
|
|
self.data.extend_from_slice(&buf);
|
|
Box::pin(std::future::ready(Ok(())))
|
|
}
|
|
|
|
fn read_bytes(&'_ mut self, count: usize) -> FsFuture<'_, Bytes> {
|
|
let start = self.position as usize;
|
|
let end = std::cmp::min(start + count, self.data.len());
|
|
|
|
if start >= self.data.len() {
|
|
Box::pin(std::future::ready(Ok(Bytes::new())))
|
|
} else {
|
|
let bytes = Bytes::copy_from_slice(&self.data[start..end]);
|
|
self.position = end as u64;
|
|
Box::pin(std::future::ready(Ok(bytes)))
|
|
}
|
|
}
|
|
|
|
fn seek(&'_ mut self, pos: std::io::SeekFrom) -> FsFuture<'_, u64> {
|
|
let new_pos = match pos {
|
|
std::io::SeekFrom::Start(offset) => offset,
|
|
std::io::SeekFrom::Current(offset) => {
|
|
let current = self.position as i64;
|
|
(current + offset) as u64
|
|
}
|
|
std::io::SeekFrom::End(offset) => {
|
|
let end = self.data.len() as i64;
|
|
(end + offset) as u64
|
|
}
|
|
};
|
|
self.position = new_pos;
|
|
Box::pin(std::future::ready(Ok(new_pos)))
|
|
}
|
|
|
|
fn flush(&'_ mut self) -> FsFuture<'_, ()> {
|
|
if self.is_write {
|
|
if let Some(path) = &self.path {
|
|
if let Err(_e) = std::fs::write(path, &self.data) {
|
|
return Box::pin(std::future::ready(Err(FsError::NotImplemented)));
|
|
}
|
|
if let Some(hook) = &self.upload_hook {
|
|
if let Err(e) = hook.trigger(path, &self.user_uuid) {
|
|
log::warn!("Upload hook failed for {:?}: {}", path, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Box::pin(std::future::ready(Ok(())))
|
|
}
|
|
}
|
|
|
|
impl DavFileSystem for VfsDavFs {
|
|
fn open<'a>(&'a self, path: &'a DavPath, options: OpenOptions) -> FsFuture<'a, Box<dyn DavFile>> {
|
|
let full_path = self.resolve_path(path);
|
|
|
|
if options.write {
|
|
let file = VfsDavFile::new_write(
|
|
full_path,
|
|
self.upload_hook.clone(),
|
|
self.user_uuid.clone(),
|
|
);
|
|
Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>)))
|
|
} else {
|
|
let flags = OpenFlags::new().read();
|
|
match self.vfs.open_file(&full_path, &flags) {
|
|
Ok(mut vfs_file) => {
|
|
let mut data = Vec::new();
|
|
loop {
|
|
let mut buf = vec![0u8; 8192];
|
|
match vfs_file.read(&mut buf) {
|
|
Ok(0) => break,
|
|
Ok(n) => data.extend_from_slice(&buf[..n]),
|
|
Err(_) => return Box::pin(std::future::ready(Err(FsError::NotFound))),
|
|
}
|
|
}
|
|
let file = VfsDavFile::new_read(data);
|
|
Box::pin(std::future::ready(Ok(Box::new(file) as Box<dyn DavFile>)))
|
|
}
|
|
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn read_dir<'a>(
|
|
&'a self,
|
|
path: &'a DavPath,
|
|
_meta: dav_server::fs::ReadDirMeta,
|
|
) -> FsFuture<'a, FsStream<Box<dyn DavDirEntry>>> {
|
|
let full_path = self.resolve_path(path);
|
|
|
|
match self.vfs.read_dir(&full_path) {
|
|
Ok(entries) => {
|
|
let results: Vec<Box<dyn DavDirEntry>> = entries
|
|
.into_iter()
|
|
.map(|e| {
|
|
let meta = VfsDavMetaData::from_stat(&e.stat);
|
|
Box::new(VfsDavDirEntry::new(e.name, meta)) as Box<dyn DavDirEntry>
|
|
})
|
|
.collect();
|
|
|
|
let stream = stream::iter(results.into_iter().map(Ok));
|
|
Box::pin(std::future::ready(Ok(Box::pin(stream) as FsStream<Box<dyn DavDirEntry>>)))
|
|
}
|
|
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
|
}
|
|
}
|
|
|
|
fn metadata<'a>(&'a self, path: &'a DavPath) -> FsFuture<'a, Box<dyn DavMetaData>> {
|
|
let full_path = self.resolve_path(path);
|
|
|
|
match self.vfs.stat(&full_path) {
|
|
Ok(stat) => {
|
|
let meta = VfsDavMetaData::from_stat(&stat);
|
|
Box::pin(std::future::ready(Ok(Box::new(meta) as Box<dyn DavMetaData>)))
|
|
}
|
|
Err(_) => Box::pin(std::future::ready(Err(FsError::NotFound))),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn create_webdav_handler(
|
|
vfs: Box<dyn VfsBackend>,
|
|
root: PathBuf,
|
|
upload_hook: Option<Arc<UploadHook>>,
|
|
user_uuid: String,
|
|
) -> dav_server::DavHandler {
|
|
let dav_fs = VfsDavFs::new(vfs, root, upload_hook, user_uuid);
|
|
dav_server::DavHandler::builder()
|
|
.filesystem(dav_fs)
|
|
.locksystem(FakeLs::new())
|
|
.strip_prefix("/webdav")
|
|
.build_handler()
|
|
} |