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