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