1use std::path::{Path, PathBuf};
4
5use normalize_path::NormalizePath as _;
6
7use crate::env::{EnvironmentLookup, EnvironmentScope};
8use crate::sys::users;
9use crate::{ExecutionParameters, ShellFd, error, openfiles, pathsearch, variables};
10
11impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
12 pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
27 let abs_path = self.absolute_path(target_dir.as_ref());
28
29 match std::fs::metadata(&abs_path) {
30 Ok(m) => {
31 if !m.is_dir() {
32 return Err(error::ErrorKind::NotADirectory(abs_path).into());
33 }
34 }
35 Err(e) => {
36 return Err(e.into());
37 }
38 }
39
40 let cleaned_path = abs_path.normalize();
42
43 let pwd = cleaned_path.to_string_lossy().to_string();
44
45 self.env.update_or_add(
46 "PWD",
47 variables::ShellValueLiteral::Scalar(pwd),
48 |_| Ok(()),
49 EnvironmentLookup::Anywhere,
50 EnvironmentScope::Global,
51 )?;
52 let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
53
54 self.env.update_or_add(
55 "OLDPWD",
56 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
57 |_| Ok(()),
58 EnvironmentLookup::Anywhere,
59 EnvironmentScope::Global,
60 )?;
61
62 Ok(())
63 }
64
65 pub fn tilde_shorten(&self, s: String) -> String {
72 if let Some(home_dir) = self.home_dir()
73 && let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref())
74 {
75 return format!("~{stripped}");
76 }
77 s
78 }
79
80 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
82 if let Some(home) = self.env.get_str("HOME", self) {
83 Some(PathBuf::from(home.to_string()))
84 } else {
85 users::get_current_user_home_dir()
87 }
88 }
89
90 pub fn find_executables_in_path<'a>(
97 &'a self,
98 filename: &'a str,
99 ) -> impl Iterator<Item = PathBuf> + 'a {
100 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
101 let paths = crate::sys::fs::split_paths(path_var.as_ref());
102
103 pathsearch::search_for_executable(paths, filename)
104 }
105
106 pub fn find_executables_in_path_with_prefix(
113 &self,
114 filename_prefix: &str,
115 case_insensitive: bool,
116 ) -> impl Iterator<Item = PathBuf> {
117 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
118 let paths = crate::sys::fs::split_paths(path_var.as_ref());
119
120 pathsearch::search_for_executable_with_prefix(paths, filename_prefix, case_insensitive)
121 }
122
123 pub fn find_first_executable_in_path<S: AsRef<str>>(
131 &self,
132 candidate_name: S,
133 ) -> Option<PathBuf> {
134 self.find_executables_in_path(candidate_name.as_ref())
135 .next()
136 }
137
138 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
147 &mut self,
148 candidate_name: S,
149 ) -> Option<PathBuf>
150 where
151 String: From<S>,
152 {
153 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
154 Some(cached_path)
155 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
156 self.program_location_cache
157 .set(candidate_name, found_path.clone());
158 Some(found_path)
159 } else {
160 None
161 }
162 }
163
164 pub fn resolve_command_in_path<S: AsRef<str>>(&self, candidate_name: S) -> Option<PathBuf> {
175 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
176 let paths = crate::sys::fs::split_paths(path_var.as_ref());
177 pathsearch::resolve_command(paths, candidate_name.as_ref())
178 }
179
180 pub fn resolve_command_in_path_using_cache<S: AsRef<str>>(
187 &mut self,
188 candidate_name: S,
189 ) -> Option<PathBuf>
190 where
191 String: From<S>,
192 {
193 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
194 return Some(cached_path);
195 }
196
197 let found_path = self.resolve_command_in_path(candidate_name.as_ref())?;
198 self.program_location_cache
199 .set(candidate_name, found_path.clone());
200
201 Some(found_path)
202 }
203
204 pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
210 let path = path.as_ref();
211 if path.as_os_str().is_empty() || path.is_absolute() {
212 path.to_owned()
213 } else {
214 self.working_dir().join(path)
215 }
216 }
217
218 pub(crate) fn open_file(
228 &self,
229 options: &std::fs::OpenOptions,
230 path: impl AsRef<Path>,
231 params: &ExecutionParameters,
232 ) -> Result<openfiles::OpenFile, std::io::Error> {
233 if let Some(result) = crate::sys::fs::try_open_special_file(path.as_ref()) {
238 return result.map(openfiles::OpenFile::from);
239 }
240
241 let path_to_open = self.absolute_path(path.as_ref());
242
243 if let Some(fd_num) = shell_fd_path_to_fd(&path_to_open)
247 && let Some(open_file) = params.try_fd(self, fd_num)
248 {
249 return Ok(open_file);
250 }
251
252 Ok(options.open(path_to_open)?.into())
253 }
254
255 pub fn replace_open_files(
262 &mut self,
263 open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
264 ) {
265 self.open_files = openfiles::OpenFiles::from(open_fds);
266 }
267
268 pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
269 &self.open_files
270 }
271}
272
273fn shell_fd_path_to_fd(path: &Path) -> Option<ShellFd> {
274 match path.to_str()? {
275 "/dev/stdin" => return Some(openfiles::OpenFiles::STDIN_FD),
276 "/dev/stdout" => return Some(openfiles::OpenFiles::STDOUT_FD),
277 "/dev/stderr" => return Some(openfiles::OpenFiles::STDERR_FD),
278 _ => {}
279 }
280
281 if let Some(parent) = path.parent()
282 && parent == Path::new("/dev/fd")
283 && let Some(filename) = path.file_name()
284 {
285 filename.to_string_lossy().parse::<ShellFd>().ok()
286 } else {
287 None
288 }
289}