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