Skip to content

Commit c7895f1

Browse files
committed
♻️ Consolidate OwnerOptions and same_owner into PermissionStrategy
Refactor permission/ownership handling to follow the TimestampStrategy pattern, creating a single source of truth for all permission-related configuration. Changes: - Add OwnerSource enum with NoRestore and FromSource variants - Change PermissionStrategy from Never/Always to Never/Preserve{owner} - Add PermissionStrategyResolver with context-specific resolution: - resolve_for_creation(): always FromSource (ownership stored) - resolve_for_extraction(): respects same_owner flag - Remove OwnerOptions struct and owner_options from CreateOptions - Update all command modules to use the new resolver pattern This consolidation eliminates the scattered ownership concerns (OwnerOptions struct, same_owner boolean, PermissionStrategy enum) into a unified architecture where ownership handling is embedded within PermissionStrategy.
1 parent f5c2a15 commit c7895f1

7 files changed

Lines changed: 285 additions & 246 deletions

File tree

cli/src/command/append.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use crate::{
77
Command, ask_password, check_password,
88
core::{
99
AclStrategy, CollectOptions, CollectedItem, CreateOptions, FflagsStrategy, KeepOptions,
10-
MacMetadataStrategy, OwnerOptions, PathFilter, PathTransformers, PathnameEditor,
11-
PermissionStrategy, TimeFilterResolver, TimestampStrategyResolver, XattrStrategy,
12-
collect_items_from_paths, create_entry, entry_option,
10+
MacMetadataStrategy, PathFilter, PathTransformers, PathnameEditor,
11+
PermissionStrategyResolver, TimeFilterResolver, TimestampStrategyResolver,
12+
XattrStrategy, collect_items_from_paths, create_entry, entry_option,
1313
re::{bsd::SubstitutionRule, gnu::TransformRule},
1414
read_paths, read_paths_stdin,
1515
},
@@ -385,22 +385,22 @@ fn append_to_archive(args: AppendCommand) -> anyhow::Result<()> {
385385
clamp_atime: args.clamp_atime,
386386
}
387387
.resolve(),
388-
permission_strategy: PermissionStrategy::from_flags(
389-
args.keep_permission,
390-
args.no_keep_permission,
391-
),
388+
permission_strategy: PermissionStrategyResolver {
389+
keep_permission: args.keep_permission,
390+
no_keep_permission: args.no_keep_permission,
391+
same_owner: true, // Must be `true` for creation
392+
uname: args.uname,
393+
gname: args.gname,
394+
uid: args.uid,
395+
gid: args.gid,
396+
numeric_owner: args.numeric_owner,
397+
}
398+
.resolve(),
392399
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
393400
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
394401
fflags_strategy: FflagsStrategy::Never,
395402
mac_metadata_strategy: MacMetadataStrategy::Never,
396403
};
397-
let owner_options = OwnerOptions::new(
398-
args.uname,
399-
args.gname,
400-
args.uid,
401-
args.gid,
402-
args.numeric_owner,
403-
);
404404
let time_filters = TimeFilterResolver {
405405
newer_ctime_than: args.newer_ctime_than.as_deref(),
406406
older_ctime_than: args.older_ctime_than.as_deref(),
@@ -415,7 +415,6 @@ fn append_to_archive(args: AppendCommand) -> anyhow::Result<()> {
415415
let create_options = CreateOptions {
416416
option,
417417
keep_options,
418-
owner_options,
419418
pathname_editor: PathnameEditor::new(
420419
args.strip_components,
421420
PathTransformers::new(args.substitutions, args.transforms),

cli/src/command/core.rs

Lines changed: 84 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ pub(crate) mod path;
22
mod path_filter;
33
pub(crate) mod path_lock;
44
mod path_transformer;
5+
pub(crate) mod permission;
56
pub(crate) mod re;
67
pub(crate) mod time_filter;
78
pub(crate) mod timestamp;
89

910
pub(crate) use self::path::PathnameEditor;
11+
pub(crate) use self::permission::{OwnerSource, PermissionStrategy};
1012
pub(crate) use self::timestamp::{TimeSource, TimestampStrategy};
1113
use crate::{
1214
cli::{CipherAlgorithmArgs, CompressionAlgorithmArgs, HashAlgorithmArgs},
@@ -125,24 +127,6 @@ impl XattrStrategy {
125127
}
126128
}
127129

128-
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
129-
pub(crate) enum PermissionStrategy {
130-
Never,
131-
Always,
132-
}
133-
134-
impl PermissionStrategy {
135-
pub(crate) const fn from_flags(keep_permission: bool, no_keep_permission: bool) -> Self {
136-
if no_keep_permission {
137-
Self::Never
138-
} else if keep_permission {
139-
Self::Always
140-
} else {
141-
Self::Never
142-
}
143-
}
144-
}
145-
146130
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
147131
pub(crate) enum AclStrategy {
148132
Never,
@@ -211,7 +195,7 @@ impl MacMetadataStrategy {
211195
}
212196
}
213197

214-
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
198+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
215199
pub(crate) struct KeepOptions {
216200
pub(crate) timestamp_strategy: TimestampStrategy,
217201
pub(crate) permission_strategy: PermissionStrategy,
@@ -270,36 +254,59 @@ impl TimestampStrategyResolver {
270254
}
271255
}
272256

273-
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
274-
pub(crate) struct OwnerOptions {
257+
/// Resolves CLI permission options into a `PermissionStrategy`.
258+
///
259+
/// This struct encapsulates the CLI-specific logic for determining permission behavior:
260+
/// - `no_keep_permission` forces `Never`
261+
/// - `keep_permission` enables `Preserve` with ownership handling
262+
/// - `same_owner` controls whether to restore ownership (extraction only)
263+
/// - Owner override fields (uid, gid, uname, gname) are used in both creation and extraction
264+
pub(crate) struct PermissionStrategyResolver {
265+
pub(crate) keep_permission: bool,
266+
pub(crate) no_keep_permission: bool,
267+
pub(crate) same_owner: bool,
275268
pub(crate) uname: Option<String>,
276269
pub(crate) gname: Option<String>,
277270
pub(crate) uid: Option<u32>,
278271
pub(crate) gid: Option<u32>,
272+
pub(crate) numeric_owner: bool,
279273
}
280274

281-
impl OwnerOptions {
282-
#[inline]
283-
pub(crate) fn new(
284-
uname: Option<String>,
285-
gname: Option<String>,
286-
uid: Option<u32>,
287-
gid: Option<u32>,
288-
numeric_owner: bool,
289-
) -> Self {
290-
Self {
291-
uname: if numeric_owner {
292-
Some(String::new())
293-
} else {
294-
uname
295-
},
296-
gname: if numeric_owner {
297-
Some(String::new())
298-
} else {
299-
gname
300-
},
301-
uid,
302-
gid,
275+
impl PermissionStrategyResolver {
276+
/// Resolves CLI options to PermissionStrategy.
277+
///
278+
/// The `same_owner` field controls ownership handling:
279+
/// - `true`: Restore/store ownership (FromSource)
280+
/// - `false`: Skip ownership restoration (NoRestore, extraction only)
281+
///
282+
/// For creation contexts, pass `same_owner: true` since ownership
283+
/// is always stored when `--keep-permission` is enabled.
284+
pub(crate) fn resolve(self) -> PermissionStrategy {
285+
if self.no_keep_permission {
286+
PermissionStrategy::Never
287+
} else if self.keep_permission {
288+
PermissionStrategy::Preserve {
289+
owner: if self.same_owner {
290+
OwnerSource::FromSource {
291+
uname: if self.numeric_owner {
292+
Some(String::new())
293+
} else {
294+
self.uname
295+
},
296+
gname: if self.numeric_owner {
297+
Some(String::new())
298+
} else {
299+
self.gname
300+
},
301+
uid: self.uid,
302+
gid: self.gid,
303+
}
304+
} else {
305+
OwnerSource::NoRestore
306+
},
307+
}
308+
} else {
309+
PermissionStrategy::Never
303310
}
304311
}
305312
}
@@ -308,7 +315,6 @@ impl OwnerOptions {
308315
pub(crate) struct CreateOptions {
309316
pub(crate) option: WriteOptions,
310317
pub(crate) keep_options: KeepOptions,
311-
pub(crate) owner_options: OwnerOptions,
312318
pub(crate) pathname_editor: PathnameEditor,
313319
}
314320

@@ -638,7 +644,6 @@ pub(crate) fn create_entry(
638644
CreateOptions {
639645
option,
640646
keep_options,
641-
owner_options,
642647
pathname_editor,
643648
}: &CreateOptions,
644649
) -> io::Result<Option<NormalEntry>> {
@@ -656,22 +661,22 @@ pub(crate) fn create_entry(
656661
return Ok(None);
657662
};
658663
let entry = EntryBuilder::new_hard_link(entry_name, reference)?;
659-
apply_metadata(entry, path, keep_options, owner_options, metadata)?.build()
664+
apply_metadata(entry, path, keep_options, metadata)?.build()
660665
}
661666
StoreAs::Symlink => {
662667
let source = fs::read_link(path)?;
663668
let reference = pathname_editor.edit_symlink(&source);
664669
let entry = EntryBuilder::new_symlink(entry_name, reference)?;
665-
apply_metadata(entry, path, keep_options, owner_options, metadata)?.build()
670+
apply_metadata(entry, path, keep_options, metadata)?.build()
666671
}
667672
StoreAs::File => {
668673
let mut entry = EntryBuilder::new_file(entry_name, option)?;
669674
write_from_path(&mut entry, path)?;
670-
apply_metadata(entry, path, keep_options, owner_options, metadata)?.build()
675+
apply_metadata(entry, path, keep_options, metadata)?.build()
671676
}
672677
StoreAs::Dir => {
673678
let entry = EntryBuilder::new_dir(entry_name);
674-
apply_metadata(entry, path, keep_options, owner_options, metadata)?.build()
679+
apply_metadata(entry, path, keep_options, metadata)?.build()
675680
}
676681
}
677682
.map(Some)
@@ -704,7 +709,6 @@ pub(crate) fn apply_metadata(
704709
mut entry: EntryBuilder,
705710
path: &Path,
706711
keep_options: &KeepOptions,
707-
owner_options: &OwnerOptions,
708712
meta: &fs::Metadata,
709713
) -> io::Result<EntryBuilder> {
710714
if let TimestampStrategy::Preserve {
@@ -724,24 +728,34 @@ pub(crate) fn apply_metadata(
724728
}
725729
}
726730
#[cfg(unix)]
727-
if let PermissionStrategy::Always = keep_options.permission_strategy {
731+
if let PermissionStrategy::Preserve { ref owner } = keep_options.permission_strategy {
728732
use crate::utils::fs::{Group, User};
729733
use std::os::unix::fs::{MetadataExt, PermissionsExt};
730734

731735
let mode = meta.permissions().mode() as u16;
732-
let uid = owner_options.uid.unwrap_or(meta.uid());
733-
let gid = owner_options.gid.unwrap_or(meta.gid());
736+
// Extract owner overrides from OwnerSource
737+
let (o_uid, o_gid, o_uname, o_gname) = match owner {
738+
OwnerSource::NoRestore => unreachable!("NoRestore is not used during creation"),
739+
OwnerSource::FromSource {
740+
uid,
741+
gid,
742+
uname,
743+
gname,
744+
} => (*uid, *gid, uname.as_deref(), gname.as_deref()),
745+
};
746+
let uid = o_uid.unwrap_or(meta.uid());
747+
let gid = o_gid.unwrap_or(meta.gid());
734748
entry.permission(pna::Permission::new(
735749
uid.into(),
736-
match owner_options.uname.as_deref() {
750+
match o_uname {
737751
None => User::from_uid(uid.into())?
738752
.name()
739753
.unwrap_or_default()
740754
.into(),
741755
Some(uname) => uname.into(),
742756
},
743757
gid.into(),
744-
match owner_options.gname.as_deref() {
758+
match o_gname {
745759
None => Group::from_gid(gid.into())?
746760
.name()
747761
.unwrap_or_default()
@@ -752,19 +766,29 @@ pub(crate) fn apply_metadata(
752766
));
753767
}
754768
#[cfg(windows)]
755-
if let PermissionStrategy::Always = keep_options.permission_strategy {
769+
if let PermissionStrategy::Preserve { ref owner } = keep_options.permission_strategy {
756770
use crate::utils::os::windows::{fs::stat, security::SecurityDescriptor};
757771

758772
let sd = SecurityDescriptor::try_from(path)?;
759773
let stat = stat(sd.path.as_ptr() as _)?;
760774
let mode = stat.st_mode;
761775
let user = sd.owner_sid()?;
762776
let group = sd.group_sid()?;
777+
// Extract owner overrides from OwnerSource
778+
let (o_uid, o_gid, o_uname, o_gname) = match owner {
779+
OwnerSource::NoRestore => unreachable!("NoRestore is not used during creation"),
780+
OwnerSource::FromSource {
781+
uid,
782+
gid,
783+
uname,
784+
gname,
785+
} => (*uid, *gid, uname.clone(), gname.clone()),
786+
};
763787
entry.permission(pna::Permission::new(
764-
owner_options.uid.map_or(u64::MAX, Into::into),
765-
owner_options.uname.clone().unwrap_or(user.name),
766-
owner_options.gid.map_or(u64::MAX, Into::into),
767-
owner_options.gname.clone().unwrap_or(group.name),
788+
o_uid.map_or(u64::MAX, Into::into),
789+
o_uname.unwrap_or(user.name),
790+
o_gid.map_or(u64::MAX, Into::into),
791+
o_gname.unwrap_or(group.name),
768792
mode,
769793
));
770794
}

cli/src/command/core/permission.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// How to handle ownership during permission operations.
2+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
3+
pub(crate) enum OwnerSource {
4+
/// Don't restore ownership (extraction only - same as !same_owner)
5+
/// During extraction: chmod() is called but lchown() is NOT
6+
NoRestore,
7+
/// Use ownership from source with optional overrides.
8+
/// During creation: Override values stored in archive (None = use filesystem)
9+
/// During extraction: Override values restored from archive (None = use archive)
10+
FromSource {
11+
uname: Option<String>,
12+
gname: Option<String>,
13+
uid: Option<u32>,
14+
gid: Option<u32>,
15+
},
16+
}
17+
18+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
19+
pub(crate) enum PermissionStrategy {
20+
Never,
21+
/// Preserve permissions with configurable ownership handling
22+
Preserve {
23+
owner: OwnerSource,
24+
},
25+
}

0 commit comments

Comments
 (0)