Skip to content

Commit 62cf039

Browse files
committed
⚗️ Add --fflags option to cli and add private chunk for store fflags
1 parent d0484d1 commit 62cf039

12 files changed

Lines changed: 462 additions & 63 deletions

File tree

cli/src/chunk.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod acl;
2+
mod fflag;
23

34
pub use acl::*;
5+
pub use fflag::*;

cli/src/chunk/fflag.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use pna::{ChunkType, RawChunk};
2+
3+
/// Private chunk type for file flags (fflags).
4+
/// Name follows PNA chunk naming convention where case has semantic meaning:
5+
/// - lowercase first letter: ancillary (not critical)
6+
/// - lowercase second letter: private (not public)
7+
/// - uppercase third letter: reserved
8+
/// - lowercase fourth letter: safe to copy
9+
#[allow(non_upper_case_globals)]
10+
pub const ffLg: ChunkType = unsafe { ChunkType::from_unchecked(*b"ffLg") };
11+
12+
pub fn fflag_chunk(flag: &str) -> RawChunk {
13+
RawChunk::from_data(ffLg, flag.as_bytes())
14+
}

cli/src/command/append.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ use crate::{
66
command::{
77
Command, ask_password, check_password,
88
core::{
9-
AclStrategy, CreateOptions, KeepOptions, OwnerOptions, PathFilter, PathTransformers,
10-
PathnameEditor, PermissionStrategy, StoreAs, TimeFilterResolver, TimeOptions,
11-
TimestampStrategy, XattrStrategy, collect_items, create_entry, entry_option,
9+
AclStrategy, CreateOptions, FflagsStrategy, KeepOptions, OwnerOptions, PathFilter,
10+
PathTransformers, PathnameEditor, PermissionStrategy, StoreAs, TimeFilterResolver,
11+
TimeOptions, TimestampStrategy, XattrStrategy, collect_items, create_entry,
12+
entry_option,
1213
re::{bsd::SubstitutionRule, gnu::TransformRule},
1314
read_paths, read_paths_stdin,
1415
},
@@ -339,6 +340,7 @@ fn append_to_archive(args: AppendCommand) -> anyhow::Result<()> {
339340
),
340341
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
341342
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
343+
fflags_strategy: FflagsStrategy::Never,
342344
};
343345
let owner_options = OwnerOptions::new(
344346
args.uname,

cli/src/command/core.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,31 @@ impl AclStrategy {
159159
}
160160
}
161161

162+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
163+
pub(crate) enum FflagsStrategy {
164+
Never,
165+
Always,
166+
}
167+
168+
impl FflagsStrategy {
169+
pub(crate) const fn from_flags(keep_fflags: bool, no_keep_fflags: bool) -> Self {
170+
if no_keep_fflags {
171+
Self::Never
172+
} else if keep_fflags {
173+
Self::Always
174+
} else {
175+
Self::Never
176+
}
177+
}
178+
}
179+
162180
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
163181
pub(crate) struct KeepOptions {
164182
pub(crate) timestamp_strategy: TimestampStrategy,
165183
pub(crate) permission_strategy: PermissionStrategy,
166184
pub(crate) xattr_strategy: XattrStrategy,
167185
pub(crate) acl_strategy: AclStrategy,
186+
pub(crate) fflags_strategy: FflagsStrategy,
168187
}
169188

170189
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
@@ -730,6 +749,23 @@ pub(crate) fn apply_metadata<'p>(
730749
if let XattrStrategy::Always = keep_options.xattr_strategy {
731750
log::warn!("Currently extended attribute is not supported on this platform.");
732751
}
752+
if let FflagsStrategy::Always = keep_options.fflags_strategy {
753+
match utils::fs::get_flags(path) {
754+
Ok(flags) => {
755+
for flag in flags {
756+
entry.add_extra_chunk(crate::chunk::fflag_chunk(&flag));
757+
}
758+
}
759+
Err(e) if e.kind() == std::io::ErrorKind::Unsupported => {
760+
log::warn!(
761+
"File flags are not supported on filesystem for '{}': {}",
762+
path.display(),
763+
e
764+
);
765+
}
766+
Err(e) => return Err(e),
767+
}
768+
}
733769
Ok(entry)
734770
}
735771

cli/src/command/create.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use crate::{
66
command::{
77
Command, ask_password, check_password,
88
core::{
9-
AclStrategy, CreateOptions, KeepOptions, MIN_SPLIT_PART_BYTES, OwnerOptions,
10-
PathFilter, PathTransformers, PathnameEditor, PermissionStrategy, StoreAs,
11-
TimeFilterResolver, TimeOptions, TimestampStrategy, XattrStrategy, collect_items,
12-
create_entry, entry_option,
9+
AclStrategy, CreateOptions, FflagsStrategy, KeepOptions, MIN_SPLIT_PART_BYTES,
10+
OwnerOptions, PathFilter, PathTransformers, PathnameEditor, PermissionStrategy,
11+
StoreAs, TimeFilterResolver, TimeOptions, TimestampStrategy, XattrStrategy,
12+
collect_items, create_entry, entry_option,
1313
re::{bsd::SubstitutionRule, gnu::TransformRule},
1414
read_paths, read_paths_stdin, write_split_archive,
1515
},
@@ -423,6 +423,7 @@ fn create_archive(args: CreateCommand) -> anyhow::Result<()> {
423423
),
424424
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
425425
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
426+
fflags_strategy: FflagsStrategy::Never,
426427
};
427428
let owner_options = OwnerOptions::new(
428429
args.uname,

cli/src/command/extract.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use crate::{
99
command::{
1010
Command, ask_password,
1111
core::{
12-
AclStrategy, KeepOptions, OwnerOptions, PathFilter, PathTransformers, PathnameEditor,
13-
PermissionStrategy, TimestampStrategy, XattrStrategy, collect_split_archives,
12+
AclStrategy, FflagsStrategy, KeepOptions, OwnerOptions, PathFilter, PathTransformers,
13+
PathnameEditor, PermissionStrategy, TimestampStrategy, XattrStrategy,
14+
collect_split_archives,
1415
path_lock::PathLocks,
1516
re::{bsd::SubstitutionRule, gnu::TransformRule},
1617
read_paths, run_process_archive,
@@ -280,6 +281,7 @@ fn extract_archive(args: ExtractCommand) -> anyhow::Result<()> {
280281
),
281282
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
282283
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
284+
fflags_strategy: FflagsStrategy::Never,
283285
};
284286
let owner_options = OwnerOptions::new(
285287
args.uname,
@@ -754,6 +756,29 @@ where
754756
if let AclStrategy::Always = keep_options.acl_strategy {
755757
log::warn!("Please enable `acl` feature and rebuild and install pna.");
756758
}
759+
if let FflagsStrategy::Always = keep_options.fflags_strategy {
760+
let mut flags = Vec::new();
761+
for chunk in item.extra_chunks() {
762+
if chunk.ty() == crate::chunk::ffLg {
763+
if let Ok(flag) = std::str::from_utf8(chunk.data()) {
764+
flags.push(flag.to_string());
765+
}
766+
}
767+
}
768+
if !flags.is_empty() {
769+
match utils::fs::set_flags(path, &flags) {
770+
Ok(()) => {}
771+
Err(e) if e.kind() == std::io::ErrorKind::Unsupported => {
772+
log::warn!(
773+
"File flags are not supported on filesystem for '{}': {}",
774+
path.display(),
775+
e
776+
);
777+
}
778+
Err(e) => return Err(e),
779+
}
780+
}
781+
}
757782
Ok(())
758783
}
759784

cli/src/command/stdio.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use crate::{
88
append::{open_archive_then_seek_to_end, run_append_archive},
99
ask_password, check_password,
1010
core::{
11-
AclStrategy, CreateOptions, KeepOptions, OwnerOptions, PathFilter, PathTransformers,
12-
PathnameEditor, PermissionStrategy, TimeFilterResolver, TimeOptions, TimestampStrategy,
13-
XattrStrategy, collect_items, collect_split_archives, entry_option,
11+
AclStrategy, CreateOptions, FflagsStrategy, KeepOptions, OwnerOptions, PathFilter,
12+
PathTransformers, PathnameEditor, PermissionStrategy, TimeFilterResolver, TimeOptions,
13+
TimestampStrategy, XattrStrategy, collect_items, collect_split_archives, entry_option,
1414
path_lock::PathLocks,
1515
re::{bsd::SubstitutionRule, gnu::TransformRule},
1616
read_paths,
@@ -73,6 +73,7 @@ use std::{env, io, path::PathBuf, sync::Arc, time::SystemTime};
7373
group(ArgGroup::new("ctime-newer-than-source").args(["newer_ctime", "newer_ctime_than"])),
7474
group(ArgGroup::new("mtime-older-than-source").args(["older_mtime", "older_mtime_than"])),
7575
group(ArgGroup::new("mtime-newer-than-source").args(["newer_mtime", "newer_mtime_than"])),
76+
group(ArgGroup::new("keep-fflags-flag").args(["keep_fflags", "no_keep_fflags"])),
7677
)]
7778
#[cfg_attr(windows, command(
7879
group(ArgGroup::new("windows-unstable-keep-permission").args(["keep_permission", "no_keep_permission"]).requires("unstable")),
@@ -190,6 +191,18 @@ pub(crate) struct StdioCommand {
190191
help = "Do not archive acl of files. This is the inverse option of --keep-acl (unstable)"
191192
)]
192193
no_keep_acl: bool,
194+
#[arg(
195+
long,
196+
visible_aliases = ["preserve-fflags", "fflags"],
197+
help = "Archiving the file flags of the files (unstable)"
198+
)]
199+
keep_fflags: bool,
200+
#[arg(
201+
long,
202+
visible_aliases = ["no-preserve-fflags", "no-fflags"],
203+
help = "Do not archive file flags of files. This is the inverse option of --keep-fflags (unstable)"
204+
)]
205+
no_keep_fflags: bool,
193206
#[arg(long, help = "Solid mode archive")]
194207
pub(crate) solid: bool,
195208
#[command(flatten)]
@@ -526,6 +539,7 @@ fn run_create_archive(args: StdioCommand) -> anyhow::Result<()> {
526539
),
527540
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
528541
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
542+
fflags_strategy: FflagsStrategy::from_flags(args.keep_fflags, args.no_keep_fflags),
529543
};
530544
let owner_options = OwnerOptions::new(
531545
args.uname,
@@ -606,6 +620,7 @@ fn run_extract_archive(args: StdioCommand) -> anyhow::Result<()> {
606620
),
607621
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
608622
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
623+
fflags_strategy: FflagsStrategy::from_flags(args.keep_fflags, args.no_keep_fflags),
609624
},
610625
owner_options: OwnerOptions::new(
611626
args.uname,
@@ -736,6 +751,7 @@ fn run_append(args: StdioCommand) -> anyhow::Result<()> {
736751
),
737752
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
738753
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
754+
fflags_strategy: FflagsStrategy::from_flags(args.keep_fflags, args.no_keep_fflags),
739755
};
740756
let owner_options = OwnerOptions::new(
741757
args.uname,

cli/src/command/update.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ use crate::{
1010
command::{
1111
Command, ask_password, check_password,
1212
core::{
13-
AclStrategy, CreateOptions, KeepOptions, OwnerOptions, PathFilter, PathTransformers,
14-
PathnameEditor, PermissionStrategy, TimeFilterResolver, TimeOptions, TimestampStrategy,
15-
TransformStrategy, TransformStrategyKeepSolid, TransformStrategyUnSolid, XattrStrategy,
16-
collect_items, collect_split_archives, create_entry, entry_option,
13+
AclStrategy, CreateOptions, FflagsStrategy, KeepOptions, OwnerOptions, PathFilter,
14+
PathTransformers, PathnameEditor, PermissionStrategy, TimeFilterResolver, TimeOptions,
15+
TimestampStrategy, TransformStrategy, TransformStrategyKeepSolid,
16+
TransformStrategyUnSolid, XattrStrategy, collect_items, collect_split_archives,
17+
create_entry, entry_option,
1718
re::{bsd::SubstitutionRule, gnu::TransformRule},
1819
read_paths, read_paths_stdin,
1920
},
@@ -348,6 +349,7 @@ fn update_archive<Strategy: TransformStrategy>(args: UpdateCommand) -> anyhow::R
348349
),
349350
xattr_strategy: XattrStrategy::from_flags(args.keep_xattr, args.no_keep_xattr),
350351
acl_strategy: AclStrategy::from_flags(args.keep_acl, args.no_keep_acl),
352+
fflags_strategy: FflagsStrategy::Never,
351353
};
352354
let owner_options = OwnerOptions::new(
353355
args.uname,

cli/src/utils/fs.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,50 @@ pub(crate) fn lchown<P: AsRef<Path>>(
6666
inner(path.as_ref(), owner, group)
6767
}
6868

69+
pub(crate) fn get_flags<P: AsRef<Path>>(path: P) -> io::Result<Vec<String>> {
70+
#[cfg(any(
71+
target_os = "macos",
72+
target_os = "linux",
73+
target_os = "android",
74+
target_os = "freebsd"
75+
))]
76+
fn inner(path: &Path) -> io::Result<Vec<String>> {
77+
crate::utils::os::unix::fs::get_flags(path)
78+
}
79+
#[cfg(not(any(
80+
target_os = "macos",
81+
target_os = "linux",
82+
target_os = "android",
83+
target_os = "freebsd"
84+
)))]
85+
fn inner(_path: &Path) -> io::Result<Vec<String>> {
86+
Ok(Vec::new())
87+
}
88+
inner(path.as_ref())
89+
}
90+
91+
pub(crate) fn set_flags<P: AsRef<Path>>(path: P, flags: &[String]) -> io::Result<()> {
92+
#[cfg(any(
93+
target_os = "macos",
94+
target_os = "linux",
95+
target_os = "android",
96+
target_os = "freebsd"
97+
))]
98+
fn inner(path: &Path, flags: &[String]) -> io::Result<()> {
99+
crate::utils::os::unix::fs::set_flags(path, flags)
100+
}
101+
#[cfg(not(any(
102+
target_os = "macos",
103+
target_os = "linux",
104+
target_os = "android",
105+
target_os = "freebsd"
106+
)))]
107+
fn inner(_path: &Path, _flags: &[String]) -> io::Result<()> {
108+
Ok(())
109+
}
110+
inner(path.as_ref(), flags)
111+
}
112+
69113
#[inline]
70114
pub(crate) fn file_create(path: impl AsRef<Path>, overwrite: bool) -> io::Result<fs::File> {
71115
if overwrite {

cli/src/utils/fs/nodump.rs

Lines changed: 8 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,9 @@
1-
#[cfg(any(target_os = "linux", target_os = "android"))]
2-
mod platform {
3-
use nix::{
4-
fcntl::{OFlag, open},
5-
ioctl_read_bad,
6-
sys::stat::Mode,
7-
};
8-
use std::os::fd::AsRawFd;
9-
use std::{io, path::Path};
10-
11-
const FS_NODUMP_FL: libc::c_int = 0x00000040;
12-
13-
ioctl_read_bad!(fs_ioc_getflags, libc::FS_IOC_GETFLAGS, libc::c_int);
14-
15-
pub(crate) fn is_nodump(path: &Path) -> io::Result<bool> {
16-
let fd = open(path, OFlag::O_RDONLY | OFlag::O_NOFOLLOW, Mode::empty())?;
17-
let mut flags: libc::c_int = 0;
18-
unsafe { fs_ioc_getflags(fd.as_raw_fd(), &mut flags) }?;
19-
Ok((flags & FS_NODUMP_FL) != 0)
20-
}
21-
}
22-
23-
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
24-
mod platform {
25-
use nix::sys::stat::lstat;
26-
use std::{io, path::Path};
27-
28-
pub(crate) fn is_nodump(path: &Path) -> io::Result<bool> {
29-
let stat = lstat(path)?;
30-
Ok((stat.st_flags & libc::UF_NODUMP as libc::c_uint) != 0)
31-
}
1+
use crate::utils::fs::get_flags;
2+
use std::{io, path::Path};
3+
4+
/// Check if the file has the nodump flag set.
5+
/// This is used to skip files during backup operations.
6+
pub(crate) fn is_nodump(path: &Path) -> io::Result<bool> {
7+
let flags = get_flags(path)?;
8+
Ok(flags.iter().any(|f| f == "nodump"))
329
}
33-
34-
#[cfg(not(any(
35-
target_os = "linux",
36-
target_os = "android",
37-
target_os = "macos",
38-
target_os = "freebsd"
39-
)))]
40-
mod platform {
41-
use std::{io, path::Path};
42-
43-
pub(crate) fn is_nodump(_path: &Path) -> io::Result<bool> {
44-
Ok(false)
45-
}
46-
}
47-
48-
pub(crate) use platform::is_nodump;

0 commit comments

Comments
 (0)