brush_core/sys/unix/
fs.rs1use std::os::unix::ffi::OsStringExt;
4use std::os::unix::fs::FileTypeExt;
5use std::path::Path;
6
7use crate::error;
8
9pub use std::os::unix::fs::MetadataExt;
10
11const DEFAULT_EXECUTABLE_SEARCH_PATHS: &[&str] = &[
12 "/usr/local/sbin",
13 "/usr/local/bin",
14 "/usr/sbin",
15 "/usr/bin",
16 "/sbin",
17 "/bin",
18];
19
20const DEFAULT_STANDARD_UTILS_PATHS: &[&str] =
21 &["/bin", "/usr/bin", "/sbin", "/usr/sbin", "/etc", "/usr/etc"];
22
23impl crate::sys::fs::PathExt for Path {
24 fn readable(&self) -> bool {
25 nix::unistd::access(self, nix::unistd::AccessFlags::R_OK).is_ok()
26 }
27
28 fn writable(&self) -> bool {
29 nix::unistd::access(self, nix::unistd::AccessFlags::W_OK).is_ok()
30 }
31
32 fn executable(&self) -> bool {
33 nix::unistd::access(self, nix::unistd::AccessFlags::X_OK).is_ok()
34 }
35
36 fn exists_and_is_block_device(&self) -> bool {
37 try_get_file_type(self).is_some_and(|ft| ft.is_block_device())
38 }
39
40 fn exists_and_is_char_device(&self) -> bool {
41 try_get_file_type(self).is_some_and(|ft| ft.is_char_device())
42 }
43
44 fn exists_and_is_fifo(&self) -> bool {
45 try_get_file_type(self).is_some_and(|ft: std::fs::FileType| ft.is_fifo())
46 }
47
48 fn exists_and_is_socket(&self) -> bool {
49 try_get_file_type(self).is_some_and(|ft| ft.is_socket())
50 }
51
52 fn exists_and_is_setgid(&self) -> bool {
53 const S_ISGID: u32 = 0o2000;
54 let file_mode = try_get_file_mode(self);
55 file_mode.is_some_and(|mode| mode & S_ISGID != 0)
56 }
57
58 fn exists_and_is_setuid(&self) -> bool {
59 const S_ISUID: u32 = 0o4000;
60 let file_mode = try_get_file_mode(self);
61 file_mode.is_some_and(|mode| mode & S_ISUID != 0)
62 }
63
64 fn exists_and_is_sticky_bit(&self) -> bool {
65 const S_ISVTX: u32 = 0o1000;
66 let file_mode = try_get_file_mode(self);
67 file_mode.is_some_and(|mode| mode & S_ISVTX != 0)
68 }
69
70 fn get_device_and_inode(&self) -> Result<(u64, u64), crate::error::Error> {
71 let metadata = self.metadata()?;
72 Ok((metadata.dev(), metadata.ino()))
73 }
74}
75
76fn try_get_file_type(path: &Path) -> Option<std::fs::FileType> {
77 path.metadata().map(|metadata| metadata.file_type()).ok()
78}
79
80fn try_get_file_mode(path: &Path) -> Option<u32> {
81 path.metadata().map(|metadata| metadata.mode()).ok()
82}
83
84pub(crate) fn get_default_executable_search_paths() -> Vec<String> {
85 DEFAULT_EXECUTABLE_SEARCH_PATHS
86 .iter()
87 .map(|s| (*s).to_owned())
88 .collect()
89}
90
91pub fn get_default_standard_utils_paths() -> Vec<String> {
94 if let Ok(Some(cs_path)) = confstr_cs_path() {
100 if !cs_path.is_empty() {
101 return cs_path.split(':').map(|s| s.to_string()).collect();
102 }
103 }
104
105 DEFAULT_STANDARD_UTILS_PATHS
106 .iter()
107 .map(|s| (*s).to_owned())
108 .collect()
109}
110
111fn confstr_cs_path() -> Result<Option<String>, std::io::Error> {
112 let value = confstr(nix::libc::_CS_PATH)?;
113
114 if let Some(value) = value {
115 let value_str = value
116 .into_string()
117 .map_err(|_err| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid data"))?;
118 Ok(Some(value_str))
119 } else {
120 Ok(None)
121 }
122}
123
124fn confstr(name: nix::libc::c_int) -> Result<Option<std::ffi::OsString>, std::io::Error> {
130 let required_size = unsafe { nix::libc::confstr(name, std::ptr::null_mut(), 0) };
135
136 if required_size == 0 {
140 return Ok(None);
141 }
142
143 let mut buffer = Vec::<u8>::with_capacity(required_size);
144
145 let final_size =
151 unsafe { nix::libc::confstr(name, buffer.as_mut_ptr().cast(), buffer.capacity()) };
152
153 if final_size == 0 {
154 return Err(std::io::Error::last_os_error());
155 }
156
157 if final_size > buffer.capacity() {
161 return Err(std::io::Error::other(
162 "confstr needed more space than advertised",
163 ));
164 }
165
166 unsafe { buffer.set_len(final_size) };
172
173 if !matches!(buffer.pop(), Some(0)) {
175 return Err(std::io::Error::other(
176 "confstr did not null-terminate the returned string",
177 ));
178 }
179
180 Ok(Some(std::ffi::OsString::from_vec(buffer)))
181}
182
183pub fn open_null_file() -> Result<std::fs::File, error::Error> {
185 let f = std::fs::File::options()
186 .read(true)
187 .write(true)
188 .open("/dev/null")?;
189
190 Ok(f)
191}