Implement SMB ACLs (NFSv4) at VFS layer
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- 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:
Warren
2026-06-20 22:33:03 +08:00
parent de5f8d3cfb
commit 1ca4913291
2 changed files with 274 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
use super::open_flags::OpenFlags; use super::open_flags::OpenFlags;
use super::util; 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::fs::{self, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::fs::{MetadataExt, PermissionsExt}; 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))) 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 { impl LocalFs {
@@ -616,3 +668,106 @@ struct VfsSnapshotMeta {
created: SystemTime, created: SystemTime,
source_path: String, 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())),
}
}
}

View File

@@ -235,6 +235,33 @@ pub trait VfsBackend: Send + Sync {
fn restore_previous_version(&self, _path: &Path, _gmt_token: &str) -> Result<(), VfsError> { fn restore_previous_version(&self, _path: &Path, _gmt_token: &str) -> Result<(), VfsError> {
Err(VfsError::Unsupported("restore_previous_version".to_string())) 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, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfsCompression { pub enum VfsCompression {