Skip to content

Commit

Permalink
💥 Changed ACL-related chunk specifications
Browse files Browse the repository at this point in the history
The current specification for `faCe` chunks does not distinguish between no ACL information and an empty ACL.
To address this, we will introduce a new `faCl` chunk.
The absence of an `faCe` chunk while an `faCl` chunk exists will indicate an empty ACL.
To improve data size efficiency, we will remove the platform specifier from the `faCe` chunk and have the `faCl` chunk hold it instead.
The new specifications for the body part of each chunk are as follows:
`faCl` chunk
`<platform specifier>`
`faCe` chunk
`<flags>:<owner type>:<owner identifier>:<acl type>:<permissions>`
  • Loading branch information
ChanTsune committed Nov 8, 2024
1 parent 6a78e12 commit 07a6c45
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 163 deletions.
319 changes: 227 additions & 92 deletions cli/src/chunk/acl.rs

Large diffs are not rendered by default.

28 changes: 22 additions & 6 deletions cli/src/command/acl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ impl AclEntries {

fn to_ace(&self) -> Ace {
Ace {
platform: AcePlatform::General,
flags: if self.default {
Flag::DEFAULT
} else {
Expand Down Expand Up @@ -233,8 +232,11 @@ fn archive_get_acl(args: GetAclCommand) -> io::Result<()> {
"# group: {}",
permission.map(|it| it.gname()).unwrap_or("-")
);
for ace in entry.acl()? {
println!("{}", ace);
for (platform, acl) in entry.acl()? {
println!("# platform: {}", platform);
for ace in acl {
println!("{}", ace);
}
}
}
Ok(())
Expand Down Expand Up @@ -302,7 +304,15 @@ where
RawChunk<T>: Chunk,
RawChunk<T>: From<RawChunk>,
{
let mut acl = entry.acl().unwrap_or_default();
let platform = AcePlatform::General;
let platform = &platform;
let mut acls = entry.acl().unwrap_or_default();
let acl = if let Some(acl) = acls.get_mut(platform) {
acl
} else {
return entry;
};

let extra_without_known = entry
.extra_chunks()
.iter()
Expand All @@ -323,9 +333,15 @@ where
log::debug!("Removing ace {}", remove.to_ace());
acl.retain(|it| !remove.is_match(it));
}
let extra_chunks = acl
let mut acl_chunks = Vec::new();
for (platform, aces) in acls {
acl_chunks.push(RawChunk::from_data(crate::chunk::faCl, platform.to_bytes()).into());
for ace in aces {
acl_chunks.push(RawChunk::from_data(crate::chunk::faCe, ace.to_bytes()).into());
}
}
let extra_chunks = acl_chunks
.into_iter()
.map(|it| RawChunk::from_data(crate::chunk::faCe, it.to_bytes()).into())
.chain(extra_without_known)
.collect::<Vec<_>>();
entry.with_extra_chunks(&extra_chunks)
Expand Down
5 changes: 3 additions & 2 deletions cli/src/command/commons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,9 @@ pub(crate) fn apply_metadata(
if keep_options.keep_acl {
use crate::chunk;
use pna::RawChunk;
let ace_list = utils::acl::get_facl(path)?;
for ace in ace_list {
let acl = utils::acl::get_facl(path)?;
entry.add_extra_chunk(RawChunk::from_data(chunk::faCl, acl.platform.to_bytes()));
for ace in acl.entries {
entry.add_extra_chunk(RawChunk::from_data(chunk::faCe, ace.to_bytes()));
}
}
Expand Down
18 changes: 15 additions & 3 deletions cli/src/command/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,23 @@ where
windows
))]
if keep_options.keep_acl {
use crate::chunk::{acl_convert_current_platform, AcePlatform, Acl};
use crate::ext::*;
use itertools::Itertools;

let acl = item.acl()?;
if !acl.is_empty() {
utils::acl::set_facl(&path, acl)?;
let platform = AcePlatform::CURRENT;
let acls = item.acl()?;
if let Some((platform, acl)) = acls.into_iter().find_or_first(|(p, _)| p.eq(&platform))
{
if !acl.is_empty() {
utils::acl::set_facl(
&path,
acl_convert_current_platform(Acl {
platform,
entries: acl,
}),
)?;
}
}
}
#[cfg(not(any(
Expand Down
4 changes: 2 additions & 2 deletions cli/src/command/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ struct TableRow {
modified: String,
name: String,
xattrs: Vec<ExtendedAttribute>,
acl: Vec<chunk::Ace>,
acl: Vec<chunk::AceWithPlatform>,
privates: Vec<RawChunk>,
}

Expand Down Expand Up @@ -184,7 +184,7 @@ where
header.path().to_string()
},
xattrs: entry.xattrs().to_vec(),
acl: entry.acl()?,
acl: entry.acl_with_platform()?,
privates: entry
.extra_chunks()
.iter()
Expand Down
1 change: 1 addition & 0 deletions cli/src/command/strip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ where
.is_some_and(|it| it.is_empty());
let mut keep_private_chunks = Vec::new();
if options.keep_acl {
keep_private_chunks.push(crate::chunk::faCl);
keep_private_chunks.push(crate::chunk::faCe);
}
if let Some(chunks) = &options.keep_private {
Expand Down
46 changes: 38 additions & 8 deletions cli/src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
use crate::chunk::{self, Ace};
use crate::chunk::{self, Ace, AcePlatform, AceWithPlatform};
use pna::{prelude::*, NormalEntry, RawChunk};
use std::collections::HashMap;
use std::io;

pub(crate) trait NormalEntryExt {
fn acl(&self) -> io::Result<Vec<Ace>>;
fn acl(&self) -> io::Result<HashMap<AcePlatform, Vec<Ace>>>;
fn acl_with_platform(&self) -> io::Result<Vec<AceWithPlatform>> {
let acl = self.acl()?;
Ok(acl
.into_iter()
.map(|(platform, ace)| {
ace.into_iter().map(move |it| AceWithPlatform {
platform: platform.clone(),
ace: it,
})
})
.flatten()

Check warning

Code scanning / clippy

called map(..).flatten() on Iterator Warning

called map(..).flatten() on Iterator

Check warning

Code scanning / clippy

called map(..).flatten() on Iterator Warning

called map(..).flatten() on Iterator
.collect())
}
}

impl<T> NormalEntryExt for NormalEntry<T>
where
RawChunk<T>: Chunk,
{
#[inline]
fn acl(&self) -> io::Result<Vec<Ace>> {
self.extra_chunks()
.iter()
.filter(|c| c.ty() == chunk::faCe)
.map(|c| Ace::try_from(c.data()).map_err(io::Error::other))
.collect()
fn acl(&self) -> io::Result<HashMap<AcePlatform, Vec<Ace>>> {
let mut acls = HashMap::new();
let mut platform = AcePlatform::General;
for c in self.extra_chunks().iter() {
match c.ty() {
chunk::faCl => {
platform = AcePlatform::try_from(c.data()).map_err(io::Error::other)?
}
chunk::faCe => {
let ace = AceWithPlatform::try_from(c.data()).map_err(io::Error::other)?;
if ace.platform != platform {
acls.entry(ace.platform)
} else {
acls.entry(platform.clone())
}
.or_insert_with(Vec::new)
.push(ace.ace);
}
_ => continue,
}
}
Ok(acls)
}
}
21 changes: 11 additions & 10 deletions cli/src/utils/os/unix/acl.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::chunk::{
ace_convert_current_platform, Ace, AcePlatform, Flag, Identifier, OwnerType, Permission,
};
use crate::chunk::{Ace, AcePlatform, Acl, Flag, Identifier, OwnerType, Permission};
use std::io;
use std::path::Path;

pub fn set_facl<P: AsRef<Path>>(path: P, acl: Vec<Ace>) -> io::Result<()> {
pub fn set_facl<P: AsRef<Path>>(path: P, acl: Acl) -> io::Result<()> {
let path = path.as_ref();
let mut acl_entries: Vec<exacl::AclEntry> = acl.into_iter().map(Into::into).collect::<Vec<_>>();
let mut acl_entries: Vec<exacl::AclEntry> =
acl.entries.into_iter().map(Into::into).collect::<Vec<_>>();
#[cfg(target_os = "macos")]
{
use std::os::unix::fs::MetadataExt;
Expand Down Expand Up @@ -86,9 +85,12 @@ pub fn set_facl<P: AsRef<Path>>(path: P, acl: Vec<Ace>) -> io::Result<()> {
exacl::setfacl(&[path], &acl_entries, None)
}

pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<Vec<Ace>> {
pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<Acl> {
let ace_list = exacl::getfacl(path.as_ref(), None)?;
Ok(ace_list.into_iter().map(Into::into).collect())
Ok(Acl {
platform: AcePlatform::CURRENT,
entries: ace_list.into_iter().map(Into::into).collect(),
})
}

#[allow(clippy::from_over_into)]
Expand Down Expand Up @@ -183,7 +185,6 @@ impl Into<Ace> for exacl::AclEntry {
}

Ace {
platform: AcePlatform::CURRENT,
flags,
owner_type: match self.kind {
exacl::AclEntryKind::User if self.name.is_empty() => OwnerType::Owner,
Expand All @@ -207,7 +208,7 @@ impl Into<Ace> for exacl::AclEntry {
#[allow(clippy::from_over_into)]
impl Into<exacl::AclEntry> for Ace {
fn into(self) -> exacl::AclEntry {
let slf = ace_convert_current_platform(self);
let slf = self;
let (kind, name) = match slf.owner_type {
OwnerType::Owner => (exacl::AclEntryKind::User, String::new()),
OwnerType::User(u) => (exacl::AclEntryKind::User, u.0),
Expand Down Expand Up @@ -337,7 +338,7 @@ mod tests {
fn ace_mutual_convert() {
let acl_entry = exacl::AclEntry {
kind: exacl::AclEntryKind::User,
name: "name".to_string(),
name: "name".into(),
perms: exacl::Perm::all(),
flags: exacl::Flag::all(),
allow: false,
Expand Down
86 changes: 46 additions & 40 deletions cli/src/utils/os/windows/acl.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use crate::chunk;
use crate::chunk::{ace_convert_current_platform, AcePlatform, Identifier, OwnerType};
use crate::utils::os::windows::security::{SecurityDescriptor, Sid, SidType};
use crate::{
chunk::{self, AcePlatform, Identifier, OwnerType},
utils::os::windows::security::{SecurityDescriptor, Sid, SidType},
};
use field_offset::offset_of;
use std::path::Path;
use std::ptr::null_mut;
use std::{io, mem};
use std::{io, mem, path::Path, ptr::null_mut};
use windows::Win32::Security::{
AddAccessAllowedAceEx, AddAccessDeniedAceEx, GetAce, InitializeAcl, ACCESS_ALLOWED_ACE,
ACCESS_DENIED_ACE, ACE_FLAGS, ACE_HEADER, ACL as Win32ACL, ACL_REVISION_DS,
Expand All @@ -19,21 +18,25 @@ use windows::Win32::Storage::FileSystem::{
};
use windows::Win32::System::SystemServices::{ACCESS_ALLOWED_ACE_TYPE, ACCESS_DENIED_ACE_TYPE};

pub fn set_facl<P: AsRef<Path>>(path: P, ace_list: Vec<chunk::Ace>) -> io::Result<()> {
pub fn set_facl<P: AsRef<Path>>(path: P, ace_list: chunk::Acl) -> io::Result<()> {
let acl = ACL::try_from(path.as_ref())?;
let group_sid = acl.security_descriptor.group_sid()?;
let owner_sid = acl.security_descriptor.owner_sid()?;
let acl_entries = ace_list
.entries
.into_iter()
.map(|it| it.into_acl_entry_with(&owner_sid, &group_sid))
.collect::<Vec<_>>();
acl.set_d_acl(&acl_entries)
}

pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<Vec<chunk::Ace>> {
pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<chunk::Acl> {
let acl = ACL::try_from(path.as_ref())?;
let ace_list = acl.get_d_acl()?;
Ok(ace_list.into_iter().map(Into::into).collect())
Ok(chunk::Acl {
platform: AcePlatform::Windows,
entries: ace_list.into_iter().map(Into::into).collect(),
})
}

#[allow(non_camel_case_types)]
Expand Down Expand Up @@ -186,7 +189,7 @@ const FLAGS_MAPPING_TABLE: [(chunk::Flag, ACE_FLAGS); 6] = [

impl chunk::Ace {
fn into_acl_entry_with(self, owner_sid: &Sid, group_sid: &Sid) -> ACLEntry {
let slf = ace_convert_current_platform(self);
let slf = self;
let sid = match slf.owner_type {
OwnerType::Owner => owner_sid.clone(),
OwnerType::User(i) => Sid::try_from_name(&i.0, None).unwrap(),
Expand Down Expand Up @@ -235,7 +238,6 @@ impl Into<chunk::Ace> for ACLEntry {
t => panic!("Unsupported ace type {:?}", t),
};
chunk::Ace {
platform: AcePlatform::Windows,
flags: {
let mut flags = chunk::Flag::empty();
for (f, g) in FLAGS_MAPPING_TABLE {
Expand Down Expand Up @@ -276,7 +278,7 @@ impl Into<chunk::Ace> for ACLEntry {
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::Ace;
use crate::chunk::{Ace, Acl};
#[test]
fn acl_for_everyone() {
let path = "everyone.txt";
Expand All @@ -285,41 +287,45 @@ mod tests {

set_facl(
&path,
vec![Ace {
Acl {
platform: AcePlatform::General,
flags: chunk::Flag::empty(),
owner_type: OwnerType::Group(Identifier(sid.name.clone())),
allow: true,
permission: chunk::Permission::READ
| chunk::Permission::WRITE
| chunk::Permission::EXECUTE,
}],
entries: vec![Ace {
flags: chunk::Flag::empty(),
owner_type: OwnerType::Group(Identifier(sid.name.clone())),
allow: true,
permission: chunk::Permission::READ
| chunk::Permission::WRITE
| chunk::Permission::EXECUTE,
}],
},
)
.unwrap();
let acl = get_facl(&path).unwrap();
assert_eq!(acl.len(), 1);
assert_eq!(acl.entries.len(), 1);

assert_eq!(
&acl[0],
&Ace {
&acl.entries[0],
&Acl {
platform: AcePlatform::Windows,
flags: chunk::Flag::empty(),
owner_type: OwnerType::Group(Identifier(sid.name)),
allow: true,
permission: chunk::Permission::READ
| chunk::Permission::WRITE
| chunk::Permission::EXECUTE
| chunk::Permission::DELETE
| chunk::Permission::APPEND
| chunk::Permission::READATTR
| chunk::Permission::WRITEATTR
| chunk::Permission::READEXTATTR
| chunk::Permission::WRITEEXTATTR
| chunk::Permission::READSECURITY
| chunk::Permission::WRITESECURITY
| chunk::Permission::SYNC
| chunk::Permission::READ_DATA
| chunk::Permission::WRITE_DATA,
entries: vec![Ace {
flags: chunk::Flag::empty(),
owner_type: OwnerType::Group(Identifier(sid.name)),
allow: true,
permission: chunk::Permission::READ
| chunk::Permission::WRITE
| chunk::Permission::EXECUTE
| chunk::Permission::DELETE
| chunk::Permission::APPEND
| chunk::Permission::READATTR
| chunk::Permission::WRITEATTR
| chunk::Permission::READEXTATTR
| chunk::Permission::WRITEEXTATTR
| chunk::Permission::READSECURITY
| chunk::Permission::WRITESECURITY
| chunk::Permission::SYNC
| chunk::Permission::READ_DATA
| chunk::Permission::WRITE_DATA,
}]
}
);
}
Expand Down

0 comments on commit 07a6c45

Please sign in to comment.