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<VfsAcl> for recursive default_acl - Foundation for SMB/NFSv4 ACL support All 229 tests pass.
This commit is contained in:
@@ -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<VfsAcl, VfsError> {
|
||||
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<bool, VfsError> {
|
||||
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<String>,
|
||||
mask: Vec<String>,
|
||||
principal: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
struct VfsAclMeta {
|
||||
aces: Vec<VfsAceMeta>,
|
||||
default_acl: Option<Box<VfsAclMeta>>,
|
||||
}
|
||||
|
||||
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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VfsAcl, VfsError> {
|
||||
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<bool, VfsError> {
|
||||
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<VfsAceFlag>,
|
||||
/// 权限掩码
|
||||
pub mask: Vec<VfsAceMask>,
|
||||
/// 主体(用户/组SID或名称)
|
||||
pub principal: String,
|
||||
}
|
||||
|
||||
/// ACL列表(NFSv4/SMB)
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct VfsAcl {
|
||||
/// ACE列表
|
||||
pub aces: Vec<VfsAce>,
|
||||
/// 默认ACL(仅目录)
|
||||
pub default_acl: Option<Box<VfsAcl>>,
|
||||
}
|
||||
|
||||
/// 压缩算法类型
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VfsCompression {
|
||||
|
||||
Reference in New Issue
Block a user