xet_runtime/file_utils/
privilege_context.rs1use std::fs::File;
2#[cfg(unix)]
3use std::os::unix::fs::MetadataExt;
4use std::path::Path;
5
6#[cfg(unix)]
7use colored::Colorize;
8use lazy_static::lazy_static;
9use tracing::error;
10#[cfg(windows)]
11use winapi::um::{
12 processthreadsapi::GetCurrentProcess,
13 processthreadsapi::OpenProcessToken,
14 securitybaseapi::GetTokenInformation,
15 winnt::{HANDLE, TOKEN_ELEVATION, TOKEN_QUERY, TokenElevation},
16};
17
18#[cfg(test)]
19static mut WARNING_PRINTED: bool = false;
20
21fn is_elevated_impl() -> bool {
23 #[cfg(unix)]
26 {
27 unsafe { libc::geteuid() == 0 }
28 }
29
30 #[cfg(windows)]
31 {
32 let mut token: HANDLE = std::ptr::null_mut();
33 if unsafe { OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) } == 0 {
34 return false;
35 }
36
37 let mut elevation: TOKEN_ELEVATION = unsafe { std::mem::zeroed() };
38 let mut return_length = 0;
39 let success = unsafe {
40 GetTokenInformation(
41 token,
42 TokenElevation,
43 &mut elevation as *mut _ as *mut _,
44 std::mem::size_of::<TOKEN_ELEVATION>() as u32,
45 &mut return_length,
46 )
47 };
48
49 if success == 0 {
50 false
51 } else {
52 elevation.TokenIsElevated != 0
53 }
54 }
55
56 #[cfg(not(any(unix, windows)))]
57 {
58 false
60 }
61}
62
63lazy_static! {
64 static ref IS_ELEVATED: bool = is_elevated_impl();
65}
66
67pub fn is_elevated() -> bool {
68 *IS_ELEVATED
69}
70
71#[derive(Debug, Clone, Copy)]
84pub enum PrivilegedExecutionContext {
85 Regular,
86 Elevated,
87}
88
89impl PrivilegedExecutionContext {
90 pub fn current() -> PrivilegedExecutionContext {
91 match is_elevated() {
92 false => PrivilegedExecutionContext::Regular,
93 true => PrivilegedExecutionContext::Elevated,
94 }
95 }
96
97 pub fn is_elevated(&self) -> bool {
98 match self {
99 PrivilegedExecutionContext::Regular => false,
100 PrivilegedExecutionContext::Elevated => true,
101 }
102 }
103
104 pub fn create_dir_all(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
108 let path = std::env::current_dir()?.join(path);
110 let path = path.as_path();
111
112 let mut root = path;
114 while !root.exists() {
115 let Some(pparent) = root.parent() else {
116 return Err(std::io::Error::new(
117 std::io::ErrorKind::InvalidInput,
118 format!("Path {root:?} has no parent."),
119 ));
120 };
121
122 root = pparent;
123 }
124
125 std::fs::create_dir_all(path).inspect_err(|err| {
127 if err.kind() == std::io::ErrorKind::PermissionDenied {
128 permission_warning(root, true);
129 }
130 })?;
131
132 #[cfg(unix)]
136 if self.is_elevated() {
137 let root_meta = std::fs::metadata(root)?;
138 let mut path = path;
139 while path != root {
140 std::os::unix::fs::chown(path, Some(root_meta.uid()), Some(root_meta.gid()))?;
141 let Some(pparent) = path.parent() else {
142 return Err(std::io::Error::new(
143 std::io::ErrorKind::InvalidInput,
144 format!("Path {path:?} has no parent."),
145 ));
146 };
147 path = pparent;
148 }
149 }
150
151 Ok(())
152 }
153
154 pub fn create_file(&self, path: impl AsRef<Path>) -> std::io::Result<File> {
158 let path = std::env::current_dir()?.join(path);
160 let path = path.as_path();
161
162 let Some(pparent) = path.parent() else {
163 return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("Path {path:?} has no parent.")));
164 };
165
166 self.create_dir_all(pparent)?;
167
168 #[allow(unused_variables)]
169 let parent_meta = std::fs::metadata(pparent)?;
170
171 #[cfg(unix)]
172 let exist = path.exists();
173
174 let create = || {
175 std::fs::OpenOptions::new()
176 .create(true)
177 .truncate(false)
178 .write(true)
179 .open(path)
180 .inspect_err(|err| {
181 if err.kind() == std::io::ErrorKind::PermissionDenied {
182 permission_warning(path, false);
183 }
184 })
185 };
186
187 create()?;
189
190 #[cfg(unix)]
194 if !exist && self.is_elevated() {
195 std::os::unix::fs::chown(path, Some(parent_meta.uid()), Some(parent_meta.gid()))?;
197 }
198
199 create()
201 }
202}
203
204pub fn create_dir_all(path: impl AsRef<Path>) -> std::io::Result<()> {
205 PrivilegedExecutionContext::current().create_dir_all(path)
206}
207
208pub fn create_file(path: impl AsRef<Path>) -> std::io::Result<File> {
209 PrivilegedExecutionContext::current().create_file(path)
210}
211
212#[allow(unused_variables)]
213fn permission_warning(path: &Path, recursive: bool) {
214 #[cfg(unix)]
215 {
216 let username = whoami::username().unwrap_or_else(|_| "unknown".to_string());
217 let message = format!(
218 "The process doesn't have correct read-write permission into path {path:?}, please resets
219 ownership by 'sudo chown{}{} {path:?}'.",
220 if recursive { " -R " } else { " " },
221 username
222 );
223
224 eprintln!("{}", message.bright_blue());
225 }
226
227 #[cfg(windows)]
228 eprintln!(
229 "The process doesn't have correct read-write permission into path {path:?}, please resets
230 permission in the Properties dialog box under the Security tab."
231 );
232
233 error!("Permission denied for path {path:?}");
234
235 #[cfg(test)]
236 unsafe {
237 WARNING_PRINTED = true
238 };
239}
240
241#[cfg(all(test, unix))]
242mod test {
243 use std::os::unix::fs::MetadataExt;
244 use std::path::Path;
245
246 use anyhow::Result;
247
248 use super::{PrivilegedExecutionContext, WARNING_PRINTED};
249
250 #[test]
251 #[ignore = "run manually"]
252 fn test_create_dir_all() -> Result<()> {
253 let test_path = std::env::var("HF_XET_TEST_PATH")?;
265 std::env::set_current_dir(test_path)?;
266 let permission = PrivilegedExecutionContext::current();
267
268 let test = Path::new("rootdir/regdir1/regdir2");
269
270 assert!(permission.create_dir_all(test).is_err());
271 unsafe { assert!(WARNING_PRINTED) };
272
273 Ok(())
274 }
275
276 #[test]
277 #[ignore = "run manually"]
278 fn test_create_dir_all_sudo() -> Result<()> {
279 let test_path = std::env::var("HF_XET_TEST_PATH")?;
292 std::env::set_current_dir(test_path)?;
293 let permission = PrivilegedExecutionContext::current();
294
295 let test = Path::new("regdir/regdir1/regdir2");
296
297 permission.create_dir_all(test)?;
298
299 assert!(test.exists());
300
301 assert!(std::fs::metadata(test)?.uid() != 0);
303
304 let parent = test.parent().unwrap();
305
306 assert!(std::fs::metadata(parent)?.uid() != 0);
308
309 Ok(())
310 }
311
312 #[test]
313 #[ignore = "run manually"]
314 fn test_create_file() -> Result<()> {
315 let test_path = std::env::var("HF_XET_TEST_PATH")?;
329 std::env::set_current_dir(test_path)?;
330 let permission = PrivilegedExecutionContext::current();
331
332 let test1 = Path::new("rootdir/regdir1/regdir2/file");
333
334 assert!(permission.create_file(test1).is_err());
335 unsafe { assert!(WARNING_PRINTED) };
336
337 unsafe { WARNING_PRINTED = false };
338
339 let test2 = Path::new("rootdir/file");
340 assert!(permission.create_file(test2).is_err());
341 unsafe { assert!(WARNING_PRINTED) };
342
343 Ok(())
344 }
345
346 #[test]
347 #[ignore = "run manually"]
348 fn test_create_file_sudo() -> Result<()> {
349 let test_path = std::env::var("HF_XET_TEST_PATH")?;
361 std::env::set_current_dir(test_path)?;
362
363 let test = Path::new("regdir/regdir1/regdir2/file");
364
365 let permission = PrivilegedExecutionContext::current();
366 permission.create_file(test)?;
367
368 assert!(test.exists());
369
370 assert!(std::fs::metadata(test)?.uid() != 0);
372
373 let parent = test.parent().unwrap();
374
375 assert!(std::fs::metadata(parent)?.uid() != 0);
377
378 Ok(())
379 }
380}