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