1#![cfg(unix)]
43
44use nix::unistd::chown;
45use nix::unistd::{Gid, Uid, Group as NixGroup, User};
46use std::path::Path;
47use std::fmt::{self, Display};
48use std::error::Error;
49use std::convert::{TryFrom, TryInto, Infallible};
50use std::fs;
51use std::io;
52use std::os::unix::fs::MetadataExt;
53
54#[derive(Debug)]
56pub enum FileOwnerError {
57 IoError(io::Error),
58 NixError(nix::Error),
59 UserNotFound(String),
60 GroupNotFound(String),
61}
62
63impl Display for FileOwnerError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 FileOwnerError::IoError(_) => write!(f, "I/O error"),
67 FileOwnerError::NixError(_) => write!(f, "*nix error"),
68 FileOwnerError::UserNotFound(name) => write!(f, "user name {:?} not found", name),
69 FileOwnerError::GroupNotFound(name) => write!(f, "group name {:?} not found", name),
70 }
71 }
72}
73
74impl Error for FileOwnerError {
75 fn source(&self) -> Option<&(dyn Error + 'static)> {
76 match self {
77 FileOwnerError::IoError(err) => Some(err),
78 FileOwnerError::NixError(err) => Some(err),
79 FileOwnerError::UserNotFound(_) => None,
80 FileOwnerError::GroupNotFound(_) => None,
81 }
82 }
83}
84
85impl From<io::Error> for FileOwnerError {
86 fn from(err: io::Error) -> FileOwnerError {
87 FileOwnerError::IoError(err)
88 }
89}
90
91impl From<nix::Error> for FileOwnerError {
92 fn from(err: nix::Error) -> FileOwnerError {
93 FileOwnerError::NixError(err)
94 }
95}
96
97impl From<Infallible> for FileOwnerError {
98 fn from(_err: Infallible) -> FileOwnerError {
99 unreachable!()
100 }
101}
102
103#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
105pub struct Owner(Uid);
106
107impl Owner {
108 pub fn from_uid(uid: u32) -> Owner {
110 Owner(Uid::from_raw(uid.try_into().unwrap()))
111 }
112
113 pub fn from_name(user: &str) -> Result<Owner, FileOwnerError> {
115 Ok(Owner(User::from_name(user)?.ok_or_else(|| FileOwnerError::UserNotFound(user.to_owned()))?.uid))
116 }
117
118 pub fn id(&self) -> u32 {
120 self.0.as_raw().try_into().unwrap()
121 }
122
123 pub fn name(&self) -> Result<Option<String>, FileOwnerError> {
125 Ok(User::from_uid(self.0)?.map(|u| u.name))
126 }
127}
128
129impl From<u32> for Owner {
130 fn from(uid: u32) -> Owner {
131 Owner::from_uid(uid)
132 }
133}
134
135impl<'s> TryFrom<&'s str> for Owner {
136 type Error = FileOwnerError;
137
138 fn try_from(name: &'s str) -> Result<Owner, Self::Error> {
139 Owner::from_name(name)
140 }
141}
142
143impl Display for Owner {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 if let Some(name) = self.name().ok().flatten() {
146 write!(f, "{}", name)
147 } else {
148 write!(f, "{}", self.id())
149 }
150 }
151}
152
153#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
155pub struct Group(Gid);
156
157impl Group {
158 pub fn from_gid(gid: u32) -> Group {
160 Group(Gid::from_raw(gid.try_into().unwrap()))
161 }
162
163 pub fn from_name(group: &str) -> Result<Group, FileOwnerError> {
165 Ok(Group(NixGroup::from_name(group)?.ok_or_else(|| FileOwnerError::GroupNotFound(group.to_owned()))?.gid))
166 }
167
168 pub fn id(&self) -> u32 {
170 self.0.as_raw().try_into().unwrap()
171 }
172
173 pub fn name(&self) -> Result<Option<String>, FileOwnerError> {
175 Ok(NixGroup::from_gid(self.0)?.map(|u| u.name))
176 }
177}
178
179impl From<u32> for Group {
180 fn from(gid: u32) -> Group {
181 Group::from_gid(gid)
182 }
183}
184
185impl<'s> TryFrom<&'s str> for Group {
186 type Error = FileOwnerError;
187
188 fn try_from(name: &'s str) -> Result<Group, Self::Error> {
189 Group::from_name(name)
190 }
191}
192
193impl Display for Group {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 if let Some(name) = self.name().ok().flatten() {
196 write!(f, "{}", name)
197 } else {
198 write!(f, "{}", self.id())
199 }
200 }
201}
202
203pub fn set_owner<E: Into<FileOwnerError>>(path: impl AsRef<Path>, owner: impl TryInto<Owner, Error = E>) -> Result<(), FileOwnerError> {
205 Ok(chown(path.as_ref(), Some(owner.try_into().map_err(Into::into)?.0), None)?)
206}
207
208pub fn set_group<E: Into<FileOwnerError>>(path: impl AsRef<Path>, group: impl TryInto<Group, Error = E>) -> Result<(), FileOwnerError> {
210 Ok(chown(path.as_ref(), None, Some(group.try_into().map_err(Into::into)?.0))?)
211}
212
213pub fn set_owner_group<E1: Into<FileOwnerError>, E2: Into<FileOwnerError>>(path: impl AsRef<Path>, owner: impl TryInto<Owner, Error = E1>, group: impl TryInto<Group, Error = E2>) -> Result<(), FileOwnerError> {
215 Ok(chown(path.as_ref(), Some(owner.try_into().map_err(Into::into)?.0), Some(group.try_into().map_err(Into::into)?.0))?)
216}
217
218pub fn owner(path: impl AsRef<Path>) -> Result<Owner, FileOwnerError> {
220 Ok(Owner::from_uid(fs::metadata(path)?.uid().try_into().unwrap()))
221}
222
223pub fn group(path: impl AsRef<Path>) -> Result<Group, FileOwnerError> {
225 Ok(Group::from_gid(fs::metadata(path)?.gid().try_into().unwrap()))
226}
227
228pub fn owner_group(path: impl AsRef<Path>) -> Result<(Owner, Group), FileOwnerError> {
230 let meta = fs::metadata(path)?;
231 Ok((Owner::from_uid(meta.uid().try_into().unwrap()), Group::from_gid(meta.gid().try_into().unwrap())))
232}
233
234pub trait PathExt {
236 fn set_owner<E: Into<FileOwnerError>>(&self, owner: impl TryInto<Owner, Error = E>) -> Result<(), FileOwnerError>;
238
239 fn set_group<E: Into<FileOwnerError>>(&self, group: impl TryInto<Group, Error = E>) -> Result<(), FileOwnerError>;
241
242 fn set_owner_group<E1: Into<FileOwnerError>, E2: Into<FileOwnerError>>(&self, owner: impl TryInto<Owner, Error = E1>, group: impl TryInto<Group, Error = E2>) -> Result<(), FileOwnerError>;
244
245 fn owner(&self) -> Result<Owner, FileOwnerError>;
247
248 fn group(&self) -> Result<Group, FileOwnerError>;
250
251 fn owner_group(&self) -> Result<(Owner, Group), FileOwnerError>;
253}
254
255impl<T: AsRef<Path>> PathExt for T {
256 fn set_owner<E: Into<FileOwnerError>>(&self, owner: impl TryInto<Owner, Error = E>) -> Result<(), FileOwnerError> {
257 set_owner(self, owner)
258 }
259
260 fn set_group<E: Into<FileOwnerError>>(&self, group: impl TryInto<Group, Error = E>) -> Result<(), FileOwnerError> {
261 set_group(self, group)
262 }
263
264 fn set_owner_group<E1: Into<FileOwnerError>, E2: Into<FileOwnerError>>(&self, owner: impl TryInto<Owner, Error = E1>, group: impl TryInto<Group, Error = E2>) -> Result<(), FileOwnerError> {
265 set_owner_group(self, owner, group)
266 }
267
268 fn owner(&self) -> Result<Owner, FileOwnerError> {
269 owner(self)
270 }
271
272 fn group(&self) -> Result<Group, FileOwnerError> {
273 group(self)
274 }
275
276 fn owner_group(&self) -> Result<(Owner, Group), FileOwnerError> {
277 owner_group(self)
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_display() {
287 let nobody_id = Owner::from_name("nobody").unwrap().id();
288 let nogroup_id = Group::from_name("nogroup").unwrap().id();
289
290 assert_eq!(&Owner::from_uid(nobody_id).to_string(), "nobody");
291 assert_eq!(&Group::from_gid(nogroup_id).to_string(), "nogroup");
292
293 assert_eq!(&Owner::from_uid(321321).to_string(), "321321");
294 assert_eq!(&Group::from_gid(321321).to_string(), "321321");
295 }
296
297 #[test]
298 #[ignore]
299 fn test_set_get() {
300 let nobody_id = Owner::from_name("nobody").unwrap().id();
301 let nogroup_id = Group::from_name("nogroup").unwrap().id();
302
303 let file1 = tempfile::NamedTempFile::new().unwrap();
304 let file_path1 = file1.path();
305 let file2 = tempfile::NamedTempFile::new().unwrap();
306 let file_path2 = file2.path();
307
308 set_owner(file_path1, "nobody").unwrap();
309 assert_eq!(owner(file_path1).unwrap().name().unwrap().as_deref(), Some("nobody"));
310 set_owner(file_path2, nobody_id).unwrap();
311 assert_eq!(owner(file_path2).unwrap().name().unwrap().as_deref(), Some("nobody"));
312
313 set_group(file_path1, "nogroup").unwrap();
314 assert_eq!(group(file_path1).unwrap().name().unwrap().as_deref(), Some("nogroup"));
315 set_group(file_path2, nogroup_id).unwrap();
316 assert_eq!(group(file_path2).unwrap().name().unwrap().as_deref(), Some("nogroup"));
317 }
318
319 #[test]
320 #[ignore]
321 fn test_set_get_all() {
322 let nobody_id = Owner::from_name("nobody").unwrap().id();
323 let nogroup_id = Group::from_name("nogroup").unwrap().id();
324
325 let file1 = tempfile::NamedTempFile::new().unwrap();
326 let file_path1 = file1.path();
327 let file2 = tempfile::NamedTempFile::new().unwrap();
328 let file_path2 = file2.path();
329 let file3 = tempfile::NamedTempFile::new().unwrap();
330 let file_path3 = file3.path();
331 let file4 = tempfile::NamedTempFile::new().unwrap();
332 let file_path4 = file4.path();
333
334 set_owner_group(file_path1, "nobody", "nogroup").unwrap();
335 assert_eq!(owner(file_path1).unwrap().name().unwrap().as_deref(), Some("nobody"));
336 assert_eq!(group(file_path1).unwrap().name().unwrap().as_deref(), Some("nogroup"));
337 set_owner_group(file_path2, nobody_id, nogroup_id).unwrap();
338 assert_eq!(owner(file_path2).unwrap().name().unwrap().as_deref(), Some("nobody"));
339 assert_eq!(group(file_path2).unwrap().name().unwrap().as_deref(), Some("nogroup"));
340
341 set_owner_group(file_path3, nobody_id, "nogroup").unwrap();
342 assert_eq!(owner(file_path3).unwrap().name().unwrap().as_deref(), Some("nobody"));
343 assert_eq!(group(file_path3).unwrap().name().unwrap().as_deref(), Some("nogroup"));
344 set_owner_group(file_path4, "nobody", nogroup_id).unwrap();
345 assert_eq!(owner(file_path4).unwrap().name().unwrap().as_deref(), Some("nobody"));
346 assert_eq!(group(file_path4).unwrap().name().unwrap().as_deref(), Some("nogroup"));
347 }
348
349 #[test]
350 #[ignore]
351 fn test_get_all() {
352 let file = tempfile::NamedTempFile::new().unwrap();
353 let file_path = file.path();
354
355 set_owner_group(file_path, "nobody", "nogroup").unwrap();
356
357 let (o, g) = owner_group(file_path).unwrap();
358 assert_eq!(o.name().unwrap().as_deref(), Some("nobody"));
359 assert_eq!(g.name().unwrap().as_deref(), Some("nogroup"));
360 }
361
362 #[test]
363 #[ignore]
364 fn test_ext_traits() {
365 let nobody_id = Owner::from_name("nobody").unwrap().id();
366 let nogroup_id = Group::from_name("nogroup").unwrap().id();
367
368 let file = tempfile::NamedTempFile::new().unwrap();
369 let file_path = file.path();
370
371 file_path.set_owner("nobody").unwrap();
372 file_path.set_group("nogroup").unwrap();
373
374 assert_eq!(file_path.owner().unwrap().name().unwrap().as_deref(), Some("nobody"));
375 assert_eq!(file_path.group().unwrap().name().unwrap().as_deref(), Some("nogroup"));
376
377 file_path.set_owner_group("nobody", "nogroup").unwrap();
378
379 let (o, g) = file_path.owner_group().unwrap();
380 assert_eq!(o.name().unwrap().as_deref(), Some("nobody"));
381 assert_eq!(g.name().unwrap().as_deref(), Some("nogroup"));
382
383 assert_eq!(o.id(), nobody_id);
384 assert_eq!(g.id(), nogroup_id);
385 }
386}