From 1ca4913291a904ea1f6188c1714306ce5b10a0a6 Mon Sep 17 00:00:00 2001 From: Warren Date: Sat, 20 Jun 2026 22:33:03 +0800 Subject: [PATCH] Implement SMB ACLs (NFSv4) at VFS layer - Add ACL structures: - VfsAceType (Allow/Deny/Audit/Alarm) - VfsAceFlag (inheritance flags) - VfsAceMask (permission masks) - VfsAce (access control entry) - VfsAcl (ACL list with default_acl) - Add VfsBackend methods: - get_acl() - retrieve ACL from .acl JSON - set_acl() - store ACL as .acl JSON - check_acl() - check permission for principal - add_ace() - add ACE to ACL - remove_ace() - remove ACE by index - LocalFs implementation: - VfsAclMeta serialization struct - ACL stored as JSON metadata (similar to quota/snapshot) - Box for recursive default_acl - Foundation for SMB/NFSv4 ACL support All 229 tests pass. --- markbase-core/src/vfs/local_fs.rs | 157 +++++++++++++++++++++++++++++- markbase-core/src/vfs/mod.rs | 118 ++++++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) diff --git a/markbase-core/src/vfs/local_fs.rs b/markbase-core/src/vfs/local_fs.rs index 3217276..75092a6 100644 --- a/markbase-core/src/vfs/local_fs.rs +++ b/markbase-core/src/vfs/local_fs.rs @@ -1,6 +1,6 @@ use super::open_flags::OpenFlags; use super::util; -use super::{VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsPreviousVersion, VfsQuota, VfsQuotaUsage, VfsSnapshotInfo, VfsStat}; +use super::{VfsAce, VfsAceFlag, VfsAceMask, VfsAceType, VfsAcl, VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsPreviousVersion, VfsQuota, VfsQuotaUsage, VfsSnapshotInfo, VfsStat}; use std::fs::{self, File, OpenOptions}; use std::io::{Read, Seek, SeekFrom, Write}; use std::os::unix::fs::{MetadataExt, PermissionsExt}; @@ -485,6 +485,58 @@ impl VfsBackend for LocalFs { Err(VfsError::NotFound(format!("No snapshot found with GMT token: {}", gmt_token))) } + + fn get_acl(&self, path: &Path) -> Result { + let acl_file = path.join(".acl"); + if !acl_file.exists() { + return Ok(VfsAcl::default()); + } + + let json = fs::read_to_string(&acl_file) + .map_err(|e| util::map_io_error(&acl_file, e))?; + let acl_meta: VfsAclMeta = serde_json::from_str(&json) + .map_err(|e| VfsError::Io(format!("Failed to parse ACL meta: {}", e)))?; + + Ok(acl_meta.to_acl()) + } + + fn set_acl(&self, path: &Path, acl: &VfsAcl) -> Result<(), VfsError> { + let acl_file = path.join(".acl"); + let acl_meta = VfsAclMeta::from_acl(acl); + let json = serde_json::to_string(&acl_meta) + .map_err(|e| VfsError::Io(format!("Failed to serialize ACL meta: {}", e)))?; + fs::write(&acl_file, json) + .map_err(|e| util::map_io_error(&acl_file, e)) + } + + fn check_acl(&self, path: &Path, principal: &str, mask: VfsAceMask) -> Result { + let acl = self.get_acl(path)?; + + for ace in &acl.aces { + if ace.principal == principal || ace.principal == "*" { + if ace.mask.contains(&mask) { + return Ok(ace.ace_type == VfsAceType::Allow); + } + } + } + + Ok(true) + } + + fn add_ace(&self, path: &Path, ace: &VfsAce) -> Result<(), VfsError> { + let mut acl = self.get_acl(path)?; + acl.aces.push(ace.clone()); + self.set_acl(path, &acl) + } + + fn remove_ace(&self, path: &Path, ace_index: usize) -> Result<(), VfsError> { + let mut acl = self.get_acl(path)?; + if ace_index >= acl.aces.len() { + return Err(VfsError::NotFound(format!("ACE index {} out of range", ace_index))); + } + acl.aces.remove(ace_index); + self.set_acl(path, &acl) + } } impl LocalFs { @@ -616,3 +668,106 @@ struct VfsSnapshotMeta { created: SystemTime, source_path: String, } + +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +struct VfsAceMeta { + ace_type: String, + flags: Vec, + mask: Vec, + principal: String, +} + +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +struct VfsAclMeta { + aces: Vec, + default_acl: Option>, +} + +impl VfsAclMeta { + fn from_acl(acl: &VfsAcl) -> Self { + Self { + aces: acl.aces.iter().map(|ace| VfsAceMeta { + ace_type: match ace.ace_type { + VfsAceType::Allow => "allow".to_string(), + VfsAceType::Deny => "deny".to_string(), + VfsAceType::Audit => "audit".to_string(), + VfsAceType::Alarm => "alarm".to_string(), + }, + flags: ace.flags.iter().map(|f| match f { + VfsAceFlag::FileInherit => "file_inherit".to_string(), + VfsAceFlag::DirectoryInherit => "directory_inherit".to_string(), + VfsAceFlag::NoPropagateInherit => "no_propagate".to_string(), + VfsAceFlag::InheritOnly => "inherit_only".to_string(), + VfsAceFlag::Inherited => "inherited".to_string(), + VfsAceFlag::SuccessfulAccess => "successful_access".to_string(), + VfsAceFlag::FailedAccess => "failed_access".to_string(), + }).collect(), + mask: ace.mask.iter().map(|m| match m { + VfsAceMask::ReadData => "read_data".to_string(), + VfsAceMask::WriteData => "write_data".to_string(), + VfsAceMask::Execute => "execute".to_string(), + VfsAceMask::ListDirectory => "list_directory".to_string(), + VfsAceMask::AddFile => "add_file".to_string(), + VfsAceMask::AddSubdirectory => "add_subdirectory".to_string(), + VfsAceMask::DeleteChild => "delete_child".to_string(), + VfsAceMask::Delete => "delete".to_string(), + VfsAceMask::ReadAttributes => "read_attributes".to_string(), + VfsAceMask::WriteAttributes => "write_attributes".to_string(), + VfsAceMask::ReadNfsAcl => "read_acl".to_string(), + VfsAceMask::WriteNfsAcl => "write_acl".to_string(), + VfsAceMask::ReadOwner => "read_owner".to_string(), + VfsAceMask::WriteOwner => "write_owner".to_string(), + VfsAceMask::Synchronize => "synchronize".to_string(), + VfsAceMask::FullControl => "full_control".to_string(), + }).collect(), + principal: ace.principal.clone(), + }).collect(), + default_acl: acl.default_acl.as_ref().map(|dacl| Box::new(Self::from_acl(dacl))), + } + } + + fn to_acl(&self) -> VfsAcl { + VfsAcl { + aces: self.aces.iter().map(|ace| VfsAce { + ace_type: match ace.ace_type.as_str() { + "allow" => VfsAceType::Allow, + "deny" => VfsAceType::Deny, + "audit" => VfsAceType::Audit, + "alarm" => VfsAceType::Alarm, + _ => VfsAceType::Allow, + }, + flags: ace.flags.iter().map(|f| match f.as_str() { + "file_inherit" => VfsAceFlag::FileInherit, + "directory_inherit" => VfsAceFlag::DirectoryInherit, + "no_propagate" => VfsAceFlag::NoPropagateInherit, + "inherit_only" => VfsAceFlag::InheritOnly, + "inherited" => VfsAceFlag::Inherited, + "successful_access" => VfsAceFlag::SuccessfulAccess, + "failed_access" => VfsAceFlag::FailedAccess, + _ => VfsAceFlag::FileInherit, + }).collect(), + mask: ace.mask.iter().map(|m| match m.as_str() { + "read_data" => VfsAceMask::ReadData, + "write_data" => VfsAceMask::WriteData, + "execute" => VfsAceMask::Execute, + "list_directory" => VfsAceMask::ListDirectory, + "add_file" => VfsAceMask::AddFile, + "add_subdirectory" => VfsAceMask::AddSubdirectory, + "delete_child" => VfsAceMask::DeleteChild, + "delete" => VfsAceMask::Delete, + "read_attributes" => VfsAceMask::ReadAttributes, + "write_attributes" => VfsAceMask::WriteAttributes, + "read_acl" => VfsAceMask::ReadNfsAcl, + "write_acl" => VfsAceMask::WriteNfsAcl, + "read_owner" => VfsAceMask::ReadOwner, + "write_owner" => VfsAceMask::WriteOwner, + "synchronize" => VfsAceMask::Synchronize, + "full_control" => VfsAceMask::FullControl, + _ => VfsAceMask::ReadData, + }).collect(), + principal: ace.principal.clone(), + }).collect(), + default_acl: self.default_acl.as_ref().map(|dacl| Box::new(dacl.to_acl())), + } + } +} diff --git a/markbase-core/src/vfs/mod.rs b/markbase-core/src/vfs/mod.rs index 22a8e3e..41374de 100644 --- a/markbase-core/src/vfs/mod.rs +++ b/markbase-core/src/vfs/mod.rs @@ -235,6 +235,33 @@ pub trait VfsBackend: Send + Sync { fn restore_previous_version(&self, _path: &Path, _gmt_token: &str) -> Result<(), VfsError> { Err(VfsError::Unsupported("restore_previous_version".to_string())) } + + // ===== ACL support (NFSv4/SMB) ===== + + /// 获取文件ACL + fn get_acl(&self, _path: &Path) -> Result { + Err(VfsError::Unsupported("get_acl".to_string())) + } + + /// 设置文件ACL + fn set_acl(&self, _path: &Path, _acl: &VfsAcl) -> Result<(), VfsError> { + Err(VfsError::Unsupported("set_acl".to_string())) + } + + /// 检查ACL权限 + fn check_acl(&self, _path: &Path, _principal: &str, _mask: VfsAceMask) -> Result { + Ok(true) // Default: no ACL, always allow + } + + /// 添加ACE + fn add_ace(&self, _path: &Path, _ace: &VfsAce) -> Result<(), VfsError> { + Err(VfsError::Unsupported("add_ace".to_string())) + } + + /// 移除ACE + fn remove_ace(&self, _path: &Path, _ace_index: usize) -> Result<(), VfsError> { + Err(VfsError::Unsupported("remove_ace".to_string())) + } } /// 快照信息 @@ -291,6 +318,97 @@ pub struct VfsPreviousVersion { pub size: u64, } +/// ACL访问控制条目类型(NFSv4/SMB) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VfsAceType { + /// 允许访问 + Allow, + /// 拒绝访问 + Deny, + /// 审计(SMB) + Audit, + /// 警报(SMB) + Alarm, +} + +/// ACL继承标志(NFSv4/SMB) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VfsAceFlag { + /// 文件继承 + FileInherit, + /// 目录继承 + DirectoryInherit, + /// 无继承(仅当前对象) + NoPropagateInherit, + /// 仅继承(不应用于当前对象) + InheritOnly, + /// 已继承 + Inherited, + /// 成功审计(SMB) + SuccessfulAccess, + /// 失败审计(SMB) + FailedAccess, +} + +/// ACL权限掩码(NFSv4) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VfsAceMask { + /// 读数据 + ReadData, + /// 写数据 + WriteData, + /// 执行 + Execute, + /// 列目录(读数据+目录) + ListDirectory, + /// 添加文件(写数据+目录) + AddFile, + /// 添加子目录 + AddSubdirectory, + /// 删除子项 + DeleteChild, + /// 删除 + Delete, + /// 读属性 + ReadAttributes, + /// 写属性 + WriteAttributes, + /// 读ACL + ReadNfsAcl, + /// 写ACL + WriteNfsAcl, + /// 读取所有权 + ReadOwner, + /// 写入所有权 + WriteOwner, + /// 同步 + Synchronize, + /// 完全控制(所有权限) + FullControl, +} + +/// ACL访问控制条目(ACE) +#[derive(Debug, Clone)] +pub struct VfsAce { + /// ACE类型 + pub ace_type: VfsAceType, + /// ACE标志 + pub flags: Vec, + /// 权限掩码 + pub mask: Vec, + /// 主体(用户/组SID或名称) + pub principal: String, +} + +/// ACL列表(NFSv4/SMB) +#[derive(Debug, Clone, Default)] +pub struct VfsAcl { + /// ACE列表 + pub aces: Vec, + /// 默认ACL(仅目录) + pub default_acl: Option>, +} + /// 压缩算法类型 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VfsCompression {