cap_primitives/rustix/fs/
dir_utils.rs1use crate::fs::OpenOptions;
2use ambient_authority::AmbientAuthority;
3use rustix::fs::OFlags;
4use std::ffi::{OsStr, OsString};
5use std::ops::Deref;
6#[cfg(unix)]
7use std::os::unix::{
8 ffi::{OsStrExt, OsStringExt},
9 fs::OpenOptionsExt,
10};
11#[cfg(target_os = "wasi")]
12use std::os::wasi::{
13 ffi::{OsStrExt, OsStringExt},
14 fs::OpenOptionsExt,
15};
16use std::path::{Path, PathBuf};
17use std::{fs, io};
18
19pub(crate) fn path_requires_dir(path: &Path) -> bool {
23 let bytes = path.as_os_str().as_bytes();
24
25 bytes.ends_with(b"/") || bytes.ends_with(b"/.")
29}
30
31pub(crate) fn path_has_trailing_dot(path: &Path) -> bool {
35 let mut bytes = path.as_os_str().as_bytes();
36
37 while let Some((last, rest)) = bytes.split_last() {
39 if *last == b'/' {
40 bytes = rest;
41 } else {
42 break;
43 }
44 }
45
46 bytes.ends_with(b"/.") || bytes == b"."
47}
48
49pub(crate) fn path_has_trailing_slash(path: &Path) -> bool {
53 let bytes = path.as_os_str().as_bytes();
54
55 bytes.ends_with(b"/")
56}
57
58pub(crate) fn append_dir_suffix(path: PathBuf) -> PathBuf {
61 let mut bytes = path.into_os_string().into_vec();
62 bytes.push(b'/');
63 OsString::from_vec(bytes).into()
64}
65
66#[allow(clippy::indexing_slicing)]
70pub(crate) fn strip_dir_suffix(path: &Path) -> impl Deref<Target = Path> + '_ {
71 let mut bytes = path.as_os_str().as_bytes();
72 while bytes.len() > 1 && *bytes.last().unwrap() == b'/' {
73 bytes = &bytes[..bytes.len() - 1];
74 }
75 OsStr::from_bytes(bytes).as_ref()
76}
77
78pub(crate) fn dir_options() -> OpenOptions {
80 OpenOptions::new().read(true).dir_required(true).clone()
81}
82
83pub(crate) fn readdir_options() -> OpenOptions {
86 OpenOptions::new()
87 .read(true)
88 .dir_required(true)
89 .readdir_required(true)
90 .clone()
91}
92
93pub(crate) fn canonicalize_options() -> OpenOptions {
95 OpenOptions::new().read(true).clone()
96}
97
98pub(crate) fn open_ambient_dir_impl(
106 path: &Path,
107 ambient_authority: AmbientAuthority,
108) -> io::Result<fs::File> {
109 let _ = ambient_authority;
110
111 let mut options = fs::OpenOptions::new();
112 options.read(true);
113
114 #[cfg(not(target_os = "wasi"))]
115 options.custom_flags((OFlags::DIRECTORY | target_o_path()).bits() as i32);
118 #[cfg(target_os = "wasi")]
119 options.directory(true);
120
121 options.open(path)
122}
123
124#[inline]
126pub(crate) const fn target_o_path() -> OFlags {
127 #[cfg(any(
128 target_os = "android",
129 target_os = "emscripten",
130 target_os = "freebsd",
131 target_os = "fuchsia",
132 target_os = "linux",
133 target_os = "redox",
134 ))]
135 {
136 OFlags::PATH
137 }
138
139 #[cfg(any(
140 target_os = "dragonfly",
141 target_os = "ios",
142 target_os = "macos",
143 target_os = "tvos",
144 target_os = "watchos",
145 target_os = "visionos",
146 target_os = "netbsd",
147 target_os = "openbsd",
148 target_os = "wasi",
149 target_os = "illumos",
150 target_os = "solaris",
151 ))]
152 {
153 OFlags::empty()
154 }
155}
156
157#[cfg(racy_asserts)]
158#[test]
159fn append_dir_suffix_tests() {
160 assert!(append_dir_suffix(Path::new("foo").to_path_buf())
161 .display()
162 .to_string()
163 .ends_with('/'));
164}
165
166#[test]
167fn strip_dir_suffix_tests() {
168 assert_eq!(&*strip_dir_suffix(Path::new("/foo//")), Path::new("/foo"));
169 assert_eq!(&*strip_dir_suffix(Path::new("/foo/")), Path::new("/foo"));
170 assert_eq!(&*strip_dir_suffix(Path::new("foo/")), Path::new("foo"));
171 assert_eq!(&*strip_dir_suffix(Path::new("foo")), Path::new("foo"));
172 assert_eq!(&*strip_dir_suffix(Path::new("/")), Path::new("/"));
173 assert_eq!(&*strip_dir_suffix(Path::new("//")), Path::new("/"));
174 assert_eq!(&*strip_dir_suffix(Path::new("/.")), Path::new("/."));
175 assert_eq!(&*strip_dir_suffix(Path::new("//.")), Path::new("/."));
176 assert_eq!(&*strip_dir_suffix(Path::new(".")), Path::new("."));
177 assert_eq!(&*strip_dir_suffix(Path::new("foo/.")), Path::new("foo/."));
178}
179
180#[test]
181fn test_path_requires_dir() {
182 assert!(!path_requires_dir(Path::new(".")));
183 assert!(path_requires_dir(Path::new("/")));
184 assert!(path_requires_dir(Path::new("//")));
185 assert!(path_requires_dir(Path::new("/./.")));
186 assert!(path_requires_dir(Path::new("foo/")));
187 assert!(path_requires_dir(Path::new("foo//")));
188 assert!(path_requires_dir(Path::new("foo//.")));
189 assert!(path_requires_dir(Path::new("foo/./.")));
190 assert!(path_requires_dir(Path::new("foo/./")));
191 assert!(path_requires_dir(Path::new("foo/.//")));
192}
193
194#[test]
195fn test_path_has_trailing_dot() {
196 assert!(!path_has_trailing_dot(Path::new("foo")));
197 assert!(!path_has_trailing_dot(Path::new("foo.")));
198
199 assert!(!path_has_trailing_dot(Path::new("/./foo")));
200 assert!(!path_has_trailing_dot(Path::new("..")));
201 assert!(!path_has_trailing_dot(Path::new("/..")));
202
203 assert!(!path_has_trailing_dot(Path::new("/")));
204 assert!(!path_has_trailing_dot(Path::new("//")));
205 assert!(!path_has_trailing_dot(Path::new("foo//")));
206 assert!(!path_has_trailing_dot(Path::new("foo/")));
207
208 assert!(path_has_trailing_dot(Path::new(".")));
209
210 assert!(path_has_trailing_dot(Path::new("/./.")));
211 assert!(path_has_trailing_dot(Path::new("foo//.")));
212 assert!(path_has_trailing_dot(Path::new("foo/./.")));
213 assert!(path_has_trailing_dot(Path::new("foo/./")));
214 assert!(path_has_trailing_dot(Path::new("foo/.//")));
215}
216
217#[test]
218fn test_path_has_trailing_slash() {
219 assert!(path_has_trailing_slash(Path::new("/")));
220 assert!(path_has_trailing_slash(Path::new("//")));
221 assert!(path_has_trailing_slash(Path::new("foo//")));
222 assert!(path_has_trailing_slash(Path::new("foo/")));
223 assert!(path_has_trailing_slash(Path::new("foo/./")));
224 assert!(path_has_trailing_slash(Path::new("foo/.//")));
225
226 assert!(!path_has_trailing_slash(Path::new("foo")));
227 assert!(!path_has_trailing_slash(Path::new("foo.")));
228 assert!(!path_has_trailing_slash(Path::new("/./foo")));
229 assert!(!path_has_trailing_slash(Path::new("..")));
230 assert!(!path_has_trailing_slash(Path::new("/..")));
231 assert!(!path_has_trailing_slash(Path::new(".")));
232 assert!(!path_has_trailing_slash(Path::new("/./.")));
233 assert!(!path_has_trailing_slash(Path::new("foo//.")));
234 assert!(!path_has_trailing_slash(Path::new("foo/./.")));
235}