wasmer_wasix/fs/
path_posix.rs1use std::{
12 borrow::Cow,
13 path::{Path, PathBuf},
14};
15
16use wasmer_wasix_types::wasi::Errno;
17
18#[derive(Clone, Copy)]
19pub(crate) enum PosixPathComponent<'a> {
20 RootDir,
21 CurDir,
22 ParentDir,
23 Normal(&'a str),
24}
25
26#[derive(Debug)]
27pub(crate) struct PosixPath<'a> {
28 path: Cow<'a, str>,
29}
30
31#[derive(Debug)]
32pub(crate) struct PosixPathBuf {
33 path: String,
34}
35
36impl<'a> PosixPath<'a> {
37 pub(crate) fn new(path: &'a str) -> Self {
38 Self {
39 path: Cow::Borrowed(path),
40 }
41 }
42
43 pub(crate) fn from_path(path: &'a Path) -> Self {
44 Self {
45 path: path.to_string_lossy(),
46 }
47 }
48
49 pub(crate) fn as_str(&self) -> &str {
50 self.path.as_ref()
51 }
52
53 pub(crate) fn is_absolute(&self) -> bool {
54 self.as_str().starts_with('/')
55 }
56
57 pub(crate) fn strip_root_prefix(&self) -> PosixPathBuf {
58 PosixPathBuf::from(self.as_str().strip_prefix('/').unwrap_or(self.as_str()))
59 }
60
61 pub(crate) fn strip_prefix<'b>(&'b self, prefix: &PosixPath<'_>) -> Option<PosixPath<'b>> {
62 let path = self.as_str();
63 let prefix = prefix.as_str();
64
65 if prefix == "/" {
66 return path.strip_prefix('/').map(PosixPath::new);
67 }
68
69 if path == prefix {
70 return Some(PosixPath::new(""));
71 }
72
73 let suffix = path.strip_prefix(prefix)?;
74 suffix.strip_prefix('/').map(PosixPath::new)
75 }
76
77 pub(crate) fn parent(&self) -> PosixPathBuf {
78 let path = self.as_str();
79 let trimmed = path.trim_end_matches('/');
80 let parent = trimmed
81 .rsplit_once('/')
82 .map(|(parent, _)| parent)
83 .unwrap_or_default();
84 PosixPathBuf::from(parent)
85 }
86
87 pub(crate) fn components(
88 &self,
89 include_root: bool,
90 preserve_trailing_slash: bool,
91 ) -> Vec<PosixPathComponent<'_>> {
92 let path = self.as_str();
93 let mut components = Vec::new();
94
95 if include_root && path.starts_with('/') {
96 components.push(PosixPathComponent::RootDir);
97 }
98
99 for component in path.split('/').filter(|component| !component.is_empty()) {
100 components.push(match component {
101 "." => PosixPathComponent::CurDir,
102 ".." => PosixPathComponent::ParentDir,
103 component => PosixPathComponent::Normal(component),
104 });
105 }
106
107 if preserve_trailing_slash && path.ends_with('/') {
108 components.push(PosixPathComponent::CurDir);
109 }
110
111 components
112 }
113
114 pub(crate) fn join(&self, relative: &PosixPath<'_>) -> PosixPathBuf {
115 let base = self.as_str();
116 let relative = relative.as_str();
117
118 if relative.is_empty() || relative == "." {
119 return PosixPathBuf::from(base);
120 }
121
122 if relative.starts_with('/') || base.is_empty() || base == "." {
123 PosixPathBuf::from(relative)
124 } else if base == "/" {
125 PosixPathBuf::from(format!("/{relative}"))
126 } else if base.ends_with('/') {
127 PosixPathBuf::from(format!("{base}{relative}"))
128 } else {
129 PosixPathBuf::from(format!("{base}/{relative}"))
130 }
131 }
132
133 pub(crate) fn parent_path_and_name(&self) -> Result<(PosixPathBuf, String), Errno> {
134 let path = self.as_str();
135 let trimmed = path.trim_end_matches('/');
136 if trimmed.is_empty() {
137 return Err(Errno::Inval);
138 }
139
140 let (parent, name) = match trimmed.rsplit_once('/') {
141 Some(("", name)) if path.starts_with('/') => ("/", name),
142 Some((parent, name)) => (parent, name),
143 None => ("", trimmed),
144 };
145
146 if name.is_empty() {
147 return Err(Errno::Inval);
148 }
149
150 Ok((PosixPathBuf::from(parent), name.to_string()))
151 }
152
153 pub(crate) fn normalize_virtual_symlink_key(&self) -> PosixPathBuf {
154 let mut normalized = Vec::new();
155
156 for component in self.components(false, false) {
157 match component {
158 PosixPathComponent::RootDir | PosixPathComponent::CurDir => {}
159 PosixPathComponent::ParentDir => {
160 normalized.pop();
161 }
162 PosixPathComponent::Normal(component) => normalized.push(component.to_owned()),
163 }
164 }
165
166 PosixPathBuf::from_components(self.is_absolute(), &normalized, "/")
167 }
168}
169
170impl PosixPathBuf {
171 pub(crate) fn from_components(
172 is_absolute: bool,
173 components: &[String],
174 empty_path: &str,
175 ) -> Self {
176 if components.is_empty() {
177 PosixPathBuf::from(empty_path)
178 } else if is_absolute {
179 PosixPathBuf::from(format!("/{}", components.join("/")))
180 } else {
181 PosixPathBuf::from(components.join("/"))
182 }
183 }
184
185 pub(crate) fn as_posix_path(&self) -> PosixPath<'_> {
186 PosixPath::new(&self.path)
187 }
188
189 pub(crate) fn as_str(&self) -> &str {
190 &self.path
191 }
192
193 pub(crate) fn into_path_buf(self) -> PathBuf {
194 PathBuf::from(self.path)
195 }
196
197 pub(crate) fn resolve_relative(
198 symlink_parent: &PosixPath<'_>,
199 relative_path: &PosixPath<'_>,
200 preserve_after_first_normal: bool,
201 ) -> Result<Self, Errno> {
202 let mut resolved = Vec::new();
203 symlink_parent.push_normalized_relative(&mut resolved)?;
204
205 if !preserve_after_first_normal {
206 relative_path.push_normalized_relative(&mut resolved)?;
207 return Ok(PosixPathBuf::from_components(false, &resolved, "."));
208 }
209
210 let mut validation = resolved.clone();
211 relative_path.push_normalized_relative(&mut validation)?;
212
213 let mut remaining = Vec::new();
214 let mut preserve_remaining = false;
215
216 relative_path.visit_relative_components(|component| {
217 if preserve_remaining {
218 match component {
219 PosixPathComponent::RootDir => {}
220 PosixPathComponent::CurDir => remaining.push(".".to_owned()),
221 PosixPathComponent::ParentDir => remaining.push("..".to_owned()),
222 PosixPathComponent::Normal(component) => remaining.push(component.to_owned()),
223 }
224 return Ok(());
225 }
226
227 match component {
228 PosixPathComponent::RootDir | PosixPathComponent::CurDir => {}
229 PosixPathComponent::ParentDir => {
230 resolved.pop().ok_or(Errno::Perm)?;
231 }
232 PosixPathComponent::Normal(component) => {
233 remaining.push(component.to_owned());
234 preserve_remaining = true;
235 }
236 }
237 Ok(())
238 })?;
239
240 if !remaining.is_empty() {
241 resolved.extend(remaining);
242 }
243
244 Ok(PosixPathBuf::from_components(false, &resolved, "."))
245 }
246}
247
248impl<'a> PosixPath<'a> {
249 fn visit_relative_components<'b, F>(&'b self, mut visit: F) -> Result<(), Errno>
250 where
251 F: FnMut(PosixPathComponent<'b>) -> Result<(), Errno>,
252 {
253 if self.is_absolute() {
254 return Err(Errno::Perm);
255 }
256
257 for component in self.components(false, false) {
258 visit(component)?;
259 }
260
261 Ok(())
262 }
263
264 fn push_normalized_relative(&self, resolved: &mut Vec<String>) -> Result<(), Errno> {
265 self.visit_relative_components(|component| {
266 match component {
267 PosixPathComponent::RootDir | PosixPathComponent::CurDir => {}
268 PosixPathComponent::Normal(component) => resolved.push(component.to_owned()),
269 PosixPathComponent::ParentDir => {
270 resolved.pop().ok_or(Errno::Perm)?;
271 }
272 }
273 Ok(())
274 })
275 }
276}
277
278impl From<&str> for PosixPathBuf {
279 fn from(path: &str) -> Self {
280 Self {
281 path: path.to_owned(),
282 }
283 }
284}
285
286impl From<String> for PosixPathBuf {
287 fn from(path: String) -> Self {
288 Self { path }
289 }
290}