veridian_kernel/process/
cwd.rs1#[cfg(feature = "alloc")]
9extern crate alloc;
10
11#[cfg(feature = "alloc")]
12use alloc::string::String;
13#[cfg(feature = "alloc")]
14use alloc::vec::Vec;
15
16use crate::error::KernelError;
17
18#[cfg(feature = "alloc")]
24pub struct ProcessCwd {
25 path: String,
27}
28
29#[cfg(feature = "alloc")]
30impl Default for ProcessCwd {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36#[cfg(feature = "alloc")]
37impl ProcessCwd {
38 pub fn new() -> Self {
40 Self {
41 path: String::from("/"),
42 }
43 }
44
45 pub fn with_path(path: &str) -> Result<Self, KernelError> {
47 if !path.starts_with('/') {
48 return Err(KernelError::InvalidArgument {
49 name: "path",
50 value: "initial CWD must be an absolute path",
51 });
52 }
53 let normalized = normalize_path(path);
54 Ok(Self { path: normalized })
55 }
56
57 pub fn get(&self) -> &str {
59 &self.path
60 }
61
62 pub fn set(&mut self, path: &str) -> Result<(), KernelError> {
66 if !path.starts_with('/') {
67 return Err(KernelError::InvalidArgument {
68 name: "path",
69 value: "CWD must be an absolute path",
70 });
71 }
72
73 if path.is_empty() {
74 return Err(KernelError::InvalidArgument {
75 name: "path",
76 value: "path cannot be empty",
77 });
78 }
79
80 self.path = normalize_path(path);
81 Ok(())
82 }
83
84 pub fn resolve(&self, relative: &str) -> String {
89 resolve_path(relative, &self.path)
90 }
91}
92
93#[cfg(feature = "alloc")]
103pub fn resolve_path(path: &str, cwd: &str) -> String {
104 if path.starts_with('/') {
105 normalize_path(path)
107 } else {
108 let mut combined = String::with_capacity(cwd.len() + 1 + path.len());
110 combined.push_str(cwd);
111 if !cwd.ends_with('/') {
112 combined.push('/');
113 }
114 combined.push_str(path);
115 normalize_path(&combined)
116 }
117}
118
119#[cfg(feature = "alloc")]
125pub fn normalize_path(path: &str) -> String {
126 let mut components: Vec<&str> = Vec::new();
127
128 for component in path.split('/') {
129 match component {
130 "" | "." => {
131 }
133 ".." => {
134 components.pop();
136 }
137 other => {
138 components.push(other);
139 }
140 }
141 }
142
143 if components.is_empty() {
144 return String::from("/");
145 }
146
147 let mut result = String::with_capacity(path.len());
148 for component in &components {
149 result.push('/');
150 result.push_str(component);
151 }
152
153 result
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
163 fn test_normalize_root() {
164 assert_eq!(normalize_path("/"), "/");
165 }
166
167 #[test]
168 fn test_normalize_simple() {
169 assert_eq!(normalize_path("/usr/bin"), "/usr/bin");
170 }
171
172 #[test]
173 fn test_normalize_trailing_slash() {
174 assert_eq!(normalize_path("/usr/bin/"), "/usr/bin");
175 }
176
177 #[test]
178 fn test_normalize_double_slash() {
179 assert_eq!(normalize_path("/usr//bin"), "/usr/bin");
180 }
181
182 #[test]
183 fn test_normalize_triple_slash() {
184 assert_eq!(normalize_path("///"), "/");
185 }
186
187 #[test]
188 fn test_normalize_dot() {
189 assert_eq!(normalize_path("/usr/./bin"), "/usr/bin");
190 }
191
192 #[test]
193 fn test_normalize_dotdot() {
194 assert_eq!(normalize_path("/usr/local/../bin"), "/usr/bin");
195 }
196
197 #[test]
198 fn test_normalize_dotdot_at_root() {
199 assert_eq!(normalize_path("/.."), "/");
200 }
201
202 #[test]
203 fn test_normalize_multiple_dotdot() {
204 assert_eq!(normalize_path("/a/b/c/../../d"), "/a/d");
205 }
206
207 #[test]
208 fn test_normalize_complex() {
209 assert_eq!(normalize_path("/usr//local/../bin/./gcc"), "/usr/bin/gcc");
210 }
211
212 #[test]
213 fn test_normalize_all_dotdot() {
214 assert_eq!(normalize_path("/a/b/../../.."), "/");
215 }
216
217 #[test]
220 fn test_resolve_absolute() {
221 assert_eq!(resolve_path("/etc/hosts", "/home"), "/etc/hosts");
222 }
223
224 #[test]
225 fn test_resolve_relative_simple() {
226 assert_eq!(resolve_path("foo", "/home"), "/home/foo");
227 }
228
229 #[test]
230 fn test_resolve_relative_nested() {
231 assert_eq!(resolve_path("foo/bar", "/home"), "/home/foo/bar");
232 }
233
234 #[test]
235 fn test_resolve_relative_dotdot() {
236 assert_eq!(resolve_path("../bin", "/usr/local"), "/usr/bin");
237 }
238
239 #[test]
240 fn test_resolve_dot() {
241 assert_eq!(resolve_path(".", "/var/log"), "/var/log");
242 }
243
244 #[test]
245 fn test_resolve_relative_from_root() {
246 assert_eq!(resolve_path("usr/bin", "/"), "/usr/bin");
247 }
248
249 #[test]
250 fn test_resolve_dotdot_past_root() {
251 assert_eq!(resolve_path("../../..", "/a"), "/");
252 }
253
254 #[test]
257 fn test_process_cwd_default() {
258 let cwd = ProcessCwd::new();
259 assert_eq!(cwd.get(), "/");
260 }
261
262 #[test]
263 fn test_process_cwd_with_path() {
264 let cwd = ProcessCwd::with_path("/home/user").unwrap();
265 assert_eq!(cwd.get(), "/home/user");
266 }
267
268 #[test]
269 fn test_process_cwd_with_path_relative_fails() {
270 let result = ProcessCwd::with_path("relative/path");
271 assert!(result.is_err());
272 }
273
274 #[test]
275 fn test_process_cwd_set() {
276 let mut cwd = ProcessCwd::new();
277 assert!(cwd.set("/home/user").is_ok());
278 assert_eq!(cwd.get(), "/home/user");
279 }
280
281 #[test]
282 fn test_process_cwd_set_normalizes() {
283 let mut cwd = ProcessCwd::new();
284 assert!(cwd.set("/home//user/../admin/./docs").is_ok());
285 assert_eq!(cwd.get(), "/home/admin/docs");
286 }
287
288 #[test]
289 fn test_process_cwd_set_relative_fails() {
290 let mut cwd = ProcessCwd::new();
291 let result = cwd.set("relative");
292 assert!(result.is_err());
293 }
294
295 #[test]
296 fn test_process_cwd_resolve_absolute() {
297 let cwd = ProcessCwd::with_path("/home/user").unwrap();
298 assert_eq!(cwd.resolve("/etc/passwd"), "/etc/passwd");
299 }
300
301 #[test]
302 fn test_process_cwd_resolve_relative() {
303 let cwd = ProcessCwd::with_path("/home/user").unwrap();
304 assert_eq!(
305 cwd.resolve("Documents/file.txt"),
306 "/home/user/Documents/file.txt"
307 );
308 }
309
310 #[test]
311 fn test_process_cwd_resolve_dotdot() {
312 let cwd = ProcessCwd::with_path("/home/user").unwrap();
313 assert_eq!(cwd.resolve("../admin"), "/home/admin");
314 }
315}