1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::os::unix::fs::MetadataExt;
4use std::os::unix::prelude::PermissionsExt;
5use tracing::instrument;
6
7pub trait Metadata {
8 fn uid(&self) -> u32;
9 fn gid(&self) -> u32;
10 fn atime(&self) -> i64;
11 fn atime_nsec(&self) -> i64;
12 fn mtime(&self) -> i64;
13 fn mtime_nsec(&self) -> i64;
14 fn permissions(&self) -> std::fs::Permissions;
15 fn ctime(&self) -> i64 {
18 0
19 }
20 fn ctime_nsec(&self) -> i64 {
21 0
22 }
23 fn size(&self) -> u64 {
26 0
27 }
28}
29
30impl Metadata for std::fs::Metadata {
31 fn uid(&self) -> u32 {
32 MetadataExt::uid(self)
33 }
34 fn gid(&self) -> u32 {
35 MetadataExt::gid(self)
36 }
37 fn atime(&self) -> i64 {
38 MetadataExt::atime(self)
39 }
40 fn atime_nsec(&self) -> i64 {
41 MetadataExt::atime_nsec(self)
42 }
43 fn mtime(&self) -> i64 {
44 MetadataExt::mtime(self)
45 }
46 fn mtime_nsec(&self) -> i64 {
47 MetadataExt::mtime_nsec(self)
48 }
49 fn permissions(&self) -> std::fs::Permissions {
50 self.permissions()
51 }
52 fn ctime(&self) -> i64 {
53 MetadataExt::ctime(self)
54 }
55 fn ctime_nsec(&self) -> i64 {
56 MetadataExt::ctime_nsec(self)
57 }
58 fn size(&self) -> u64 {
59 self.len()
60 }
61}
62
63#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
64pub struct UserAndTimeSettings {
65 pub uid: bool,
66 pub gid: bool,
67 pub time: bool,
68}
69
70impl UserAndTimeSettings {
71 #[must_use]
72 pub fn any(&self) -> bool {
73 self.uid || self.gid || self.time
74 }
75}
76
77pub type ModeMask = u32;
78
79#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
80pub struct FileSettings {
81 pub user_and_time: UserAndTimeSettings,
82 pub mode_mask: ModeMask,
83}
84
85impl Default for FileSettings {
86 fn default() -> Self {
87 Self {
88 user_and_time: UserAndTimeSettings::default(),
89 mode_mask: 0o0777, }
91 }
92}
93
94#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
95pub struct DirSettings {
96 pub user_and_time: UserAndTimeSettings,
97 pub mode_mask: ModeMask,
98}
99
100impl Default for DirSettings {
101 fn default() -> Self {
102 Self {
103 user_and_time: UserAndTimeSettings::default(),
104 mode_mask: 0o0777,
105 }
106 }
107}
108
109#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
110pub struct SymlinkSettings {
111 pub user_and_time: UserAndTimeSettings,
112}
113
114impl SymlinkSettings {
115 #[must_use]
116 pub fn any(&self) -> bool {
117 self.user_and_time.any()
118 }
119}
120
121#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)]
122pub struct Settings {
123 pub file: FileSettings,
124 pub dir: DirSettings,
125 pub symlink: SymlinkSettings,
126}
127
128#[instrument]
129async fn set_owner<Meta: Metadata + std::fmt::Debug>(
130 settings: &UserAndTimeSettings,
131 path: &std::path::Path,
132 metadata: &Meta,
133) -> Result<()> {
134 if !settings.uid && !settings.gid {
135 return Ok(());
136 }
137 let settings = settings.to_owned();
138 let dst = path.to_owned();
139 let uid = metadata.uid();
140 let gid = metadata.gid();
141 crate::walk::run_metadata_probed(
142 congestion::Side::Destination,
143 congestion::MetadataOp::Chmod,
144 async {
145 tokio::task::spawn_blocking(move || -> Result<()> {
146 tracing::debug!("setting uid and gid");
147 let uid_val = if settings.uid { Some(uid.into()) } else { None };
148 let gid_val = if settings.gid { Some(gid.into()) } else { None };
149 nix::unistd::fchownat(
150 nix::fcntl::AT_FDCWD,
151 &dst,
152 uid_val,
153 gid_val,
154 nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW,
155 )
156 .with_context(|| {
157 format!(
158 "cannot set {:?} owner to {:?} and/or group id to {:?}",
159 &dst, &uid_val, &gid_val
160 )
161 })?;
162 Ok(())
163 })
164 .await?
165 },
166 )
167 .await
168}
169
170#[instrument]
171async fn set_time<Meta: Metadata + std::fmt::Debug>(
172 settings: &UserAndTimeSettings,
173 path: &std::path::Path,
174 metadata: &Meta,
175) -> Result<()> {
176 if !settings.time {
177 return Ok(());
178 }
179 let dst = path.to_owned();
180 let atime = metadata.atime();
181 let atime_nsec = metadata.atime_nsec();
182 let mtime = metadata.mtime();
183 let mtime_nsec = metadata.mtime_nsec();
184 crate::walk::run_metadata_probed(
185 congestion::Side::Destination,
186 congestion::MetadataOp::Chmod,
187 async {
188 tokio::task::spawn_blocking(move || -> Result<()> {
189 tracing::debug!("setting timestamps");
190 let atime_spec = nix::sys::time::TimeSpec::new(atime, atime_nsec);
191 let mtime_spec = nix::sys::time::TimeSpec::new(mtime, mtime_nsec);
192 nix::sys::stat::utimensat(
193 nix::fcntl::AT_FDCWD,
194 &dst,
195 &atime_spec,
196 &mtime_spec,
197 nix::sys::stat::UtimensatFlags::NoFollowSymlink,
198 )
199 .with_context(|| format!("failed setting timestamps for {:?}", &dst))?;
200 Ok(())
201 })
202 .await?
203 },
204 )
205 .await
206}
207
208pub async fn set_file_metadata<Meta: Metadata + std::fmt::Debug>(
209 settings: &Settings,
210 metadata: &Meta,
211 path: &std::path::Path,
212) -> Result<()> {
213 let permissions = if settings.file.mode_mask == 0o7777 {
214 metadata.permissions()
216 } else {
217 std::fs::Permissions::from_mode(metadata.permissions().mode() & settings.file.mode_mask)
218 };
219 set_owner(&settings.file.user_and_time, path, metadata).await?;
230 let file = crate::walk::run_metadata_probed(
231 congestion::Side::Destination,
232 congestion::MetadataOp::Stat,
233 tokio::fs::File::open(path),
234 )
235 .await?;
236 crate::walk::run_metadata_probed(
237 congestion::Side::Destination,
238 congestion::MetadataOp::Chmod,
239 file.set_permissions(permissions.clone()),
240 )
241 .await
242 .with_context(|| format!("cannot set {:?} permissions to {:?}", &path, &permissions))?;
243 drop(file);
244 set_time(&settings.file.user_and_time, path, metadata).await?;
245 Ok(())
246}
247
248pub async fn set_dir_metadata<Meta: Metadata + std::fmt::Debug>(
249 settings: &Settings,
250 metadata: &Meta,
251 path: &std::path::Path,
252) -> Result<()> {
253 let permissions = if settings.dir.mode_mask == 0o7777 {
254 metadata.permissions()
256 } else {
257 std::fs::Permissions::from_mode(metadata.permissions().mode() & settings.dir.mode_mask)
258 };
259 set_owner(&settings.dir.user_and_time, path, metadata).await?;
262 crate::walk::run_metadata_probed(
263 congestion::Side::Destination,
264 congestion::MetadataOp::Chmod,
265 tokio::fs::set_permissions(path, permissions.clone()),
266 )
267 .await
268 .with_context(|| format!("cannot set {:?} permissions to {:?}", &path, &permissions))?;
269 set_time(&settings.dir.user_and_time, path, metadata).await?;
270 Ok(())
271}
272
273pub async fn set_symlink_metadata<Meta: Metadata + std::fmt::Debug>(
274 settings: &Settings,
275 metadata: &Meta,
276 path: &std::path::Path,
277) -> Result<()> {
278 set_owner(&settings.symlink.user_and_time, path, metadata).await?;
280 set_time(&settings.symlink.user_and_time, path, metadata).await?;
281 Ok(())
282}
283
284#[must_use]
285pub fn preserve_all() -> Settings {
286 let user_and_time = UserAndTimeSettings {
287 uid: true,
288 gid: true,
289 time: true,
290 };
291
292 Settings {
293 file: FileSettings {
294 user_and_time,
295 mode_mask: 0o7777,
296 },
297 dir: DirSettings {
298 user_and_time,
299 mode_mask: 0o7777,
300 },
301 symlink: SymlinkSettings { user_and_time },
302 }
303}
304
305#[must_use]
306pub fn preserve_none() -> Settings {
307 Settings::default()
308}