1use std::borrow::Borrow;
2use std::collections::HashSet;
3use std::ffi::{CStr, CString, c_int};
4use std::hash::{Hash, Hasher};
5use std::ops::Deref;
6use std::{cmp, fmt, mem, process, ptr};
7
8use bitflags::bitflags;
9
10use crate::macros::*;
11use crate::traits::IntoWords;
12use crate::{Error, ExecStatus, bash, shell};
13
14mod _bash;
15pub mod profile;
16
17pub use _bash::*;
19
20pub type BuiltinFn = fn(&[&str]) -> crate::Result<ExecStatus>;
21pub type BuiltinFnPtr = unsafe extern "C" fn(list: *mut bash::WordList) -> c_int;
22
23bitflags! {
24 pub struct Attr: u32 {
26 const NONE = 0;
27 const ENABLED = bash::BUILTIN_ENABLED;
28 const DELETED = bash::BUILTIN_DELETED;
29 const STATIC = bash::STATIC_BUILTIN;
30 const SPECIAL = bash::SPECIAL_BUILTIN;
31 const ASSIGNMENT = bash::ASSIGNMENT_BUILTIN;
32 const POSIX = bash::POSIX_BUILTIN;
33 const LOCALVAR = bash::LOCALVAR_BUILTIN;
34 const ARRAYREF = bash::ARRAYREF_BUILTIN;
35 }
36}
37
38pub mod set {
39 use super::*;
40
41 pub fn enable<S: AsRef<str>>(opts: &[S]) -> crate::Result<ExecStatus> {
42 set(["-o"].into_iter().chain(opts.iter().map(|s| s.as_ref())))
43 }
44
45 pub fn disable<S: AsRef<str>>(opts: &[S]) -> crate::Result<ExecStatus> {
46 set(["+o"].into_iter().chain(opts.iter().map(|s| s.as_ref())))
47 }
48}
49
50pub mod shopt {
51 use super::*;
52
53 pub fn enable<S: AsRef<str>>(opts: &[S]) -> crate::Result<ExecStatus> {
54 shopt(["-s"].into_iter().chain(opts.iter().map(|s| s.as_ref())))
55 }
56
57 pub fn disable<S: AsRef<str>>(opts: &[S]) -> crate::Result<ExecStatus> {
58 shopt(["-u"].into_iter().chain(opts.iter().map(|s| s.as_ref())))
59 }
60}
61
62#[derive(Clone, Copy)]
63pub struct Builtin {
64 pub name: &'static str,
65 pub func: BuiltinFn,
66 pub flags: u32,
67 pub cfunc: BuiltinFnPtr,
68 pub help: &'static str,
69 pub usage: &'static str,
70}
71
72impl fmt::Debug for Builtin {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 f.debug_struct("Builtin").field("name", &self.name).finish()
75 }
76}
77
78impl fmt::Display for Builtin {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "{}", self.name)
81 }
82}
83
84impl PartialEq for Builtin {
85 fn eq(&self, other: &Self) -> bool {
86 self.name == other.name
87 }
88}
89
90impl Eq for Builtin {}
91
92impl Hash for Builtin {
93 fn hash<H: Hasher>(&self, state: &mut H) {
94 self.name.hash(state);
95 }
96}
97
98impl Ord for Builtin {
99 fn cmp(&self, other: &Self) -> cmp::Ordering {
100 self.name.cmp(other.name)
101 }
102}
103
104impl PartialOrd for Builtin {
105 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
106 Some(self.cmp(other))
107 }
108}
109
110impl AsRef<str> for Builtin {
111 fn as_ref(&self) -> &str {
112 self.name
113 }
114}
115
116impl Borrow<str> for Builtin {
117 fn borrow(&self) -> &str {
118 self.name
119 }
120}
121
122impl Deref for Builtin {
125 type Target = BuiltinFn;
126
127 fn deref(&self) -> &Self::Target {
128 &self.func
129 }
130}
131
132impl From<Builtin> for bash::Builtin {
134 fn from(builtin: Builtin) -> bash::Builtin {
135 let name_str = CString::new(builtin.name).unwrap();
136 let name = name_str.as_ptr() as *mut _;
137 mem::forget(name_str);
138
139 let short_doc_str = CString::new(builtin.usage).unwrap();
140 let short_doc = short_doc_str.as_ptr();
141 mem::forget(short_doc_str);
142
143 let long_docs = iter_to_array!(builtin.help.lines(), str_to_raw);
144 let long_doc = long_docs.as_ptr();
145 mem::forget(long_docs);
146
147 bash::Builtin {
148 name,
149 function: Some(builtin.cfunc),
150 flags: (builtin.flags | Attr::STATIC.bits()) as i32,
151 long_doc,
152 short_doc,
153 handle: ptr::null_mut(),
154 }
155 }
156}
157
158pub fn override_funcs<I>(builtins: I, enable: bool) -> crate::Result<()>
160where
161 I: IntoIterator,
162 I::Item: AsRef<str>,
163{
164 for name in builtins {
165 let name = name.as_ref();
166 let builtin_name = CString::new(name).unwrap();
167 let builtin_ptr = builtin_name.as_ptr() as *mut _;
168 match unsafe { bash::builtin_address_internal(builtin_ptr, 1).as_mut() } {
169 Some(b) => {
170 if enable {
171 b.flags |= Attr::SPECIAL.bits() as i32;
172 } else {
173 b.flags &= !Attr::SPECIAL.bits() as i32;
174 }
175 }
176 None => {
177 return Err(Error::Base(format!("failed overriding unknown builtin: {name}")));
178 }
179 }
180 }
181
182 Ok(())
183}
184
185fn toggle_status<I>(builtins: I, enable: bool) -> crate::Result<Vec<String>>
187where
188 I: IntoIterator,
189 I::Item: AsRef<str>,
190{
191 let mut toggled = vec![];
192
193 for name in builtins {
194 let name = name.as_ref();
195 let builtin_name = CString::new(name).unwrap();
196 let builtin_ptr = builtin_name.as_ptr() as *mut _;
197 match unsafe { bash::builtin_address_internal(builtin_ptr, 1).as_mut() } {
198 Some(b) => {
199 let enabled = (b.flags & Attr::ENABLED.bits() as i32) == 1;
200 if enabled != enable {
201 if enable {
202 b.flags |= Attr::ENABLED.bits() as i32;
203 } else {
204 b.flags &= !Attr::ENABLED.bits() as i32;
205 }
206 toggled.push(name.to_string());
207 }
208 }
209 None => return Err(Error::Base(format!("unknown builtin: {name}"))),
210 }
211 }
212
213 Ok(toggled)
214}
215
216pub fn disable<I>(builtins: I) -> crate::Result<Vec<String>>
218where
219 I: IntoIterator,
220 I::Item: AsRef<str>,
221{
222 toggle_status(builtins, false)
223}
224
225pub fn enable<I>(builtins: I) -> crate::Result<Vec<String>>
227where
228 I: IntoIterator,
229 I::Item: AsRef<str>,
230{
231 toggle_status(builtins, true)
232}
233
234pub fn shell_builtins() -> (HashSet<String>, HashSet<String>) {
236 let mut enabled = HashSet::new();
237 let mut disabled = HashSet::new();
238 unsafe {
239 let end = (bash::NUM_SHELL_BUILTINS - 1) as isize;
240 for i in 0..end {
241 let builtin = *bash::SHELL_BUILTINS.offset(i);
242 if builtin.function.is_some() {
244 let name = String::from(CStr::from_ptr(builtin.name).to_str().unwrap());
245 if (builtin.flags & Attr::ENABLED.bits() as i32) == 1 {
246 enabled.insert(name);
247 } else {
248 disabled.insert(name);
249 }
250 }
251 }
252 }
253 (enabled, disabled)
254}
255
256pub fn register<I>(builtins: I)
258where
259 I: IntoIterator,
260 I::Item: Into<Builtin>,
261{
262 let mut builtin_ptrs: Vec<_> = builtins
264 .into_iter()
265 .map(Into::into)
266 .map(|b| Box::into_raw(Box::new(b.into())))
267 .collect();
268
269 unsafe {
270 bash::register_builtins(builtin_ptrs.as_mut_ptr(), builtin_ptrs.len());
272
273 for b in builtin_ptrs {
275 mem::drop(Box::from_raw(b));
276 }
277 }
278}
279
280#[derive(Debug, Default)]
282pub struct ScopedBuiltins {
283 names: Vec<String>,
284 enabled: bool,
285}
286
287impl ScopedBuiltins {
288 pub fn disable<I>(values: I) -> crate::Result<Self>
289 where
290 I: IntoIterator,
291 I::Item: AsRef<str>,
292 {
293 Ok(Self {
294 names: disable(values)?,
295 enabled: false,
296 })
297 }
298
299 pub fn enable<I>(values: I) -> crate::Result<Self>
300 where
301 I: IntoIterator,
302 I::Item: AsRef<str>,
303 {
304 Ok(Self {
305 names: enable(values)?,
306 enabled: true,
307 })
308 }
309}
310
311impl Drop for ScopedBuiltins {
312 fn drop(&mut self) {
313 if self.enabled {
314 disable(&self.names).unwrap_or_else(|_| panic!("failed disabling builtins"));
315 } else {
316 enable(&self.names).unwrap_or_else(|_| panic!("failed enabling builtins"));
317 }
318 }
319}
320
321#[derive(Debug, Default)]
323pub struct ScopedOptions {
324 shopt_enabled: Vec<String>,
325 shopt_disabled: Vec<String>,
326 set_enabled: Vec<String>,
327 set_disabled: Vec<String>,
328}
329
330impl ScopedOptions {
331 pub fn enable<'a, I>(&mut self, options: I) -> crate::Result<()>
333 where
334 I: IntoIterator<Item = &'a str>,
335 {
336 let enabled_shopt = bash::shopt_opts();
337 let enabled_set = bash::set_opts();
338
339 for opt in options {
340 if bash::SET_OPTS.contains(opt) {
341 if !enabled_set.contains(opt) {
342 set::enable(&[opt])?;
343 self.set_enabled.push(opt.into());
344 }
345 } else if bash::SHOPT_OPTS.contains(opt) {
346 if !enabled_shopt.contains(opt) {
347 shopt::enable(&[opt])?;
348 self.shopt_enabled.push(opt.into());
349 }
350 } else {
351 return Err(Error::Base(format!("unknown option: {opt}")));
352 }
353 }
354
355 Ok(())
356 }
357
358 pub fn disable<'a, I>(&mut self, options: I) -> crate::Result<()>
360 where
361 I: IntoIterator<Item = &'a str>,
362 {
363 let enabled_shopt = bash::shopt_opts();
364 let enabled_set = bash::set_opts();
365
366 for opt in options {
367 if bash::SET_OPTS.contains(opt) {
368 if enabled_set.contains(opt) {
369 set::disable(&[opt])?;
370 self.set_disabled.push(opt.into());
371 }
372 } else if bash::SHOPT_OPTS.contains(opt) {
373 if enabled_shopt.contains(opt) {
374 shopt::disable(&[opt])?;
375 self.shopt_disabled.push(opt.into());
376 }
377 } else {
378 return Err(Error::Base(format!("unknown option: {opt}")));
379 }
380 }
381
382 Ok(())
383 }
384}
385
386impl Drop for ScopedOptions {
387 fn drop(&mut self) {
388 if !self.shopt_enabled.is_empty() {
389 shopt::disable(&self.shopt_enabled).expect("failed unsetting shopt options");
390 }
391
392 if !self.shopt_disabled.is_empty() {
393 shopt::enable(&self.shopt_disabled).expect("failed setting shopt options");
394 }
395
396 if !self.set_enabled.is_empty() {
397 set::disable(&self.set_enabled).expect("failed unsetting set options");
398 }
399
400 if !self.set_disabled.is_empty() {
401 set::enable(&self.set_disabled).expect("failed setting set options");
402 }
403 }
404}
405
406pub fn handle_error<S: AsRef<str>>(cmd: S, err: Error) -> ExecStatus {
408 let lineno = shell::executing_line_number();
410 let msg = match cmd.as_ref() {
411 "command_not_found_handle" => format!("line {lineno}: {err}"),
412 s => format!("line {lineno}: {s}: error: {err}"),
413 };
414
415 let bail = matches!(err, Error::Bail(_));
416 shell::set_shm_error(&msg, bail);
418
419 if bail && !shell::in_main() {
421 process::exit(bash::EX_LONGJMP as i32);
422 }
423
424 ExecStatus::from(err)
425}
426
427fn run(builtin: &Builtin, args: *mut bash::WordList) -> ExecStatus {
429 let args = args.to_words();
431 let args: Result<Vec<_>, _> = args.into_iter().collect();
432
433 let result = match args {
435 Ok(args) => builtin(&args),
436 Err(e) => Err(Error::Base(format!("invalid args: {e}"))),
437 };
438
439 result.unwrap_or_else(|e| handle_error(builtin, e))
441}
442
443#[macro_export]
445macro_rules! make_builtin {
446 ($name:expr, $func_name:ident, $func:expr, $long_doc:expr, $usage:expr) => {
447 use std::ffi::c_int;
448
449 use $crate::builtins::Builtin;
450
451 #[unsafe(no_mangle)]
452 extern "C" fn $func_name(args: *mut $crate::bash::WordList) -> c_int {
453 i32::from($crate::builtins::run(&BUILTIN, args))
454 }
455
456 pub static BUILTIN: Builtin = Builtin {
457 name: $name,
458 func: $func,
459 flags: 0,
460 cfunc: $func_name,
461 help: $long_doc,
462 usage: $usage,
463 };
464 };
465}
466pub use make_builtin;
467
468#[cfg(test)]
469mod tests {
470 use crate::{source, variables};
471
472 use super::*;
473
474 #[test]
475 fn toggle_builtins() {
476 let (enabled, disabled) = shell_builtins();
478 assert!(!enabled.is_empty());
479 let builtin = enabled.iter().next().unwrap();
480 assert!(!disabled.contains(builtin));
481
482 disable([builtin]).unwrap();
484 let (enabled, disabled) = shell_builtins();
485 assert!(!enabled.contains(builtin));
486 assert!(disabled.contains(builtin));
487
488 enable([builtin]).unwrap();
490 let (enabled, disabled) = shell_builtins();
491 assert!(enabled.contains(builtin));
492 assert!(!disabled.contains(builtin));
493
494 assert!(enable(["nonexistent"]).is_err());
496 assert!(disable(["nonexistent"]).is_err());
497 }
498
499 #[test]
500 fn toggle_overrides() {
501 variables::bind_global("VAR", "1", None, None).unwrap();
502
503 source::string("declare() { (( VAR += 1 )); }").unwrap();
505 source::string("declare").unwrap();
506 assert_eq!(variables::optional("VAR").unwrap(), "2");
507
508 override_funcs(["declare"], true).unwrap();
510 source::string("declare").unwrap();
511 assert_eq!(variables::optional("VAR").unwrap(), "2");
512
513 override_funcs(["declare"], false).unwrap();
515 source::string("declare").unwrap();
516 assert_eq!(variables::optional("VAR").unwrap(), "3");
517
518 assert!(override_funcs(["nonexistent"], true).is_err());
520 }
521
522 #[test]
523 fn scoped_builtins() {
524 assert!(source::string("declare").is_ok());
525 let _builtins = ScopedBuiltins::disable(["declare"]).unwrap();
526 assert!(source::string("declare").is_err());
527 let _builtins = ScopedBuiltins::enable(["declare"]).unwrap();
528 assert!(source::string("declare").is_ok());
529 }
530
531 #[test]
532 fn scoped_options() {
533 let mut opts = ScopedOptions::default();
535 assert!(opts.enable(["unknown"]).is_err());
536 assert!(opts.disable(["unknown"]).is_err());
537
538 let (enable, disable) = ("autocd", "sourcepath");
540 shopt::disable(&[enable]).unwrap();
541 shopt::enable(&[disable]).unwrap();
542
543 assert!(!bash::shopt_opts().contains(enable));
544 assert!(bash::shopt_opts().contains(disable));
545 {
546 let mut opts = ScopedOptions::default();
547 opts.enable([enable]).unwrap();
549 opts.enable([enable]).unwrap();
550 opts.disable([disable]).unwrap();
551 opts.disable([disable]).unwrap();
552 assert!(bash::shopt_opts().contains(enable));
553 assert!(!bash::shopt_opts().contains(disable));
554 }
555 assert!(!bash::shopt_opts().contains(enable));
556 assert!(bash::shopt_opts().contains(disable));
557
558 let (enable, disable) = ("noglob", "verbose");
560 set::disable(&[enable]).unwrap();
561 set::enable(&[disable]).unwrap();
562
563 assert!(!bash::set_opts().contains(enable));
564 assert!(bash::set_opts().contains(disable));
565 {
566 let mut opts = ScopedOptions::default();
567 opts.enable([enable]).unwrap();
569 opts.enable([enable]).unwrap();
570 opts.disable([disable]).unwrap();
571 opts.disable([disable]).unwrap();
572 assert!(bash::set_opts().contains(enable));
573 assert!(!bash::set_opts().contains(disable));
574 }
575 assert!(!bash::set_opts().contains(enable));
576 assert!(bash::set_opts().contains(disable));
577 }
578}