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