1use super::Env;
20use crate::System;
21use crate::path::Path;
22use crate::system::AT_FDCWD;
23use crate::system::Errno;
24use crate::variable::AssignError;
25use crate::variable::PWD;
26use crate::variable::Scope::Global;
27use std::ffi::CString;
28use thiserror::Error;
29
30fn has_dot_or_dot_dot(path: &str) -> bool {
32 path.split('/').any(|c| c == "." || c == "..")
33}
34
35#[derive(Clone, Debug, Eq, Error, PartialEq)]
37pub enum PreparePwdError {
38 #[error(transparent)]
40 AssignError(#[from] AssignError),
41
42 #[error("cannot obtain the current working directory path: {0}")]
44 GetCwdError(#[from] Errno),
45}
46
47impl Env {
48 #[must_use]
57 pub fn get_pwd_if_correct(&self) -> Option<&str> {
58 self.variables.get_scalar(PWD).filter(|pwd| {
59 if !Path::new(pwd).is_absolute() {
60 return false;
61 }
62 if has_dot_or_dot_dot(pwd) {
63 return false;
64 }
65 let Ok(cstr_pwd) = CString::new(pwd.as_bytes()) else {
66 return false;
67 };
68 let Ok(s1) = self.system.fstatat(AT_FDCWD, &cstr_pwd, true) else {
69 return false;
70 };
71 let Ok(s2) = self.system.fstatat(AT_FDCWD, c".", true) else {
72 return false;
73 };
74 s1.identity() == s2.identity()
75 })
76 }
77
78 #[inline]
80 #[must_use]
81 fn has_correct_pwd(&self) -> bool {
82 self.get_pwd_if_correct().is_some()
83 }
84
85 pub fn prepare_pwd(&mut self) -> Result<(), PreparePwdError> {
94 if !self.has_correct_pwd() {
95 let dir = self
96 .system
97 .getcwd()?
98 .into_unix_string()
99 .into_string()
100 .map_err(|_| Errno::EILSEQ)?;
101 let mut var = self.variables.get_or_new(PWD, Global);
102 var.assign(dir, None)?;
103 var.export(true);
104 }
105 Ok(())
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::VirtualSystem;
113 use crate::path::PathBuf;
114 use crate::system::r#virtual::FileBody;
115 use crate::system::r#virtual::Inode;
116 use crate::variable::Value;
117 use std::cell::RefCell;
118 use std::rc::Rc;
119
120 #[test]
121 fn has_dot_or_dot_dot_cases() {
122 assert!(!has_dot_or_dot_dot(""));
123 assert!(!has_dot_or_dot_dot("foo"));
124 assert!(!has_dot_or_dot_dot(".foo"));
125 assert!(!has_dot_or_dot_dot("foo.bar"));
126 assert!(!has_dot_or_dot_dot("..."));
127 assert!(!has_dot_or_dot_dot("/"));
128 assert!(!has_dot_or_dot_dot("/bar"));
129 assert!(!has_dot_or_dot_dot("/bar/baz"));
130
131 assert!(has_dot_or_dot_dot("."));
132 assert!(has_dot_or_dot_dot("/."));
133 assert!(has_dot_or_dot_dot("./"));
134 assert!(has_dot_or_dot_dot("/./"));
135 assert!(has_dot_or_dot_dot("foo/.//bar"));
136
137 assert!(has_dot_or_dot_dot(".."));
138 assert!(has_dot_or_dot_dot("/.."));
139 assert!(has_dot_or_dot_dot("../"));
140 assert!(has_dot_or_dot_dot("/../"));
141 assert!(has_dot_or_dot_dot("/foo//../bar"));
142 }
143
144 fn env_with_symlink_to_dir() -> Env {
145 let mut system = Box::new(VirtualSystem::new());
146 let mut state = system.state.borrow_mut();
147 state
148 .file_system
149 .save(
150 "/foo/bar/dir",
151 Rc::new(RefCell::new(Inode {
152 body: FileBody::Directory {
153 files: Default::default(),
154 },
155 permissions: Default::default(),
156 })),
157 )
158 .unwrap();
159 state
160 .file_system
161 .save(
162 "/foo/link",
163 Rc::new(RefCell::new(Inode {
164 body: FileBody::Symlink {
165 target: "bar/dir".into(),
166 },
167 permissions: Default::default(),
168 })),
169 )
170 .unwrap();
171 drop(state);
172 system.current_process_mut().cwd = PathBuf::from("/foo/bar/dir");
173 Env::with_system(system)
174 }
175
176 #[test]
177 fn prepare_pwd_no_value() {
178 let mut env = env_with_symlink_to_dir();
179
180 let result = env.prepare_pwd();
181 assert_eq!(result, Ok(()));
182 let pwd = env.variables.get(PWD).unwrap();
183 assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
184 assert!(pwd.is_exported);
185 }
186
187 #[test]
188 fn prepare_pwd_with_correct_path() {
189 let mut env = env_with_symlink_to_dir();
190 env.variables
191 .get_or_new(PWD, Global)
192 .assign("/foo/link", None)
193 .unwrap();
194
195 let result = env.prepare_pwd();
196 assert_eq!(result, Ok(()));
197 let pwd = env.variables.get(PWD).unwrap();
198 assert_eq!(pwd.value, Some(Value::scalar("/foo/link")));
199 }
200
201 #[test]
202 fn prepare_pwd_with_dot() {
203 let mut env = env_with_symlink_to_dir();
204 env.variables
205 .get_or_new(PWD, Global)
206 .assign("/foo/./link", None)
207 .unwrap();
208
209 let result = env.prepare_pwd();
210 assert_eq!(result, Ok(()));
211 let pwd = env.variables.get(PWD).unwrap();
212 assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
213 assert!(pwd.is_exported);
214 }
215
216 #[test]
217 fn prepare_pwd_with_dot_dot() {
218 let mut env = env_with_symlink_to_dir();
219 env.variables
220 .get_or_new(PWD, Global)
221 .assign("/foo/./link", None)
222 .unwrap();
223
224 let result = env.prepare_pwd();
225 assert_eq!(result, Ok(()));
226 let pwd = env.variables.get(PWD).unwrap();
227 assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
228 assert!(pwd.is_exported);
229 }
230
231 #[test]
232 fn prepare_pwd_with_wrong_path() {
233 let mut env = env_with_symlink_to_dir();
234 env.variables
235 .get_or_new(PWD, Global)
236 .assign("/foo/bar", None)
237 .unwrap();
238
239 let result = env.prepare_pwd();
240 assert_eq!(result, Ok(()));
241 let pwd = env.variables.get(PWD).unwrap();
242 assert_eq!(pwd.value, Some(Value::scalar("/foo/bar/dir")));
243 assert!(pwd.is_exported);
244 }
245
246 #[test]
247 fn prepare_pwd_with_non_absolute_path() {
248 let mut system = Box::new(VirtualSystem::new());
249 let mut state = system.state.borrow_mut();
250 state
251 .file_system
252 .save(
253 "/link",
254 Rc::new(RefCell::new(Inode {
255 body: FileBody::Symlink { target: ".".into() },
256 permissions: Default::default(),
257 })),
258 )
259 .unwrap();
260 drop(state);
261 system.current_process_mut().cwd = PathBuf::from("/");
262
263 let mut env = Env::with_system(system);
264 env.variables
265 .get_or_new(PWD, Global)
266 .assign("link", None)
267 .unwrap();
268
269 let result = env.prepare_pwd();
270 assert_eq!(result, Ok(()));
271 let pwd = env.variables.get(PWD).unwrap();
272 assert_eq!(pwd.value, Some(Value::scalar("/")));
273 assert!(pwd.is_exported);
274 }
275}