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