1use std::ffi::CString;
4use std::sync::Arc;
5
6use itertools::Itertools;
7
8use crate::{CreateOptions, extensions, namedoptions};
9
10#[derive(Clone, Default)]
18pub struct SpawnOptions {
19 pub cgroup_procs: Option<Arc<CString>>,
25 pub on_spawn: Option<Arc<dyn Fn(SpawnedProcess) + Send + Sync>>,
28}
29
30impl std::fmt::Debug for SpawnOptions {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.debug_struct("SpawnOptions")
33 .field("cgroup_procs", &self.cgroup_procs)
34 .field("on_spawn", &self.on_spawn.as_ref().map(|_| "Fn"))
35 .finish()
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct SpawnedProcess {
42 pub pid: i32,
44 pub process_group: Option<i32>,
46}
47
48#[derive(Clone, Default)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[expect(clippy::module_name_repetitions)]
52pub struct RuntimeOptions {
53 pub export_variables_on_modification: bool,
57 pub notify_job_termination_immediately: bool,
59 pub exit_on_nonzero_command_exit: bool,
61 pub disable_filename_globbing: bool,
63 pub remember_command_locations: bool,
65 pub place_all_assignment_args_in_command_env: bool,
67 pub enable_job_control: bool,
69 pub do_not_execute_commands: bool,
71 pub real_effective_uid_mismatch: bool,
73 pub exit_after_one_command: bool,
75 pub treat_unset_variables_as_error: bool,
77 pub print_shell_input_lines: bool,
79 pub print_commands_and_arguments: bool,
81 pub perform_brace_expansion: bool,
83 pub disallow_overwriting_regular_files_via_output_redirection: bool,
85 pub shell_functions_inherit_err_trap: bool,
87 pub enable_bang_style_history_substitution: bool,
89 pub do_not_resolve_symlinks_when_changing_dir: bool,
91 pub shell_functions_inherit_debug_and_return_traps: bool,
93
94 pub emacs_mode: bool,
98 pub enable_command_history: bool,
100 pub ignore_eof: bool,
102 pub return_last_failure_from_pipeline: bool,
104 pub posix_mode: bool,
106 pub vi_mode: bool,
108
109 pub array_expand_once: bool,
113 pub assoc_expand_once: bool,
115 pub auto_cd: bool,
117 pub bash_source_full_path: bool,
119 pub cdable_vars: bool,
121 pub cd_autocorrect_spelling: bool,
123 pub check_hashtable_before_command_exec: bool,
125 pub check_jobs_before_exit: bool,
127 pub check_window_size_after_external_commands: bool,
129 pub save_multiline_cmds_in_history: bool,
131 pub compat31: bool,
133 pub compat32: bool,
135 pub compat40: bool,
137 pub compat41: bool,
139 pub compat42: bool,
141 pub compat43: bool,
143 pub compat44: bool,
145 pub quote_all_metachars_in_completion: bool,
147 pub expand_dir_names_on_completion: bool,
149 pub autocorrect_dir_spelling_on_completion: bool,
151 pub glob_matches_dotfiles: bool,
153 pub exit_on_exec_fail: bool,
155 pub expand_aliases: bool,
157 pub enable_debugger: bool,
159 pub extended_globbing: bool,
161 pub extquote: bool,
163 pub fail_expansion_on_globs_without_match: bool,
165 pub force_fignore: bool,
167 pub glob_ranges_use_c_locale: bool,
169 pub glob_skip_dots: bool,
171 pub enable_star_star_glob: bool,
173 pub errors_in_gnu_format: bool,
175 pub append_to_history_file: bool,
177 pub allow_reedit_failed_history_subst: bool,
179 pub allow_modifying_history_substitution: bool,
181 pub enable_hostname_completion: bool,
183 pub send_sighup_to_all_jobs_on_exit: bool,
185 pub command_subst_inherits_errexit: bool,
187 pub interactive_comments: bool,
189 pub run_last_pipeline_cmd_in_current_shell: bool,
191 pub embed_newlines_in_multiline_cmds_in_history: bool,
193 pub local_vars_inherit_value_and_attrs: bool,
195 pub localvar_unset: bool,
197 pub login_shell: bool,
199 pub mail_warn: bool,
201 pub no_empty_cmd_completion: bool,
203 pub case_insensitive_pathname_expansion: bool,
205 pub case_insensitive_conditionals: bool,
207 pub no_expand_translation: bool,
209 pub expand_non_matching_patterns_to_null: bool,
211 pub patsub_replacement: bool,
213 pub programmable_completion: bool,
215 pub programmable_completion_alias: bool,
217 pub expand_prompt_strings: bool,
219 pub restricted_shell: bool,
221 pub shift_verbose: bool,
223 pub source_builtin_searches_path: bool,
225 pub var_redir_close: bool,
227 pub echo_builtin_expands_escape_sequences: bool,
229
230 pub interactive: bool,
234 pub read_commands_from_stdin: bool,
236 pub command_string_mode: bool,
238 pub sh_mode: bool,
240 pub external_cmd_leads_session: bool,
242 pub kill_external_commands_on_drop: bool,
245 #[cfg_attr(feature = "serde", serde(skip))]
248 pub spawn: SpawnOptions,
249 pub max_function_call_depth: Option<usize>,
251}
252
253impl RuntimeOptions {
254 pub fn defaults_from<SE: extensions::ShellExtensions>(
261 create_options: &CreateOptions<SE>,
262 ) -> Self {
263 let mut options = Self {
265 interactive: create_options.interactive,
266 disallow_overwriting_regular_files_via_output_redirection: create_options
267 .disallow_overwriting_regular_files_via_output_redirection,
268 do_not_execute_commands: create_options.do_not_execute_commands,
269 enable_command_history: create_options.interactive,
270 enable_job_control: create_options.interactive,
271 exit_after_one_command: create_options.exit_after_one_command,
272 read_commands_from_stdin: create_options.read_commands_from_stdin,
273 command_string_mode: create_options.command_string_mode,
274 sh_mode: create_options.sh_mode,
275 posix_mode: create_options.posix,
276 print_commands_and_arguments: create_options.print_commands_and_arguments,
277 print_shell_input_lines: create_options.verbose,
278 treat_unset_variables_as_error: create_options.treat_unset_variables_as_error,
279 exit_on_nonzero_command_exit: create_options.exit_on_nonzero_command_exit,
280 external_cmd_leads_session: create_options.external_cmd_leads_session,
281 kill_external_commands_on_drop: create_options.kill_external_commands_on_drop,
282 login_shell: create_options.login,
283 disable_filename_globbing: create_options.disable_pathname_expansion,
284 remember_command_locations: true,
285 check_window_size_after_external_commands: true,
286 save_multiline_cmds_in_history: true,
287 extquote: true,
288 force_fignore: true,
289 case_insensitive_pathname_expansion:
290 crate::sys::fs::default_case_insensitive_path_expansion(),
291 enable_hostname_completion: true,
292 interactive_comments: true,
293 expand_prompt_strings: true,
294 source_builtin_searches_path: true,
295 perform_brace_expansion: true,
296 quote_all_metachars_in_completion: true,
297 programmable_completion: true,
298 glob_ranges_use_c_locale: true,
299 glob_skip_dots: true,
300 patsub_replacement: true,
301 max_function_call_depth: create_options.max_function_call_depth,
302 ..Self::default()
303 };
304
305 if create_options.interactive {
307 options.enable_bang_style_history_substitution = true;
308 options.emacs_mode = !create_options.no_editing;
309 options.expand_aliases = true;
310 }
311
312 for enabled_option in &create_options.enabled_options {
314 if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
315 .get(enabled_option.as_str())
316 {
317 option.set(&mut options, true);
318 }
319 }
320 for disabled_option in &create_options.disabled_options {
321 if let Some(option) = namedoptions::options(namedoptions::ShellOptionKind::SetO)
322 .get(disabled_option.as_str())
323 {
324 option.set(&mut options, false);
325 }
326 }
327
328 for enabled_option in &create_options.enabled_shopt_options {
330 if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
331 .get(enabled_option.as_str())
332 {
333 shopt_option.set(&mut options, true);
334 }
335 }
336 for disabled_option in &create_options.disabled_shopt_options {
337 if let Some(shopt_option) = namedoptions::options(namedoptions::ShellOptionKind::Shopt)
338 .get(disabled_option.as_str())
339 {
340 shopt_option.set(&mut options, false);
341 }
342 }
343
344 options
345 }
346
347 pub fn option_flags(&self) -> String {
350 let mut cs = vec![];
351
352 for o in namedoptions::options(namedoptions::ShellOptionKind::Set).iter() {
353 if o.definition.get(self)
354 && let Some(c) = o.name.chars().next()
355 {
356 cs.push(c);
357 }
358 }
359
360 cs.sort_by_key(|flag| option_flag_sort_key(*flag));
362
363 cs.into_iter().collect()
364 }
365
366 pub fn seto_optstr(&self) -> String {
368 let mut cs = vec![];
369
370 for option in namedoptions::options(namedoptions::ShellOptionKind::SetO).iter() {
371 if option.definition.get(self) {
372 cs.push(option.name);
373 }
374 }
375
376 cs.sort_unstable();
377 cs.into_iter().join(":")
378 }
379
380 pub fn shopt_optstr(&self) -> String {
382 let mut cs = vec![];
383
384 for option in namedoptions::options(namedoptions::ShellOptionKind::Shopt).iter() {
385 if option.definition.get(self) {
386 cs.push(option.name);
387 }
388 }
389
390 cs.sort_unstable();
391 cs.into_iter().join(":")
392 }
393}
394
395const fn option_flag_sort_key(ch: char) -> (u8, char) {
401 let group = if ch.is_ascii_lowercase() && !matches!(ch, 'c' | 's') {
406 0
407 } else if ch.is_ascii_uppercase() {
408 1
409 } else {
410 2
411 };
412
413 (group, ch)
414}
415
416#[cfg(test)]
417mod tests {
418 use super::option_flag_sort_key;
419
420 #[test]
421 fn lowercase_excluding_c_and_s_sort_first() {
422 let mut flags = vec!['b', 'A', 'Z', 's', 'c', 'a'];
423 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
424
425 assert_eq!(flags, vec!['a', 'b', 'A', 'Z', 'c', 's']);
426 }
427
428 #[test]
429 fn uppercase_sorted_before_miscellaneous() {
430 let mut flags = vec!['P', 'B', '1', 'T'];
431 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
432
433 assert_eq!(flags, vec!['B', 'P', 'T', '1']);
434 }
435
436 #[test]
437 fn miscellaneous_characters_respect_ascii_order() {
438 let mut flags = vec!['s', 'c', '%', ':'];
439 flags.sort_by_key(|flag| option_flag_sort_key(*flag));
440
441 assert_eq!(flags, vec!['%', ':', 'c', 's']);
442 }
443}