1use super::args::InitFile;
31use std::cell::RefCell;
32use std::ffi::CString;
33use std::rc::Rc;
34use thiserror::Error;
35use yash_env::Env;
36use yash_env::input::{Echo, FdReader};
37use yash_env::io::Fd;
38use yash_env::io::move_fd_internal;
39use yash_env::option::Option::Interactive;
40use yash_env::option::State::Off;
41use yash_env::parser::Config;
42use yash_env::stack::Frame;
43use yash_env::system::{
44 Close, Dup, Errno, Fcntl, GetUid, Isatty, Mode, OfdAccess, Open, OpenFlag, Write,
45};
46use yash_env::variable::ENV;
47use yash_semantics::expansion::expand_text;
48use yash_semantics::read_eval_loop;
49use yash_semantics::{Handle, Runtime};
50use yash_syntax::parser::lex::Lexer;
51use yash_syntax::source::Source;
52
53#[derive(Clone, Debug, Error, PartialEq)]
55#[error(transparent)]
56pub enum DefaultFilePathError {
57 ParseError(#[from] yash_syntax::parser::Error),
60 ExpansionError(#[from] yash_semantics::expansion::Error),
63}
64
65impl<S> Handle<S> for DefaultFilePathError
66where
67 S: Fcntl + Isatty + Write,
68{
69 async fn handle(&self, env: &mut Env<S>) -> yash_semantics::Result {
70 match self {
71 DefaultFilePathError::ParseError(e) => e.handle(env).await,
72 DefaultFilePathError::ExpansionError(e) => e.handle(env).await,
73 }
74 }
75}
76
77pub async fn default_rcfile_path<S>(env: &mut Env<S>) -> Result<String, DefaultFilePathError>
96where
97 S: Runtime + 'static,
98{
99 let raw_value = env.variables.get_scalar(ENV).unwrap_or_default();
100
101 let text = {
102 let name = ENV.to_owned();
103 let source = Source::VariableValue { name };
104 let mut lexer = Lexer::from_memory(raw_value, source);
105 lexer.text(|_| false, |_| false).await?
106 };
107
108 Ok(expand_text(env, &text).await?.0)
109}
110
111pub async fn resolve_rcfile_path<S>(
125 env: &mut Env<S>,
126 file: InitFile,
127) -> Result<String, DefaultFilePathError>
128where
129 S: GetUid + Runtime + 'static,
130{
131 if file == InitFile::None
132 || env.options.get(Interactive) == Off
133 || env.system.getuid() != env.system.geteuid()
134 || env.system.getgid() != env.system.getegid()
135 {
136 return Ok(String::default());
137 }
138
139 match file {
140 InitFile::None => unreachable!(),
141 InitFile::Default => default_rcfile_path(env).await,
142 InitFile::File { path } => Ok(path),
143 }
144}
145
146pub async fn run_init_file<S>(env: &mut Env<S>, path: &str)
154where
155 S: Runtime + 'static,
156{
157 if path.is_empty() {
158 return;
159 }
160
161 fn open_fd<S>(system: &mut S, path: String) -> Result<Fd, Errno>
162 where
163 S: Close + Dup + Open,
164 {
165 let c_path = CString::new(path).map_err(|_| Errno::EILSEQ)?;
166 let fd = system.open(
167 &c_path,
168 OfdAccess::ReadOnly,
169 OpenFlag::CloseOnExec.into(),
170 Mode::empty(),
171 )?;
172 move_fd_internal(system, fd)
173 }
174
175 let fd = match open_fd(&mut env.system, path.to_owned()) {
176 Ok(fd) => fd,
177 Err(errno) => {
178 env.system
179 .print_error(&format!(
180 "{}: cannot open initialization file {path:?}: {errno}\n",
181 &env.arg0
182 ))
183 .await;
184 return;
185 }
186 };
187
188 let env = &mut *env.push_frame(Frame::InitFile);
189 let system = env.system.clone();
190 let ref_env = RefCell::new(&mut *env);
191 let input = Box::new(Echo::new(FdReader::new(fd, system), &ref_env));
192 let mut config = Config::with_input(input);
193 config.source = Some(Rc::new(Source::InitFile {
194 path: path.to_owned(),
195 }));
196 let mut lexer = config.into();
197 _ = read_eval_loop(&ref_env, &mut { lexer }).await;
198
199 if let Err(errno) = env.system.close(fd) {
200 env.system
201 .print_error(&format!(
202 "{}: cannot close initialization file {path:?}: {errno}\n",
203 &env.arg0
204 ))
205 .await;
206 }
207}
208
209pub async fn run_rcfile<S>(env: &mut Env<S>, file: InitFile)
215where
216 S: GetUid + Runtime + 'static,
217{
218 match resolve_rcfile_path(env, file).await {
219 Ok(path) => run_init_file(env, &path).await,
220 Err(e) => drop(e.handle(env).await),
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227 use assert_matches::assert_matches;
228 use futures_util::FutureExt as _;
229 use yash_env::VirtualSystem;
230 use yash_env::option::State::On;
231 use yash_env::system::{Gid, Uid};
232 use yash_env::variable::Scope::Global;
233
234 #[test]
235 fn default_rcfile_path_with_unset_env() {
236 let mut env = Env::new_virtual();
237 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
238 assert_eq!(result.unwrap(), "");
239 }
240
241 #[test]
242 fn default_rcfile_path_with_empty_env() {
243 let mut env = Env::new_virtual();
244 env.variables
245 .get_or_new(ENV, Global)
246 .assign("", None)
247 .unwrap();
248 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
249 assert_eq!(result.unwrap(), "");
250 }
251
252 #[test]
253 fn default_rcfile_path_with_env_without_expansion() {
254 let mut env = Env::new_virtual();
255 env.variables
256 .get_or_new(ENV, Global)
257 .assign("foo", None)
258 .unwrap();
259 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
260 assert_eq!(result.unwrap(), "foo");
261 }
262
263 #[test]
264 fn default_rcfile_path_with_env_with_unparsable_expansion() {
265 let mut env = Env::new_virtual();
266 env.variables
267 .get_or_new(ENV, Global)
268 .assign("foo${bar", None)
269 .unwrap();
270 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
271 assert_matches!(result, Err(DefaultFilePathError::ParseError(_)));
272 }
273
274 #[test]
275 fn default_rcfile_path_with_env_with_failing_expansion() {
276 let mut env = Env::new_virtual();
277 env.variables
278 .get_or_new(ENV, Global)
279 .assign("${unset?}", None)
280 .unwrap();
281 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
282 assert_matches!(result, Err(DefaultFilePathError::ExpansionError(_)));
283 }
284
285 #[test]
286 fn resolve_rcfile_path_none() {
287 let mut env = Env::new_virtual();
288 env.options.set(Interactive, On);
289 let result = resolve_rcfile_path(&mut env, InitFile::None)
290 .now_or_never()
291 .unwrap();
292 assert_eq!(result.unwrap(), "");
293 }
294
295 #[test]
296 fn resolve_rcfile_path_default() {
297 let mut env = Env::new_virtual();
298 env.options.set(Interactive, On);
299 env.variables
300 .get_or_new(ENV, Global)
301 .assign("foo/bar", None)
302 .unwrap();
303 let result = resolve_rcfile_path(&mut env, InitFile::Default)
304 .now_or_never()
305 .unwrap();
306 assert_eq!(result.unwrap(), "foo/bar");
307 }
308
309 #[test]
310 fn resolve_rcfile_path_exact() {
311 let mut env = Env::new_virtual();
312 env.options.set(Interactive, On);
313 let path = "/path/to/rcfile".to_string();
314 let file = InitFile::File { path };
315 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
316 assert_eq!(result.unwrap(), "/path/to/rcfile");
317 }
318
319 #[test]
320 fn resolve_rcfile_path_non_interactive() {
321 let mut env = Env::new_virtual();
322 env.options.set(Interactive, Off);
323 let path = "/path/to/rcfile".to_string();
324 let file = InitFile::File { path };
325 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
326 assert_eq!(result.unwrap(), "");
327 }
328
329 #[test]
330 fn resolve_rcfile_path_non_real_user() {
331 let system = VirtualSystem::new();
332 system.current_process_mut().set_uid(Uid(0));
333 system.current_process_mut().set_euid(Uid(10));
334 let mut env = Env::with_system(system);
335 env.options.set(Interactive, On);
336 let path = "/path/to/rcfile".to_string();
337 let file = InitFile::File { path };
338 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
339 assert_eq!(result.unwrap(), "");
340 }
341
342 #[test]
343 fn resolve_rcfile_path_non_real_group() {
344 let system = VirtualSystem::new();
345 system.current_process_mut().set_gid(Gid(0));
346 system.current_process_mut().set_egid(Gid(10));
347 let mut env = Env::with_system(system);
348 env.options.set(Interactive, On);
349 let path = "/path/to/rcfile".to_string();
350 let file = InitFile::File { path };
351 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
352 assert_eq!(result.unwrap(), "");
353 }
354}