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