1use std::path::{Path, PathBuf};
4
5use normalize_path::NormalizePath as _;
6
7use crate::{
8 ExecutionParameters, ShellFd,
9 env::{EnvironmentLookup, EnvironmentScope},
10 error, openfiles, pathsearch,
11 sys::{fs::PathExt as _, users},
12 variables,
13};
14
15impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
16 pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
22 let abs_path = self.absolute_path(target_dir.as_ref());
23
24 match std::fs::metadata(&abs_path) {
25 Ok(m) => {
26 if !m.is_dir() {
27 return Err(error::ErrorKind::NotADirectory(abs_path).into());
28 }
29 }
30 Err(e) => {
31 return Err(e.into());
32 }
33 }
34
35 let cleaned_path = abs_path.normalize();
37
38 let pwd = cleaned_path.to_string_lossy().to_string();
39
40 self.env.update_or_add(
41 "PWD",
42 variables::ShellValueLiteral::Scalar(pwd),
43 |_| Ok(()),
44 EnvironmentLookup::Anywhere,
45 EnvironmentScope::Global,
46 )?;
47 let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
48
49 self.env.update_or_add(
50 "OLDPWD",
51 variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
52 |_| Ok(()),
53 EnvironmentLookup::Anywhere,
54 EnvironmentScope::Global,
55 )?;
56
57 Ok(())
58 }
59
60 pub fn tilde_shorten(&self, s: String) -> String {
66 if let Some(home_dir) = self.home_dir()
67 && let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref())
68 {
69 return format!("~{stripped}");
70 }
71 s
72 }
73
74 pub(crate) fn home_dir(&self) -> Option<PathBuf> {
76 if let Some(home) = self.env.get_str("HOME", self) {
77 Some(PathBuf::from(home.to_string()))
78 } else {
79 users::get_current_user_home_dir()
81 }
82 }
83
84 pub fn find_executables_in_path<'a>(
90 &'a self,
91 filename: &'a str,
92 ) -> impl Iterator<Item = PathBuf> + 'a {
93 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
94 let paths = crate::sys::fs::split_paths(path_var.as_ref());
95
96 pathsearch::search_for_executable(paths, filename)
97 }
98
99 pub fn find_executables_in_path_with_prefix(
106 &self,
107 filename_prefix: &str,
108 case_insensitive: bool,
109 ) -> impl Iterator<Item = PathBuf> {
110 let path_var = self.env.get_str("PATH", self).unwrap_or_default();
111 let paths = crate::sys::fs::split_paths(path_var.as_ref());
112
113 pathsearch::search_for_executable_with_prefix(paths, filename_prefix, case_insensitive)
114 }
115
116 pub fn find_first_executable_in_path<S: AsRef<str>>(
123 &self,
124 candidate_name: S,
125 ) -> Option<PathBuf> {
126 let path = self.env_str("PATH").unwrap_or_default();
127 for one_dir in crate::sys::fs::split_paths(path.as_ref()) {
128 let candidate_path = one_dir.join(candidate_name.as_ref());
129 if candidate_path.executable() {
130 return Some(candidate_path);
131 }
132 }
133 None
134 }
135
136 pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
144 &mut self,
145 candidate_name: S,
146 ) -> Option<PathBuf>
147 where
148 String: From<S>,
149 {
150 if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
151 Some(cached_path)
152 } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
153 self.program_location_cache
154 .set(candidate_name, found_path.clone());
155 Some(found_path)
156 } else {
157 None
158 }
159 }
160
161 pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
167 let path = path.as_ref();
168 if path.as_os_str().is_empty() || path.is_absolute() {
169 path.to_owned()
170 } else {
171 self.working_dir().join(path)
172 }
173 }
174
175 pub(crate) fn open_file(
183 &self,
184 options: &std::fs::OpenOptions,
185 path: impl AsRef<Path>,
186 params: &ExecutionParameters,
187 ) -> Result<openfiles::OpenFile, std::io::Error> {
188 if let Some(result) = crate::sys::fs::try_open_special_file(path.as_ref()) {
193 return result.map(openfiles::OpenFile::from);
194 }
195
196 let path_to_open = self.absolute_path(path.as_ref());
197
198 if let Some(parent) = path_to_open.parent()
202 && parent == Path::new("/dev/fd")
203 && let Some(filename) = path_to_open.file_name()
204 && let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<ShellFd>()
205 && let Some(open_file) = params.try_fd(self, fd_num)
206 {
207 return open_file.try_clone();
208 }
209
210 Ok(options.open(path_to_open)?.into())
211 }
212
213 pub fn replace_open_files(
220 &mut self,
221 open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
222 ) {
223 self.open_files = openfiles::OpenFiles::from(open_fds);
224 }
225
226 pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
227 &self.open_files
228 }
229}