aria_primitives/
private_fs.rs1use std::fs::{self, DirBuilder, File, Metadata, Permissions};
7use std::io;
8use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
9use std::path::Path;
10
11const PRIVATE_DIRECTORY_MODE: u32 = 0o700;
12pub const PRIVATE_FILE_MODE: u32 = 0o600;
13
14pub fn prepare_directory(path: &Path) -> Result<(), PrivateDirectoryError> {
15 match DirBuilder::new().mode(PRIVATE_DIRECTORY_MODE).create(path) {
16 Ok(()) => {}
17 Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
18 Err(source) => return Err(PrivateDirectoryError::Create(source)),
19 }
20
21 let metadata = fs::symlink_metadata(path).map_err(PrivateDirectoryError::Inspect)?;
22 if !metadata.file_type().is_dir() {
23 return Err(PrivateDirectoryError::NotDirectory);
24 }
25
26 let expected_uid = rustix::process::getuid().as_raw();
27 if metadata.uid() != expected_uid {
28 return Err(PrivateDirectoryError::WrongOwner {
29 expected_uid,
30 actual_uid: metadata.uid(),
31 });
32 }
33
34 if metadata.permissions().mode() & 0o777 != PRIVATE_DIRECTORY_MODE {
35 fs::set_permissions(path, Permissions::from_mode(PRIVATE_DIRECTORY_MODE))
36 .map_err(PrivateDirectoryError::SetPermissions)?;
37 }
38 Ok(())
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum PrivateDirectoryError {
43 #[error("failed to create the directory")]
44 Create(#[source] io::Error),
45
46 #[error("failed to inspect the directory")]
47 Inspect(#[source] io::Error),
48
49 #[error("the path is not a directory")]
50 NotDirectory,
51
52 #[error("the directory belongs to uid {actual_uid}, expected uid {expected_uid}")]
53 WrongOwner { expected_uid: u32, actual_uid: u32 },
54
55 #[error("failed to secure the directory permissions")]
56 SetPermissions(#[source] io::Error),
57}
58
59pub fn prepare_file(file: &File) -> Result<Metadata, PrivateFileError> {
61 let metadata = own_regular_file(file)?;
62 if metadata.permissions().mode() & 0o777 != PRIVATE_FILE_MODE {
63 file.set_permissions(Permissions::from_mode(PRIVATE_FILE_MODE))
64 .map_err(PrivateFileError::SetPermissions)?;
65 }
66 Ok(metadata)
67}
68
69pub fn verify_file(file: &File) -> Result<Metadata, PrivateFileError> {
71 let metadata = own_regular_file(file)?;
72 let actual_mode = metadata.permissions().mode() & 0o777;
73 if actual_mode != PRIVATE_FILE_MODE {
74 return Err(PrivateFileError::NotPrivate {
75 expected_mode: PRIVATE_FILE_MODE,
76 actual_mode,
77 });
78 }
79 Ok(metadata)
80}
81
82fn own_regular_file(file: &File) -> Result<Metadata, PrivateFileError> {
83 let metadata = file.metadata().map_err(PrivateFileError::Inspect)?;
84 if !metadata.is_file() {
85 return Err(PrivateFileError::NotRegularFile);
86 }
87
88 let expected_uid = rustix::process::getuid().as_raw();
89 if metadata.uid() != expected_uid {
90 return Err(PrivateFileError::WrongOwner {
91 expected_uid,
92 actual_uid: metadata.uid(),
93 });
94 }
95 Ok(metadata)
96}
97
98#[derive(Debug, thiserror::Error)]
99pub enum PrivateFileError {
100 #[error("failed to inspect the file")]
101 Inspect(#[source] io::Error),
102
103 #[error("the path is not a regular file")]
104 NotRegularFile,
105
106 #[error("the file belongs to uid {actual_uid}, expected uid {expected_uid}")]
107 WrongOwner { expected_uid: u32, actual_uid: u32 },
108
109 #[error("the file has mode {actual_mode:o}; expected {expected_mode:o}")]
110 NotPrivate {
111 expected_mode: u32,
112 actual_mode: u32,
113 },
114
115 #[error("failed to secure the file permissions")]
116 SetPermissions(#[source] io::Error),
117}