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> {
18 let abs_path = self.absolute_path(target_dir.as_ref());
19
20 match std::fs::metadata(&abs_path) {
21 Ok(m) => {
22 if !m.is_dir() {
23 return Err(error::ErrorKind::NotADirectory(abs_path).into());
24 }
25 }
26 Err(e) => {
27 return Err(e.into());
28 }
29 }
30
31 let cleaned_path = abs_path.normalize();
33
34 let pwd = cleaned_path.to_string_lossy().to_string();
35
36 self.env.update_or_add(
37 "PWD",
38 variables::ShellValueLiteral::Scalar(pwd),
39 |_| Ok(()),
40 EnvironmentLookup::Anywhere,
41 EnvironmentScope::Global,
42 )?;
43 let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
44
45 self.env.update_or_add(
46 "OLDPWD",
47 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
48 |_| Ok(()),
49 EnvironmentLookup::Anywhere,
50 EnvironmentScope::Global,
51 )?;
52
53 Ok(())
54 }
55
56 pub fn tilde_shorten(&self, s: String) -> String {
63 if let Some(home_dir) = self.home_dir()
64 && let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref())
65 {
66 return format!("~{stripped}");
67 }
68 s
69 }
70
71 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
73 if let Some(home) = self.env.get_str("HOME", self) {
74 Some(PathBuf::from(home.to_string()))
75 } else {
76 users::get_current_user_home_dir()
78 }
79 }
80
81 pub fn find_executables_in_path<'a>(
88 &'a self,
89 filename: &'a str,
90 ) -> impl Iterator<Item = PathBuf> + 'a {
91 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
92 let paths = crate::sys::fs::split_paths(path_var.as_ref());
93
94 pathsearch::search_for_executable(paths, filename)
95 }
96
97 pub fn find_executables_in_path_with_prefix(
104 &self,
105 filename_prefix: &str,
106 case_insensitive: bool,
107 ) -> impl Iterator<Item = PathBuf> {
108 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
109 let paths = crate::sys::fs::split_paths(path_var.as_ref());
110
111 pathsearch::search_for_executable_with_prefix(paths, filename_prefix, case_insensitive)
112 }
113
114 pub fn find_first_executable_in_path<S: AsRef<str>>(
122 &self,
123 candidate_name: S,
124 ) -> Option<PathBuf> {
125 self.find_executables_in_path(candidate_name.as_ref())
126 .next()
127 }
128
129 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
138 &mut self,
139 candidate_name: S,
140 ) -> Option<PathBuf>
141 where
142 String: From<S>,
143 {
144 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
145 Some(cached_path)
146 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
147 self.program_location_cache
148 .set(candidate_name, found_path.clone());
149 Some(found_path)
150 } else {
151 None
152 }
153 }
154
155 pub fn resolve_command_in_path<S: AsRef<str>>(&self, candidate_name: S) -> Option<PathBuf> {
166 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
167 let paths = crate::sys::fs::split_paths(path_var.as_ref());
168 pathsearch::resolve_command(paths, candidate_name.as_ref())
169 }
170
171 pub fn resolve_command_in_path_using_cache<S: AsRef<str>>(
178 &mut self,
179 candidate_name: S,
180 ) -> Option<PathBuf>
181 where
182 String: From<S>,
183 {
184 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
185 return Some(cached_path);
186 }
187
188 let found_path = self.resolve_command_in_path(candidate_name.as_ref())?;
189 self.program_location_cache
190 .set(candidate_name, found_path.clone());
191
192 Some(found_path)
193 }
194
195 pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
201 let path = path.as_ref();
202 if path.as_os_str().is_empty() || path.is_absolute() {
203 path.to_owned()
204 } else {
205 self.working_dir().join(path)
206 }
207 }
208
209 pub(crate) fn open_file(
219 &self,
220 options: &std::fs::OpenOptions,
221 path: impl AsRef<Path>,
222 params: &ExecutionParameters,
223 ) -> Result<openfiles::OpenFile, std::io::Error> {
224 if let Some(result) = crate::sys::fs::try_open_special_file(path.as_ref()) {
229 return result.map(openfiles::OpenFile::from);
230 }
231
232 let path_to_open = self.absolute_path(path.as_ref());
233
234 if let Some(fd_num) = shell_fd_path_to_fd(&path_to_open)
238 && let Some(open_file) = params.try_fd(self, fd_num)
239 {
240 return Ok(open_file);
241 }
242
243 Ok(options.open(path_to_open)?.into())
244 }
245
246 pub fn replace_open_files(
253 &mut self,
254 open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
255 ) {
256 self.open_files = openfiles::OpenFiles::from(open_fds);
257 }
258
259 pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
260 &self.open_files
261 }
262}
263
264fn shell_fd_path_to_fd(path: &Path) -> Option<ShellFd> {
265 match path.to_str()? {
266 "/dev/stdin" => return Some(openfiles::OpenFiles::STDIN_FD),
267 "/dev/stdout" => return Some(openfiles::OpenFiles::STDOUT_FD),
268 "/dev/stderr" => return Some(openfiles::OpenFiles::STDERR_FD),
269 _ => {}
270 }
271
272 if let Some(parent) = path.parent()
273 && parent == Path::new("/dev/fd")
274 && let Some(filename) = path.file_name()
275 {
276 filename.to_string_lossy().parse::<ShellFd>().ok()
277 } else {
278 None
279 }
280}