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::{
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 async fn open_fd<S>(system: &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
167 .open(
168 &c_path,
169 OfdAccess::ReadOnly,
170 OpenFlag::CloseOnExec.into(),
171 Mode::empty(),
172 )
173 .await?;
174 move_fd_internal(system, fd)
175 }
176
177 let fd = match open_fd(&env.system, path.to_owned()).await {
178 Ok(fd) => fd,
179 Err(errno) => {
180 env.system
181 .print_error(&format!(
182 "{}: cannot open initialization file {path:?}: {errno}\n",
183 &env.arg0
184 ))
185 .await;
186 return;
187 }
188 };
189
190 let env = &mut *env.push_frame(Frame::InitFile);
191 let system = env.system.clone();
192 let ref_env = RefCell::new(&mut *env);
193 let input = Box::new(Echo::new(FdReader2::new(fd, system), &ref_env));
194 let mut config = Config::with_input(input);
195 config.source = Some(Rc::new(Source::InitFile {
196 path: path.to_owned(),
197 }));
198 let mut lexer = config.into();
199 _ = read_eval_loop(&ref_env, &mut { lexer }).await;
200
201 if let Err(errno) = env.system.close(fd) {
202 env.system
203 .print_error(&format!(
204 "{}: cannot close initialization file {path:?}: {errno}\n",
205 &env.arg0
206 ))
207 .await;
208 }
209}
210
211pub async fn run_rcfile<S>(env: &mut Env<S>, file: InitFile)
217where
218 S: GetUid + Runtime + 'static,
219{
220 match resolve_rcfile_path(env, file).await {
221 Ok(path) => run_init_file(env, &path).await,
222 Err(e) => drop(e.handle(env).await),
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use assert_matches::assert_matches;
230 use futures_util::FutureExt as _;
231 use yash_env::VirtualSystem;
232 use yash_env::option::State::On;
233 use yash_env::system::{Gid, Uid};
234 use yash_env::variable::Scope::Global;
235
236 #[test]
237 fn default_rcfile_path_with_unset_env() {
238 let mut env = Env::new_virtual();
239 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
240 assert_eq!(result.unwrap(), "");
241 }
242
243 #[test]
244 fn default_rcfile_path_with_empty_env() {
245 let mut env = Env::new_virtual();
246 env.variables
247 .get_or_new(ENV, Global)
248 .assign("", None)
249 .unwrap();
250 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
251 assert_eq!(result.unwrap(), "");
252 }
253
254 #[test]
255 fn default_rcfile_path_with_env_without_expansion() {
256 let mut env = Env::new_virtual();
257 env.variables
258 .get_or_new(ENV, Global)
259 .assign("foo", None)
260 .unwrap();
261 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
262 assert_eq!(result.unwrap(), "foo");
263 }
264
265 #[test]
266 fn default_rcfile_path_with_env_with_unparsable_expansion() {
267 let mut env = Env::new_virtual();
268 env.variables
269 .get_or_new(ENV, Global)
270 .assign("foo${bar", None)
271 .unwrap();
272 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
273 assert_matches!(result, Err(DefaultFilePathError::ParseError(_)));
274 }
275
276 #[test]
277 fn default_rcfile_path_with_env_with_failing_expansion() {
278 let mut env = Env::new_virtual();
279 env.variables
280 .get_or_new(ENV, Global)
281 .assign("${unset?}", None)
282 .unwrap();
283 let result = default_rcfile_path(&mut env).now_or_never().unwrap();
284 assert_matches!(result, Err(DefaultFilePathError::ExpansionError(_)));
285 }
286
287 #[test]
288 fn resolve_rcfile_path_none() {
289 let mut env = Env::new_virtual();
290 env.options.set(Interactive, On);
291 let result = resolve_rcfile_path(&mut env, InitFile::None)
292 .now_or_never()
293 .unwrap();
294 assert_eq!(result.unwrap(), "");
295 }
296
297 #[test]
298 fn resolve_rcfile_path_default() {
299 let mut env = Env::new_virtual();
300 env.options.set(Interactive, On);
301 env.variables
302 .get_or_new(ENV, Global)
303 .assign("foo/bar", None)
304 .unwrap();
305 let result = resolve_rcfile_path(&mut env, InitFile::Default)
306 .now_or_never()
307 .unwrap();
308 assert_eq!(result.unwrap(), "foo/bar");
309 }
310
311 #[test]
312 fn resolve_rcfile_path_exact() {
313 let mut env = Env::new_virtual();
314 env.options.set(Interactive, On);
315 let path = "/path/to/rcfile".to_string();
316 let file = InitFile::File { path };
317 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
318 assert_eq!(result.unwrap(), "/path/to/rcfile");
319 }
320
321 #[test]
322 fn resolve_rcfile_path_non_interactive() {
323 let mut env = Env::new_virtual();
324 env.options.set(Interactive, Off);
325 let path = "/path/to/rcfile".to_string();
326 let file = InitFile::File { path };
327 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
328 assert_eq!(result.unwrap(), "");
329 }
330
331 #[test]
332 fn resolve_rcfile_path_non_real_user() {
333 let system = VirtualSystem::new();
334 system.current_process_mut().set_uid(Uid(0));
335 system.current_process_mut().set_euid(Uid(10));
336 let mut env = Env::with_system(system);
337 env.options.set(Interactive, On);
338 let path = "/path/to/rcfile".to_string();
339 let file = InitFile::File { path };
340 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
341 assert_eq!(result.unwrap(), "");
342 }
343
344 #[test]
345 fn resolve_rcfile_path_non_real_group() {
346 let system = VirtualSystem::new();
347 system.current_process_mut().set_gid(Gid(0));
348 system.current_process_mut().set_egid(Gid(10));
349 let mut env = Env::with_system(system);
350 env.options.set(Interactive, On);
351 let path = "/path/to/rcfile".to_string();
352 let file = InitFile::File { path };
353 let result = resolve_rcfile_path(&mut env, file).now_or_never().unwrap();
354 assert_eq!(result.unwrap(), "");
355 }
356}