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_and_time<Meta: Metadata + std::fmt::Debug>(
130 settings: &UserAndTimeSettings,
131 path: &std::path::Path,
132 metadata: &Meta,
133) -> Result<()> {
134 let settings = settings.to_owned();
135 let dst = path.to_owned();
136 let uid = metadata.uid();
137 let gid = metadata.gid();
138 let atime = metadata.atime();
139 let atime_nsec = metadata.atime_nsec();
140 let mtime = metadata.mtime();
141 let mtime_nsec = metadata.mtime_nsec();
142 tokio::task::spawn_blocking(move || -> Result<()> {
143 if settings.uid || settings.gid {
144 tracing::debug!("setting uid ang gid");
146 let uid_val = if settings.uid { Some(uid.into()) } else { None };
147 let gid_val = if settings.gid { Some(gid.into()) } else { None };
148 nix::unistd::fchownat(
149 None,
150 &dst,
151 uid_val,
152 gid_val,
153 nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW,
154 )
155 .with_context(|| {
156 format!(
157 "cannot set {:?} owner to {:?} and/or group id to {:?}",
158 &dst, &uid_val, &gid_val
159 )
160 })?;
161 }
162 if settings.time {
164 tracing::debug!("setting timestamps");
165 let atime_spec = nix::sys::time::TimeSpec::new(atime, atime_nsec);
166 let mtime_spec = nix::sys::time::TimeSpec::new(mtime, mtime_nsec);
167 nix::sys::stat::utimensat(
168 None,
169 &dst,
170 &atime_spec,
171 &mtime_spec,
172 nix::sys::stat::UtimensatFlags::NoFollowSymlink,
173 )
174 .with_context(|| format!("failed setting timestamps for {:?}", &dst))?;
175 }
176 Ok(())
177 })
178 .await?
179}
180
181pub async fn set_file_metadata<Meta: Metadata + std::fmt::Debug>(
182 settings: &Settings,
183 metadata: &Meta,
184 path: &std::path::Path,
185) -> Result<()> {
186 let permissions = if settings.file.mode_mask == 0o7777 {
187 metadata.permissions()
189 } else {
190 std::fs::Permissions::from_mode(metadata.permissions().mode() & settings.file.mode_mask)
191 };
192 let file = tokio::fs::File::open(path).await?;
193 file.set_permissions(permissions.clone())
194 .await
195 .with_context(|| format!("cannot set {:?} permissions to {:?}", &path, &permissions))?;
196 drop(file);
198 set_owner_and_time(&settings.file.user_and_time, path, metadata).await?;
199 Ok(())
200}
201
202pub async fn set_dir_metadata<Meta: Metadata + std::fmt::Debug>(
203 settings: &Settings,
204 metadata: &Meta,
205 path: &std::path::Path,
206) -> Result<()> {
207 let permissions = if settings.dir.mode_mask == 0o7777 {
208 metadata.permissions()
210 } else {
211 std::fs::Permissions::from_mode(metadata.permissions().mode() & settings.dir.mode_mask)
212 };
213 tokio::fs::set_permissions(path, permissions.clone())
214 .await
215 .with_context(|| format!("cannot set {:?} permissions to {:?}", &path, &permissions))?;
216 set_owner_and_time(&settings.dir.user_and_time, path, metadata).await?;
217 Ok(())
218}
219
220pub async fn set_symlink_metadata<Meta: Metadata + std::fmt::Debug>(
221 settings: &Settings,
222 metadata: &Meta,
223 path: &std::path::Path,
224) -> Result<()> {
225 set_owner_and_time(&settings.file.user_and_time, path, metadata).await?;
227 Ok(())
228}
229
230#[must_use]
231pub fn preserve_all() -> Settings {
232 let user_and_time = UserAndTimeSettings {
233 uid: true,
234 gid: true,
235 time: true,
236 };
237
238 Settings {
239 file: FileSettings {
240 user_and_time,
241 mode_mask: 0o7777,
242 },
243 dir: DirSettings {
244 user_and_time,
245 mode_mask: 0o7777,
246 },
247 symlink: SymlinkSettings { user_and_time },
248 }
249}
250
251#[must_use]
252pub fn preserve_default() -> Settings {
253 Settings::default()
254}