1use crate::history::HistoryEngine;
6use crate::math::MathEval;
7use crate::pcre::PcreState;
8use crate::prompt::{expand_prompt, PromptContext};
9use crate::tcp::TcpSessions;
10use crate::zftp::Zftp;
11use crate::zprof::Profiler;
12use crate::zutil::StyleTable;
13use compsys::cache::CompsysCache;
14use compsys::CompInitResult;
15use parking_lot::Mutex;
16use std::collections::HashSet;
17
18#[derive(Debug, Clone)]
20pub enum AdviceKind {
21 Before,
23 After,
25 Around,
27}
28
29#[derive(Debug, Clone)]
31pub struct Intercept {
32 pub pattern: String,
34 pub kind: AdviceKind,
36 pub code: String,
38 pub id: u32,
40}
41
42pub struct CompInitBgResult {
44 pub result: CompInitResult,
45 pub cache: CompsysCache,
46}
47use std::io::Write;
48use std::sync::LazyLock;
49
50struct PluginSnapshot {
52 functions: std::collections::HashSet<String>,
53 aliases: std::collections::HashSet<String>,
54 global_aliases: std::collections::HashSet<String>,
55 suffix_aliases: std::collections::HashSet<String>,
56 variables: HashMap<String, String>,
57 arrays: std::collections::HashSet<String>,
58 assoc_arrays: std::collections::HashSet<String>,
59 fpath: Vec<PathBuf>,
60 options: HashMap<String, bool>,
61 hooks: HashMap<String, Vec<String>>,
62 autoloads: std::collections::HashSet<String>,
63}
64
65static REGEX_CACHE: LazyLock<Mutex<std::collections::HashMap<String, regex::Regex>>> =
67 LazyLock::new(|| Mutex::new(std::collections::HashMap::with_capacity(64)));
68
69fn intercept_matches(pattern: &str, cmd_name: &str, full_cmd: &str) -> bool {
72 if pattern == "*" || pattern == "all" {
73 return true;
74 }
75 if pattern == cmd_name {
76 return true;
77 }
78 if pattern.contains('*') || pattern.contains('?') {
80 if let Ok(pat) = glob::Pattern::new(pattern) {
81 return pat.matches(cmd_name) || pat.matches(full_cmd);
82 }
83 }
84 false
85}
86
87fn cached_regex(pattern: &str) -> Option<regex::Regex> {
89 let mut cache = REGEX_CACHE.lock();
90 if let Some(re) = cache.get(pattern) {
91 return Some(re.clone());
92 }
93 match regex::Regex::new(pattern) {
94 Ok(re) => {
95 cache.insert(pattern.to_string(), re.clone());
96 Some(re)
97 }
98 Err(_) => None,
99 }
100}
101
102static ZSH_OPTIONS_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
104 [
105 "aliases",
106 "allexport",
107 "alwayslastprompt",
108 "alwaystoend",
109 "appendcreate",
110 "appendhistory",
111 "autocd",
112 "autocontinue",
113 "autolist",
114 "automenu",
115 "autonamedirs",
116 "autoparamkeys",
117 "autoparamslash",
118 "autopushd",
119 "autoremoveslash",
120 "autoresume",
121 "badpattern",
122 "banghist",
123 "bareglobqual",
124 "bashautolist",
125 "bashrematch",
126 "beep",
127 "bgnice",
128 "braceccl",
129 "bsdecho",
130 "caseglob",
131 "casematch",
132 "cbases",
133 "cdablevars",
134 "cdsilent",
135 "chasedots",
136 "chaselinks",
137 "checkjobs",
138 "checkrunningjobs",
139 "clobber",
140 "combiningchars",
141 "completealiases",
142 "completeinword",
143 "continueonerror",
144 "correct",
145 "correctall",
146 "cprecedences",
147 "cshjunkiehistory",
148 "cshjunkieloops",
149 "cshjunkiequotes",
150 "cshnullcmd",
151 "cshnullglob",
152 "debugbeforecmd",
153 "dotglob",
154 "dvorak",
155 "emacs",
156 "equals",
157 "errexit",
158 "errreturn",
159 "evallineno",
160 "exec",
161 "extendedglob",
162 "extendedhistory",
163 "flowcontrol",
164 "forcefloat",
165 "functionargzero",
166 "glob",
167 "globassign",
168 "globcomplete",
169 "globdots",
170 "globstarshort",
171 "globsubst",
172 "globalexport",
173 "globalrcs",
174 "hashall",
175 "hashcmds",
176 "hashdirs",
177 "hashexecutablesonly",
178 "hashlistall",
179 "histallowclobber",
180 "histappend",
181 "histbeep",
182 "histexpand",
183 "histexpiredupsfirst",
184 "histfcntllock",
185 "histfindnodups",
186 "histignorealldups",
187 "histignoredups",
188 "histignorespace",
189 "histlexwords",
190 "histnofunctions",
191 "histnostore",
192 "histreduceblanks",
193 "histsavebycopy",
194 "histsavenodups",
195 "histsubstpattern",
196 "histverify",
197 "hup",
198 "ignorebraces",
199 "ignoreclosebraces",
200 "ignoreeof",
201 "incappendhistory",
202 "incappendhistorytime",
203 "interactive",
204 "interactivecomments",
205 "ksharrays",
206 "kshautoload",
207 "kshglob",
208 "kshoptionprint",
209 "kshtypeset",
210 "kshzerosubscript",
211 "listambiguous",
212 "listbeep",
213 "listpacked",
214 "listrowsfirst",
215 "listtypes",
216 "localloops",
217 "localoptions",
218 "localpatterns",
219 "localtraps",
220 "log",
221 "login",
222 "longlistjobs",
223 "magicequalsubst",
224 "mailwarn",
225 "mailwarning",
226 "markdirs",
227 "menucomplete",
228 "monitor",
229 "multibyte",
230 "multifuncdef",
231 "multios",
232 "nomatch",
233 "notify",
234 "nullglob",
235 "numericglobsort",
236 "octalzeroes",
237 "onecmd",
238 "overstrike",
239 "pathdirs",
240 "pathscript",
241 "physical",
242 "pipefail",
243 "posixaliases",
244 "posixargzero",
245 "posixbuiltins",
246 "posixcd",
247 "posixidentifiers",
248 "posixjobs",
249 "posixstrings",
250 "posixtraps",
251 "printeightbit",
252 "printexitvalue",
253 "privileged",
254 "promptbang",
255 "promptcr",
256 "promptpercent",
257 "promptsp",
258 "promptsubst",
259 "promptvars",
260 "pushdignoredups",
261 "pushdminus",
262 "pushdsilent",
263 "pushdtohome",
264 "rcexpandparam",
265 "rcquotes",
266 "rcs",
267 "recexact",
268 "rematchpcre",
269 "restricted",
270 "rmstarsilent",
271 "rmstarwait",
272 "sharehistory",
273 "shfileexpansion",
274 "shglob",
275 "shinstdin",
276 "shnullcmd",
277 "shoptionletters",
278 "shortloops",
279 "shortrepeat",
280 "shwordsplit",
281 "singlecommand",
282 "singlelinezle",
283 "sourcetrace",
284 "stdin",
285 "sunkeyboardhack",
286 "trackall",
287 "transientrprompt",
288 "trapsasync",
289 "typesetsilent",
290 "typesettounset",
291 "unset",
292 "verbose",
293 "vi",
294 "warncreateglobal",
295 "warnnestedvar",
296 "xtrace",
297 "zle",
298 ]
299 .into_iter()
300 .collect()
301});
302
303static BUILTIN_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
305 [
306 "cd", "chdir", "pwd", "echo", "export", "unset", "source", "exit",
307 "return", "bye", "logout", "log", "true", "false", "test", "local",
308 "declare", "typeset", "read", "shift", "eval", "jobs", "fg", "bg",
309 "kill", "disown", "wait", "autoload", "history", "fc", "trap",
310 "suspend", "alias", "unalias", "set", "shopt", "setopt", "unsetopt",
311 "getopts", "type", "hash", "command", "builtin", "let", "pushd",
312 "popd", "dirs", "printf", "break", "continue", "disable", "enable",
313 "emulate", "exec", "float", "integer", "functions", "print", "whence",
314 "where", "which", "ulimit", "limit", "unlimit", "umask", "rehash",
315 "unhash", "times", "zmodload", "r", "ttyctl", "noglob", "zstat",
316 "stat", "strftime", "zsleep", "zln", "zmv", "zcp", "coproc",
317 "zparseopts", "readonly", "unfunction", "getln", "pushln", "bindkey",
318 "zle", "sched", "zformat", "zcompile", "vared", "echotc", "echoti",
319 "zpty", "zprof", "zsocket", "ztcp", "zregexparse", "clone",
320 "comparguments", "compcall", "compctl", "compdef", "compdescribe",
321 "compfiles", "compgroups", "compinit", "compquote", "comptags",
322 "comptry", "compvalues", "cdreplay", "cap", "getcap", "setcap",
323 "zftp", "zcurses", "sysread", "syswrite", "syserror", "sysopen",
324 "sysseek", "private", "zgetattr", "zsetattr", "zdelattr", "zlistattr",
325 "[", ".", ":", "compgen", "complete",
326 ]
327 .into_iter()
328 .collect()
329});
330
331fn float_to_hex(val: f64, uppercase: bool) -> String {
333 if val.is_nan() {
334 return if uppercase { "NAN" } else { "nan" }.to_string();
335 }
336 if val.is_infinite() {
337 return if val > 0.0 {
338 if uppercase {
339 "INF"
340 } else {
341 "inf"
342 }
343 } else {
344 if uppercase {
345 "-INF"
346 } else {
347 "-inf"
348 }
349 }
350 .to_string();
351 }
352 if val == 0.0 {
353 let sign = if val.is_sign_negative() { "-" } else { "" };
354 return if uppercase {
355 format!("{}0X0P+0", sign)
356 } else {
357 format!("{}0x0p+0", sign)
358 };
359 }
360
361 let sign = if val < 0.0 { "-" } else { "" };
362 let abs_val = val.abs();
363 let bits = abs_val.to_bits();
364 let exponent = ((bits >> 52) & 0x7ff) as i32 - 1023;
365 let mantissa = bits & 0xfffffffffffff;
366
367 let hex_mantissa = format!("{:013x}", mantissa);
368 let hex_mantissa = hex_mantissa.trim_end_matches('0');
369 let hex_mantissa = if hex_mantissa.is_empty() {
370 "0"
371 } else {
372 hex_mantissa
373 };
374
375 if uppercase {
376 format!("{}0X1.{}P{:+}", sign, hex_mantissa.to_uppercase(), exponent)
377 } else {
378 format!("{}0x1.{}p{:+}", sign, hex_mantissa, exponent)
379 }
380}
381
382fn shell_quote(s: &str) -> String {
384 if s.is_empty() {
385 return "''".to_string();
386 }
387 let needs_quotes = s.chars().any(|c| {
389 matches!(
390 c,
391 ' ' | '\t'
392 | '\n'
393 | '\''
394 | '"'
395 | '\\'
396 | '$'
397 | '`'
398 | '!'
399 | '*'
400 | '?'
401 | '['
402 | ']'
403 | '{'
404 | '}'
405 | '('
406 | ')'
407 | '<'
408 | '>'
409 | '|'
410 | '&'
411 | ';'
412 | '#'
413 | '~'
414 )
415 });
416 if !needs_quotes {
417 return s.to_string();
418 }
419 format!("'{}'", s.replace('\'', "'\\''"))
421}
422
423fn shell_quote_value(s: &str) -> String {
426 if s.is_empty() {
427 return "''".to_string();
428 }
429 let needs_quotes = s.chars().any(|c| {
430 matches!(
431 c,
432 ' ' | '\t'
433 | '\n'
434 | '\''
435 | '"'
436 | '\\'
437 | '$'
438 | '`'
439 | '!'
440 | '*'
441 | '?'
442 | '['
443 | ']'
444 | '{'
445 | '}'
446 | '('
447 | ')'
448 | '<'
449 | '>'
450 | '|'
451 | '&'
452 | ';'
453 | '#'
454 | '~'
455 | '^'
456 )
457 });
458 if !needs_quotes {
459 return s.to_string();
460 }
461 format!("'{}'", s.replace('\'', "'\\''"))
462}
463
464use crate::jobs::{continue_job, wait_for_child, wait_for_job, JobState, JobTable};
465use crate::parser::{
466 CaseTerminator, CompoundCommand, CondExpr, ListOp, Redirect, RedirectOp, ShellCommand,
467 ShellParser, ShellWord, SimpleCommand, VarModifier, ZshParamFlag,
468};
469use crate::zwc::ZwcFile;
470use std::collections::HashMap;
471use std::env;
472use std::fs::{self, File, OpenOptions};
473use std::io;
474use std::path::{Path, PathBuf};
475use std::process::{Child, Command, Stdio};
476
477#[derive(Debug, Clone, Default)]
479pub struct CompSpec {
480 pub actions: Vec<String>, pub wordlist: Option<String>, pub function: Option<String>, pub command: Option<String>, pub globpat: Option<String>, pub prefix: Option<String>, pub suffix: Option<String>, }
488
489#[derive(Debug, Clone)]
491pub struct CompMatch {
492 pub word: String, pub display: Option<String>, pub prefix: Option<String>, pub suffix: Option<String>, pub hidden_prefix: Option<String>, pub hidden_suffix: Option<String>, pub ignored_prefix: Option<String>, pub ignored_suffix: Option<String>, pub group: Option<String>, pub description: Option<String>, pub remove_suffix: Option<String>, pub file_match: bool, pub quote_match: bool, }
506
507impl Default for CompMatch {
508 fn default() -> Self {
509 Self {
510 word: String::new(),
511 display: None,
512 prefix: None,
513 suffix: None,
514 hidden_prefix: None,
515 hidden_suffix: None,
516 ignored_prefix: None,
517 ignored_suffix: None,
518 group: None,
519 description: None,
520 remove_suffix: None,
521 file_match: false,
522 quote_match: false,
523 }
524 }
525}
526
527#[derive(Debug, Clone, Default)]
529pub struct CompGroup {
530 pub name: String,
531 pub matches: Vec<CompMatch>,
532 pub explanation: Option<String>,
533 pub sorted: bool,
534}
535
536#[derive(Debug, Clone, Default)]
538pub struct CompState {
539 pub context: String, pub exact: String, pub exact_string: String, pub ignored: i32, pub insert: String, pub insert_positions: String, pub last_prompt: String, pub list: String, pub list_lines: i32, pub list_max: i32, pub nmatches: i32, pub old_insert: String, pub old_list: String, pub parameter: String, pub pattern_insert: String, pub pattern_match: String, pub quote: String, pub quoting: String, pub redirect: String, pub restore: String, pub to_end: String, pub unambiguous: String, pub unambiguous_cursor: i32, pub unambiguous_positions: String, pub vared: String, }
565
566#[derive(Debug, Clone)]
568pub struct ZStyle {
569 pub pattern: String,
570 pub style: String,
571 pub values: Vec<String>,
572}
573
574bitflags::bitflags! {
575 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
577 pub struct AutoloadFlags: u32 {
578 const NO_ALIAS = 0b00000001; const ZSH_STYLE = 0b00000010; const KSH_STYLE = 0b00000100; const TRACE = 0b00001000; const USE_CALLER_DIR = 0b00010000; const LOADED = 0b00100000; }
585}
586
587pub struct ZptyState {
589 pub pid: u32,
590 pub cmd: String,
591 pub stdin: Option<std::process::ChildStdin>,
592 pub stdout: Option<std::process::ChildStdout>,
593 pub child: Option<std::process::Child>,
594}
595
596pub struct ScheduledCommand {
598 pub id: u32,
599 pub run_at: std::time::SystemTime,
600 pub command: String,
601}
602
603#[derive(Clone, Default)]
605pub struct ProfileEntry {
606 pub calls: u64,
607 pub total_time_us: u64,
608 pub self_time_us: u64,
609}
610
611pub struct UnixSocketState {
613 pub path: Option<PathBuf>,
614 pub listening: bool,
615 pub stream: Option<std::os::unix::net::UnixStream>,
616 pub listener: Option<std::os::unix::net::UnixListener>,
617}
618
619pub struct ShellExecutor {
620 pub functions: HashMap<String, ShellCommand>,
621 pub aliases: HashMap<String, String>,
622 pub global_aliases: HashMap<String, String>, pub suffix_aliases: HashMap<String, String>, pub last_status: i32,
625 pub variables: HashMap<String, String>,
626 pub arrays: HashMap<String, Vec<String>>,
627 pub assoc_arrays: HashMap<String, HashMap<String, String>>, pub jobs: JobTable,
629 pub fpath: Vec<PathBuf>,
630 pub zwc_cache: HashMap<PathBuf, ZwcFile>,
631 pub positional_params: Vec<String>,
632 pub history: Option<HistoryEngine>,
633 process_sub_counter: u32,
634 pub traps: HashMap<String, String>,
635 pub options: HashMap<String, bool>,
636 pub completions: HashMap<String, CompSpec>, pub dir_stack: Vec<PathBuf>,
638 pub comp_matches: Vec<CompMatch>, pub comp_groups: Vec<CompGroup>, pub comp_state: CompState, pub zstyles: Vec<ZStyle>, pub comp_words: Vec<String>, pub comp_current: i32, pub comp_prefix: String, pub comp_suffix: String, pub comp_iprefix: String, pub comp_isuffix: String, pub readonly_vars: std::collections::HashSet<String>, pub local_save_stack: Vec<(String, Option<String>)>,
652 pub local_scope_depth: usize,
654 pub autoload_pending: HashMap<String, AutoloadFlags>, pub hook_functions: HashMap<String, Vec<String>>, pub named_dirs: HashMap<String, PathBuf>, pub zptys: HashMap<String, ZptyState>,
661 pub open_fds: HashMap<i32, std::fs::File>,
663 pub next_fd: i32,
664 pub scheduled_commands: Vec<ScheduledCommand>,
666 pub profile_data: HashMap<String, ProfileEntry>,
668 pub profiling_enabled: bool,
669 pub unix_sockets: HashMap<i32, UnixSocketState>,
671 pub compsys_cache: Option<CompsysCache>,
673 pub compinit_pending: Option<(std::sync::mpsc::Receiver<CompInitBgResult>, std::time::Instant)>,
675 pub plugin_cache: Option<crate::plugin_cache::PluginCache>,
677 pub deferred_compdefs: Vec<Vec<String>>,
679 pub command_hash: HashMap<String, String>,
681 returning: Option<i32>, breaking: i32, continuing: i32, pub pcre_state: PcreState,
687 pub tcp_sessions: TcpSessions,
688 pub zftp: Zftp,
689 pub profiler: Profiler,
690 pub style_table: StyleTable,
691 pub zsh_compat: bool,
693 pub posix_mode: bool,
695 pub worker_pool: std::sync::Arc<crate::worker::WorkerPool>,
697 pub intercepts: Vec<Intercept>,
700 pub async_jobs: HashMap<u32, crossbeam_channel::Receiver<(i32, String)>>,
702 pub next_async_id: u32,
704 pub defer_stack: Vec<Vec<String>>,
706}
707
708impl ShellExecutor {
709 pub fn new() -> Self {
710 tracing::debug!("ShellExecutor::new() initializing");
711 let fpath = env::var("FPATH")
713 .unwrap_or_default()
714 .split(':')
715 .filter(|s| !s.is_empty())
716 .map(PathBuf::from)
717 .collect();
718
719 let history = HistoryEngine::new().ok();
720
721 let mut variables = HashMap::new();
723 variables.insert("ZSH_VERSION".to_string(), "5.9".to_string());
724 variables.insert(
725 "ZSH_PATCHLEVEL".to_string(),
726 "zsh-5.9-0-g73d3173".to_string(),
727 );
728 variables.insert("ZSH_NAME".to_string(), "zsh".to_string());
729 variables.insert(
730 "SHLVL".to_string(),
731 env::var("SHLVL")
732 .map(|v| {
733 v.parse::<i32>()
734 .map(|n| (n + 1).to_string())
735 .unwrap_or_else(|_| "1".to_string())
736 })
737 .unwrap_or_else(|_| "1".to_string()),
738 );
739
740 Self {
741 functions: HashMap::new(),
742 aliases: HashMap::new(),
743 global_aliases: HashMap::new(),
744 suffix_aliases: HashMap::new(),
745 last_status: 0,
746 variables,
747 arrays: {
748 let mut a = HashMap::new();
749 let path_dirs: Vec<String> = env::var("PATH")
751 .unwrap_or_default()
752 .split(':')
753 .map(|s| s.to_string())
754 .collect();
755 a.insert("path".to_string(), path_dirs);
756 a
757 },
758 assoc_arrays: HashMap::new(),
759 jobs: JobTable::new(),
760 fpath,
761 zwc_cache: HashMap::new(),
762 positional_params: Vec::new(),
763 history,
764 completions: HashMap::new(),
765 dir_stack: Vec::new(),
766 process_sub_counter: 0,
767 traps: HashMap::new(),
768 options: Self::default_options(),
769 comp_matches: Vec::new(),
771 comp_groups: Vec::new(),
772 comp_state: CompState::default(),
773 zstyles: Vec::new(),
774 comp_words: Vec::new(),
775 comp_current: 0,
776 comp_prefix: String::new(),
777 comp_suffix: String::new(),
778 comp_iprefix: String::new(),
779 comp_isuffix: String::new(),
780 readonly_vars: std::collections::HashSet::new(),
781 local_save_stack: Vec::new(),
782 local_scope_depth: 0,
783 autoload_pending: HashMap::new(),
784 hook_functions: HashMap::new(),
785 named_dirs: HashMap::new(),
786 zptys: HashMap::new(),
787 open_fds: HashMap::new(),
788 next_fd: 10,
789 scheduled_commands: Vec::new(),
790 profile_data: HashMap::new(),
791 profiling_enabled: false,
792 unix_sockets: HashMap::new(),
793 compsys_cache: {
794 let cache_path = compsys::cache::default_cache_path();
795 if cache_path.exists() {
796 let db_size = std::fs::metadata(&cache_path).map(|m| m.len()).unwrap_or(0);
797 match CompsysCache::open(&cache_path) {
798 Ok(c) => {
799 tracing::info!(
800 db_bytes = db_size,
801 path = %cache_path.display(),
802 "compsys: sqlite cache opened"
803 );
804 Some(c)
805 }
806 Err(e) => {
807 tracing::warn!(error = %e, "compsys: failed to open cache");
808 None
809 }
810 }
811 } else {
812 tracing::debug!("compsys: no cache at {}", cache_path.display());
813 None
814 }
815 },
816 compinit_pending: None, plugin_cache: {
818 let pc_path = crate::plugin_cache::default_cache_path();
819 if let Some(parent) = pc_path.parent() {
820 let _ = std::fs::create_dir_all(parent);
821 }
822 match crate::plugin_cache::PluginCache::open(&pc_path) {
823 Ok(pc) => {
824 let (plugins, functions) = pc.stats();
825 tracing::info!(
826 plugins,
827 cached_functions = functions,
828 path = %pc_path.display(),
829 "plugin_cache: sqlite opened"
830 );
831 Some(pc)
832 }
833 Err(e) => {
834 tracing::warn!(error = %e, "plugin_cache: failed to open");
835 None
836 }
837 }
838 },
839 deferred_compdefs: Vec::new(),
840 command_hash: HashMap::new(),
841 returning: None,
842 breaking: 0,
843 continuing: 0,
844 pcre_state: PcreState::new(),
845 tcp_sessions: TcpSessions::new(),
846 zftp: Zftp::new(),
847 profiler: Profiler::new(),
848 style_table: StyleTable::new(),
849 zsh_compat: false,
850 posix_mode: false,
851 worker_pool: {
852 let config = crate::config::load();
853 let pool_size = crate::config::resolve_pool_size(&config.worker_pool);
854 std::sync::Arc::new(crate::worker::WorkerPool::new(pool_size))
855 },
856 intercepts: Vec::new(),
857 async_jobs: HashMap::new(),
858 next_async_id: 1,
859 defer_stack: Vec::new(),
860 }
861 }
862
863 pub fn enter_posix_mode(&mut self) {
866 self.posix_mode = true;
867 self.plugin_cache = None;
868 self.compsys_cache = None;
869 self.compinit_pending = None;
870 self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
874 tracing::info!("POSIX strict mode: SQLite caches dropped, worker pool shrunk to 1");
875 }
876
877 pub fn run_hooks(&mut self, hook_name: &str) {
879 if let Some(funcs) = self.hook_functions.get(hook_name).cloned() {
880 for func_name in funcs {
881 if self.functions.contains_key(&func_name) {
882 let _ = self.execute_script(&format!("{}", func_name));
883 }
884 }
885 }
886 let array_name = format!("{}_functions", hook_name);
888 if let Some(funcs) = self.arrays.get(&array_name).cloned() {
889 for func_name in funcs {
890 if self.functions.contains_key(&func_name) {
891 let _ = self.execute_script(&format!("{}", func_name));
892 }
893 }
894 }
895 }
896
897 pub fn add_hook(&mut self, hook_name: &str, func_name: &str) {
899 self.hook_functions
900 .entry(hook_name.to_string())
901 .or_default()
902 .push(func_name.to_string());
903 }
904
905 pub fn add_named_dir(&mut self, name: &str, path: &str) {
907 self.named_dirs
908 .insert(name.to_string(), PathBuf::from(path));
909 }
910
911 pub fn expand_tilde_named(&self, path: &str) -> String {
913 if path.starts_with('~') {
914 let rest = &path[1..];
915 let (name, suffix) = if let Some(slash_pos) = rest.find('/') {
917 (&rest[..slash_pos], &rest[slash_pos..])
918 } else {
919 (rest, "")
920 };
921
922 if name.is_empty() {
923 if let Ok(home) = std::env::var("HOME") {
925 return format!("{}{}", home, suffix);
926 }
927 } else if let Some(dir) = self.named_dirs.get(name) {
928 return format!("{}{}", dir.display(), suffix);
929 }
930 }
931 path.to_string()
932 }
933
934 fn all_zsh_options() -> &'static [&'static str] {
935 &[
936 "aliases",
937 "aliasfuncdef",
938 "allexport",
939 "alwayslastprompt",
940 "alwaystoend",
941 "appendcreate",
942 "appendhistory",
943 "autocd",
944 "autocontinue",
945 "autolist",
946 "automenu",
947 "autonamedirs",
948 "autoparamkeys",
949 "autoparamslash",
950 "autopushd",
951 "autoremoveslash",
952 "autoresume",
953 "badpattern",
954 "banghist",
955 "bareglobqual",
956 "bashautolist",
957 "bashrematch",
958 "beep",
959 "bgnice",
960 "braceccl",
961 "braceexpand",
962 "bsdecho",
963 "caseglob",
964 "casematch",
965 "casepaths",
966 "cbases",
967 "cdablevars",
968 "cdsilent",
969 "chasedots",
970 "chaselinks",
971 "checkjobs",
972 "checkrunningjobs",
973 "clobber",
974 "clobberempty",
975 "combiningchars",
976 "completealiases",
977 "completeinword",
978 "continueonerror",
979 "correct",
980 "correctall",
981 "cprecedences",
982 "cshjunkiehistory",
983 "cshjunkieloops",
984 "cshjunkiequotes",
985 "cshnullcmd",
986 "cshnullglob",
987 "debugbeforecmd",
988 "dotglob",
989 "dvorak",
990 "emacs",
991 "equals",
992 "errexit",
993 "errreturn",
994 "evallineno",
995 "exec",
996 "extendedglob",
997 "extendedhistory",
998 "flowcontrol",
999 "forcefloat",
1000 "functionargzero",
1001 "glob",
1002 "globassign",
1003 "globcomplete",
1004 "globdots",
1005 "globstarshort",
1006 "globsubst",
1007 "globalexport",
1008 "globalrcs",
1009 "hashall",
1010 "hashcmds",
1011 "hashdirs",
1012 "hashexecutablesonly",
1013 "hashlistall",
1014 "histallowclobber",
1015 "histappend",
1016 "histbeep",
1017 "histexpand",
1018 "histexpiredupsfirst",
1019 "histfcntllock",
1020 "histfindnodups",
1021 "histignorealldups",
1022 "histignoredups",
1023 "histignorespace",
1024 "histlexwords",
1025 "histnofunctions",
1026 "histnostore",
1027 "histreduceblanks",
1028 "histsavebycopy",
1029 "histsavenodups",
1030 "histsubstpattern",
1031 "histverify",
1032 "hup",
1033 "ignorebraces",
1034 "ignoreclosebraces",
1035 "ignoreeof",
1036 "incappendhistory",
1037 "incappendhistorytime",
1038 "interactive",
1039 "interactivecomments",
1040 "ksharrays",
1041 "kshautoload",
1042 "kshglob",
1043 "kshoptionprint",
1044 "kshtypeset",
1045 "kshzerosubscript",
1046 "listambiguous",
1047 "listbeep",
1048 "listpacked",
1049 "listrowsfirst",
1050 "listtypes",
1051 "localloops",
1052 "localoptions",
1053 "localpatterns",
1054 "localtraps",
1055 "log",
1056 "login",
1057 "longlistjobs",
1058 "magicequalsubst",
1059 "mailwarn",
1060 "mailwarning",
1061 "markdirs",
1062 "menucomplete",
1063 "monitor",
1064 "multibyte",
1065 "multifuncdef",
1066 "multios",
1067 "nomatch",
1068 "notify",
1069 "nullglob",
1070 "numericglobsort",
1071 "octalzeroes",
1072 "onecmd",
1073 "overstrike",
1074 "pathdirs",
1075 "pathscript",
1076 "physical",
1077 "pipefail",
1078 "posixaliases",
1079 "posixargzero",
1080 "posixbuiltins",
1081 "posixcd",
1082 "posixidentifiers",
1083 "posixjobs",
1084 "posixstrings",
1085 "posixtraps",
1086 "printeightbit",
1087 "printexitvalue",
1088 "privileged",
1089 "promptbang",
1090 "promptcr",
1091 "promptpercent",
1092 "promptsp",
1093 "promptsubst",
1094 "promptvars",
1095 "pushdignoredups",
1096 "pushdminus",
1097 "pushdsilent",
1098 "pushdtohome",
1099 "rcexpandparam",
1100 "rcquotes",
1101 "rcs",
1102 "recexact",
1103 "rematchpcre",
1104 "restricted",
1105 "rmstarsilent",
1106 "rmstarwait",
1107 "sharehistory",
1108 "shfileexpansion",
1109 "shglob",
1110 "shinstdin",
1111 "shnullcmd",
1112 "shoptionletters",
1113 "shortloops",
1114 "shortrepeat",
1115 "shwordsplit",
1116 "singlecommand",
1117 "singlelinezle",
1118 "sourcetrace",
1119 "stdin",
1120 "sunkeyboardhack",
1121 "trackall",
1122 "transientrprompt",
1123 "trapsasync",
1124 "typesetsilent",
1125 "typesettounset",
1126 "unset",
1127 "verbose",
1128 "vi",
1129 "warncreateglobal",
1130 "warnnestedvar",
1131 "xtrace",
1132 "zle",
1133 ]
1134 }
1135
1136 fn default_options() -> HashMap<String, bool> {
1137 let mut opts = HashMap::new();
1138 for opt in Self::all_zsh_options() {
1140 opts.insert(opt.to_string(), false);
1141 }
1142 let defaults_on = [
1144 "aliases",
1145 "alwayslastprompt",
1146 "appendhistory",
1147 "autolist",
1148 "automenu",
1149 "autoparamkeys",
1150 "autoparamslash",
1151 "autoremoveslash",
1152 "badpattern",
1153 "banghist",
1154 "bareglobqual",
1155 "beep",
1156 "bgnice",
1157 "caseglob",
1158 "casematch",
1159 "checkjobs",
1160 "checkrunningjobs",
1161 "clobber",
1162 "debugbeforecmd",
1163 "equals",
1164 "evallineno",
1165 "exec",
1166 "flowcontrol",
1167 "functionargzero",
1168 "glob",
1169 "globalexport",
1170 "globalrcs",
1171 "hashcmds",
1172 "hashdirs",
1173 "hashlistall",
1174 "histbeep",
1175 "histsavebycopy",
1176 "hup",
1177 "interactive",
1178 "listambiguous",
1179 "listbeep",
1180 "listtypes",
1181 "monitor",
1182 "multibyte",
1183 "multifuncdef",
1184 "multios",
1185 "nomatch",
1186 "notify",
1187 "promptcr",
1188 "promptpercent",
1189 "promptsp",
1190 "rcs",
1191 "shinstdin",
1192 "shortloops",
1193 "unset",
1194 "zle",
1195 ];
1196 for opt in defaults_on {
1197 opts.insert(opt.to_string(), true);
1198 }
1199 opts
1200 }
1201
1202 fn normalize_option_name(name: &str) -> (String, bool) {
1204 let normalized = name.to_lowercase().replace(['-', '_'], "");
1205 if let Some(stripped) = normalized.strip_prefix("no") {
1206 if ZSH_OPTIONS_SET.contains(stripped) {
1208 return (stripped.to_string(), false);
1209 }
1210 }
1211 (normalized, true)
1212 }
1213
1214 fn option_matches_pattern(opt: &str, pattern: &str) -> bool {
1216 let pat = pattern.to_lowercase().replace(['-', '_'], "");
1217 let opt_lower = opt.to_lowercase();
1218
1219 if pat.contains('*') || pat.contains('?') || pat.contains('[') {
1220 let regex_pat = pat.replace('.', "\\.").replace('*', ".*").replace('?', ".");
1221 let full_pattern = format!("^{}$", regex_pat);
1222 cached_regex(&full_pattern)
1223 .map(|re| re.is_match(&opt_lower))
1224 .unwrap_or(false)
1225 } else {
1226 opt_lower == pat
1227 }
1228 }
1229
1230 pub fn autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
1232 if let Some(func) = self.functions.get(name) {
1234 return Some(func.clone());
1235 }
1236
1237 for i in 0..self.fpath.len() {
1239 let dir = self.fpath[i].clone();
1240 let zwc_path = dir.with_extension("zwc");
1242 if zwc_path.exists() {
1243 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
1244 return Some(func);
1245 }
1246 }
1247
1248 let func_zwc = dir.join(format!("{}.zwc", name));
1250 if func_zwc.exists() {
1251 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
1252 return Some(func);
1253 }
1254 }
1255
1256 if dir.is_dir() {
1258 if let Ok(entries) = fs::read_dir(&dir) {
1259 for entry in entries.flatten() {
1260 let path = entry.path();
1261 if path.extension().map_or(false, |e| e == "zwc") {
1262 if let Some(func) = self.load_function_from_zwc(&path, name) {
1263 return Some(func);
1264 }
1265 }
1266 }
1267 }
1268 }
1269 }
1270
1271 None
1272 }
1273
1274 fn load_function_from_zwc(&mut self, path: &Path, name: &str) -> Option<ShellCommand> {
1276 let zwc = if let Some(cached) = self.zwc_cache.get(path) {
1278 cached
1279 } else {
1280 let zwc = ZwcFile::load(path).ok()?;
1282 self.zwc_cache.insert(path.to_path_buf(), zwc);
1283 self.zwc_cache.get(path)?
1284 };
1285
1286 let func = zwc.get_function(name)?;
1288 let decoded = zwc.decode_function(func)?;
1289
1290 let shell_func = decoded.to_shell_function()?;
1292
1293 if let ShellCommand::FunctionDef(fname, body) = &shell_func {
1295 self.functions.insert(fname.clone(), (**body).clone());
1296 }
1297
1298 Some(shell_func)
1299 }
1300
1301 pub fn add_fpath(&mut self, path: PathBuf) {
1303 if !self.fpath.contains(&path) {
1304 self.fpath.insert(0, path);
1305 }
1306 }
1307
1308 fn glob_match(&self, s: &str, pattern: &str) -> bool {
1310 let mut regex_pattern = String::from("^");
1312 let mut chars = pattern.chars().peekable();
1313
1314 while let Some(c) = chars.next() {
1315 match c {
1316 '*' => regex_pattern.push_str(".*"),
1317 '?' => regex_pattern.push('.'),
1318 '[' => {
1319 regex_pattern.push('[');
1320 while let Some(cc) = chars.next() {
1322 if cc == ']' {
1323 regex_pattern.push(']');
1324 break;
1325 }
1326 regex_pattern.push(cc);
1327 }
1328 }
1329 '(' => {
1330 regex_pattern.push('(');
1332 }
1333 ')' => regex_pattern.push(')'),
1334 '|' => regex_pattern.push('|'),
1335 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
1336 regex_pattern.push('\\');
1337 regex_pattern.push(c);
1338 }
1339 _ => regex_pattern.push(c),
1340 }
1341 }
1342 regex_pattern.push('$');
1343
1344 regex::Regex::new(®ex_pattern)
1345 .map(|re| re.is_match(s))
1346 .unwrap_or(false)
1347 }
1348
1349 pub fn glob_match_static(s: &str, pattern: &str) -> bool {
1352 let mut regex_pattern = String::from("^");
1353 let mut chars = pattern.chars().peekable();
1354 while let Some(c) = chars.next() {
1355 match c {
1356 '*' => regex_pattern.push_str(".*"),
1357 '?' => regex_pattern.push('.'),
1358 '[' => {
1359 regex_pattern.push('[');
1360 while let Some(cc) = chars.next() {
1361 if cc == ']' {
1362 regex_pattern.push(']');
1363 break;
1364 }
1365 regex_pattern.push(cc);
1366 }
1367 }
1368 '(' => regex_pattern.push('('),
1369 ')' => regex_pattern.push(')'),
1370 '|' => regex_pattern.push('|'),
1371 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
1372 regex_pattern.push('\\');
1373 regex_pattern.push(c);
1374 }
1375 _ => regex_pattern.push(c),
1376 }
1377 }
1378 regex_pattern.push('$');
1379 regex::Regex::new(®ex_pattern)
1380 .map(|re| re.is_match(s))
1381 .unwrap_or(false)
1382 }
1383
1384 pub fn execute_script_file(&mut self, file_path: &str) -> Result<i32, String> {
1387 let path = std::path::Path::new(file_path);
1388 let mtime = crate::plugin_cache::file_mtime(path);
1389
1390 if let (Some(ref cache), Some((mt_s, mt_ns))) = (&self.plugin_cache, mtime) {
1392 if let Some(ast_bytes) = cache.check_ast(file_path, mt_s, mt_ns) {
1393 if let Ok(commands) = bincode::deserialize::<Vec<crate::parser::ShellCommand>>(&ast_bytes) {
1394 tracing::info!(
1395 path = file_path,
1396 cmds = commands.len(),
1397 bytes = ast_bytes.len(),
1398 "execute_script_file: bytecode cache hit, skipping lex+parse"
1399 );
1400 for cmd in commands {
1401 self.execute_command(&cmd)?;
1402 }
1403 return Ok(self.last_status);
1404 }
1405 }
1406 }
1407
1408 let content = std::fs::read_to_string(file_path)
1410 .map_err(|e| format!("{}: {}", file_path, e))?;
1411 let expanded = self.expand_history(&content);
1412 let mut parser = ShellParser::new(&expanded);
1413 let mut commands = parser.parse_script()?;
1414 tracing::debug!(
1415 path = file_path,
1416 cmds = commands.len(),
1417 "execute_script_file: bytecode cache miss, parsed from source"
1418 );
1419
1420 crate::ast_opt::optimize(&mut commands);
1422
1423 for cmd in &commands {
1425 self.execute_command(cmd)?;
1426 }
1427
1428 if let Some((mt_s, mt_ns)) = mtime {
1430 if let Ok(ast_bytes) = bincode::serialize(&commands) {
1431 let store_path = file_path.to_string();
1432 let cache_db_path = crate::plugin_cache::default_cache_path();
1433 let ast_size = ast_bytes.len();
1434 self.worker_pool.submit(move || {
1435 match crate::plugin_cache::PluginCache::open(&cache_db_path) {
1436 Ok(cache) => {
1437 if let Err(e) = cache.store_ast(&store_path, mt_s, mt_ns, &ast_bytes) {
1438 tracing::error!(path = %store_path, error = %e, "AST cache store failed");
1439 } else {
1440 tracing::debug!(path = %store_path, bytes = ast_size, "bytecode cached");
1441 }
1442 }
1443 Err(e) => tracing::error!(error = %e, "plugin_cache: open for AST write failed"),
1444 }
1445 });
1446 }
1447 }
1448
1449 Ok(self.last_status)
1450 }
1451
1452 #[tracing::instrument(skip(self, script), fields(len = script.len()))]
1453 pub fn execute_script(&mut self, script: &str) -> Result<i32, String> {
1454 let expanded = self.expand_history(script);
1456
1457 let mut parser = ShellParser::new(&expanded);
1458 let commands = parser.parse_script()?;
1459 tracing::trace!(cmds = commands.len(), "execute_script: parsed");
1460
1461 let compiler = crate::shell_compiler::ShellCompiler::new();
1469 let chunk = compiler.compile(&commands);
1470
1471 if !chunk.ops.is_empty() {
1472 let mut vm = fusevm::VM::new(chunk);
1473 match vm.run() {
1474 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
1475 self.last_status = vm.last_status;
1476 }
1477 fusevm::VMResult::Error(_) => {
1478 for cmd in &commands {
1480 self.execute_command(cmd)?;
1481 }
1482 }
1483 }
1484 } else {
1485 for cmd in &commands {
1487 self.execute_command(cmd)?;
1488 }
1489 }
1490
1491 if let Some(action) = self.traps.remove("EXIT") {
1494 tracing::debug!("firing EXIT trap");
1495 let _ = self.execute_script(&action);
1496 }
1497
1498 Ok(self.last_status)
1499 }
1500
1501 fn expand_history(&self, input: &str) -> String {
1503 let Some(ref engine) = self.history else {
1504 return input.to_string();
1505 };
1506
1507 if !input.contains('!') && !input.starts_with('^') {
1509 return input.to_string();
1510 }
1511
1512 let history_count = engine.count().unwrap_or(0) as usize;
1513 if history_count == 0 {
1514 return input.to_string();
1515 }
1516
1517 let chars: Vec<char> = input.chars().collect();
1518
1519 if chars.first() == Some(&'^') {
1521 if let Some(expanded) = self.history_quick_subst(&chars, engine) {
1522 return expanded;
1523 }
1524 }
1525
1526 let mut result = String::new();
1527 let mut i = 0;
1528 let mut in_single_quote = false;
1529 let mut in_brace = 0; let mut last_subst: Option<(String, String)> = None; while i < chars.len() {
1533 if chars[i] == '\'' && in_brace == 0 {
1535 in_single_quote = !in_single_quote;
1536 result.push(chars[i]);
1537 i += 1;
1538 continue;
1539 }
1540 if in_single_quote {
1541 result.push(chars[i]);
1542 i += 1;
1543 continue;
1544 }
1545
1546 if i + 1 < chars.len() && chars[i] == '$' && chars[i + 1] == '{' {
1548 in_brace += 1;
1549 result.push(chars[i]);
1550 i += 1;
1551 result.push(chars[i]);
1552 i += 1;
1553 continue;
1554 }
1555 if chars[i] == '}' && in_brace > 0 {
1556 in_brace -= 1;
1557 result.push(chars[i]);
1558 i += 1;
1559 continue;
1560 }
1561
1562 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '!' {
1564 result.push('!');
1565 i += 2;
1566 continue;
1567 }
1568
1569 if chars[i] == '!' && in_brace == 0 {
1570 if i + 1 >= chars.len() {
1571 result.push('!');
1573 i += 1;
1574 continue;
1575 }
1576
1577 let next = chars[i + 1];
1578 if next == ' ' || next == '\t' || next == '=' || next == '(' || next == '\n' {
1580 result.push('!');
1581 i += 1;
1582 continue;
1583 }
1584
1585 let (event_str, new_i) = self.history_resolve_event(&chars, i, engine, &result);
1587 if let Some(ev) = event_str {
1588 let (final_str, final_i) =
1590 self.history_apply_designators_and_modifiers(&chars, new_i, &ev, &mut last_subst);
1591 result.push_str(&final_str);
1592 i = final_i;
1593 } else {
1594 result.push('!');
1596 i += 1;
1597 }
1598 continue;
1599 }
1600 result.push(chars[i]);
1601 i += 1;
1602 }
1603
1604 result
1605 }
1606
1607 fn history_quick_subst(
1610 &self,
1611 chars: &[char],
1612 engine: &crate::history::HistoryEngine,
1613 ) -> Option<String> {
1614 let mut i = 1; let mut old = String::new();
1616 while i < chars.len() && chars[i] != '^' {
1617 old.push(chars[i]);
1618 i += 1;
1619 }
1620 if i >= chars.len() {
1621 return None;
1622 }
1623 i += 1; let mut new = String::new();
1625 while i < chars.len() && chars[i] != '^' && chars[i] != '\n' {
1626 new.push(chars[i]);
1627 i += 1;
1628 }
1629 let prev = engine.get_by_offset(0).ok()??;
1630 Some(prev.command.replacen(&old, &new, 1))
1631 }
1632
1633 fn history_resolve_event(
1636 &self,
1637 chars: &[char],
1638 bang_pos: usize,
1639 engine: &crate::history::HistoryEngine,
1640 current_line: &str,
1641 ) -> (Option<String>, usize) {
1642 let mut i = bang_pos + 1; let in_brace = i < chars.len() && chars[i] == '{';
1646 if in_brace {
1647 i += 1;
1648 }
1649
1650 let c = if i < chars.len() { chars[i] } else { return (None, bang_pos); };
1651
1652 let (event, new_i) = match c {
1653 '!' => {
1654 let entry = engine.get_by_offset(0).ok().flatten();
1656 (entry.map(|e| e.command), i + 1)
1657 }
1658 '#' => {
1659 (Some(current_line.to_string()), i + 1)
1661 }
1662 '-' => {
1663 i += 1;
1665 let start = i;
1666 while i < chars.len() && chars[i].is_ascii_digit() {
1667 i += 1;
1668 }
1669 if i > start {
1670 let n: usize = chars[start..i].iter().collect::<String>().parse().unwrap_or(0);
1671 if n > 0 {
1672 let entry = engine.get_by_offset(n - 1).ok().flatten();
1673 (entry.map(|e| e.command), i)
1674 } else {
1675 (None, bang_pos)
1676 }
1677 } else {
1678 (None, bang_pos)
1679 }
1680 }
1681 '?' => {
1682 i += 1;
1684 let start = i;
1685 while i < chars.len() && chars[i] != '?' && chars[i] != '\n' {
1686 i += 1;
1687 }
1688 let search: String = chars[start..i].iter().collect();
1689 if i < chars.len() && chars[i] == '?' {
1690 i += 1;
1691 }
1692 let entry = engine.search(&search, 1).ok().and_then(|v| v.into_iter().next());
1693 (entry.map(|e| e.command), i)
1694 }
1695 c if c.is_ascii_digit() => {
1696 let start = i;
1698 while i < chars.len() && chars[i].is_ascii_digit() {
1699 i += 1;
1700 }
1701 let n: i64 = chars[start..i].iter().collect::<String>().parse().unwrap_or(0);
1702 if n > 0 {
1703 let entry = engine.get_by_number(n).ok().flatten();
1704 (entry.map(|e| e.command), i)
1705 } else {
1706 (None, bang_pos)
1707 }
1708 }
1709 '$' => {
1710 let entry = engine.get_by_offset(0).ok().flatten();
1712 let word = entry.and_then(|e| {
1713 Self::history_split_words(&e.command).last().cloned()
1714 });
1715 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
1717 i + 2
1718 } else {
1719 i + 1
1720 };
1721 return (word, final_i);
1722 }
1723 '^' => {
1724 let entry = engine.get_by_offset(0).ok().flatten();
1726 let word = entry.and_then(|e| {
1727 let words = Self::history_split_words(&e.command);
1728 words.get(1).cloned()
1729 });
1730 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
1731 i + 2
1732 } else {
1733 i + 1
1734 };
1735 return (word, final_i);
1736 }
1737 '*' => {
1738 let entry = engine.get_by_offset(0).ok().flatten();
1740 let word = entry.map(|e| {
1741 let words = Self::history_split_words(&e.command);
1742 if words.len() > 1 { words[1..].join(" ") } else { String::new() }
1743 });
1744 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
1745 i + 2
1746 } else {
1747 i + 1
1748 };
1749 return (word, final_i);
1750 }
1751 c if c.is_alphabetic() || c == '_' || c == '/' || c == '.' => {
1752 let start = i;
1754 while i < chars.len()
1755 && !chars[i].is_whitespace()
1756 && chars[i] != ':'
1757 && chars[i] != '!'
1758 && chars[i] != '}'
1759 {
1760 i += 1;
1761 }
1762 let prefix: String = chars[start..i].iter().collect();
1763 let entry = engine
1764 .search_prefix(&prefix, 1)
1765 .ok()
1766 .and_then(|v| v.into_iter().next());
1767 (entry.map(|e| e.command), i)
1768 }
1769 _ => (None, bang_pos),
1770 };
1771
1772 let final_i = if in_brace && new_i < chars.len() && chars[new_i] == '}' {
1774 new_i + 1
1775 } else {
1776 new_i
1777 };
1778
1779 (event, final_i)
1780 }
1781
1782 fn history_split_words(cmd: &str) -> Vec<String> {
1784 let mut words = Vec::new();
1785 let mut current = String::new();
1786 let mut in_sq = false;
1787 let mut in_dq = false;
1788 let mut escaped = false;
1789
1790 for c in cmd.chars() {
1791 if escaped {
1792 current.push(c);
1793 escaped = false;
1794 continue;
1795 }
1796 if c == '\\' {
1797 current.push(c);
1798 escaped = true;
1799 continue;
1800 }
1801 if c == '\'' && !in_dq {
1802 in_sq = !in_sq;
1803 current.push(c);
1804 continue;
1805 }
1806 if c == '"' && !in_sq {
1807 in_dq = !in_dq;
1808 current.push(c);
1809 continue;
1810 }
1811 if c.is_whitespace() && !in_sq && !in_dq {
1812 if !current.is_empty() {
1813 words.push(std::mem::take(&mut current));
1814 }
1815 continue;
1816 }
1817 current.push(c);
1818 }
1819 if !current.is_empty() {
1820 words.push(current);
1821 }
1822 words
1823 }
1824
1825 fn history_apply_designators_and_modifiers(
1829 &self,
1830 chars: &[char],
1831 mut i: usize,
1832 event: &str,
1833 last_subst: &mut Option<(String, String)>,
1834 ) -> (String, usize) {
1835 let words = Self::history_split_words(event);
1836 let argc = words.len().saturating_sub(1); let mut sline = event.to_string();
1840
1841 if i < chars.len() && chars[i] == ':' {
1842 i += 1;
1843 if i < chars.len() {
1844 let (farg, larg, new_i) = self.history_parse_word_range(chars, i, argc);
1846 i = new_i;
1847 if farg.is_some() || larg.is_some() {
1848 let f = farg.unwrap_or(0);
1849 let l = larg.unwrap_or(argc);
1850 let selected: Vec<&String> = words.iter().enumerate()
1851 .filter(|(idx, _)| *idx >= f && *idx <= l)
1852 .map(|(_, w)| w)
1853 .collect();
1854 sline = selected.iter().map(|s| s.as_str()).collect::<Vec<_>>().join(" ");
1855 }
1856 }
1857 } else if i < chars.len() && chars[i] == '*' {
1858 i += 1;
1860 if words.len() > 1 {
1861 sline = words[1..].join(" ");
1862 } else {
1863 sline = String::new();
1864 }
1865 }
1866
1867 while i < chars.len() && chars[i] == ':' {
1869 i += 1;
1870 if i >= chars.len() {
1871 break;
1872 }
1873 let mut global = false;
1874 if chars[i] == 'g' && i + 1 < chars.len() {
1875 global = true;
1876 i += 1;
1877 }
1878 match chars[i] {
1879 'h' => {
1880 i += 1;
1882 if let Some(pos) = sline.rfind('/') {
1883 if pos > 0 {
1884 sline = sline[..pos].to_string();
1885 } else {
1886 sline = "/".to_string();
1887 }
1888 }
1889 }
1890 't' => {
1891 i += 1;
1893 if let Some(pos) = sline.rfind('/') {
1894 sline = sline[pos + 1..].to_string();
1895 }
1896 }
1897 'r' => {
1898 i += 1;
1900 if let Some(pos) = sline.rfind('.') {
1901 if pos > 0 && sline[..pos].rfind('/').map_or(true, |sp| sp < pos) {
1902 sline = sline[..pos].to_string();
1903 }
1904 }
1905 }
1906 'e' => {
1907 i += 1;
1909 if let Some(pos) = sline.rfind('.') {
1910 sline = sline[pos + 1..].to_string();
1911 } else {
1912 sline = String::new();
1913 }
1914 }
1915 'l' => {
1916 i += 1;
1918 sline = sline.to_lowercase();
1919 }
1920 'u' => {
1921 i += 1;
1923 sline = sline.to_uppercase();
1924 }
1925 'p' => {
1926 i += 1;
1928 }
1930 'q' => {
1931 i += 1;
1933 sline = format!("'{}'", sline.replace('\'', "'\\''"));
1934 }
1935 'Q' => {
1936 i += 1;
1938 sline = sline.replace('\'', "").replace('"', "");
1939 }
1940 'a' => {
1941 i += 1;
1943 if !sline.starts_with('/') {
1944 if let Ok(cwd) = std::env::current_dir() {
1945 sline = format!("{}/{}", cwd.display(), sline);
1946 }
1947 }
1948 }
1949 'A' => {
1950 i += 1;
1952 if let Ok(real) = std::fs::canonicalize(&sline) {
1953 sline = real.to_string_lossy().to_string();
1954 }
1955 }
1956 's' | 'S' => {
1957 i += 1;
1959 if i < chars.len() {
1960 let delim = chars[i];
1961 i += 1;
1962 let mut old_s = String::new();
1963 while i < chars.len() && chars[i] != delim {
1964 old_s.push(chars[i]);
1965 i += 1;
1966 }
1967 if i < chars.len() { i += 1; } let mut new_s = String::new();
1969 while i < chars.len() && chars[i] != delim && chars[i] != ':' && chars[i] != ' ' {
1970 new_s.push(chars[i]);
1971 i += 1;
1972 }
1973 if i < chars.len() && chars[i] == delim { i += 1; } *last_subst = Some((old_s.clone(), new_s.clone()));
1975 if global {
1976 sline = sline.replace(&old_s, &new_s);
1977 } else {
1978 sline = sline.replacen(&old_s, &new_s, 1);
1979 }
1980 }
1981 }
1982 '&' => {
1983 i += 1;
1985 if let Some((ref old_s, ref new_s)) = last_subst {
1986 if global {
1987 sline = sline.replace(old_s.as_str(), new_s.as_str());
1988 } else {
1989 sline = sline.replacen(old_s.as_str(), new_s.as_str(), 1);
1990 }
1991 }
1992 }
1993 _ => {
1994 if global {
1995 }
1998 break;
1999 }
2000 }
2001 }
2002
2003 (sline, i)
2004 }
2005
2006 fn history_parse_word_range(
2008 &self,
2009 chars: &[char],
2010 mut i: usize,
2011 argc: usize,
2012 ) -> (Option<usize>, Option<usize>, usize) {
2013 if i >= chars.len() {
2014 return (None, None, i);
2015 }
2016
2017 match chars[i] {
2019 'h' | 't' | 'r' | 'e' | 's' | 'S' | 'g' | 'p' | 'q' | 'Q' | 'l' | 'u' | 'a' | 'A' | '&' => {
2020 return (None, None, i - 1); }
2023 _ => {}
2024 }
2025
2026 let farg = if chars[i] == '^' {
2027 i += 1;
2028 Some(1usize)
2029 } else if chars[i] == '$' {
2030 i += 1;
2031 return (Some(argc), Some(argc), i);
2032 } else if chars[i] == '*' {
2033 i += 1;
2034 return (Some(1), Some(argc), i);
2035 } else if chars[i].is_ascii_digit() {
2036 let start = i;
2037 while i < chars.len() && chars[i].is_ascii_digit() {
2038 i += 1;
2039 }
2040 let n: usize = chars[start..i].iter().collect::<String>().parse().unwrap_or(0);
2041 Some(n)
2042 } else {
2043 None
2044 };
2045
2046 if i < chars.len() && chars[i] == '-' {
2048 i += 1;
2049 if i < chars.len() && chars[i] == '$' {
2050 i += 1;
2051 return (farg, Some(argc), i);
2052 } else if i < chars.len() && chars[i].is_ascii_digit() {
2053 let start = i;
2054 while i < chars.len() && chars[i].is_ascii_digit() {
2055 i += 1;
2056 }
2057 let m: usize = chars[start..i].iter().collect::<String>().parse().unwrap_or(0);
2058 return (farg, Some(m), i);
2059 } else {
2060 return (farg, Some(argc.saturating_sub(1)), i);
2062 }
2063 }
2064
2065 if farg.is_some() {
2066 (farg, farg, i)
2067 } else {
2068 (None, None, i)
2069 }
2070 }
2071
2072 #[tracing::instrument(level = "trace", skip_all)]
2073 pub fn execute_command(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
2074 match cmd {
2075 ShellCommand::Simple(simple) => self.execute_simple(simple),
2076 ShellCommand::Pipeline(cmds, negated) => {
2077 let status = self.execute_pipeline(cmds)?;
2078 if *negated {
2079 self.last_status = if status == 0 { 1 } else { 0 };
2080 } else {
2081 self.last_status = status;
2082 }
2083 Ok(self.last_status)
2084 }
2085 ShellCommand::List(items) => self.execute_list(items),
2086 ShellCommand::Compound(compound) => self.execute_compound(compound),
2087 ShellCommand::FunctionDef(name, body) => {
2088 if name.is_empty() {
2089 let result = self.execute_command(body);
2091 if let Some(ret) = self.returning.take() {
2093 self.last_status = ret;
2094 return Ok(ret);
2095 }
2096 result
2097 } else {
2098 self.functions.insert(name.clone(), (**body).clone());
2100 self.last_status = 0;
2101 Ok(0)
2102 }
2103 }
2104 }
2105 }
2106
2107 #[tracing::instrument(level = "trace", skip_all)]
2108 fn execute_simple(&mut self, cmd: &SimpleCommand) -> Result<i32, String> {
2109 for (var, val, is_append) in &cmd.assignments {
2111 match val {
2112 ShellWord::ArrayLiteral(elements) => {
2113 let new_elements: Vec<String> = elements
2118 .iter()
2119 .flat_map(|e| self.expand_word_split(e))
2120 .collect();
2121
2122 if self.assoc_arrays.contains_key(var) {
2124 if *is_append {
2126 let assoc = self.assoc_arrays.get_mut(var).unwrap();
2127 let mut iter = new_elements.iter();
2128 while let Some(key) = iter.next() {
2129 if let Some(val) = iter.next() {
2130 assoc.insert(key.clone(), val.clone());
2131 }
2132 }
2133 } else {
2134 let mut assoc = HashMap::new();
2135 let mut iter = new_elements.iter();
2136 while let Some(key) = iter.next() {
2137 if let Some(val) = iter.next() {
2138 assoc.insert(key.clone(), val.clone());
2139 }
2140 }
2141 self.assoc_arrays.insert(var.clone(), assoc);
2142 }
2143 } else if *is_append {
2144 let arr = self.arrays.entry(var.clone()).or_insert_with(Vec::new);
2146 arr.extend(new_elements);
2147 } else {
2148 self.arrays.insert(var.clone(), new_elements);
2149 }
2150 }
2151 _ => {
2152 let expanded = self.expand_word(val);
2153
2154 if let Some(bracket_pos) = var.find('[') {
2156 if var.ends_with(']') {
2157 let array_name = &var[..bracket_pos];
2158 let key = &var[bracket_pos + 1..var.len() - 1];
2159 let key = self.expand_string(key); if self.assoc_arrays.contains_key(array_name) {
2163 let assoc = self.assoc_arrays.get_mut(array_name).unwrap();
2164 if *is_append {
2165 let existing = assoc.get(&key).cloned().unwrap_or_default();
2166 assoc.insert(key, existing + &expanded);
2167 } else {
2168 assoc.insert(key, expanded);
2169 }
2170 } else if let Ok(idx) = key.parse::<i64>() {
2171 let idx = if idx < 0 { 0 } else { (idx - 1) as usize }; let arr = self
2174 .arrays
2175 .entry(array_name.to_string())
2176 .or_insert_with(Vec::new);
2177 while arr.len() <= idx {
2178 arr.push(String::new());
2179 }
2180 if *is_append {
2181 arr[idx].push_str(&expanded);
2182 } else {
2183 arr[idx] = expanded;
2184 }
2185 } else {
2186 let assoc = self
2188 .assoc_arrays
2189 .entry(array_name.to_string())
2190 .or_insert_with(HashMap::new);
2191 if *is_append {
2192 let existing = assoc.get(&key).cloned().unwrap_or_default();
2193 assoc.insert(key, existing + &expanded);
2194 } else {
2195 assoc.insert(key, expanded);
2196 }
2197 }
2198 continue;
2199 }
2200 }
2201
2202 let final_value = if *is_append {
2204 let existing = self.variables.get(var).cloned().unwrap_or_default();
2205 existing + &expanded
2206 } else {
2207 expanded
2208 };
2209
2210 if self.readonly_vars.contains(var) {
2211 eprintln!("zshrs: read-only variable: {}", var);
2212 self.last_status = 1;
2213 return Ok(1);
2214 }
2215 if cmd.words.is_empty() {
2216 env::set_var(var, &final_value);
2218 }
2219 self.variables.insert(var.clone(), final_value);
2220 }
2221 }
2222 }
2223
2224 if cmd.words.is_empty() {
2225 self.last_status = 0;
2226 return Ok(0);
2227 }
2228
2229 let is_noglob = cmd.words.first().map(|w| self.expand_word(w) == "noglob").unwrap_or(false);
2231 let saved_noglob = if is_noglob {
2232 let saved = self.options.get("noglob").copied();
2233 self.options.insert("noglob".to_string(), true);
2234 saved
2235 } else {
2236 None
2237 };
2238
2239 let preflight = self.preflight_command_subs(&cmd.words);
2243
2244 let mut words: Vec<String> = cmd
2245 .words
2246 .iter()
2247 .enumerate()
2248 .flat_map(|(i, w)| {
2249 if let Some(rx) = &preflight[i] {
2250 vec![rx.recv().unwrap_or_default()]
2252 } else {
2253 self.expand_word_glob(w)
2254 }
2255 })
2256 .collect();
2257
2258 if is_noglob {
2260 match saved_noglob {
2261 Some(v) => { self.options.insert("noglob".to_string(), v); }
2262 None => { self.options.remove("noglob"); }
2263 }
2264 }
2265 if words.is_empty() {
2266 self.last_status = 0;
2267 return Ok(0);
2268 }
2269
2270 if !self.global_aliases.is_empty() {
2272 let global_aliases = self.global_aliases.clone();
2273 words = words
2274 .into_iter()
2275 .map(|w| global_aliases.get(&w).cloned().unwrap_or(w))
2276 .collect();
2277 }
2278
2279 if self.options.get("xtrace").copied().unwrap_or(false) {
2281 let ps4 = self.variables.get("PS4").cloned().unwrap_or_else(|| "+".to_string());
2282 eprintln!("{}{}", ps4, words.join(" "));
2283 }
2284
2285 let cmd_name = &words[0];
2287 if let Some(alias_value) = self.aliases.get(cmd_name).cloned() {
2288 let expanded_cmd = if words.len() > 1 {
2290 format!("{} {}", alias_value, words[1..].join(" "))
2291 } else {
2292 alias_value
2293 };
2294 return self.execute_script(&expanded_cmd);
2296 }
2297
2298 if !self.suffix_aliases.is_empty() {
2300 let cmd_path = std::path::Path::new(cmd_name);
2301 if let Some(ext) = cmd_path.extension().and_then(|e| e.to_str()) {
2302 if let Some(handler) = self.suffix_aliases.get(ext).cloned() {
2303 let expanded_cmd = format!("{} {}", handler, words.join(" "));
2305 return self.execute_script(&expanded_cmd);
2306 }
2307 }
2308 }
2309
2310 let args = &words[1..];
2311
2312 let is_exec_with_redirects_only =
2315 cmd_name == "exec" && args.is_empty() && !cmd.redirects.is_empty();
2316
2317 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
2319 for redirect in &cmd.redirects {
2320 let target = self.expand_word(&redirect.target);
2321
2322 if let Some(ref var_name) = redirect.fd_var {
2324 use std::os::unix::io::IntoRawFd;
2325 let file_result = match redirect.op {
2326 RedirectOp::Write | RedirectOp::Clobber => std::fs::File::create(&target),
2327 RedirectOp::Append => std::fs::OpenOptions::new()
2328 .create(true)
2329 .append(true)
2330 .open(&target),
2331 RedirectOp::Read => std::fs::File::open(&target),
2332 _ => continue,
2333 };
2334 match file_result {
2335 Ok(file) => {
2336 let new_fd = file.into_raw_fd();
2337 self.variables.insert(var_name.clone(), new_fd.to_string());
2338 if !is_exec_with_redirects_only {
2340 }
2342 }
2343 Err(e) => {
2344 eprintln!("{}: {}: {}", cmd_name, target, e);
2345 return Ok(1);
2346 }
2347 }
2348 continue;
2349 }
2350
2351 let fd = redirect.fd.unwrap_or(match redirect.op {
2352 RedirectOp::Read
2353 | RedirectOp::HereDoc
2354 | RedirectOp::HereString
2355 | RedirectOp::ReadWrite => 0,
2356 _ => 1,
2357 });
2358
2359 match redirect.op {
2360 RedirectOp::Write | RedirectOp::Clobber => {
2361 use std::os::unix::io::IntoRawFd;
2362 if !is_exec_with_redirects_only {
2363 let saved = unsafe { libc::dup(fd) };
2364 if saved >= 0 {
2365 saved_fds.push((fd, saved));
2366 }
2367 }
2368 if let Ok(file) = std::fs::File::create(&target) {
2369 let new_fd = file.into_raw_fd();
2370 unsafe {
2371 libc::dup2(new_fd, fd);
2372 }
2373 unsafe {
2374 libc::close(new_fd);
2375 }
2376 }
2377 }
2378 RedirectOp::Append => {
2379 use std::os::unix::io::IntoRawFd;
2380 if !is_exec_with_redirects_only {
2381 let saved = unsafe { libc::dup(fd) };
2382 if saved >= 0 {
2383 saved_fds.push((fd, saved));
2384 }
2385 }
2386 if let Ok(file) = std::fs::OpenOptions::new()
2387 .create(true)
2388 .append(true)
2389 .open(&target)
2390 {
2391 let new_fd = file.into_raw_fd();
2392 unsafe {
2393 libc::dup2(new_fd, fd);
2394 }
2395 unsafe {
2396 libc::close(new_fd);
2397 }
2398 }
2399 }
2400 RedirectOp::Read => {
2401 use std::os::unix::io::IntoRawFd;
2402 if !is_exec_with_redirects_only {
2403 let saved = unsafe { libc::dup(fd) };
2404 if saved >= 0 {
2405 saved_fds.push((fd, saved));
2406 }
2407 }
2408 if let Ok(file) = std::fs::File::open(&target) {
2409 let new_fd = file.into_raw_fd();
2410 unsafe {
2411 libc::dup2(new_fd, fd);
2412 }
2413 unsafe {
2414 libc::close(new_fd);
2415 }
2416 }
2417 }
2418 RedirectOp::DupWrite | RedirectOp::DupRead => {
2419 if let Ok(target_fd) = target.parse::<i32>() {
2420 if !is_exec_with_redirects_only {
2421 let saved = unsafe { libc::dup(fd) };
2422 if saved >= 0 {
2423 saved_fds.push((fd, saved));
2424 }
2425 }
2426 unsafe {
2427 libc::dup2(target_fd, fd);
2428 }
2429 }
2430 }
2431 _ => {}
2432 }
2433 }
2434
2435 if is_exec_with_redirects_only {
2437 self.last_status = 0;
2438 return Ok(0);
2439 }
2440
2441 let status = match cmd_name.as_str() {
2443 "cd" => self.builtin_cd(args),
2444 "pwd" => self.builtin_pwd(&cmd.redirects),
2445 "echo" => self.builtin_echo(args, &cmd.redirects),
2446 "export" => self.builtin_export(args),
2447 "unset" => self.builtin_unset(args),
2448 "source" | "." => self.builtin_source(args),
2449 "exit" | "bye" | "logout" => self.builtin_exit(args),
2450 "return" => self.builtin_return(args),
2451 "true" => 0,
2452 "false" => 1,
2453 ":" => 0,
2454 "chdir" => self.builtin_cd(args),
2455 "test" | "[" => self.builtin_test(args),
2456 "local" => self.builtin_local(args),
2457 "declare" | "typeset" => self.builtin_declare(args),
2458 "read" => self.builtin_read(args),
2459 "shift" => self.builtin_shift(args),
2460 "eval" => self.builtin_eval(args),
2461 "jobs" => self.builtin_jobs(args),
2462 "fg" => self.builtin_fg(args),
2463 "bg" => self.builtin_bg(args),
2464 "kill" => self.builtin_kill(args),
2465 "disown" => self.builtin_disown(args),
2466 "wait" => self.builtin_wait(args),
2467 "autoload" => self.builtin_autoload(args),
2468 "history" => self.builtin_history(args),
2469 "fc" => self.builtin_fc(args),
2470 "trap" => self.builtin_trap(args),
2471 "suspend" => self.builtin_suspend(args),
2472 "alias" => self.builtin_alias(args),
2473 "unalias" => self.builtin_unalias(args),
2474 "set" => self.builtin_set(args),
2475 "shopt" => self.builtin_shopt(args),
2476 "bind" => self.builtin_bindkey(args),
2478 "caller" => self.builtin_caller(args),
2479 "help" => self.builtin_help(args),
2480 "doctor" => self.builtin_doctor(args),
2481 "dbview" => self.builtin_dbview(args),
2482 "profile" => self.builtin_profile(args),
2483 "intercept" => self.builtin_intercept(args),
2484 "intercept_proceed" => self.builtin_intercept_proceed(args),
2485 "async" => self.builtin_async(args),
2487 "await" => self.builtin_await(args),
2488 "pmap" => self.builtin_pmap(args),
2489 "pgrep" => self.builtin_pgrep(args),
2490 "peach" => self.builtin_peach(args),
2491 "barrier" => self.builtin_barrier(args),
2492 "readarray" | "mapfile" => self.builtin_readarray(args),
2493 "setopt" => self.builtin_setopt(args),
2494 "unsetopt" => self.builtin_unsetopt(args),
2495 "getopts" => self.builtin_getopts(args),
2496 "type" => self.builtin_type(args),
2497 "hash" => self.builtin_hash(args),
2498 "add-zsh-hook" => self.builtin_add_zsh_hook(args),
2499 "command" => self.builtin_command(args, &cmd.redirects),
2500 "builtin" => self.builtin_builtin(args, &cmd.redirects),
2501 "let" => self.builtin_let(args),
2502 "compgen" => self.builtin_compgen(args),
2503 "complete" => self.builtin_complete(args),
2504 "compopt" => self.builtin_compopt(args),
2505 "compadd" => self.builtin_compadd(args),
2506 "compset" => self.builtin_compset(args),
2507 "compdef" => self.builtin_compdef(args),
2508 "compinit" => self.builtin_compinit(args),
2509 "cdreplay" => self.builtin_cdreplay(args),
2510 "zstyle" => self.builtin_zstyle(args),
2511 "ztie" => self.builtin_ztie(args),
2513 "zuntie" => self.builtin_zuntie(args),
2514 "zgdbmpath" => self.builtin_zgdbmpath(args),
2515 "pushd" => self.builtin_pushd(args),
2516 "popd" => self.builtin_popd(args),
2517 "dirs" => self.builtin_dirs(args),
2518 "printf" => self.builtin_printf(args),
2519 "break" => self.builtin_break(args),
2521 "continue" => self.builtin_continue(args),
2522 "disable" => self.builtin_disable(args),
2524 "enable" => self.builtin_enable(args),
2525 "emulate" => self.builtin_emulate(args),
2527 "promptinit" => self.builtin_promptinit(args),
2529 "prompt" => self.builtin_prompt(args),
2530 "pcre_compile" => self.builtin_pcre_compile(args),
2532 "pcre_match" => self.builtin_pcre_match(args),
2533 "pcre_study" => self.builtin_pcre_study(args),
2534 "exec" => self.builtin_exec(args),
2536 "float" => self.builtin_float(args),
2538 "integer" => self.builtin_integer(args),
2539 "functions" => self.builtin_functions(args),
2541 "print" => self.builtin_print(args),
2543 "whence" => self.builtin_whence(args),
2545 "where" => self.builtin_where(args),
2546 "which" => self.builtin_which(args),
2547 "ulimit" => self.builtin_ulimit(args),
2549 "limit" => self.builtin_limit(args),
2550 "unlimit" => self.builtin_unlimit(args),
2551 "umask" => self.builtin_umask(args),
2553 "rehash" => self.builtin_rehash(args),
2555 "unhash" => self.builtin_unhash(args),
2556 "times" => self.builtin_times(args),
2558 "zmodload" => self.builtin_zmodload(args),
2560 "r" => self.builtin_r(args),
2562 "ttyctl" => self.builtin_ttyctl(args),
2564 "noglob" => self.builtin_noglob(args, &cmd.redirects),
2566 "zstat" | "stat" => self.builtin_zstat(args),
2568 "strftime" => self.builtin_strftime(args),
2570 "zsleep" => self.builtin_zsleep(args),
2572 "zsystem" => self.builtin_zsystem(args),
2574 "sync" => self.builtin_sync(args),
2576 "mkdir" => self.builtin_mkdir(args),
2577 "rmdir" => self.builtin_rmdir(args),
2578 "ln" => self.builtin_ln(args),
2579 "mv" => self.builtin_mv(args),
2580 "cp" => self.builtin_cp(args),
2581 "rm" => self.builtin_rm(args),
2582 "chown" => self.builtin_chown(args),
2583 "chmod" => self.builtin_chmod(args),
2584 "zln" | "zmv" | "zcp" => self.builtin_zfiles(cmd_name, args),
2585 "coproc" => self.builtin_coproc(args),
2587 "zparseopts" => self.builtin_zparseopts(args),
2589 "readonly" => self.builtin_readonly(args),
2591 "unfunction" => self.builtin_unfunction(args),
2592 "getln" => self.builtin_getln(args),
2594 "pushln" => self.builtin_pushln(args),
2595 "bindkey" => self.builtin_bindkey(args),
2597 "zle" => self.builtin_zle(args),
2599 "sched" => self.builtin_sched(args),
2601 "zformat" => self.builtin_zformat(args),
2603 "zcompile" => self.builtin_zcompile(args),
2605 "vared" => self.builtin_vared(args),
2607 "echotc" => self.builtin_echotc(args),
2609 "echoti" => self.builtin_echoti(args),
2610 "zpty" => self.builtin_zpty(args),
2612 "zprof" => self.builtin_zprof(args),
2613 "zsocket" => self.builtin_zsocket(args),
2614 "ztcp" => self.builtin_ztcp(args),
2615 "zregexparse" => self.builtin_zregexparse(args),
2616 "clone" => self.builtin_clone(args),
2617 "log" => self.builtin_log(args),
2618 "comparguments" => self.builtin_comparguments(args),
2620 "compcall" => self.builtin_compcall(args),
2621 "compctl" => self.builtin_compctl(args),
2622 "compdescribe" => self.builtin_compdescribe(args),
2623 "compfiles" => self.builtin_compfiles(args),
2624 "compgroups" => self.builtin_compgroups(args),
2625 "compquote" => self.builtin_compquote(args),
2626 "comptags" => self.builtin_comptags(args),
2627 "comptry" => self.builtin_comptry(args),
2628 "compvalues" => self.builtin_compvalues(args),
2629 "cap" | "getcap" | "setcap" => self.builtin_cap(args),
2631 "zftp" => self.builtin_zftp(args),
2633 "zcurses" => self.builtin_zcurses(args),
2635 "sysread" => self.builtin_sysread(args),
2637 "syswrite" => self.builtin_syswrite(args),
2638 "syserror" => self.builtin_syserror(args),
2639 "sysopen" => self.builtin_sysopen(args),
2640 "sysseek" => self.builtin_sysseek(args),
2641 "mapfile" => 0, "private" => self.builtin_private(args),
2645 "zgetattr" | "zsetattr" | "zdelattr" | "zlistattr" => {
2647 self.builtin_zattr(cmd_name, args)
2648 }
2649 "_arguments" | "_describe" | "_description" | "_message" | "_tags" | "_requested"
2652 | "_all_labels" | "_next_label" | "_files" | "_path_files" | "_directories" | "_cd"
2653 | "_default" | "_dispatch" | "_complete" | "_main_complete" | "_normal"
2654 | "_approximate" | "_correct" | "_expand" | "_history" | "_match" | "_menu"
2655 | "_oldlist" | "_list" | "_prefix" | "_generic" | "_wanted" | "_alternative"
2656 | "_values" | "_sequence" | "_sep_parts" | "_multi_parts" | "_combination"
2657 | "_parameters" | "_command" | "_command_names" | "_commands" | "_functions"
2658 | "_aliases" | "_builtins" | "_jobs" | "_pids" | "_process_names" | "_signals"
2659 | "_users" | "_groups" | "_hosts" | "_domains" | "_urls" | "_email_addresses"
2660 | "_options" | "_contexts" | "_set_options" | "_unset_options" | "_vars"
2661 | "_env_variables" | "_shell_variables" | "_arrays" | "_globflags" | "_globquals"
2662 | "_globqual_delims" | "_subscript" | "_history_modifiers" | "_brace_parameter"
2663 | "_tilde" | "_style" | "_cache_invalid" | "_store_cache" | "_retrieve_cache"
2664 | "_call_function" | "_call_program" | "_pick_variant" | "_setup"
2665 | "_comp_priv_prefix" | "_regex_arguments" | "_regex_words" | "_guard"
2666 | "_gnu_generic" | "_long_options" | "_x_arguments" | "_sub_commands"
2667 | "_cmdstring" | "_cmdambivalent" | "_first" | "_precommand" | "_user_at_host"
2668 | "_user_expand" | "_path_commands" | "_globbed_files" | "_have_glob_qual" => {
2669 0
2672 }
2673 _ => {
2674 if !self.intercepts.is_empty() {
2678 let full_cmd = if args.is_empty() {
2679 cmd_name.to_string()
2680 } else {
2681 format!("{} {}", cmd_name, args.join(" "))
2682 };
2683 if let Some(result) = self.run_intercepts(cmd_name, &full_cmd, args) {
2684 return result;
2685 }
2686 }
2687
2688 if let Some(func) = self.functions.get(cmd_name).cloned() {
2690 return self.call_function(&func, args);
2691 }
2692
2693 if self.maybe_autoload(cmd_name) {
2695 if let Some(func) = self.functions.get(cmd_name).cloned() {
2696 return self.call_function(&func, args);
2697 }
2698 }
2699
2700 if self.autoload_function(cmd_name).is_some() {
2702 if let Some(func) = self.functions.get(cmd_name).cloned() {
2703 return self.call_function(&func, args);
2704 }
2705 }
2706
2707 self.execute_external(cmd_name, args, &cmd.redirects)?
2709 }
2710 };
2711
2712 for (fd, saved) in saved_fds.into_iter().rev() {
2714 unsafe {
2715 libc::dup2(saved, fd);
2716 libc::close(saved);
2717 }
2718 }
2719
2720 self.last_status = status;
2721 Ok(status)
2722 }
2723
2724 #[tracing::instrument(level = "debug", skip_all)]
2726 fn call_function(&mut self, func: &ShellCommand, args: &[String]) -> Result<i32, String> {
2727 let saved_params = std::mem::take(&mut self.positional_params);
2729
2730 let saved_local_vars = self.local_save_stack.len();
2733 self.local_scope_depth += 1;
2734
2735 self.positional_params = args.to_vec();
2737
2738 let result = self.execute_command(func);
2740
2741 let final_result = if let Some(ret) = self.returning.take() {
2743 self.last_status = ret;
2744 Ok(ret)
2745 } else {
2746 result
2747 };
2748
2749 self.local_scope_depth -= 1;
2751 while self.local_save_stack.len() > saved_local_vars {
2752 if let Some((name, old_val)) = self.local_save_stack.pop() {
2753 match old_val {
2754 Some(v) => { self.variables.insert(name, v); }
2755 None => { self.variables.remove(&name); }
2756 }
2757 }
2758 }
2759
2760 self.positional_params = saved_params;
2762
2763 final_result
2764 }
2765
2766 fn execute_external(
2767 &mut self,
2768 cmd: &str,
2769 args: &[String],
2770 redirects: &[Redirect],
2771 ) -> Result<i32, String> {
2772 self.execute_external_bg(cmd, args, redirects, false)
2773 }
2774
2775 fn execute_external_bg(
2776 &mut self,
2777 cmd: &str,
2778 args: &[String],
2779 redirects: &[Redirect],
2780 background: bool,
2781 ) -> Result<i32, String> {
2782 tracing::trace!(cmd, bg = background, "exec external");
2783 let mut command = Command::new(cmd);
2784 command.args(args);
2785
2786 for redir in redirects {
2788 let target = self.expand_word(&redir.target);
2789 match redir.op {
2790 RedirectOp::Read => match File::open(&target) {
2791 Ok(f) => {
2792 command.stdin(Stdio::from(f));
2793 }
2794 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
2795 },
2796 RedirectOp::Write => match File::create(&target) {
2797 Ok(f) => {
2798 command.stdout(Stdio::from(f));
2799 }
2800 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
2801 },
2802 RedirectOp::Append => {
2803 match OpenOptions::new().create(true).append(true).open(&target) {
2804 Ok(f) => {
2805 command.stdout(Stdio::from(f));
2806 }
2807 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
2808 }
2809 }
2810 RedirectOp::WriteBoth => match File::create(&target) {
2811 Ok(f) => {
2812 let f2 = f
2813 .try_clone()
2814 .map_err(|e| format!("Cannot clone fd: {}", e))?;
2815 command.stdout(Stdio::from(f));
2816 command.stderr(Stdio::from(f2));
2817 }
2818 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
2819 },
2820 RedirectOp::AppendBoth => {
2821 match OpenOptions::new().create(true).append(true).open(&target) {
2822 Ok(f) => {
2823 let f2 = f
2824 .try_clone()
2825 .map_err(|e| format!("Cannot clone fd: {}", e))?;
2826 command.stdout(Stdio::from(f));
2827 command.stderr(Stdio::from(f2));
2828 }
2829 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
2830 }
2831 }
2832 RedirectOp::HereDoc => {
2833 if let Some(ref content) = redir.heredoc_content {
2835 let expanded = self.expand_string(content);
2837 command.stdin(Stdio::piped());
2838 use std::io::Write;
2841 let mut temp_file = tempfile::NamedTempFile::new()
2842 .map_err(|e| format!("Cannot create temp file: {}", e))?;
2843 temp_file
2844 .write_all(expanded.as_bytes())
2845 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
2846 let temp_path = temp_file.into_temp_path();
2847 let f = File::open(&temp_path)
2848 .map_err(|e| format!("Cannot open temp file: {}", e))?;
2849 command.stdin(Stdio::from(f));
2850 }
2851 }
2852 RedirectOp::HereString => {
2853 use std::io::Write;
2855 let content = format!("{}\n", target);
2856 let mut temp_file = tempfile::NamedTempFile::new()
2857 .map_err(|e| format!("Cannot create temp file: {}", e))?;
2858 temp_file
2859 .write_all(content.as_bytes())
2860 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
2861 let temp_path = temp_file.into_temp_path();
2862 let f = File::open(&temp_path)
2863 .map_err(|e| format!("Cannot open temp file: {}", e))?;
2864 command.stdin(Stdio::from(f));
2865 }
2866 _ => {
2867 }
2869 }
2870
2871 if let Some(ref var_name) = redir.fd_var {
2873 #[cfg(unix)]
2876 {
2877 use std::os::unix::io::AsRawFd;
2878 let fd = match redir.op {
2879 RedirectOp::Write | RedirectOp::Append => {
2880 let f = if redir.op == RedirectOp::Write {
2881 File::create(&target)
2882 } else {
2883 OpenOptions::new().create(true).append(true).open(&target)
2884 };
2885 match f {
2886 Ok(file) => {
2887 let raw_fd = file.as_raw_fd();
2888 std::mem::forget(file); raw_fd
2890 }
2891 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
2892 }
2893 }
2894 RedirectOp::Read => match File::open(&target) {
2895 Ok(file) => {
2896 let raw_fd = file.as_raw_fd();
2897 std::mem::forget(file);
2898 raw_fd
2899 }
2900 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
2901 },
2902 _ => continue,
2903 };
2904 self.variables.insert(var_name.clone(), fd.to_string());
2905 }
2906 }
2907 }
2908
2909 if background {
2910 match command.spawn() {
2911 Ok(child) => {
2912 let pid = child.id();
2913 let cmd_str = format!("{} {}", cmd, args.join(" "));
2914 let job_id = self.jobs.add_job(child, cmd_str, JobState::Running);
2915 println!("[{}] {}", job_id, pid);
2916 Ok(0)
2917 }
2918 Err(e) => {
2919 if e.kind() == io::ErrorKind::NotFound {
2920 eprintln!("zshrs: command not found: {}", cmd);
2921 Ok(127)
2922 } else {
2923 Err(format!("zshrs: {}: {}", cmd, e))
2924 }
2925 }
2926 }
2927 } else {
2928 match command.status() {
2929 Ok(status) => Ok(status.code().unwrap_or(1)),
2930 Err(e) => {
2931 if e.kind() == io::ErrorKind::NotFound {
2932 eprintln!("zshrs: command not found: {}", cmd);
2933 Ok(127)
2934 } else {
2935 Err(format!("zshrs: {}: {}", cmd, e))
2936 }
2937 }
2938 }
2939 }
2940 }
2941
2942 #[tracing::instrument(level = "trace", skip_all, fields(stages = cmds.len()))]
2943 fn execute_pipeline(&mut self, cmds: &[ShellCommand]) -> Result<i32, String> {
2944 if cmds.len() == 1 {
2945 return self.execute_command(&cmds[0]);
2946 }
2947
2948 let mut children: Vec<Child> = Vec::new();
2949 let mut prev_stdout: Option<std::process::ChildStdout> = None;
2950
2951 for (i, cmd) in cmds.iter().enumerate() {
2952 if let ShellCommand::Simple(simple) = cmd {
2953 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
2954 if words.is_empty() {
2955 continue;
2956 }
2957
2958 let mut command = Command::new(&words[0]);
2959 command.args(&words[1..]);
2960
2961 if let Some(stdout) = prev_stdout.take() {
2962 command.stdin(Stdio::from(stdout));
2963 }
2964
2965 if i < cmds.len() - 1 {
2966 command.stdout(Stdio::piped());
2967 }
2968
2969 match command.spawn() {
2970 Ok(mut child) => {
2971 prev_stdout = child.stdout.take();
2972 children.push(child);
2973 }
2974 Err(e) => {
2975 eprintln!("zshrs: {}: {}", words[0], e);
2976 return Ok(127);
2977 }
2978 }
2979 }
2980 }
2981
2982 let mut last_status = 0;
2984 for mut child in children {
2985 if let Ok(status) = child.wait() {
2986 last_status = status.code().unwrap_or(1);
2987 }
2988 }
2989
2990 Ok(last_status)
2991 }
2992
2993 fn execute_list(&mut self, items: &[(ShellCommand, ListOp)]) -> Result<i32, String> {
2994 for (cmd, op) in items {
2995 let background = matches!(op, ListOp::Amp);
2997
2998 let status = if background {
2999 self.execute_command_bg(cmd)?
3000 } else {
3001 self.execute_command(cmd)?
3002 };
3003
3004 if self.returning.is_some() || self.breaking > 0 || self.continuing > 0 {
3006 return Ok(status);
3007 }
3008
3009 match op {
3010 ListOp::And => {
3011 if status != 0 {
3012 return Ok(status);
3013 }
3014 }
3015 ListOp::Or => {
3016 if status == 0 {
3017 return Ok(0);
3018 }
3019 }
3020 ListOp::Amp => {
3021 }
3023 ListOp::Semi | ListOp::Newline => {
3024 }
3026 }
3027 }
3028
3029 Ok(self.last_status)
3030 }
3031
3032 fn execute_command_bg(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
3033 if let ShellCommand::Simple(simple) = cmd {
3035 if simple.words.is_empty() {
3036 return Ok(0);
3037 }
3038 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
3039 let cmd_name = &words[0];
3040 let args: Vec<String> = words[1..].to_vec();
3041 return self.execute_external_bg(cmd_name, &args, &simple.redirects, true);
3042 }
3043 self.execute_command(cmd)
3045 }
3046
3047 #[tracing::instrument(level = "trace", skip_all)]
3048 fn execute_compound(&mut self, compound: &CompoundCommand) -> Result<i32, String> {
3049 match compound {
3050 CompoundCommand::BraceGroup(cmds) => {
3051 for cmd in cmds {
3052 self.execute_command(cmd)?;
3053 if self.returning.is_some() {
3054 break;
3055 }
3056 }
3057 Ok(self.last_status)
3058 }
3059 CompoundCommand::Subshell(cmds) => {
3060 let saved_vars = self.variables.clone();
3063 let saved_arrays = self.arrays.clone();
3064 let saved_assoc = self.assoc_arrays.clone();
3065 let saved_params = self.positional_params.clone();
3066
3067 for cmd in cmds {
3068 self.execute_command(cmd)?;
3069 if self.returning.is_some() {
3070 break;
3071 }
3072 }
3073 let status = self.last_status;
3074
3075 self.variables = saved_vars;
3077 self.arrays = saved_arrays;
3078 self.assoc_arrays = saved_assoc;
3079 self.positional_params = saved_params;
3080 self.last_status = status;
3081
3082 Ok(status)
3083 }
3084
3085 CompoundCommand::If {
3086 conditions,
3087 else_part,
3088 } => {
3089 for (cond, body) in conditions {
3090 for cmd in cond {
3092 self.execute_command(cmd)?;
3093 }
3094
3095 if self.last_status == 0 {
3096 for cmd in body {
3098 self.execute_command(cmd)?;
3099 }
3100 return Ok(self.last_status);
3101 }
3102 }
3103
3104 if let Some(else_cmds) = else_part {
3106 for cmd in else_cmds {
3107 self.execute_command(cmd)?;
3108 }
3109 }
3110
3111 Ok(self.last_status)
3112 }
3113
3114 CompoundCommand::For { var, words, body } => {
3115 let items: Vec<String> = if let Some(words) = words {
3116 words
3117 .iter()
3118 .flat_map(|w| self.expand_word_split(w))
3119 .collect()
3120 } else {
3121 self.positional_params.clone()
3123 };
3124
3125 for item in items {
3126 env::set_var(var, &item);
3127 self.variables.insert(var.clone(), item);
3128
3129 for cmd in body {
3130 self.execute_command(cmd)?;
3131 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
3132 break;
3133 }
3134 }
3135
3136 if self.continuing > 0 {
3137 self.continuing -= 1;
3138 if self.continuing > 0 {
3139 break;
3140 }
3141 continue;
3142 }
3143 if self.breaking > 0 {
3144 self.breaking -= 1;
3145 break;
3146 }
3147 if self.returning.is_some() {
3148 break;
3149 }
3150 }
3151
3152 Ok(self.last_status)
3153 }
3154
3155 CompoundCommand::ForArith {
3156 init,
3157 cond,
3158 step,
3159 body,
3160 } => {
3161 if !init.is_empty() {
3164 self.evaluate_arithmetic_expr(init);
3165 }
3166
3167 loop {
3169 if !cond.is_empty() {
3171 let cond_result = self.eval_arith_expr(cond);
3172 if cond_result == 0 {
3173 break;
3174 }
3175 }
3176
3177 for cmd in body {
3179 self.execute_command(cmd)?;
3180 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
3181 break;
3182 }
3183 }
3184
3185 if self.continuing > 0 {
3186 self.continuing -= 1;
3187 if self.continuing > 0 {
3188 break;
3189 }
3190 continue;
3191 }
3192 if self.breaking > 0 {
3193 self.breaking -= 1;
3194 break;
3195 }
3196 if self.returning.is_some() {
3197 break;
3198 }
3199
3200 if !step.is_empty() {
3202 self.evaluate_arithmetic_expr(step);
3203 }
3204 }
3205 Ok(self.last_status)
3206 }
3207
3208 CompoundCommand::While { condition, body } => {
3209 loop {
3210 for cmd in condition {
3211 self.execute_command(cmd)?;
3212 if self.breaking > 0 || self.returning.is_some() {
3213 break;
3214 }
3215 }
3216
3217 if self.last_status != 0 || self.breaking > 0 || self.returning.is_some() {
3218 break;
3219 }
3220
3221 for cmd in body {
3222 self.execute_command(cmd)?;
3223 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
3224 break;
3225 }
3226 }
3227
3228 if self.continuing > 0 {
3229 self.continuing -= 1;
3230 if self.continuing > 0 {
3231 break;
3232 }
3233 continue;
3234 }
3235 if self.breaking > 0 {
3236 self.breaking -= 1;
3237 break;
3238 }
3239 }
3240 Ok(self.last_status)
3241 }
3242
3243 CompoundCommand::Until { condition, body } => {
3244 loop {
3245 for cmd in condition {
3246 self.execute_command(cmd)?;
3247 if self.breaking > 0 || self.returning.is_some() {
3248 break;
3249 }
3250 }
3251
3252 if self.last_status == 0 || self.breaking > 0 || self.returning.is_some() {
3253 break;
3254 }
3255
3256 for cmd in body {
3257 self.execute_command(cmd)?;
3258 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
3259 break;
3260 }
3261 }
3262
3263 if self.continuing > 0 {
3264 self.continuing -= 1;
3265 if self.continuing > 0 {
3266 break;
3267 }
3268 continue;
3269 }
3270 if self.breaking > 0 {
3271 self.breaking -= 1;
3272 break;
3273 }
3274 }
3275 Ok(self.last_status)
3276 }
3277
3278 CompoundCommand::Case { word, cases } => {
3279 let value = self.expand_word(word);
3280
3281 for (patterns, body, term) in cases {
3282 for pattern in patterns {
3283 let pat = self.expand_word(pattern);
3284 if self.matches_pattern(&value, &pat) {
3285 for cmd in body {
3286 self.execute_command(cmd)?;
3287 }
3288
3289 match term {
3290 CaseTerminator::Break => return Ok(self.last_status),
3291 CaseTerminator::Fallthrough => {
3292 }
3294 CaseTerminator::Continue => {
3295 break;
3297 }
3298 }
3299 }
3300 }
3301 }
3302
3303 Ok(self.last_status)
3304 }
3305
3306 CompoundCommand::Select { var, words, body } => {
3307 if let Some(words) = words {
3309 if let Some(first) = words.first() {
3310 let val = self.expand_word(first);
3311 env::set_var(var, &val);
3312 for cmd in body {
3313 self.execute_command(cmd)?;
3314 }
3315 }
3316 }
3317 Ok(self.last_status)
3318 }
3319
3320 CompoundCommand::Repeat { count, body } => {
3321 let n: i64 = self
3322 .expand_word(&ShellWord::Literal(count.clone()))
3323 .parse()
3324 .unwrap_or(0);
3325
3326 for _ in 0..n {
3327 for cmd in body {
3328 self.execute_command(cmd)?;
3329 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
3330 break;
3331 }
3332 }
3333
3334 if self.continuing > 0 {
3335 self.continuing -= 1;
3336 if self.continuing > 0 {
3337 break;
3338 }
3339 continue;
3340 }
3341 if self.breaking > 0 {
3342 self.breaking -= 1;
3343 break;
3344 }
3345 if self.returning.is_some() {
3346 break;
3347 }
3348 }
3349
3350 Ok(self.last_status)
3351 }
3352
3353 CompoundCommand::Try {
3354 try_body,
3355 always_body,
3356 } => {
3357 for cmd in try_body {
3360 if let Err(_e) = self.execute_command(cmd) {
3361 break;
3362 }
3363 if self.returning.is_some() {
3364 break;
3365 }
3366 }
3367
3368 let endval = self.last_status;
3370
3371 let save_returning = self.returning.take();
3373 let save_breaking = self.breaking;
3374 let save_continuing = self.continuing;
3375 self.breaking = 0;
3376 self.continuing = 0;
3377
3378 for cmd in always_body {
3380 let _ = self.execute_command(cmd);
3381 }
3382
3383 if self.returning.is_none() {
3386 self.returning = save_returning;
3387 }
3388 if self.breaking == 0 {
3389 self.breaking = save_breaking;
3390 }
3391 if self.continuing == 0 {
3392 self.continuing = save_continuing;
3393 }
3394
3395 self.last_status = endval;
3396 Ok(endval)
3397 }
3398
3399 CompoundCommand::Cond(expr) => {
3400 let result = self.eval_cond_expr(expr);
3401 self.last_status = if result { 0 } else { 1 };
3402 Ok(self.last_status)
3403 }
3404
3405 CompoundCommand::Arith(expr) => {
3406 let result = self.evaluate_arithmetic_expr(expr);
3408 self.last_status = if result != 0 { 0 } else { 1 };
3410 Ok(self.last_status)
3411 }
3412
3413 CompoundCommand::Coproc { name, body } => {
3414 let (stdin_read, stdin_write) =
3416 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
3417 let (stdout_read, stdout_write) =
3418 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
3419
3420 let cmd_str = match body.as_ref() {
3422 ShellCommand::Simple(simple) => simple
3423 .words
3424 .iter()
3425 .map(|w| self.expand_word(w))
3426 .collect::<Vec<_>>()
3427 .join(" "),
3428 ShellCommand::Compound(CompoundCommand::BraceGroup(_cmds)) => {
3429 "bash -c 'true'".to_string()
3432 }
3433 _ => "true".to_string(),
3434 };
3435
3436 let parts: Vec<&str> = cmd_str.split_whitespace().collect();
3438 if parts.is_empty() {
3439 return Ok(0);
3440 }
3441
3442 let mut command = Command::new(parts[0]);
3443 if parts.len() > 1 {
3444 command.args(&parts[1..]);
3445 }
3446
3447 use std::os::unix::io::{FromRawFd, IntoRawFd};
3448
3449 command.stdin(unsafe { Stdio::from_raw_fd(stdin_read.into_raw_fd()) });
3450 command.stdout(unsafe { Stdio::from_raw_fd(stdout_write.into_raw_fd()) });
3451
3452 match command.spawn() {
3453 Ok(child) => {
3454 let pid = child.id();
3455 let coproc_name = name.clone().unwrap_or_else(|| "COPROC".to_string());
3456
3457 let read_fd = stdout_read.into_raw_fd();
3461 let write_fd = stdin_write.into_raw_fd();
3462
3463 self.arrays.insert(
3464 coproc_name.clone(),
3465 vec![read_fd.to_string(), write_fd.to_string()],
3466 );
3467
3468 self.variables
3470 .insert(format!("{}_PID", coproc_name), pid.to_string());
3471
3472 let cmd_str_clone = cmd_str.clone();
3473 self.jobs.add_job(child, cmd_str_clone, JobState::Running);
3474
3475 Ok(0)
3476 }
3477 Err(e) => {
3478 if e.kind() == io::ErrorKind::NotFound {
3479 eprintln!("zshrs: command not found: {}", parts[0]);
3480 Ok(127)
3481 } else {
3482 Err(format!("zshrs: coproc: {}: {}", parts[0], e))
3483 }
3484 }
3485 }
3486 }
3487
3488 CompoundCommand::WithRedirects(cmd, redirects) => {
3489 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
3491
3492 for redirect in redirects {
3494 let fd = redirect.fd.unwrap_or(match redirect.op {
3495 RedirectOp::Read
3496 | RedirectOp::HereDoc
3497 | RedirectOp::HereString
3498 | RedirectOp::ReadWrite => 0,
3499 _ => 1,
3500 });
3501
3502 let target = self.expand_word(&redirect.target);
3503
3504 match redirect.op {
3505 RedirectOp::Write | RedirectOp::Clobber => {
3506 use std::os::unix::io::IntoRawFd;
3507 let saved = unsafe { libc::dup(fd) };
3508 if saved >= 0 {
3509 saved_fds.push((fd, saved));
3510 }
3511 if let Ok(file) = std::fs::File::create(&target) {
3512 let new_fd = file.into_raw_fd();
3513 unsafe {
3514 libc::dup2(new_fd, fd);
3515 }
3516 unsafe {
3517 libc::close(new_fd);
3518 }
3519 }
3520 }
3521 RedirectOp::Append => {
3522 use std::os::unix::io::IntoRawFd;
3523 let saved = unsafe { libc::dup(fd) };
3524 if saved >= 0 {
3525 saved_fds.push((fd, saved));
3526 }
3527 if let Ok(file) = std::fs::OpenOptions::new()
3528 .create(true)
3529 .append(true)
3530 .open(&target)
3531 {
3532 let new_fd = file.into_raw_fd();
3533 unsafe {
3534 libc::dup2(new_fd, fd);
3535 }
3536 unsafe {
3537 libc::close(new_fd);
3538 }
3539 }
3540 }
3541 RedirectOp::Read => {
3542 use std::os::unix::io::IntoRawFd;
3543 let saved = unsafe { libc::dup(fd) };
3544 if saved >= 0 {
3545 saved_fds.push((fd, saved));
3546 }
3547 if let Ok(file) = std::fs::File::open(&target) {
3548 let new_fd = file.into_raw_fd();
3549 unsafe {
3550 libc::dup2(new_fd, fd);
3551 }
3552 unsafe {
3553 libc::close(new_fd);
3554 }
3555 }
3556 }
3557 RedirectOp::DupWrite | RedirectOp::DupRead => {
3558 if let Ok(target_fd) = target.parse::<i32>() {
3559 let saved = unsafe { libc::dup(fd) };
3560 if saved >= 0 {
3561 saved_fds.push((fd, saved));
3562 }
3563 unsafe {
3564 libc::dup2(target_fd, fd);
3565 }
3566 }
3567 }
3568 _ => {}
3569 }
3570 }
3571
3572 let result = self.execute_command(cmd);
3574
3575 for (fd, saved) in saved_fds.into_iter().rev() {
3577 unsafe {
3578 libc::dup2(saved, fd);
3579 libc::close(saved);
3580 }
3581 }
3582
3583 result
3584 }
3585 }
3586 }
3587
3588 #[tracing::instrument(level = "trace", skip_all)]
3590 fn expand_word_glob(&mut self, word: &ShellWord) -> Vec<String> {
3591 match word {
3592 ShellWord::SingleQuoted(s) => vec![s.clone()],
3593 ShellWord::DoubleQuoted(parts) => {
3594 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
3596 }
3597 _ => {
3598 let expanded = self.expand_word(word);
3599
3600 let brace_expanded = self.expand_braces(&expanded);
3602
3603 let noglob = self.options.get("noglob").copied().unwrap_or(false)
3605 || self.options.get("GLOB").map(|v| !v).unwrap_or(false);
3606 brace_expanded
3607 .into_iter()
3608 .flat_map(|s| {
3609 if !noglob
3610 && (s.contains('*')
3611 || s.contains('?')
3612 || s.contains('[')
3613 || self.has_extglob_pattern(&s))
3614 {
3615 self.expand_glob(&s)
3616 } else {
3617 vec![s]
3618 }
3619 })
3620 .collect()
3621 }
3622 }
3623 }
3624
3625 fn expand_braces(&self, s: &str) -> Vec<String> {
3627 let mut depth = 0;
3629 let mut brace_start = None;
3630
3631 for (i, c) in s.char_indices() {
3632 match c {
3633 '{' => {
3634 if depth == 0 {
3635 brace_start = Some(i);
3636 }
3637 depth += 1;
3638 }
3639 '}' => {
3640 depth -= 1;
3641 if depth == 0 {
3642 if let Some(start) = brace_start {
3643 let prefix = &s[..start];
3644 let content = &s[start + 1..i];
3645 let suffix = &s[i + 1..];
3646
3647 let expansions = if content.contains("..") {
3649 self.expand_brace_sequence(content)
3650 } else if content.contains(',') {
3651 self.expand_brace_list(content)
3652 } else {
3653 return vec![s.to_string()];
3655 };
3656
3657 let mut results = Vec::new();
3659 for exp in expansions {
3660 let combined = format!("{}{}{}", prefix, exp, suffix);
3661 results.extend(self.expand_braces(&combined));
3663 }
3664 return results;
3665 }
3666 }
3667 }
3668 _ => {}
3669 }
3670 }
3671
3672 vec![s.to_string()]
3674 }
3675
3676 fn expand_brace_list(&self, content: &str) -> Vec<String> {
3678 let mut parts = Vec::new();
3680 let mut current = String::new();
3681 let mut depth = 0;
3682
3683 for c in content.chars() {
3684 match c {
3685 '{' => {
3686 depth += 1;
3687 current.push(c);
3688 }
3689 '}' => {
3690 depth -= 1;
3691 current.push(c);
3692 }
3693 ',' if depth == 0 => {
3694 parts.push(current.clone());
3695 current.clear();
3696 }
3697 _ => current.push(c),
3698 }
3699 }
3700 parts.push(current);
3701
3702 parts
3703 }
3704
3705 fn expand_brace_sequence(&self, content: &str) -> Vec<String> {
3707 let parts: Vec<&str> = content.splitn(3, "..").collect();
3708 if parts.len() < 2 {
3709 return vec![content.to_string()];
3710 }
3711
3712 let start = parts[0];
3713 let end = parts[1];
3714 let step: i64 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
3715
3716 if let (Ok(start_num), Ok(end_num)) = (start.parse::<i64>(), end.parse::<i64>()) {
3718 let mut results = Vec::new();
3719 if start_num <= end_num {
3720 let mut i = start_num;
3721 while i <= end_num {
3722 results.push(i.to_string());
3723 i += step;
3724 }
3725 } else {
3726 let mut i = start_num;
3727 while i >= end_num {
3728 results.push(i.to_string());
3729 i -= step;
3730 }
3731 }
3732 return results;
3733 }
3734
3735 if start.len() == 1 && end.len() == 1 {
3737 let start_char = start.chars().next().unwrap();
3738 let end_char = end.chars().next().unwrap();
3739 let mut results = Vec::new();
3740
3741 if start_char <= end_char {
3742 let mut c = start_char;
3743 while c <= end_char {
3744 results.push(c.to_string());
3745 c = (c as u8 + step as u8) as char;
3746 if c as u8 > end_char as u8 {
3747 break;
3748 }
3749 }
3750 } else {
3751 let mut c = start_char;
3752 while c >= end_char {
3753 results.push(c.to_string());
3754 if (c as u8) < step as u8 {
3755 break;
3756 }
3757 c = (c as u8 - step as u8) as char;
3758 }
3759 }
3760 return results;
3761 }
3762
3763 vec![content.to_string()]
3764 }
3765
3766 fn expand_glob(&self, pattern: &str) -> Vec<String> {
3768 let (glob_pattern, qualifiers) = self.parse_glob_qualifiers(pattern);
3770
3771 if self.has_extglob_pattern(&glob_pattern) {
3773 let expanded = self.expand_extglob(&glob_pattern);
3774 return self.filter_by_qualifiers(expanded, &qualifiers);
3775 }
3776
3777 let nullglob = self.options.get("nullglob").copied().unwrap_or(false);
3778 let dotglob = self.options.get("dotglob").copied().unwrap_or(false);
3779 let nocaseglob = self.options.get("nocaseglob").copied().unwrap_or(false);
3780
3781 let expanded = if glob_pattern.contains("**/") {
3786 self.expand_glob_parallel(&glob_pattern, dotglob, nocaseglob)
3787 } else {
3788 let options = glob::MatchOptions {
3789 case_sensitive: !nocaseglob,
3790 require_literal_separator: false,
3791 require_literal_leading_dot: !dotglob,
3792 };
3793 match glob::glob_with(&glob_pattern, options) {
3794 Ok(paths) => paths
3795 .filter_map(|p| p.ok())
3796 .map(|p| p.to_string_lossy().to_string())
3797 .collect(),
3798 Err(_) => vec![],
3799 }
3800 };
3801
3802 let mut expanded = self.filter_by_qualifiers(expanded, &qualifiers);
3803 expanded.sort();
3804
3805 if expanded.is_empty() {
3806 if nullglob {
3807 vec![]
3808 } else {
3809 vec![pattern.to_string()]
3810 }
3811 } else {
3812 expanded
3813 }
3814 }
3815
3816 fn expand_glob_parallel(
3822 &self,
3823 pattern: &str,
3824 dotglob: bool,
3825 nocaseglob: bool,
3826 ) -> Vec<String> {
3827 use walkdir::WalkDir;
3828
3829 let (base, file_glob) = if let Some(pos) = pattern.find("**/") {
3833 let base = if pos == 0 { "." } else { &pattern[..pos.saturating_sub(1)] };
3834 let rest = &pattern[pos + 3..]; (base.to_string(), rest.to_string())
3836 } else {
3837 return vec![];
3838 };
3839
3840 if file_glob.contains("**/") {
3843 let options = glob::MatchOptions {
3844 case_sensitive: !nocaseglob,
3845 require_literal_separator: false,
3846 require_literal_leading_dot: !dotglob,
3847 };
3848 return match glob::glob_with(pattern, options) {
3849 Ok(paths) => paths
3850 .filter_map(|p| p.ok())
3851 .map(|p| p.to_string_lossy().to_string())
3852 .collect(),
3853 Err(_) => vec![],
3854 };
3855 }
3856
3857 let match_opts = glob::MatchOptions {
3859 case_sensitive: !nocaseglob,
3860 require_literal_separator: false,
3861 require_literal_leading_dot: !dotglob,
3862 };
3863 let file_pat = match glob::Pattern::new(&file_glob) {
3864 Ok(p) => p,
3865 Err(_) => return vec![],
3866 };
3867
3868 let top_entries: Vec<std::path::PathBuf> = match std::fs::read_dir(&base) {
3870 Ok(rd) => rd
3871 .filter_map(|e| e.ok())
3872 .map(|e| e.path())
3873 .collect(),
3874 Err(_) => return vec![],
3875 };
3876
3877 let mut results: Vec<String> = Vec::new();
3879 for entry in &top_entries {
3880 if entry.is_file() || entry.is_symlink() {
3881 if let Some(name) = entry.file_name().and_then(|n| n.to_str()) {
3882 if file_pat.matches_with(name, match_opts) {
3883 results.push(entry.to_string_lossy().to_string());
3884 }
3885 }
3886 }
3887 }
3888
3889 let subdirs: Vec<std::path::PathBuf> = top_entries
3891 .into_iter()
3892 .filter(|p| p.is_dir())
3893 .filter(|p| {
3894 dotglob
3895 || !p
3896 .file_name()
3897 .and_then(|n| n.to_str())
3898 .map(|n| n.starts_with('.'))
3899 .unwrap_or(false)
3900 })
3901 .collect();
3902
3903 if subdirs.is_empty() {
3904 return results;
3905 }
3906
3907 let (tx, rx) = std::sync::mpsc::channel::<Vec<String>>();
3908
3909 for subdir in &subdirs {
3910 let tx = tx.clone();
3911 let subdir = subdir.clone();
3912 let file_pat = file_pat.clone();
3913 let skip_dot = !dotglob;
3914 self.worker_pool.submit(move || {
3915 let mut matches = Vec::new();
3916 let walker = WalkDir::new(&subdir)
3917 .follow_links(false)
3918 .into_iter()
3919 .filter_entry(move |e| {
3920 if skip_dot {
3922 if let Some(name) = e.file_name().to_str() {
3923 if name.starts_with('.') && e.depth() > 0 {
3924 return false;
3925 }
3926 }
3927 }
3928 true
3929 });
3930 for entry in walker.filter_map(|e| e.ok()) {
3931 if entry.file_type().is_file() || entry.file_type().is_symlink() {
3932 if let Some(name) = entry.file_name().to_str() {
3933 if file_pat.matches_with(name, match_opts) {
3934 matches.push(entry.path().to_string_lossy().to_string());
3935 }
3936 }
3937 }
3938 }
3939 let _ = tx.send(matches);
3940 });
3941 }
3942
3943 drop(tx);
3945
3946 for batch in rx {
3948 results.extend(batch);
3949 }
3950
3951 results
3952 }
3953
3954 fn parse_glob_qualifiers(&self, pattern: &str) -> (String, String) {
3957 if !pattern.ends_with(')') {
3960 return (pattern.to_string(), String::new());
3961 }
3962
3963 let chars: Vec<char> = pattern.chars().collect();
3965 let mut depth = 0;
3966 let mut qual_start = None;
3967
3968 for i in (0..chars.len()).rev() {
3969 match chars[i] {
3970 ')' => depth += 1,
3971 '(' => {
3972 depth -= 1;
3973 if depth == 0 {
3974 qual_start = Some(i);
3975 break;
3976 }
3977 }
3978 _ => {}
3979 }
3980 }
3981
3982 if let Some(start) = qual_start {
3983 let qual_content: String = chars[start + 1..chars.len() - 1].iter().collect();
3984
3985 if !qual_content.contains('|') && self.looks_like_glob_qualifiers(&qual_content) {
3989 let base_pattern: String = chars[..start].iter().collect();
3990 return (base_pattern, qual_content);
3991 }
3992 }
3993
3994 (pattern.to_string(), String::new())
3995 }
3996
3997 fn looks_like_glob_qualifiers(&self, s: &str) -> bool {
3999 if s.is_empty() {
4000 return false;
4001 }
4002 let valid_chars = "./@=p*%brwxAIERWXsStfedDLNnMmcaou^-+:0123456789,[]FT";
4005 s.chars()
4006 .all(|c| valid_chars.contains(c) || c.is_whitespace())
4007 }
4008
4009 fn prefetch_metadata(
4014 &self,
4015 files: &[String],
4016 ) -> HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)> {
4017 if files.len() < 32 {
4018 return files
4020 .iter()
4021 .map(|f| {
4022 let meta = std::fs::metadata(f).ok();
4023 let symlink_meta = std::fs::symlink_metadata(f).ok();
4024 (f.clone(), (meta, symlink_meta))
4025 })
4026 .collect();
4027 }
4028
4029 let pool_size = self.worker_pool.size();
4030 let chunk_size = (files.len() + pool_size - 1) / pool_size;
4031 let (tx, rx) = std::sync::mpsc::channel();
4032
4033 for chunk in files.chunks(chunk_size) {
4034 let tx = tx.clone();
4035 let chunk: Vec<String> = chunk.to_vec();
4036 self.worker_pool.submit(move || {
4037 let batch: Vec<(String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>))> =
4038 chunk
4039 .into_iter()
4040 .map(|f| {
4041 let meta = std::fs::metadata(&f).ok();
4042 let symlink_meta = std::fs::symlink_metadata(&f).ok();
4043 (f, (meta, symlink_meta))
4044 })
4045 .collect();
4046 let _ = tx.send(batch);
4047 });
4048 }
4049 drop(tx);
4050
4051 let mut map = HashMap::with_capacity(files.len());
4052 for batch in rx {
4053 for (path, metas) in batch {
4054 map.insert(path, metas);
4055 }
4056 }
4057 map
4058 }
4059
4060 fn filter_by_qualifiers(&self, files: Vec<String>, qualifiers: &str) -> Vec<String> {
4061 if qualifiers.is_empty() {
4062 return files;
4063 }
4064
4065 let meta_cache = self.prefetch_metadata(&files);
4068
4069 let mut result = files;
4070 let mut negate = false;
4071 let mut chars = qualifiers.chars().peekable();
4072
4073 while let Some(c) = chars.next() {
4074 match c {
4075 '^' => negate = !negate,
4077
4078 '.' => {
4080 result = result
4081 .into_iter()
4082 .filter(|f| {
4083 let is_file = meta_cache
4084 .get(f)
4085 .and_then(|(m, _)| m.as_ref())
4086 .map(|m| m.is_file())
4087 .unwrap_or(false);
4088 if negate { !is_file } else { is_file }
4089 })
4090 .collect();
4091 negate = false;
4092 }
4093 '/' => {
4094 result = result
4095 .into_iter()
4096 .filter(|f| {
4097 let is_dir = meta_cache
4098 .get(f)
4099 .and_then(|(m, _)| m.as_ref())
4100 .map(|m| m.is_dir())
4101 .unwrap_or(false);
4102 if negate { !is_dir } else { is_dir }
4103 })
4104 .collect();
4105 negate = false;
4106 }
4107 '@' => {
4108 result = result
4109 .into_iter()
4110 .filter(|f| {
4111 let is_link = meta_cache
4112 .get(f)
4113 .and_then(|(_, sm)| sm.as_ref())
4114 .map(|m| m.file_type().is_symlink())
4115 .unwrap_or(false);
4116 if negate { !is_link } else { is_link }
4117 })
4118 .collect();
4119 negate = false;
4120 }
4121 '=' => {
4122 use std::os::unix::fs::FileTypeExt;
4124 result = result
4125 .into_iter()
4126 .filter(|f| {
4127 let is_socket = meta_cache
4128 .get(f)
4129 .and_then(|(_, sm)| sm.as_ref())
4130 .map(|m| m.file_type().is_socket())
4131 .unwrap_or(false);
4132 if negate { !is_socket } else { is_socket }
4133 })
4134 .collect();
4135 negate = false;
4136 }
4137 'p' => {
4138 use std::os::unix::fs::FileTypeExt;
4140 result = result
4141 .into_iter()
4142 .filter(|f| {
4143 let is_fifo = meta_cache
4144 .get(f)
4145 .and_then(|(_, sm)| sm.as_ref())
4146 .map(|m| m.file_type().is_fifo())
4147 .unwrap_or(false);
4148 if negate { !is_fifo } else { is_fifo }
4149 })
4150 .collect();
4151 negate = false;
4152 }
4153 '*' => {
4154 use std::os::unix::fs::PermissionsExt;
4156 result = result
4157 .into_iter()
4158 .filter(|f| {
4159 let is_exec = meta_cache
4160 .get(f)
4161 .and_then(|(m, _)| m.as_ref())
4162 .map(|m| m.is_file() && (m.permissions().mode() & 0o111) != 0)
4163 .unwrap_or(false);
4164 if negate { !is_exec } else { is_exec }
4165 })
4166 .collect();
4167 negate = false;
4168 }
4169 '%' => {
4170 use std::os::unix::fs::FileTypeExt;
4172 let next = chars.peek().copied();
4173 result = result
4174 .into_iter()
4175 .filter(|f| {
4176 let is_device = meta_cache
4177 .get(f)
4178 .and_then(|(_, sm)| sm.as_ref())
4179 .map(|m| match next {
4180 Some('b') => m.file_type().is_block_device(),
4181 Some('c') => m.file_type().is_char_device(),
4182 _ => {
4183 m.file_type().is_block_device()
4184 || m.file_type().is_char_device()
4185 }
4186 })
4187 .unwrap_or(false);
4188 if negate { !is_device } else { is_device }
4189 })
4190 .collect();
4191 if next == Some('b') || next == Some('c') {
4192 chars.next();
4193 }
4194 negate = false;
4195 }
4196
4197 'r' => {
4199 result = self.filter_by_permission(result, 0o400, negate, &meta_cache);
4200 negate = false;
4201 }
4202 'w' => {
4203 result = self.filter_by_permission(result, 0o200, negate, &meta_cache);
4204 negate = false;
4205 }
4206 'x' => {
4207 result = self.filter_by_permission(result, 0o100, negate, &meta_cache);
4208 negate = false;
4209 }
4210 'A' => {
4211 result = self.filter_by_permission(result, 0o040, negate, &meta_cache);
4212 negate = false;
4213 }
4214 'I' => {
4215 result = self.filter_by_permission(result, 0o020, negate, &meta_cache);
4216 negate = false;
4217 }
4218 'E' => {
4219 result = self.filter_by_permission(result, 0o010, negate, &meta_cache);
4220 negate = false;
4221 }
4222 'R' => {
4223 result = self.filter_by_permission(result, 0o004, negate, &meta_cache);
4224 negate = false;
4225 }
4226 'W' => {
4227 result = self.filter_by_permission(result, 0o002, negate, &meta_cache);
4228 negate = false;
4229 }
4230 'X' => {
4231 result = self.filter_by_permission(result, 0o001, negate, &meta_cache);
4232 negate = false;
4233 }
4234 's' => {
4235 result = self.filter_by_permission(result, 0o4000, negate, &meta_cache);
4236 negate = false;
4237 }
4238 'S' => {
4239 result = self.filter_by_permission(result, 0o2000, negate, &meta_cache);
4240 negate = false;
4241 }
4242 't' => {
4243 result = self.filter_by_permission(result, 0o1000, negate, &meta_cache);
4244 negate = false;
4245 }
4246
4247 'F' => {
4249 result = result
4251 .into_iter()
4252 .filter(|f| {
4253 let path = std::path::Path::new(f);
4254 let is_nonempty = path.is_dir()
4255 && std::fs::read_dir(path)
4256 .map(|mut d| d.next().is_some())
4257 .unwrap_or(false);
4258 if negate {
4259 !is_nonempty
4260 } else {
4261 is_nonempty
4262 }
4263 })
4264 .collect();
4265 negate = false;
4266 }
4267
4268 'U' => {
4270 let euid = unsafe { libc::geteuid() };
4272 result = result
4273 .into_iter()
4274 .filter(|f| {
4275 use std::os::unix::fs::MetadataExt;
4276 let is_owned = meta_cache
4277 .get(f)
4278 .and_then(|(m, _)| m.as_ref())
4279 .map(|m| m.uid() == euid)
4280 .unwrap_or(false);
4281 if negate { !is_owned } else { is_owned }
4282 })
4283 .collect();
4284 negate = false;
4285 }
4286 'G' => {
4287 let egid = unsafe { libc::getegid() };
4289 result = result
4290 .into_iter()
4291 .filter(|f| {
4292 use std::os::unix::fs::MetadataExt;
4293 let is_owned = meta_cache
4294 .get(f)
4295 .and_then(|(m, _)| m.as_ref())
4296 .map(|m| m.gid() == egid)
4297 .unwrap_or(false);
4298 if negate { !is_owned } else {
4299 is_owned
4300 }
4301 })
4302 .collect();
4303 negate = false;
4304 }
4305
4306 'o' => {
4308 if chars.peek() == Some(&'n') {
4310 chars.next();
4311 result.sort();
4313 } else if chars.peek() == Some(&'L') {
4314 chars.next();
4315 result.sort_by_key(|f| {
4317 meta_cache.get(f).and_then(|(m, _)| m.as_ref()).map(|m| m.len()).unwrap_or(0)
4318 });
4319 } else if chars.peek() == Some(&'m') {
4320 chars.next();
4321 result.sort_by_key(|f| {
4323 meta_cache.get(f).and_then(|(m, _)| m.as_ref())
4324 .and_then(|m| m.modified().ok())
4325 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
4326 });
4327 } else if chars.peek() == Some(&'a') {
4328 chars.next();
4329 result.sort_by_key(|f| {
4331 meta_cache.get(f).and_then(|(m, _)| m.as_ref())
4332 .and_then(|m| m.accessed().ok())
4333 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
4334 });
4335 }
4336 }
4337 'O' => {
4338 if chars.peek() == Some(&'n') {
4340 chars.next();
4341 result.sort();
4342 result.reverse();
4343 } else if chars.peek() == Some(&'L') {
4344 chars.next();
4345 result.sort_by_key(|f| {
4346 meta_cache.get(f).and_then(|(m, _)| m.as_ref()).map(|m| m.len()).unwrap_or(0)
4347 });
4348 result.reverse();
4349 } else if chars.peek() == Some(&'m') {
4350 chars.next();
4351 result.sort_by_key(|f| {
4352 meta_cache.get(f).and_then(|(m, _)| m.as_ref())
4353 .and_then(|m| m.modified().ok())
4354 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
4355 });
4356 result.reverse();
4357 } else {
4358 result.reverse();
4360 }
4361 }
4362
4363 '[' => {
4365 let mut range_str = String::new();
4366 while let Some(&ch) = chars.peek() {
4367 if ch == ']' {
4368 chars.next();
4369 break;
4370 }
4371 range_str.push(chars.next().unwrap());
4372 }
4373
4374 if let Some((start, end)) = self.parse_subscript_range(&range_str, result.len())
4375 {
4376 result = result.into_iter().skip(start).take(end - start).collect();
4377 }
4378 }
4379
4380 'D' => {
4382 }
4384 'N' => {
4385 }
4387
4388 _ => {}
4390 }
4391 }
4392
4393 result
4394 }
4395
4396 fn filter_by_permission(
4398 &self,
4399 files: Vec<String>,
4400 mode: u32,
4401 negate: bool,
4402 meta_cache: &HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)>,
4403 ) -> Vec<String> {
4404 use std::os::unix::fs::PermissionsExt;
4405 files
4406 .into_iter()
4407 .filter(|f| {
4408 let has_perm = meta_cache
4409 .get(f)
4410 .and_then(|(m, _)| m.as_ref())
4411 .map(|m| (m.permissions().mode() & mode) != 0)
4412 .unwrap_or(false);
4413 if negate { !has_perm } else { has_perm }
4414 })
4415 .collect()
4416 }
4417
4418 fn parse_subscript_range(&self, s: &str, len: usize) -> Option<(usize, usize)> {
4420 if s.is_empty() || len == 0 {
4421 return None;
4422 }
4423
4424 let parts: Vec<&str> = s.split(',').collect();
4425
4426 let parse_idx = |idx_str: &str| -> Option<usize> {
4427 let idx: i64 = idx_str.trim().parse().ok()?;
4428 if idx < 0 {
4429 let abs = (-idx) as usize;
4431 if abs > len {
4432 None
4433 } else {
4434 Some(len - abs)
4435 }
4436 } else if idx == 0 {
4437 Some(0)
4438 } else {
4439 Some((idx as usize).saturating_sub(1).min(len))
4441 }
4442 };
4443
4444 match parts.len() {
4445 1 => {
4446 let idx = parse_idx(parts[0])?;
4448 Some((idx, idx + 1))
4449 }
4450 2 => {
4451 let start = parse_idx(parts[0])?;
4453 let end = parse_idx(parts[1])?.saturating_add(1);
4454 Some((start.min(end), start.max(end)))
4455 }
4456 _ => None,
4457 }
4458 }
4459
4460 fn has_extglob_pattern(&self, pattern: &str) -> bool {
4462 let chars: Vec<char> = pattern.chars().collect();
4463 for i in 0..chars.len().saturating_sub(1) {
4464 if (chars[i] == '?'
4465 || chars[i] == '*'
4466 || chars[i] == '+'
4467 || chars[i] == '@'
4468 || chars[i] == '!')
4469 && chars[i + 1] == '('
4470 {
4471 return true;
4472 }
4473 }
4474 false
4475 }
4476
4477 fn extglob_to_regex(&self, pattern: &str) -> String {
4479 let mut regex = String::from("^");
4480 let chars: Vec<char> = pattern.chars().collect();
4481 let mut i = 0;
4482
4483 while i < chars.len() {
4484 let c = chars[i];
4485
4486 if i + 1 < chars.len() && chars[i + 1] == '(' {
4488 match c {
4489 '?' => {
4490 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
4492 let inner_regex = self.extglob_inner_to_regex(&inner);
4493 regex.push_str(&format!("({})?", inner_regex));
4494 i = end + 1;
4495 continue;
4496 }
4497 '*' => {
4498 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
4500 let inner_regex = self.extglob_inner_to_regex(&inner);
4501 regex.push_str(&format!("({})*", inner_regex));
4502 i = end + 1;
4503 continue;
4504 }
4505 '+' => {
4506 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
4508 let inner_regex = self.extglob_inner_to_regex(&inner);
4509 regex.push_str(&format!("({})+", inner_regex));
4510 i = end + 1;
4511 continue;
4512 }
4513 '@' => {
4514 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
4516 let inner_regex = self.extglob_inner_to_regex(&inner);
4517 regex.push_str(&format!("({})", inner_regex));
4518 i = end + 1;
4519 continue;
4520 }
4521 '!' => {
4522 let (_, end) = self.extract_extglob_inner(&chars, i + 2);
4525 regex.push_str(".*"); i = end + 1;
4527 continue;
4528 }
4529 _ => {}
4530 }
4531 }
4532
4533 match c {
4535 '*' => regex.push_str(".*"),
4536 '?' => regex.push('.'),
4537 '.' => regex.push_str("\\."),
4538 '[' => {
4539 regex.push('[');
4540 i += 1;
4541 while i < chars.len() && chars[i] != ']' {
4542 if chars[i] == '!' && regex.ends_with('[') {
4543 regex.push('^');
4544 } else {
4545 regex.push(chars[i]);
4546 }
4547 i += 1;
4548 }
4549 regex.push(']');
4550 }
4551 '^' | '$' | '(' | ')' | '{' | '}' | '|' | '\\' => {
4552 regex.push('\\');
4553 regex.push(c);
4554 }
4555 _ => regex.push(c),
4556 }
4557 i += 1;
4558 }
4559
4560 regex.push('$');
4561 regex
4562 }
4563
4564 fn extract_extglob_inner(&self, chars: &[char], start: usize) -> (String, usize) {
4566 let mut inner = String::new();
4567 let mut depth = 1;
4568 let mut i = start;
4569
4570 while i < chars.len() && depth > 0 {
4571 if chars[i] == '(' {
4572 depth += 1;
4573 } else if chars[i] == ')' {
4574 depth -= 1;
4575 if depth == 0 {
4576 return (inner, i);
4577 }
4578 }
4579 inner.push(chars[i]);
4580 i += 1;
4581 }
4582
4583 (inner, i)
4584 }
4585
4586 fn extglob_inner_to_regex(&self, inner: &str) -> String {
4588 let alternatives: Vec<String> = inner
4590 .split('|')
4591 .map(|alt| {
4592 let mut result = String::new();
4593 for c in alt.chars() {
4594 match c {
4595 '*' => result.push_str(".*"),
4596 '?' => result.push('.'),
4597 '.' => result.push_str("\\."),
4598 '^' | '$' | '(' | ')' | '{' | '}' | '\\' => {
4599 result.push('\\');
4600 result.push(c);
4601 }
4602 _ => result.push(c),
4603 }
4604 }
4605 result
4606 })
4607 .collect();
4608
4609 alternatives.join("|")
4610 }
4611
4612 fn expand_extglob(&self, pattern: &str) -> Vec<String> {
4614 let (search_dir, file_pattern) = if let Some(last_slash) = pattern.rfind('/') {
4616 (&pattern[..last_slash], &pattern[last_slash + 1..])
4617 } else {
4618 (".", pattern)
4619 };
4620
4621 if let Some((neg_pat, suffix)) = self.extract_neg_extglob(file_pattern) {
4623 return self.expand_neg_extglob(search_dir, &neg_pat, &suffix, pattern);
4624 }
4625
4626 let regex_str = self.extglob_to_regex(file_pattern);
4628
4629 let re = match cached_regex(®ex_str) {
4630 Some(r) => r,
4631 None => return vec![pattern.to_string()],
4632 };
4633
4634 let mut results = Vec::new();
4635
4636 if let Ok(entries) = std::fs::read_dir(search_dir) {
4637 for entry in entries.flatten() {
4638 let name = entry.file_name().to_string_lossy().to_string();
4639 if name.starts_with('.') && !file_pattern.starts_with('.') {
4641 continue;
4642 }
4643
4644 if re.is_match(&name) {
4645 let full_path = if search_dir == "." {
4646 name
4647 } else {
4648 format!("{}/{}", search_dir, name)
4649 };
4650 results.push(full_path);
4651 }
4652 }
4653 }
4654
4655 if results.is_empty() {
4656 vec![pattern.to_string()]
4657 } else {
4658 results.sort();
4659 results
4660 }
4661 }
4662
4663 fn expand_neg_extglob(
4665 &self,
4666 search_dir: &str,
4667 neg_pat: &str,
4668 suffix: &str,
4669 original_pattern: &str,
4670 ) -> Vec<String> {
4671 let mut results = Vec::new();
4672
4673 if let Ok(entries) = std::fs::read_dir(search_dir) {
4674 for entry in entries.flatten() {
4675 let name = entry.file_name().to_string_lossy().to_string();
4676 if name.starts_with('.') {
4678 continue;
4679 }
4680
4681 if !name.ends_with(suffix) {
4683 continue;
4684 }
4685
4686 let basename = &name[..name.len() - suffix.len()];
4687 let alts: Vec<&str> = neg_pat.split('|').collect();
4689 let matches_neg = alts.iter().any(|alt| {
4690 if alt.contains('*') || alt.contains('?') {
4691 let alt_re = self.extglob_inner_to_regex(alt);
4692 let full_pattern = format!("^{}$", alt_re);
4693 if let Some(r) = cached_regex(&full_pattern) {
4694 r.is_match(basename)
4695 } else {
4696 *alt == basename
4697 }
4698 } else {
4699 *alt == basename
4700 }
4701 });
4702
4703 if !matches_neg {
4704 let full_path = if search_dir == "." {
4705 name
4706 } else {
4707 format!("{}/{}", search_dir, name)
4708 };
4709 results.push(full_path);
4710 }
4711 }
4712 }
4713
4714 if results.is_empty() {
4715 vec![original_pattern.to_string()]
4716 } else {
4717 results.sort();
4718 results
4719 }
4720 }
4721
4722 fn extract_neg_extglob(&self, pattern: &str) -> Option<(String, String)> {
4724 let chars: Vec<char> = pattern.chars().collect();
4725 if chars.len() >= 3 && chars[0] == '!' && chars[1] == '(' {
4726 let mut depth = 1;
4727 let mut i = 2;
4728 while i < chars.len() && depth > 0 {
4729 if chars[i] == '(' {
4730 depth += 1;
4731 } else if chars[i] == ')' {
4732 depth -= 1;
4733 }
4734 i += 1;
4735 }
4736 if depth == 0 {
4737 let inner: String = chars[2..i - 1].iter().collect();
4738 let suffix: String = chars[i..].iter().collect();
4739 return Some((inner, suffix));
4740 }
4741 }
4742 None
4743 }
4744
4745 fn expand_word_split(&mut self, word: &ShellWord) -> Vec<String> {
4747 match word {
4748 ShellWord::Literal(s) => {
4749 let brace_expanded = self.expand_braces(s);
4751 brace_expanded
4752 .into_iter()
4753 .flat_map(|item| self.expand_string_split(&item))
4754 .collect()
4755 }
4756 ShellWord::SingleQuoted(s) => vec![s.clone()],
4757 ShellWord::DoubleQuoted(parts) => {
4758 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
4760 }
4761 ShellWord::Variable(name) => {
4762 let val = env::var(name).unwrap_or_default();
4763 self.split_words(&val)
4764 }
4765 ShellWord::VariableBraced(name, modifier) => {
4766 let val = env::var(name).ok();
4767 let expanded = self.apply_var_modifier(name, val, modifier.as_deref());
4768 self.split_words(&expanded)
4769 }
4770 ShellWord::ArrayVar(name, index) => {
4771 let idx_str = self.expand_word(index);
4772 if idx_str == "@" || idx_str == "*" {
4773 self.arrays.get(name).cloned().unwrap_or_default()
4775 } else {
4776 vec![self.expand_array_access(name, index)]
4777 }
4778 }
4779 ShellWord::Glob(pattern) => match glob::glob(pattern) {
4780 Ok(paths) => {
4781 let expanded: Vec<String> = paths
4782 .filter_map(|p| p.ok())
4783 .map(|p| p.to_string_lossy().to_string())
4784 .collect();
4785 if expanded.is_empty() {
4786 vec![pattern.clone()]
4787 } else {
4788 expanded
4789 }
4790 }
4791 Err(_) => vec![pattern.clone()],
4792 },
4793 ShellWord::CommandSub(_) => {
4794 let val = self.expand_word(word);
4796 self.split_words(&val)
4797 }
4798 ShellWord::Concat(parts) => {
4799 let val = self.expand_concat_parallel(parts);
4801 self.split_words(&val)
4802 }
4803 _ => vec![self.expand_word(word)],
4804 }
4805 }
4806
4807 fn expand_string_split(&mut self, s: &str) -> Vec<String> {
4809 let mut results: Vec<String> = Vec::new();
4810 let mut current = String::new();
4811 let mut chars = s.chars().peekable();
4812
4813 while let Some(c) = chars.next() {
4814 if c == '$' {
4815 if chars.peek() == Some(&'{') {
4816 chars.next(); let mut brace_content = String::new();
4818 let mut depth = 1;
4819 while let Some(ch) = chars.next() {
4820 if ch == '{' {
4821 depth += 1;
4822 brace_content.push(ch);
4823 } else if ch == '}' {
4824 depth -= 1;
4825 if depth == 0 {
4826 break;
4827 }
4828 brace_content.push(ch);
4829 } else {
4830 brace_content.push(ch);
4831 }
4832 }
4833
4834 if let Some(bracket_start) = brace_content.find('[') {
4836 let var_name = &brace_content[..bracket_start];
4837 let bracket_content = &brace_content[bracket_start + 1..];
4838 if let Some(bracket_end) = bracket_content.find(']') {
4839 let index = &bracket_content[..bracket_end];
4840 if (index == "@" || index == "*")
4841 && bracket_end + 1 == bracket_content.len()
4842 {
4843 if !current.is_empty() {
4845 results.push(current.clone());
4846 current.clear();
4847 }
4848 if let Some(arr) = self.arrays.get(var_name) {
4849 results.extend(arr.clone());
4850 }
4851 continue;
4852 }
4853 }
4854 }
4855
4856 current.push_str(&self.expand_braced_variable(&brace_content));
4858 } else {
4859 let mut var_name = String::new();
4861 while let Some(&ch) = chars.peek() {
4862 if ch.is_alphanumeric() || ch == '_' {
4863 var_name.push(chars.next().unwrap());
4864 } else {
4865 break;
4866 }
4867 }
4868 let val = self.get_variable(&var_name);
4869 if !current.is_empty() {
4871 results.push(current.clone());
4872 current.clear();
4873 }
4874 results.extend(self.split_words(&val));
4875 }
4876 } else {
4877 current.push(c);
4878 }
4879 }
4880
4881 if !current.is_empty() {
4882 results.push(current);
4883 }
4884
4885 if results.is_empty() {
4886 results.push(String::new());
4887 }
4888
4889 results
4890 }
4891
4892 fn split_words(&self, s: &str) -> Vec<String> {
4894 let ifs = self
4895 .variables
4896 .get("IFS")
4897 .cloned()
4898 .or_else(|| env::var("IFS").ok())
4899 .unwrap_or_else(|| " \t\n".to_string());
4900
4901 if ifs.is_empty() {
4902 return vec![s.to_string()];
4903 }
4904
4905 s.split(|c: char| ifs.contains(c))
4906 .filter(|s| !s.is_empty())
4907 .map(|s| s.to_string())
4908 .collect()
4909 }
4910
4911 #[tracing::instrument(level = "trace", skip_all)]
4912 fn expand_word(&mut self, word: &ShellWord) -> String {
4913 match word {
4914 ShellWord::Literal(s) => {
4915 let expanded = self.expand_string(s);
4916 expanded
4918 }
4919 ShellWord::SingleQuoted(s) => s.clone(),
4920 ShellWord::DoubleQuoted(parts) => parts.iter().map(|p| self.expand_word(p)).collect(),
4921 ShellWord::Variable(name) => self.get_variable(name),
4922 ShellWord::VariableBraced(name, modifier) => {
4923 let val = env::var(name).ok();
4924 self.apply_var_modifier(name, val, modifier.as_deref())
4925 }
4926 ShellWord::Tilde(user) => {
4927 if let Some(u) = user {
4928 format!("/home/{}", u)
4930 } else {
4931 dirs::home_dir()
4932 .map(|p| p.to_string_lossy().to_string())
4933 .unwrap_or_else(|| "~".to_string())
4934 }
4935 }
4936 ShellWord::Glob(pattern) => {
4937 match glob::glob(pattern) {
4939 Ok(paths) => {
4940 let expanded: Vec<String> = paths
4941 .filter_map(|p| p.ok())
4942 .map(|p| p.to_string_lossy().to_string())
4943 .collect();
4944 if expanded.is_empty() {
4945 pattern.clone()
4946 } else {
4947 expanded.join(" ")
4948 }
4949 }
4950 Err(_) => pattern.clone(),
4951 }
4952 }
4953 ShellWord::Concat(parts) => self.expand_concat_parallel(parts),
4954 ShellWord::CommandSub(cmd) => self.execute_command_substitution(cmd),
4955 ShellWord::ProcessSubIn(cmd) => self.execute_process_sub_in(cmd),
4956 ShellWord::ProcessSubOut(cmd) => self.execute_process_sub_out(cmd),
4957 ShellWord::ArithSub(expr) => self.evaluate_arithmetic(expr),
4958 ShellWord::ArrayVar(name, index) => self.expand_array_access(name, index),
4959 ShellWord::ArrayLiteral(elements) => elements
4960 .iter()
4961 .map(|e| self.expand_word(e))
4962 .collect::<Vec<_>>()
4963 .join(" "),
4964 }
4965 }
4966
4967 fn preflight_command_subs(
4970 &mut self,
4971 words: &[ShellWord],
4972 ) -> Vec<Option<crossbeam_channel::Receiver<String>>> {
4973 use crate::parser::ShellWord;
4974 use std::process::{Command, Stdio};
4975
4976 let mut receivers = Vec::with_capacity(words.len());
4977
4978 let external_count = words.iter().filter(|w| {
4980 if let ShellWord::CommandSub(cmd) = w {
4981 if let ShellCommand::Simple(simple) = cmd.as_ref() {
4982 if let Some(first) = simple.words.first() {
4983 let name = self.expand_word(first);
4984 return !self.functions.contains_key(&name) && !self.is_builtin(&name);
4985 }
4986 }
4987 }
4988 false
4989 }).count();
4990
4991 if external_count < 2 {
4992 return vec![None; words.len()];
4994 }
4995
4996 for word in words {
4997 if let ShellWord::CommandSub(cmd) = word {
4998 if let ShellCommand::Simple(simple) = cmd.as_ref() {
4999 let first = simple.words.first().map(|w| self.expand_word(w));
5000 if let Some(ref name) = first {
5001 if !self.functions.contains_key(name) && !self.is_builtin(name) {
5002 let expanded: Vec<String> =
5003 simple.words.iter().map(|w| self.expand_word(w)).collect();
5004 let rx = self.worker_pool.submit_with_result(move || {
5005 let output = Command::new(&expanded[0])
5006 .args(&expanded[1..])
5007 .stdout(Stdio::piped())
5008 .stderr(Stdio::inherit())
5009 .output();
5010 match output {
5011 Ok(out) => String::from_utf8_lossy(&out.stdout)
5012 .trim_end_matches('\n')
5013 .to_string(),
5014 Err(_) => String::new(),
5015 }
5016 });
5017 receivers.push(Some(rx));
5018 continue;
5019 }
5020 }
5021 }
5022 }
5023 receivers.push(None);
5024 }
5025
5026 receivers
5027 }
5028
5029 fn expand_concat_parallel(&mut self, parts: &[ShellWord]) -> String {
5032 use crate::parser::ShellWord;
5033 use std::process::{Command, Stdio};
5034
5035 let mut preflight: Vec<Option<crossbeam_channel::Receiver<String>>> = Vec::with_capacity(parts.len());
5037
5038 for part in parts {
5039 if let ShellWord::CommandSub(cmd) = part {
5040 if let ShellCommand::Simple(simple) = cmd.as_ref() {
5041 let first = simple.words.first().map(|w| self.expand_word(w));
5042 if let Some(ref name) = first {
5043 if !self.functions.contains_key(name) && !self.is_builtin(name) {
5044 let words: Vec<String> =
5046 simple.words.iter().map(|w| self.expand_word(w)).collect();
5047 let rx = self.worker_pool.submit_with_result(move || {
5048 let output = Command::new(&words[0])
5049 .args(&words[1..])
5050 .stdout(Stdio::piped())
5051 .stderr(Stdio::inherit())
5052 .output();
5053 match output {
5054 Ok(out) => String::from_utf8_lossy(&out.stdout)
5055 .trim_end_matches('\n')
5056 .to_string(),
5057 Err(_) => String::new(),
5058 }
5059 });
5060 preflight.push(Some(rx));
5061 continue;
5062 }
5063 }
5064 }
5065 }
5066 preflight.push(None); }
5068
5069 let mut result = String::new();
5071 for (i, part) in parts.iter().enumerate() {
5072 if let Some(rx) = preflight[i].take() {
5073 result.push_str(&rx.recv().unwrap_or_default());
5075 } else {
5076 result.push_str(&self.expand_word(part));
5078 }
5079 }
5080 result
5081 }
5082
5083 fn expand_braced_variable(&mut self, content: &str) -> String {
5084 if content.starts_with("${") {
5086 let mut depth = 0;
5088 let mut inner_end = 0;
5089 for (i, c) in content.char_indices() {
5090 match c {
5091 '{' => depth += 1,
5092 '}' => {
5093 depth -= 1;
5094 if depth == 0 {
5095 inner_end = i;
5096 break;
5097 }
5098 }
5099 _ => {}
5100 }
5101 }
5102
5103 if inner_end > 0 {
5104 let inner_content = &content[2..inner_end];
5106 let inner_result = self.expand_braced_variable(inner_content);
5107
5108 let rest = &content[inner_end + 1..];
5110 if rest.starts_with('[') {
5111 if let Some(bracket_end) = rest.find(']') {
5113 let index = &rest[1..bracket_end];
5114 if let Ok(idx) = index.parse::<i64>() {
5115 let chars: Vec<char> = inner_result.chars().collect();
5116 let actual_idx = if idx < 0 {
5117 (chars.len() as i64 + idx).max(0) as usize
5118 } else if idx > 0 {
5119 (idx - 1) as usize
5120 } else {
5121 0
5122 };
5123 return chars
5124 .get(actual_idx)
5125 .map(|c| c.to_string())
5126 .unwrap_or_default();
5127 }
5128 }
5129 }
5130
5131 return inner_result;
5132 }
5133 }
5134
5135 if content.starts_with('(') {
5137 if let Some(close_paren) = content.find(')') {
5138 let flags_str = &content[1..close_paren];
5139 let rest = &content[close_paren + 1..];
5140 let flags = self.parse_zsh_flags(flags_str);
5141
5142 let has_match_flag = flags.iter().any(|f| matches!(f, ZshParamFlag::Match));
5144
5145 if let Some(filter_pos) = rest.find(":#") {
5147 let var_name = &rest[..filter_pos];
5148 let pattern = &rest[filter_pos + 2..];
5149
5150 if let Some(arr) = self.arrays.get(var_name).cloned() {
5152 let filtered: Vec<String> = if arr.len() >= 1000 {
5153 tracing::trace!(
5154 count = arr.len(),
5155 pattern,
5156 "using parallel filter (rayon) for large array"
5157 );
5158 use rayon::prelude::*;
5159 let pattern = pattern.to_string();
5160 arr.into_par_iter()
5161 .filter(|elem| {
5162 let m = Self::glob_match_static(elem, &pattern);
5163 if has_match_flag { m } else { !m }
5164 })
5165 .collect()
5166 } else {
5167 arr.into_iter()
5168 .filter(|elem| {
5169 let m = self.glob_match(elem, pattern);
5170 if has_match_flag { m } else { !m }
5171 })
5172 .collect()
5173 };
5174 return filtered.join(" ");
5175 }
5176
5177 let val = self.get_variable(var_name);
5179 let matches = self.glob_match(&val, pattern);
5180
5181 return if has_match_flag {
5182 if matches { val } else { String::new() }
5183 } else {
5184 if matches { String::new() } else { val }
5185 };
5186 }
5187
5188 let (var_name, default_val) = if rest.starts_with(":-") {
5191 ("", Some(&rest[2..]))
5193 } else if let Some(pos) = rest.find(":-") {
5194 (&rest[..pos], Some(&rest[pos + 2..]))
5196 } else if rest.starts_with(':') {
5197 ("", None)
5199 } else {
5200 let vn = rest
5202 .split(|c: char| !c.is_alphanumeric() && c != '_')
5203 .next()
5204 .unwrap_or("");
5205 (vn, None)
5206 };
5207
5208 let mut val = self.get_variable(var_name);
5209
5210 if val.is_empty() {
5212 if let Some(def) = default_val {
5213 val = self.expand_string(def);
5215 }
5216 }
5217
5218 for flag in &flags {
5220 val = self.apply_zsh_param_flag(&val, var_name, flag);
5221 }
5222 return val;
5223 }
5224 }
5225
5226 if content.starts_with('#') {
5228 let rest = &content[1..];
5229 if let Some(bracket_start) = rest.find('[') {
5230 let var_name = &rest[..bracket_start];
5231 let bracket_content = &rest[bracket_start + 1..];
5232 if let Some(bracket_end) = bracket_content.find(']') {
5233 let index = &bracket_content[..bracket_end];
5234 if index == "@" || index == "*" {
5235 return self
5237 .arrays
5238 .get(var_name)
5239 .map(|arr| arr.len().to_string())
5240 .unwrap_or_else(|| "0".to_string());
5241 }
5242 }
5243 }
5244 if self.arrays.contains_key(rest) {
5246 return self
5247 .arrays
5248 .get(rest)
5249 .map(|arr| arr.len().to_string())
5250 .unwrap_or_else(|| "0".to_string());
5251 }
5252 if self.assoc_arrays.contains_key(rest) {
5254 return self
5255 .assoc_arrays
5256 .get(rest)
5257 .map(|h| h.len().to_string())
5258 .unwrap_or_else(|| "0".to_string());
5259 }
5260 let val = self.get_variable(rest);
5262 return val.len().to_string();
5263 }
5264
5265 if content.starts_with('+') {
5267 let rest = &content[1..];
5268
5269 if let Some(bracket_start) = rest.find('[') {
5271 let var_name = &rest[..bracket_start];
5272 let bracket_content = &rest[bracket_start + 1..];
5273 if let Some(bracket_end) = bracket_content.find(']') {
5274 let key = &bracket_content[..bracket_end];
5275
5276 if let Some(val) = self.get_special_array_value(var_name, key) {
5278 return if val.is_empty() {
5279 "0".to_string()
5280 } else {
5281 "1".to_string()
5282 };
5283 }
5284
5285 if self.assoc_arrays.contains_key(var_name) {
5287 let expanded_key = self.expand_string(key);
5288 let has_key = self
5289 .assoc_arrays
5290 .get(var_name)
5291 .map(|a| a.contains_key(&expanded_key))
5292 .unwrap_or(false);
5293 return if has_key {
5294 "1".to_string()
5295 } else {
5296 "0".to_string()
5297 };
5298 }
5299
5300 if let Some(arr) = self.arrays.get(var_name) {
5302 if let Ok(idx) = key.parse::<usize>() {
5303 let actual_idx = if idx > 0 { idx - 1 } else { 0 };
5304 return if arr.get(actual_idx).is_some() {
5305 "1".to_string()
5306 } else {
5307 "0".to_string()
5308 };
5309 }
5310 }
5311
5312 return "0".to_string();
5313 }
5314 }
5315
5316 let is_set = self.variables.contains_key(rest)
5318 || self.arrays.contains_key(rest)
5319 || self.assoc_arrays.contains_key(rest)
5320 || std::env::var(rest).is_ok()
5321 || self.functions.contains_key(rest);
5322 return if is_set {
5323 "1".to_string()
5324 } else {
5325 "0".to_string()
5326 };
5327 }
5328
5329 if let Some(bracket_start) = content.find('[') {
5331 let var_name = &content[..bracket_start];
5332 let bracket_content = &content[bracket_start + 1..];
5333 if let Some(bracket_end) = bracket_content.find(']') {
5334 let index = &bracket_content[..bracket_end];
5335
5336 if let Some(val) = self.get_special_array_value(var_name, index) {
5338 return val;
5339 }
5340
5341 if self.assoc_arrays.contains_key(var_name) {
5343 if index == "@" || index == "*" {
5344 return self
5346 .assoc_arrays
5347 .get(var_name)
5348 .map(|a| a.values().cloned().collect::<Vec<_>>().join(" "))
5349 .unwrap_or_default();
5350 } else {
5351 let key = self.expand_string(index);
5353 return self
5354 .assoc_arrays
5355 .get(var_name)
5356 .and_then(|a| a.get(&key).cloned())
5357 .unwrap_or_default();
5358 }
5359 }
5360
5361 if index == "@" || index == "*" {
5363 return self
5365 .arrays
5366 .get(var_name)
5367 .map(|arr| arr.join(" "))
5368 .unwrap_or_default();
5369 }
5370
5371 use crate::subscript::{
5373 get_array_by_subscript, get_array_element_by_subscript, getindex,
5374 };
5375 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
5376
5377 if let Ok(v) = getindex(index, false, ksh_arrays) {
5378 if let Some(arr) = self.arrays.get(var_name) {
5380 if v.is_all() {
5381 return arr.join(" ");
5382 }
5383 let is_range = index.contains(',');
5387 if is_range {
5388 return get_array_by_subscript(arr, &v, ksh_arrays).join(" ");
5390 } else {
5391 return get_array_element_by_subscript(arr, &v, ksh_arrays)
5393 .unwrap_or_default();
5394 }
5395 }
5396
5397 let val = self.get_variable(var_name);
5399 if !val.is_empty() {
5400 let chars: Vec<char> = val.chars().collect();
5401 let idx = v.start;
5402 let actual_idx = if idx < 0 {
5403 (chars.len() as i64 + idx).max(0) as usize
5404 } else if idx > 0 {
5405 (idx - 1) as usize } else {
5407 0
5408 };
5409
5410 if v.end > v.start + 1 {
5411 let end_idx = if v.end < 0 {
5413 (chars.len() as i64 + v.end + 1).max(0) as usize
5414 } else {
5415 v.end as usize
5416 };
5417 let end_idx = end_idx.min(chars.len());
5418 return chars[actual_idx..end_idx].iter().collect();
5419 } else {
5420 return chars
5421 .get(actual_idx)
5422 .map(|c| c.to_string())
5423 .unwrap_or_default();
5424 }
5425 }
5426 return String::new();
5427 }
5428
5429 return String::new();
5431 }
5432 }
5433
5434 if let Some(colon_pos) = content.find(':') {
5436 let var_name = &content[..colon_pos];
5437 let rest = &content[colon_pos + 1..];
5438 let val = self.get_variable(var_name);
5439 let val_opt = if val.is_empty() {
5440 None
5441 } else {
5442 Some(val.clone())
5443 };
5444
5445 if rest.starts_with('-') {
5446 return match val_opt {
5448 Some(v) if !v.is_empty() => v,
5449 _ => self.expand_string(&rest[1..]),
5450 };
5451 } else if rest.starts_with('=') {
5452 return match val_opt {
5454 Some(v) if !v.is_empty() => v,
5455 _ => {
5456 let default = self.expand_string(&rest[1..]);
5457 self.variables.insert(var_name.to_string(), default.clone());
5458 default
5459 }
5460 };
5461 } else if rest.starts_with('?') {
5462 return match val_opt {
5464 Some(v) if !v.is_empty() => v,
5465 _ => {
5466 let msg = self.expand_string(&rest[1..]);
5467 eprintln!("zshrs: {}: {}", var_name, msg);
5468 String::new()
5469 }
5470 };
5471 } else if rest.starts_with('+') {
5472 return match val_opt {
5474 Some(v) if !v.is_empty() => self.expand_string(&rest[1..]),
5475 _ => String::new(),
5476 };
5477 } else if rest.starts_with('#') {
5478 let pattern = &rest[1..];
5481 if self.glob_match(&val, pattern) {
5483 return String::new();
5484 } else {
5485 return val;
5486 }
5487 } else if self.is_history_modifier(rest) {
5488 return self.apply_history_modifiers(&val, rest);
5491 } else if rest
5492 .chars()
5493 .next()
5494 .map(|c| c.is_ascii_digit() || c == '-')
5495 .unwrap_or(false)
5496 {
5497 let parts: Vec<&str> = rest.splitn(2, ':').collect();
5499 let offset: i64 = parts[0].parse().unwrap_or(0);
5500 let length: Option<usize> = parts.get(1).and_then(|s| s.parse().ok());
5501
5502 let start = if offset < 0 {
5503 (val.len() as i64 + offset).max(0) as usize
5504 } else {
5505 (offset as usize).min(val.len())
5506 };
5507
5508 return if let Some(len) = length {
5509 val.chars().skip(start).take(len).collect()
5510 } else {
5511 val.chars().skip(start).collect()
5512 };
5513 }
5514 }
5515
5516 if let Some(slash_pos) = content.find('/') {
5519 let var_name = &content[..slash_pos];
5520 if !var_name.is_empty()
5522 && var_name
5523 .chars()
5524 .next()
5525 .map(|c| c.is_alphabetic() || c == '_')
5526 .unwrap_or(false)
5527 && var_name.chars().all(|c| c.is_alphanumeric() || c == '_')
5528 {
5529 let rest = &content[slash_pos + 1..];
5530 let val = self.get_variable(var_name);
5531
5532 let replace_all = rest.starts_with('/');
5533 let rest = if replace_all { &rest[1..] } else { rest };
5534
5535 let parts: Vec<&str> = rest.splitn(2, '/').collect();
5536 let pattern = parts.get(0).unwrap_or(&"");
5537 let replacement = parts.get(1).unwrap_or(&"");
5538
5539 return if replace_all {
5540 val.replace(pattern, replacement)
5541 } else {
5542 val.replacen(pattern, replacement, 1)
5543 };
5544 }
5545 }
5546
5547 if let Some(hash_pos) = content.find('#') {
5550 if hash_pos > 0 {
5551 let var_name = &content[..hash_pos];
5552 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
5554 let rest = &content[hash_pos + 1..];
5555 let val = self.get_variable(var_name);
5556
5557 let long = rest.starts_with('#');
5558 let pattern = if long { &rest[1..] } else { rest };
5559
5560 let pattern_regex = regex::escape(pattern)
5562 .replace(r"\*", ".*")
5563 .replace(r"\?", ".");
5564 let full_pattern = format!("^{}", pattern_regex);
5565
5566 if let Some(re) = cached_regex(&full_pattern) {
5567 if long {
5568 let mut longest_end = 0;
5570 for m in re.find_iter(&val) {
5571 if m.end() > longest_end {
5572 longest_end = m.end();
5573 }
5574 }
5575 if longest_end > 0 {
5576 return val[longest_end..].to_string();
5577 }
5578 } else {
5579 if let Some(m) = re.find(&val) {
5581 return val[m.end()..].to_string();
5582 }
5583 }
5584 }
5585 return val;
5586 }
5587 }
5588 }
5589
5590 if let Some(pct_pos) = content.find('%') {
5592 if pct_pos > 0 {
5593 let var_name = &content[..pct_pos];
5594 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
5595 let rest = &content[pct_pos + 1..];
5596 let val = self.get_variable(var_name);
5597
5598 let long = rest.starts_with('%');
5599 let pattern = if long { &rest[1..] } else { rest };
5600
5601 if let Ok(glob) = glob::Pattern::new(pattern) {
5603 if long {
5604 for i in 0..=val.len() {
5606 if glob.matches(&val[i..]) {
5607 return val[..i].to_string();
5608 }
5609 }
5610 } else {
5611 for i in (0..=val.len()).rev() {
5613 if glob.matches(&val[i..]) {
5614 return val[..i].to_string();
5615 }
5616 }
5617 }
5618 }
5619 return val;
5620 }
5621 }
5622 }
5623
5624 if let Some(caret_pos) = content.find('^') {
5626 let var_name = &content[..caret_pos];
5627 let val = self.get_variable(var_name);
5628 let all = content[caret_pos + 1..].starts_with('^');
5629
5630 return if all {
5631 val.to_uppercase()
5632 } else {
5633 let mut chars = val.chars();
5634 match chars.next() {
5635 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
5636 None => String::new(),
5637 }
5638 };
5639 }
5640
5641 if let Some(comma_pos) = content.find(',') {
5643 let var_name = &content[..comma_pos];
5644 let val = self.get_variable(var_name);
5645 let all = content[comma_pos + 1..].starts_with(',');
5646
5647 return if all {
5648 val.to_lowercase()
5649 } else {
5650 let mut chars = val.chars();
5651 match chars.next() {
5652 Some(first) => first.to_lowercase().to_string() + chars.as_str(),
5653 None => String::new(),
5654 }
5655 };
5656 }
5657
5658 if content.starts_with('!') {
5660 let rest = &content[1..];
5661 if rest.ends_with('*') || rest.ends_with('@') {
5662 let prefix = &rest[..rest.len() - 1];
5663 let mut matches: Vec<String> = self
5664 .variables
5665 .keys()
5666 .filter(|k| k.starts_with(prefix))
5667 .cloned()
5668 .collect();
5669 for k in self.arrays.keys() {
5671 if k.starts_with(prefix) && !matches.contains(k) {
5672 matches.push(k.clone());
5673 }
5674 }
5675 matches.sort();
5676 return matches.join(" ");
5677 }
5678
5679 let var_name = self.get_variable(rest);
5681 return self.get_variable(&var_name);
5682 }
5683
5684 self.get_variable(content)
5686 }
5687
5688 fn expand_array_access(&mut self, name: &str, index: &ShellWord) -> String {
5689 use crate::subscript::{get_array_by_subscript, get_array_element_by_subscript, getindex};
5690
5691 let idx_str = self.expand_word(index);
5692 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
5693
5694 match getindex(&idx_str, false, ksh_arrays) {
5696 Ok(v) => {
5697 if let Some(arr) = self.arrays.get(name) {
5698 if v.is_all() {
5699 arr.join(" ")
5700 } else if v.start == v.end - 1 {
5701 get_array_element_by_subscript(arr, &v, ksh_arrays).unwrap_or_default()
5703 } else {
5704 get_array_by_subscript(arr, &v, ksh_arrays).join(" ")
5706 }
5707 } else {
5708 String::new()
5709 }
5710 }
5711 Err(_) => String::new(),
5712 }
5713 }
5714
5715 #[tracing::instrument(level = "trace", skip_all)]
5716 fn expand_string(&mut self, s: &str) -> String {
5717 let mut result = String::new();
5718 let mut chars = s.chars().peekable();
5719
5720 while let Some(c) = chars.next() {
5721 if c == '\x00' {
5723 if let Some(literal_char) = chars.next() {
5724 result.push(literal_char);
5725 }
5726 continue;
5727 }
5728 if c == '$' {
5729 if chars.peek() == Some(&'(') {
5730 chars.next(); if chars.peek() == Some(&'(') {
5734 chars.next(); let expr = Self::collect_until_double_paren(&mut chars);
5736 result.push_str(&self.evaluate_arithmetic(&expr));
5737 } else {
5738 let cmd_str = Self::collect_until_paren(&mut chars);
5740 result.push_str(&self.run_command_substitution(&cmd_str));
5741 }
5742 } else if chars.peek() == Some(&'{') {
5743 chars.next();
5744 let mut brace_content = String::new();
5746 let mut depth = 1;
5747 while let Some(c) = chars.next() {
5748 if c == '{' {
5749 depth += 1;
5750 brace_content.push(c);
5751 } else if c == '}' {
5752 depth -= 1;
5753 if depth == 0 {
5754 break;
5755 }
5756 brace_content.push(c);
5757 } else {
5758 brace_content.push(c);
5759 }
5760 }
5761 result.push_str(&self.expand_braced_variable(&brace_content));
5762 } else {
5763 if matches!(chars.peek(), Some(&'$') | Some(&'!') | Some(&'-')) {
5765 let sc = chars.next().unwrap();
5766 result.push_str(&self.get_variable(&sc.to_string()));
5767 continue;
5768 }
5769 if chars.peek() == Some(&'#') {
5771 let mut peek_iter = chars.clone();
5772 peek_iter.next(); if peek_iter.peek().map(|c| c.is_alphabetic() || *c == '_').unwrap_or(false) {
5774 chars.next(); let mut name = String::new();
5776 while let Some(&c) = chars.peek() {
5777 if c.is_alphanumeric() || c == '_' {
5778 name.push(chars.next().unwrap());
5779 } else {
5780 break;
5781 }
5782 }
5783 let len = if let Some(arr) = self.arrays.get(&name) {
5785 arr.len()
5786 } else {
5787 self.get_variable(&name).len()
5788 };
5789 result.push_str(&len.to_string());
5790 continue;
5791 }
5792 }
5793 let mut var_name = String::new();
5794 while let Some(&c) = chars.peek() {
5795 if c.is_alphanumeric() || c == '_' || c == '@' || c == '*' || c == '#' || c == '?' {
5796 var_name.push(chars.next().unwrap());
5797 if matches!(
5799 var_name.as_str(),
5800 "@" | "*"
5801 | "#"
5802 | "?"
5803 | "$"
5804 | "!"
5805 | "-"
5806 | "0"
5807 | "1"
5808 | "2"
5809 | "3"
5810 | "4"
5811 | "5"
5812 | "6"
5813 | "7"
5814 | "8"
5815 | "9"
5816 ) {
5817 break;
5818 }
5819 } else {
5820 break;
5821 }
5822 }
5823 result.push_str(&self.get_variable(&var_name));
5824 }
5825 } else if c == '`' {
5826 let cmd_str: String = chars.by_ref().take_while(|&c| c != '`').collect();
5828 result.push_str(&self.run_command_substitution(&cmd_str));
5829 } else if c == '<' && chars.peek() == Some(&'(') {
5830 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
5833 result.push_str(&self.run_process_sub_in(&cmd_str));
5834 } else if c == '>' && chars.peek() == Some(&'(') {
5835 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
5838 result.push_str(&self.run_process_sub_out(&cmd_str));
5839 } else if c == '~' && result.is_empty() {
5840 if let Some(home) = dirs::home_dir() {
5841 result.push_str(&home.to_string_lossy());
5842 } else {
5843 result.push(c);
5844 }
5845 } else {
5846 result.push(c);
5847 }
5848 }
5849
5850 result
5851 }
5852
5853 fn collect_until_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
5854 let mut result = String::new();
5855 let mut depth = 1;
5856
5857 while let Some(c) = chars.next() {
5858 if c == '(' {
5859 depth += 1;
5860 result.push(c);
5861 } else if c == ')' {
5862 depth -= 1;
5863 if depth == 0 {
5864 break;
5865 }
5866 result.push(c);
5867 } else {
5868 result.push(c);
5869 }
5870 }
5871
5872 result
5873 }
5874
5875 fn collect_until_double_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
5876 let mut result = String::new();
5877 let mut arith_depth = 1; let mut paren_depth = 0; while let Some(c) = chars.next() {
5881 if c == '(' {
5882 if paren_depth == 0 && chars.peek() == Some(&'(') {
5883 paren_depth += 1;
5886 result.push(c);
5887 } else {
5888 paren_depth += 1;
5889 result.push(c);
5890 }
5891 } else if c == ')' {
5892 if paren_depth > 0 {
5893 paren_depth -= 1;
5895 result.push(c);
5896 } else if chars.peek() == Some(&')') {
5897 chars.next();
5899 arith_depth -= 1;
5900 if arith_depth == 0 {
5901 break;
5902 }
5903 result.push_str("))");
5904 } else {
5905 result.push(c);
5907 }
5908 } else {
5909 result.push(c);
5910 }
5911 }
5912
5913 result
5914 }
5915
5916 fn run_process_sub_in(&mut self, cmd_str: &str) -> String {
5917 use std::fs;
5918 use std::process::Stdio;
5919
5920 let mut parser = ShellParser::new(cmd_str);
5922 let commands = match parser.parse_script() {
5923 Ok(cmds) => cmds,
5924 Err(_) => return String::new(),
5925 };
5926
5927 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
5929 let fifo_counter = self.process_sub_counter;
5930 self.process_sub_counter += 1;
5931 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
5932
5933 let _ = fs::remove_file(&fifo_path);
5935 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
5936 return String::new();
5937 }
5938
5939 let fifo_clone = fifo_path.clone();
5941 if let Some(cmd) = commands.first() {
5942 if let ShellCommand::Simple(simple) = cmd {
5943 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
5944 if !words.is_empty() {
5945 let cmd_name = words[0].clone();
5946 let args: Vec<String> = words[1..].to_vec();
5947
5948 self.worker_pool.submit(move || {
5949 if let Ok(fifo) = fs::OpenOptions::new().write(true).open(&fifo_clone) {
5951 let _ = Command::new(&cmd_name)
5952 .args(&args)
5953 .stdout(fifo)
5954 .stderr(Stdio::inherit())
5955 .status();
5956 }
5957 let _ = fs::remove_file(&fifo_clone);
5959 });
5960 }
5961 }
5962 }
5963
5964 fifo_path
5965 }
5966
5967 fn run_process_sub_out(&mut self, cmd_str: &str) -> String {
5968 use std::fs;
5969 use std::process::Stdio;
5970
5971 let mut parser = ShellParser::new(cmd_str);
5973 let commands = match parser.parse_script() {
5974 Ok(cmds) => cmds,
5975 Err(_) => return String::new(),
5976 };
5977
5978 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
5980 let fifo_counter = self.process_sub_counter;
5981 self.process_sub_counter += 1;
5982 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
5983
5984 let _ = fs::remove_file(&fifo_path);
5986 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
5987 return String::new();
5988 }
5989
5990 let fifo_clone = fifo_path.clone();
5992 if let Some(cmd) = commands.first() {
5993 if let ShellCommand::Simple(simple) = cmd {
5994 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
5995 if !words.is_empty() {
5996 let cmd_name = words[0].clone();
5997 let args: Vec<String> = words[1..].to_vec();
5998
5999 self.worker_pool.submit(move || {
6000 if let Ok(fifo) = fs::File::open(&fifo_clone) {
6002 let _ = Command::new(&cmd_name)
6003 .args(&args)
6004 .stdin(fifo)
6005 .stdout(Stdio::inherit())
6006 .stderr(Stdio::inherit())
6007 .status();
6008 }
6009 let _ = fs::remove_file(&fifo_clone);
6011 });
6012 }
6013 }
6014 }
6015
6016 fifo_path
6017 }
6018
6019 fn run_command_substitution(&mut self, cmd_str: &str) -> String {
6020 use std::process::Stdio;
6021
6022 let mut parser = ShellParser::new(cmd_str);
6028 let commands = match parser.parse_script() {
6029 Ok(cmds) => cmds,
6030 Err(_) => return String::new(),
6031 };
6032
6033 if commands.is_empty() {
6034 return String::new();
6035 }
6036
6037 let is_internal = if let ShellCommand::Simple(simple) = &commands[0] {
6040 let first = simple.words.first().map(|w| self.expand_word(w));
6041 if let Some(ref name) = first {
6042 self.functions.contains_key(name)
6043 || self.is_builtin(name)
6044 } else {
6045 true
6046 }
6047 } else {
6048 true };
6050
6051 if is_internal {
6052 let (read_fd, write_fd) = {
6054 let mut fds = [0i32; 2];
6055 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
6056 return String::new();
6057 }
6058 (fds[0], fds[1])
6059 };
6060
6061 let saved_stdout = unsafe { libc::dup(1) };
6063 unsafe { libc::dup2(write_fd, 1); }
6064 unsafe { libc::close(write_fd); }
6065
6066 for cmd in &commands {
6068 let _ = self.execute_command(cmd);
6069 }
6070
6071 use std::io::Write;
6073 let _ = io::stdout().flush();
6074
6075 unsafe { libc::dup2(saved_stdout, 1); }
6077 unsafe { libc::close(saved_stdout); }
6078
6079 use std::os::unix::io::FromRawFd;
6081 let mut output = String::new();
6082 let read_file = unsafe { std::fs::File::from_raw_fd(read_fd) };
6083 use std::io::Read;
6084 let _ = std::io::BufReader::new(read_file).read_to_string(&mut output);
6085
6086 output.trim_end_matches('\n').to_string()
6087 } else {
6088 if let ShellCommand::Simple(simple) = &commands[0] {
6090 let words: Vec<String> =
6091 simple.words.iter().map(|w| self.expand_word(w)).collect();
6092 if words.is_empty() {
6093 return String::new();
6094 }
6095
6096 let output = Command::new(&words[0])
6097 .args(&words[1..])
6098 .stdout(Stdio::piped())
6099 .stderr(Stdio::inherit())
6100 .output();
6101
6102 match output {
6103 Ok(out) => String::from_utf8_lossy(&out.stdout)
6104 .trim_end_matches('\n')
6105 .to_string(),
6106 Err(_) => String::new(),
6107 }
6108 } else {
6109 String::new()
6110 }
6111 }
6112 }
6113
6114 fn execute_process_sub_in(&mut self, cmd: &ShellCommand) -> String {
6116 if let ShellCommand::Simple(simple) = cmd {
6117 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
6118 let cmd_str = words.join(" ");
6119 self.run_process_sub_in(&cmd_str)
6120 } else {
6121 String::new()
6122 }
6123 }
6124
6125 fn execute_process_sub_out(&mut self, cmd: &ShellCommand) -> String {
6127 if let ShellCommand::Simple(simple) = cmd {
6128 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
6129 let cmd_str = words.join(" ");
6130 self.run_process_sub_out(&cmd_str)
6131 } else {
6132 String::new()
6133 }
6134 }
6135
6136 fn get_special_array_value(&self, array_name: &str, key: &str) -> Option<String> {
6139 match array_name {
6140 "options" => {
6142 if key == "@" || key == "*" {
6143 let opts: Vec<String> = self
6145 .options
6146 .iter()
6147 .map(|(k, v)| format!("{}={}", k, if *v { "on" } else { "off" }))
6148 .collect();
6149 return Some(opts.join(" "));
6150 }
6151 let opt_name = key.to_lowercase().replace('_', "");
6152 let is_on = self.options.get(&opt_name).copied().unwrap_or(false);
6153 Some(if is_on {
6154 "on".to_string()
6155 } else {
6156 "off".to_string()
6157 })
6158 }
6159
6160 "aliases" => {
6162 if key == "@" || key == "*" {
6163 let vals: Vec<String> = self.aliases.values().cloned().collect();
6164 return Some(vals.join(" "));
6165 }
6166 Some(self.aliases.get(key).cloned().unwrap_or_default())
6167 }
6168 "galiases" => {
6169 if key == "@" || key == "*" {
6170 let vals: Vec<String> = self.global_aliases.values().cloned().collect();
6171 return Some(vals.join(" "));
6172 }
6173 Some(self.global_aliases.get(key).cloned().unwrap_or_default())
6174 }
6175 "saliases" => {
6176 if key == "@" || key == "*" {
6177 let vals: Vec<String> = self.suffix_aliases.values().cloned().collect();
6178 return Some(vals.join(" "));
6179 }
6180 Some(self.suffix_aliases.get(key).cloned().unwrap_or_default())
6181 }
6182
6183 "functions" => {
6185 if key == "@" || key == "*" {
6186 let names: Vec<String> = self.functions.keys().cloned().collect();
6187 return Some(names.join(" "));
6188 }
6189 if let Some(body) = self.functions.get(key) {
6190 Some(format!("{:?}", body))
6191 } else {
6192 Some(String::new())
6193 }
6194 }
6195 "functions_source" => {
6196 Some(String::new())
6198 }
6199
6200 "commands" => {
6202 if key == "@" || key == "*" {
6203 return Some(String::new()); }
6205 if let Some(path) = self.find_in_path(key) {
6207 Some(path)
6208 } else {
6209 Some(String::new())
6210 }
6211 }
6212
6213 "builtins" => {
6215 let builtins = Self::get_builtin_names();
6216 if key == "@" || key == "*" {
6217 return Some(builtins.join(" "));
6218 }
6219 if builtins.contains(&key) {
6220 Some("defined".to_string())
6221 } else {
6222 Some(String::new())
6223 }
6224 }
6225
6226 "parameters" => {
6228 if key == "@" || key == "*" {
6229 let mut names: Vec<String> = self.variables.keys().cloned().collect();
6230 names.extend(self.arrays.keys().cloned());
6231 names.extend(self.assoc_arrays.keys().cloned());
6232 return Some(names.join(" "));
6233 }
6234 if self.assoc_arrays.contains_key(key) {
6236 Some("association".to_string())
6237 } else if self.arrays.contains_key(key) {
6238 Some("array".to_string())
6239 } else if self.variables.contains_key(key) || std::env::var(key).is_ok() {
6240 Some("scalar".to_string())
6241 } else {
6242 Some(String::new())
6243 }
6244 }
6245
6246 "nameddirs" => {
6248 if key == "@" || key == "*" {
6249 let vals: Vec<String> = self
6250 .named_dirs
6251 .values()
6252 .map(|p| p.display().to_string())
6253 .collect();
6254 return Some(vals.join(" "));
6255 }
6256 Some(
6257 self.named_dirs
6258 .get(key)
6259 .map(|p| p.display().to_string())
6260 .unwrap_or_default(),
6261 )
6262 }
6263
6264 "userdirs" => {
6266 if key == "@" || key == "*" {
6267 return Some(String::new());
6268 }
6269 #[cfg(unix)]
6271 {
6272 use std::ffi::CString;
6273 if let Ok(name) = CString::new(key) {
6274 unsafe {
6275 let pwd = libc::getpwnam(name.as_ptr());
6276 if !pwd.is_null() {
6277 let dir = std::ffi::CStr::from_ptr((*pwd).pw_dir);
6278 return Some(dir.to_string_lossy().to_string());
6279 }
6280 }
6281 }
6282 }
6283 Some(String::new())
6284 }
6285
6286 "usergroups" => {
6288 if key == "@" || key == "*" {
6289 return Some(String::new());
6290 }
6291 #[cfg(unix)]
6293 {
6294 use std::ffi::CString;
6295 if let Ok(name) = CString::new(key) {
6296 unsafe {
6297 let grp = libc::getgrnam(name.as_ptr());
6298 if !grp.is_null() {
6299 return Some((*grp).gr_gid.to_string());
6300 }
6301 }
6302 }
6303 }
6304 Some(String::new())
6305 }
6306
6307 "dirstack" => {
6309 if key == "@" || key == "*" {
6310 let dirs: Vec<String> = self
6311 .dir_stack
6312 .iter()
6313 .map(|p| p.display().to_string())
6314 .collect();
6315 return Some(dirs.join(" "));
6316 }
6317 if let Ok(idx) = key.parse::<usize>() {
6318 Some(
6319 self.dir_stack
6320 .get(idx)
6321 .map(|p| p.display().to_string())
6322 .unwrap_or_default(),
6323 )
6324 } else {
6325 Some(String::new())
6326 }
6327 }
6328
6329 "jobstates" => {
6331 if key == "@" || key == "*" {
6332 let states: Vec<String> = self
6333 .jobs
6334 .iter()
6335 .map(|(id, job)| format!("{}:{:?}", id, job.state))
6336 .collect();
6337 return Some(states.join(" "));
6338 }
6339 if let Ok(id) = key.parse::<usize>() {
6340 if let Some(job) = self.jobs.get(id) {
6341 return Some(format!("{:?}", job.state));
6342 }
6343 }
6344 Some(String::new())
6345 }
6346 "jobtexts" => {
6347 if key == "@" || key == "*" {
6348 let texts: Vec<String> = self
6349 .jobs
6350 .iter()
6351 .map(|(_, job)| job.command.clone())
6352 .collect();
6353 return Some(texts.join(" "));
6354 }
6355 if let Ok(id) = key.parse::<usize>() {
6356 if let Some(job) = self.jobs.get(id) {
6357 return Some(job.command.clone());
6358 }
6359 }
6360 Some(String::new())
6361 }
6362 "jobdirs" => {
6363 if key == "@" || key == "*" {
6365 return Some(String::new());
6366 }
6367 Some(String::new())
6368 }
6369
6370 "history" => {
6372 if key == "@" || key == "*" {
6373 if let Some(ref engine) = self.history {
6375 if let Ok(entries) = engine.recent(100) {
6376 let cmds: Vec<String> =
6377 entries.iter().map(|e| e.command.clone()).collect();
6378 return Some(cmds.join("\n"));
6379 }
6380 }
6381 return Some(String::new());
6382 }
6383 if let Ok(num) = key.parse::<usize>() {
6384 if let Some(ref engine) = self.history {
6385 if let Ok(Some(entry)) = engine.get_by_offset(num.saturating_sub(1)) {
6386 return Some(entry.command);
6387 }
6388 }
6389 }
6390 Some(String::new())
6391 }
6392 "historywords" => {
6393 Some(String::new())
6395 }
6396
6397 "modules" => {
6399 if key == "@" || key == "*" {
6402 return Some("zsh/parameter zsh/zutil".to_string());
6403 }
6404 match key {
6405 "zsh/parameter" | "zsh/zutil" | "zsh/complete" | "zsh/complist" => {
6406 Some("loaded".to_string())
6407 }
6408 _ => Some(String::new()),
6409 }
6410 }
6411
6412 "reswords" => {
6414 let reswords = [
6415 "do",
6416 "done",
6417 "esac",
6418 "then",
6419 "elif",
6420 "else",
6421 "fi",
6422 "for",
6423 "case",
6424 "if",
6425 "while",
6426 "function",
6427 "repeat",
6428 "time",
6429 "until",
6430 "select",
6431 "coproc",
6432 "nocorrect",
6433 "foreach",
6434 "end",
6435 "in",
6436 ];
6437 if key == "@" || key == "*" {
6438 return Some(reswords.join(" "));
6439 }
6440 if let Ok(idx) = key.parse::<usize>() {
6441 Some(reswords.get(idx).map(|s| s.to_string()).unwrap_or_default())
6442 } else {
6443 Some(String::new())
6444 }
6445 }
6446
6447 "patchars" => {
6449 let patchars = ["?", "*", "[", "]", "^", "#", "~", "(", ")", "|"];
6450 if key == "@" || key == "*" {
6451 return Some(patchars.join(" "));
6452 }
6453 if let Ok(idx) = key.parse::<usize>() {
6454 Some(patchars.get(idx).map(|s| s.to_string()).unwrap_or_default())
6455 } else {
6456 Some(String::new())
6457 }
6458 }
6459
6460 "funcstack" | "functrace" | "funcfiletrace" | "funcsourcetrace" => {
6462 Some(String::new())
6464 }
6465
6466 "dis_aliases"
6468 | "dis_galiases"
6469 | "dis_saliases"
6470 | "dis_functions"
6471 | "dis_functions_source"
6472 | "dis_builtins"
6473 | "dis_reswords"
6474 | "dis_patchars" => {
6475 Some(String::new())
6477 }
6478
6479 _ => None,
6481 }
6482 }
6483
6484 fn get_builtin_names() -> Vec<&'static str> {
6486 vec![
6487 ".",
6488 ":",
6489 "[",
6490 "alias",
6491 "autoload",
6492 "bg",
6493 "bind",
6494 "bindkey",
6495 "break",
6496 "builtin",
6497 "bye",
6498 "caller",
6499 "cd",
6500 "cdreplay",
6501 "chdir",
6502 "clone",
6503 "command",
6504 "compadd",
6505 "comparguments",
6506 "compcall",
6507 "compctl",
6508 "compdef",
6509 "compdescribe",
6510 "compfiles",
6511 "compgen",
6512 "compgroups",
6513 "compinit",
6514 "complete",
6515 "compopt",
6516 "compquote",
6517 "compset",
6518 "comptags",
6519 "comptry",
6520 "compvalues",
6521 "continue",
6522 "coproc",
6523 "declare",
6524 "dirs",
6525 "disable",
6526 "disown",
6527 "echo",
6528 "echotc",
6529 "echoti",
6530 "emulate",
6531 "enable",
6532 "eval",
6533 "exec",
6534 "exit",
6535 "export",
6536 "false",
6537 "fc",
6538 "fg",
6539 "float",
6540 "functions",
6541 "getln",
6542 "getopts",
6543 "hash",
6544 "help",
6545 "history",
6546 "integer",
6547 "jobs",
6548 "kill",
6549 "let",
6550 "limit",
6551 "local",
6552 "log",
6553 "logout",
6554 "mapfile",
6555 "noglob",
6556 "popd",
6557 "print",
6558 "printf",
6559 "private",
6560 "prompt",
6561 "promptinit",
6562 "pushd",
6563 "pushln",
6564 "pwd",
6565 "r",
6566 "read",
6567 "readarray",
6568 "readonly",
6569 "rehash",
6570 "return",
6571 "sched",
6572 "set",
6573 "setopt",
6574 "shift",
6575 "shopt",
6576 "source",
6577 "stat",
6578 "strftime",
6579 "suspend",
6580 "test",
6581 "times",
6582 "trap",
6583 "true",
6584 "ttyctl",
6585 "type",
6586 "typeset",
6587 "ulimit",
6588 "umask",
6589 "unalias",
6590 "unfunction",
6591 "unhash",
6592 "unlimit",
6593 "unset",
6594 "unsetopt",
6595 "vared",
6596 "wait",
6597 "whence",
6598 "where",
6599 "which",
6600 "zcompile",
6601 "zcurses",
6602 "zformat",
6603 "zle",
6604 "zmodload",
6605 "zparseopts",
6606 "zprof",
6607 "zpty",
6608 "zregexparse",
6609 "zsocket",
6610 "zstyle",
6611 "ztcp",
6612 "add-zsh-hook",
6613 ]
6614 }
6615
6616 fn get_variable(&self, name: &str) -> String {
6617 match name {
6619 "" => String::new(), "$" => std::process::id().to_string(),
6621 "@" | "*" => self.positional_params.join(" "),
6622 "#" => self.positional_params.len().to_string(),
6623 "?" => self.last_status.to_string(),
6624 "0" => self
6625 .variables
6626 .get("0")
6627 .cloned()
6628 .unwrap_or_else(|| env::args().next().unwrap_or_default()),
6629 n if !n.is_empty() && n.chars().all(|c| c.is_ascii_digit()) => {
6630 let idx: usize = n.parse().unwrap_or(0);
6631 if idx == 0 {
6632 env::args().next().unwrap_or_default()
6633 } else {
6634 self.positional_params
6635 .get(idx - 1)
6636 .cloned()
6637 .unwrap_or_default()
6638 }
6639 }
6640 _ => {
6641 self.variables
6643 .get(name)
6644 .cloned()
6645 .or_else(|| {
6646 self.arrays.get(name).map(|a| a.join(" "))
6648 })
6649 .or_else(|| env::var(name).ok())
6650 .unwrap_or_default()
6651 }
6652 }
6653 }
6654
6655 fn apply_var_modifier(
6656 &mut self,
6657 name: &str,
6658 val: Option<String>,
6659 modifier: Option<&VarModifier>,
6660 ) -> String {
6661 match modifier {
6662 None => val.unwrap_or_default(),
6663
6664 Some(VarModifier::Default(word)) => match &val {
6666 Some(v) if !v.is_empty() => v.clone(),
6667 _ => self.expand_word(word),
6668 },
6669
6670 Some(VarModifier::DefaultAssign(word)) => match &val {
6672 Some(v) if !v.is_empty() => v.clone(),
6673 _ => self.expand_word(word),
6674 },
6675
6676 Some(VarModifier::Error(word)) => match &val {
6678 Some(v) if !v.is_empty() => v.clone(),
6679 _ => {
6680 let msg = self.expand_word(word);
6681 eprintln!("zshrs: {}", msg);
6682 String::new()
6683 }
6684 },
6685
6686 Some(VarModifier::Alternate(word)) => match &val {
6688 Some(v) if !v.is_empty() => self.expand_word(word),
6689 _ => String::new(),
6690 },
6691
6692 Some(VarModifier::Length) => val
6694 .map(|v| v.len().to_string())
6695 .unwrap_or_else(|| "0".to_string()),
6696
6697 Some(VarModifier::Substring(offset, length)) => {
6699 let v = val.unwrap_or_default();
6700 let start = if *offset < 0 {
6701 (v.len() as i64 + offset).max(0) as usize
6702 } else {
6703 (*offset as usize).min(v.len())
6704 };
6705
6706 if let Some(len) = length {
6707 let len = (*len as usize).min(v.len().saturating_sub(start));
6708 v.chars().skip(start).take(len).collect()
6709 } else {
6710 v.chars().skip(start).collect()
6711 }
6712 }
6713
6714 Some(VarModifier::RemovePrefix(pattern)) => {
6716 let v = val.unwrap_or_default();
6717 let pat = self.expand_word(pattern);
6718 if v.starts_with(&pat) {
6719 v[pat.len()..].to_string()
6720 } else {
6721 v
6722 }
6723 }
6724
6725 Some(VarModifier::RemovePrefixLong(pattern)) => {
6727 let v = val.unwrap_or_default();
6728 let pat = self.expand_word(pattern);
6729 if let Ok(glob) = glob::Pattern::new(&pat) {
6731 for i in (0..=v.len()).rev() {
6732 if glob.matches(&v[..i]) {
6733 return v[i..].to_string();
6734 }
6735 }
6736 }
6737 v
6738 }
6739
6740 Some(VarModifier::RemoveSuffix(pattern)) => {
6742 let v = val.unwrap_or_default();
6743 let pat = self.expand_word(pattern);
6744 if let Ok(glob) = glob::Pattern::new(&pat) {
6746 for i in (0..=v.len()).rev() {
6747 if glob.matches(&v[i..]) {
6748 return v[..i].to_string();
6749 }
6750 }
6751 } else if v.ends_with(&pat) {
6752 return v[..v.len() - pat.len()].to_string();
6753 }
6754 v
6755 }
6756
6757 Some(VarModifier::RemoveSuffixLong(pattern)) => {
6759 let v = val.unwrap_or_default();
6760 let pat = self.expand_word(pattern);
6761 if let Ok(glob) = glob::Pattern::new(&pat) {
6763 for i in 0..=v.len() {
6764 if glob.matches(&v[i..]) {
6765 return v[..i].to_string();
6766 }
6767 }
6768 }
6769 v
6770 }
6771
6772 Some(VarModifier::Replace(pattern, replacement)) => {
6774 let v = val.unwrap_or_default();
6775 let pat = self.expand_word(pattern);
6776 let repl = self.expand_word(replacement);
6777 v.replacen(&pat, &repl, 1)
6778 }
6779
6780 Some(VarModifier::ReplaceAll(pattern, replacement)) => {
6782 let v = val.unwrap_or_default();
6783 let pat = self.expand_word(pattern);
6784 let repl = self.expand_word(replacement);
6785 v.replace(&pat, &repl)
6786 }
6787
6788 Some(VarModifier::Upper) => val.map(|v| v.to_uppercase()).unwrap_or_default(),
6790
6791 Some(VarModifier::Lower) => val.map(|v| v.to_lowercase()).unwrap_or_default(),
6793
6794 Some(VarModifier::ZshFlags(flags)) => {
6796 let mut result = val.unwrap_or_default();
6797 for flag in flags {
6798 result = self.apply_zsh_param_flag(&result, name, flag);
6799 }
6800 result
6801 }
6802
6803 Some(VarModifier::ArrayLength)
6805 | Some(VarModifier::ArrayIndex(_))
6806 | Some(VarModifier::ArrayAll) => val.unwrap_or_default(),
6807 }
6808 }
6809
6810 fn is_history_modifier(&self, s: &str) -> bool {
6812 if s.is_empty() {
6813 return false;
6814 }
6815 let first = s.chars().next().unwrap();
6816 matches!(
6817 first,
6818 'A' | 'a' | 'h' | 't' | 'r' | 'e' | 'l' | 'u' | 'q' | 'Q' | 'P'
6819 )
6820 }
6821
6822 fn apply_history_modifiers(&self, val: &str, modifiers: &str) -> String {
6825 let mut result = val.to_string();
6826 let mut chars = modifiers.chars().peekable();
6827
6828 while let Some(c) = chars.next() {
6829 match c {
6830 ':' => continue,
6831 'A' => {
6832 if let Ok(abs) = std::fs::canonicalize(&result) {
6833 result = abs.to_string_lossy().to_string();
6834 } else if !result.starts_with('/') {
6835 if let Ok(cwd) = std::env::current_dir() {
6836 result = cwd.join(&result).to_string_lossy().to_string();
6837 }
6838 }
6839 }
6840 'a' => {
6841 if !result.starts_with('/') {
6842 if let Ok(cwd) = std::env::current_dir() {
6843 result = cwd.join(&result).to_string_lossy().to_string();
6844 }
6845 }
6846 }
6847 'h' => {
6848 if let Some(pos) = result.rfind('/') {
6849 if pos == 0 {
6850 result = "/".to_string();
6851 } else {
6852 result = result[..pos].to_string();
6853 }
6854 } else {
6855 result = ".".to_string();
6856 }
6857 }
6858 't' => {
6859 if let Some(pos) = result.rfind('/') {
6860 result = result[pos + 1..].to_string();
6861 }
6862 }
6863 'r' => {
6864 if let Some(dot_pos) = result.rfind('.') {
6865 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
6866 if dot_pos > slash_pos {
6867 result = result[..dot_pos].to_string();
6868 }
6869 }
6870 }
6871 'e' => {
6872 if let Some(dot_pos) = result.rfind('.') {
6873 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
6874 if dot_pos > slash_pos {
6875 result = result[dot_pos + 1..].to_string();
6876 } else {
6877 result = String::new();
6878 }
6879 } else {
6880 result = String::new();
6881 }
6882 }
6883 'l' => result = result.to_lowercase(),
6884 'u' => result = result.to_uppercase(),
6885 'q' => result = format!("'{}'", result.replace('\'', "'\\''")),
6886 'Q' => {
6887 if result.starts_with('\'') && result.ends_with('\'') && result.len() >= 2 {
6888 result = result[1..result.len() - 1].to_string();
6889 } else if result.starts_with('"') && result.ends_with('"') && result.len() >= 2
6890 {
6891 result = result[1..result.len() - 1].to_string();
6892 }
6893 }
6894 'P' => {
6895 if let Ok(real) = std::fs::canonicalize(&result) {
6896 result = real.to_string_lossy().to_string();
6897 }
6898 }
6899 _ => break,
6900 }
6901 }
6902 result
6903 }
6904
6905 fn parse_zsh_flags(&self, s: &str) -> Vec<ZshParamFlag> {
6907 let mut flags = Vec::new();
6908 let mut chars = s.chars().peekable();
6909
6910 while let Some(c) = chars.next() {
6911 match c {
6912 'L' => flags.push(ZshParamFlag::Lower),
6913 'U' => flags.push(ZshParamFlag::Upper),
6914 'C' => flags.push(ZshParamFlag::Capitalize),
6915 'j' => {
6916 if let Some(&delim) = chars.peek() {
6918 chars.next(); let mut sep = String::new();
6920 while let Some(&ch) = chars.peek() {
6921 if ch == delim {
6922 chars.next();
6923 break;
6924 }
6925 sep.push(chars.next().unwrap());
6926 }
6927 flags.push(ZshParamFlag::Join(sep));
6928 }
6929 }
6930 'F' => flags.push(ZshParamFlag::JoinNewline),
6931 's' => {
6932 if chars.peek() == Some(&':') {
6934 chars.next();
6935 let mut sep = String::new();
6936 while let Some(&ch) = chars.peek() {
6937 if ch == ':' {
6938 chars.next();
6939 break;
6940 }
6941 sep.push(chars.next().unwrap());
6942 }
6943 flags.push(ZshParamFlag::Split(sep));
6944 }
6945 }
6946 'f' => flags.push(ZshParamFlag::SplitLines),
6947 'z' => flags.push(ZshParamFlag::SplitWords),
6948 't' => flags.push(ZshParamFlag::Type),
6949 'w' => flags.push(ZshParamFlag::Words),
6950 'b' => flags.push(ZshParamFlag::QuoteBackslash),
6951 'q' => {
6952 if chars.peek() == Some(&'q') {
6953 chars.next();
6954 flags.push(ZshParamFlag::DoubleQuote);
6955 } else {
6956 flags.push(ZshParamFlag::Quote);
6957 }
6958 }
6959 'u' => flags.push(ZshParamFlag::Unique),
6960 'O' => flags.push(ZshParamFlag::Reverse),
6961 'o' => flags.push(ZshParamFlag::Sort),
6962 'n' => flags.push(ZshParamFlag::NumericSort),
6963 'a' => flags.push(ZshParamFlag::IndexSort),
6964 'k' => flags.push(ZshParamFlag::Keys),
6965 'v' => flags.push(ZshParamFlag::Values),
6966 '#' => flags.push(ZshParamFlag::Length),
6967 'c' => flags.push(ZshParamFlag::CountChars),
6968 'e' => flags.push(ZshParamFlag::Expand),
6969 '%' => {
6970 if chars.peek() == Some(&'%') {
6971 chars.next();
6972 flags.push(ZshParamFlag::PromptExpandFull);
6973 } else {
6974 flags.push(ZshParamFlag::PromptExpand);
6975 }
6976 }
6977 'V' => flags.push(ZshParamFlag::Visible),
6978 'D' => flags.push(ZshParamFlag::Directory),
6979 'M' => flags.push(ZshParamFlag::Match),
6980 'R' => flags.push(ZshParamFlag::Remove),
6981 'S' => flags.push(ZshParamFlag::Subscript),
6982 'P' => flags.push(ZshParamFlag::Parameter),
6983 '~' => flags.push(ZshParamFlag::Glob),
6984 'l' => {
6985 if chars.peek() == Some(&':') {
6987 chars.next();
6988 let mut len_str = String::new();
6989 while let Some(&ch) = chars.peek() {
6990 if ch == ':' {
6991 chars.next();
6992 break;
6993 }
6994 len_str.push(chars.next().unwrap());
6995 }
6996 let mut fill = ' ';
6997 if let Some(&ch) = chars.peek() {
6998 if ch != ':' {
6999 fill = chars.next().unwrap();
7000 if chars.peek() == Some(&':') {
7001 chars.next();
7002 }
7003 }
7004 }
7005 if let Ok(len) = len_str.parse() {
7006 flags.push(ZshParamFlag::PadLeft(len, fill));
7007 }
7008 }
7009 }
7010 'r' => {
7011 if chars.peek() == Some(&':') {
7013 chars.next();
7014 let mut len_str = String::new();
7015 while let Some(&ch) = chars.peek() {
7016 if ch == ':' {
7017 chars.next();
7018 break;
7019 }
7020 len_str.push(chars.next().unwrap());
7021 }
7022 let mut fill = ' ';
7023 if let Some(&ch) = chars.peek() {
7024 if ch != ':' {
7025 fill = chars.next().unwrap();
7026 if chars.peek() == Some(&':') {
7027 chars.next();
7028 }
7029 }
7030 }
7031 if let Ok(len) = len_str.parse() {
7032 flags.push(ZshParamFlag::PadRight(len, fill));
7033 }
7034 }
7035 }
7036 'm' => {
7037 let mut width_str = String::new();
7039 while let Some(&ch) = chars.peek() {
7040 if ch.is_ascii_digit() {
7041 width_str.push(chars.next().unwrap());
7042 } else {
7043 break;
7044 }
7045 }
7046 if let Ok(w) = width_str.parse() {
7047 flags.push(ZshParamFlag::Width(w));
7048 }
7049 }
7050 _ => {}
7051 }
7052 }
7053 flags
7054 }
7055
7056 fn apply_zsh_param_flag(&self, val: &str, name: &str, flag: &ZshParamFlag) -> String {
7058 match flag {
7059 ZshParamFlag::Lower => val.to_lowercase(),
7060 ZshParamFlag::Upper => val.to_uppercase(),
7061 ZshParamFlag::Capitalize => val
7062 .split_whitespace()
7063 .map(|word| {
7064 let mut c = word.chars();
7065 match c.next() {
7066 None => String::new(),
7067 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
7068 }
7069 })
7070 .collect::<Vec<_>>()
7071 .join(" "),
7072 ZshParamFlag::Join(sep) => {
7073 if let Some(arr) = self.arrays.get(name) {
7074 arr.join(sep)
7075 } else {
7076 val.to_string()
7077 }
7078 }
7079 ZshParamFlag::Split(sep) => val.split(sep).collect::<Vec<_>>().join(" "),
7080 ZshParamFlag::SplitLines => val.lines().collect::<Vec<_>>().join(" "),
7081 ZshParamFlag::Type => {
7082 if self.arrays.contains_key(name) {
7083 "array".to_string()
7084 } else if self.assoc_arrays.contains_key(name) {
7085 "association".to_string()
7086 } else if self.functions.contains_key(name) {
7087 "function".to_string()
7088 } else if std::env::var(name).is_ok() || self.variables.contains_key(name) {
7089 "scalar".to_string()
7090 } else {
7091 "".to_string()
7092 }
7093 }
7094 ZshParamFlag::Words => val.split_whitespace().collect::<Vec<_>>().join(" "),
7095 ZshParamFlag::Quote => format!("'{}'", val.replace('\'', "'\\''")),
7096 ZshParamFlag::DoubleQuote => format!("\"{}\"", val.replace('"', "\\\"")),
7097 ZshParamFlag::Unique => {
7098 let words: Vec<&str> = val.split_whitespace().collect();
7101 let mut seen = std::collections::HashSet::with_capacity(
7102 if words.len() >= 1000 { words.len() } else { 0 },
7103 );
7104 if words.len() >= 1000 {
7105 tracing::trace!(
7106 count = words.len(),
7107 "unique on large array ({} elements)",
7108 words.len()
7109 );
7110 }
7111 words
7112 .into_iter()
7113 .filter(|s| seen.insert(*s))
7114 .collect::<Vec<_>>()
7115 .join(" ")
7116 }
7117 ZshParamFlag::Reverse => {
7118 let mut words: Vec<&str> = val.split_whitespace().collect();
7120 if words.len() >= 1000 {
7121 tracing::trace!(
7122 count = words.len(),
7123 "using parallel reverse sort (rayon) for large array"
7124 );
7125 use rayon::prelude::*;
7126 words.par_sort_unstable_by(|a, b| b.cmp(a));
7127 } else {
7128 words.sort_unstable_by(|a, b| b.cmp(a));
7129 }
7130 words.join(" ")
7131 }
7132 ZshParamFlag::Sort => {
7133 let mut words: Vec<&str> = val.split_whitespace().collect();
7134 if words.len() >= 1000 {
7135 tracing::trace!(
7136 count = words.len(),
7137 "using parallel sort (rayon) for large array"
7138 );
7139 use rayon::prelude::*;
7140 words.par_sort_unstable();
7141 } else {
7142 words.sort_unstable();
7143 }
7144 words.join(" ")
7145 }
7146 ZshParamFlag::NumericSort => {
7147 let mut words: Vec<&str> = val.split_whitespace().collect();
7148 let cmp = |a: &&str, b: &&str| {
7149 let na: i64 = a.parse().unwrap_or(0);
7150 let nb: i64 = b.parse().unwrap_or(0);
7151 na.cmp(&nb)
7152 };
7153 if words.len() >= 1000 {
7154 tracing::trace!(
7155 count = words.len(),
7156 "using parallel numeric sort (rayon) for large array"
7157 );
7158 use rayon::prelude::*;
7159 words.par_sort_unstable_by(cmp);
7160 } else {
7161 words.sort_unstable_by(cmp);
7162 }
7163 words.join(" ")
7164 }
7165 ZshParamFlag::Keys => {
7166 if let Some(assoc) = self.assoc_arrays.get(name) {
7167 assoc.keys().cloned().collect::<Vec<_>>().join(" ")
7168 } else {
7169 String::new()
7170 }
7171 }
7172 ZshParamFlag::Values => {
7173 if let Some(assoc) = self.assoc_arrays.get(name) {
7174 assoc.values().cloned().collect::<Vec<_>>().join(" ")
7175 } else {
7176 val.to_string()
7177 }
7178 }
7179 ZshParamFlag::Length => val.len().to_string(),
7180 ZshParamFlag::Head(n) => val
7181 .split_whitespace()
7182 .take(*n)
7183 .collect::<Vec<_>>()
7184 .join(" "),
7185 ZshParamFlag::Tail(n) => {
7186 let words: Vec<&str> = val.split_whitespace().collect();
7187 if words.len() > *n {
7188 words[words.len() - n..].join(" ")
7189 } else {
7190 val.to_string()
7191 }
7192 }
7193 ZshParamFlag::JoinNewline => {
7194 if let Some(arr) = self.arrays.get(name) {
7195 arr.join("\n")
7196 } else {
7197 val.to_string()
7198 }
7199 }
7200 ZshParamFlag::SplitWords => {
7201 val.split_whitespace().collect::<Vec<_>>().join(" ")
7203 }
7204 ZshParamFlag::QuoteBackslash => {
7205 let mut result = String::new();
7207 for c in val.chars() {
7208 if "\\*?[]{}()".contains(c) {
7209 result.push('\\');
7210 }
7211 result.push(c);
7212 }
7213 result
7214 }
7215 ZshParamFlag::IndexSort => {
7216 val.to_string()
7218 }
7219 ZshParamFlag::CountChars => {
7220 val.chars().count().to_string()
7222 }
7223 ZshParamFlag::Expand => {
7224 val.to_string()
7226 }
7227 ZshParamFlag::PromptExpand => {
7228 self.expand_prompt_string(val)
7230 }
7231 ZshParamFlag::PromptExpandFull => {
7232 self.expand_prompt_string(val)
7234 }
7235 ZshParamFlag::Visible => {
7236 val.chars()
7238 .map(|c| {
7239 if c.is_control() {
7240 format!("^{}", (c as u8 + 64) as char)
7241 } else {
7242 c.to_string()
7243 }
7244 })
7245 .collect()
7246 }
7247 ZshParamFlag::Directory => {
7248 if let Some(home) = dirs::home_dir() {
7250 let home_str = home.to_string_lossy();
7251 if val.starts_with(home_str.as_ref()) {
7252 format!("~{}", &val[home_str.len()..])
7253 } else {
7254 val.to_string()
7255 }
7256 } else {
7257 val.to_string()
7258 }
7259 }
7260 ZshParamFlag::PadLeft(len, fill) => {
7261 if val.len() >= *len {
7262 val.to_string()
7263 } else {
7264 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
7265 format!("{}{}", padding, val)
7266 }
7267 }
7268 ZshParamFlag::PadRight(len, fill) => {
7269 if val.len() >= *len {
7270 val.to_string()
7271 } else {
7272 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
7273 format!("{}{}", val, padding)
7274 }
7275 }
7276 ZshParamFlag::Width(_) => {
7277 val.to_string()
7279 }
7280 ZshParamFlag::Match => {
7281 val.to_string()
7284 }
7285 ZshParamFlag::Remove => {
7286 val.to_string()
7288 }
7289 ZshParamFlag::Subscript => {
7290 val.to_string()
7292 }
7293 ZshParamFlag::Parameter => {
7294 self.get_variable(val)
7296 }
7297 ZshParamFlag::Glob => {
7298 val.to_string()
7300 }
7301 }
7302 }
7303
7304 fn expand_prompt_string(&self, s: &str) -> String {
7306 let ctx = self.build_prompt_context();
7307 expand_prompt(s, &ctx)
7308 }
7309
7310 fn build_prompt_context(&self) -> PromptContext {
7312 let pwd = env::current_dir()
7313 .map(|p| p.to_string_lossy().to_string())
7314 .unwrap_or_else(|_| "/".to_string());
7315
7316 let home = env::var("HOME").unwrap_or_default();
7317
7318 let user = env::var("USER")
7319 .or_else(|_| env::var("LOGNAME"))
7320 .unwrap_or_else(|_| "user".to_string());
7321
7322 let host = hostname::get()
7323 .map(|h| h.to_string_lossy().to_string())
7324 .unwrap_or_else(|_| "localhost".to_string());
7325
7326 let host_short = host.split('.').next().unwrap_or(&host).to_string();
7327
7328 let shlvl = env::var("SHLVL")
7329 .ok()
7330 .and_then(|s| s.parse().ok())
7331 .unwrap_or(1);
7332
7333 PromptContext {
7334 pwd,
7335 home,
7336 user,
7337 host,
7338 host_short,
7339 tty: String::new(),
7340 lastval: self.last_status,
7341 histnum: self
7342 .history
7343 .as_ref()
7344 .and_then(|h| h.count().ok())
7345 .unwrap_or(1),
7346 shlvl,
7347 num_jobs: self.jobs.list().len() as i32,
7348 is_root: unsafe { libc::geteuid() } == 0,
7349 cmd_stack: Vec::new(),
7350 psvar: self.get_psvar(),
7351 term_width: self.get_term_width(),
7352 lineno: 1,
7353 }
7354 }
7355
7356 fn get_psvar(&self) -> Vec<String> {
7357 if let Some(arr) = self.arrays.get("psvar") {
7358 arr.clone()
7359 } else {
7360 Vec::new()
7361 }
7362 }
7363
7364 fn get_term_width(&self) -> usize {
7365 env::var("COLUMNS")
7366 .ok()
7367 .and_then(|s| s.parse().ok())
7368 .unwrap_or(80)
7369 }
7370
7371 fn execute_command_substitution(&mut self, cmd: &ShellCommand) -> String {
7373 match self.execute_command_capture(cmd) {
7374 Ok(output) => output.trim_end_matches('\n').to_string(),
7375 Err(_) => String::new(),
7376 }
7377 }
7378
7379 fn execute_command_capture(&mut self, cmd: &ShellCommand) -> Result<String, String> {
7381 if let ShellCommand::Simple(simple) = cmd {
7383 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7384 if words.is_empty() {
7385 return Ok(String::new());
7386 }
7387
7388 let cmd_name = &words[0];
7389 let args = &words[1..];
7390
7391 match cmd_name.as_str() {
7393 "echo" => {
7394 let output = args.join(" ");
7395 return Ok(format!("{}\n", output));
7396 }
7397 "printf" => {
7398 if !args.is_empty() {
7399 let format = &args[0];
7401 let result = if args.len() > 1 {
7402 let mut out = format.clone();
7404 for (i, arg) in args[1..].iter().enumerate() {
7405 out = out.replacen("%s", arg, 1);
7406 out = out.replacen(&format!("${}", i + 1), arg, 1);
7407 }
7408 out
7409 } else {
7410 format.clone()
7411 };
7412 return Ok(result);
7413 }
7414 return Ok(String::new());
7415 }
7416 "pwd" => {
7417 return Ok(env::current_dir()
7418 .map(|p| format!("{}\n", p.display()))
7419 .unwrap_or_default());
7420 }
7421 _ => {}
7422 }
7423
7424 let output = Command::new(cmd_name)
7426 .args(args)
7427 .stdout(Stdio::piped())
7428 .stderr(Stdio::inherit())
7429 .output();
7430
7431 match output {
7432 Ok(output) => {
7433 self.last_status = output.status.code().unwrap_or(1);
7434 Ok(String::from_utf8_lossy(&output.stdout).to_string())
7435 }
7436 Err(e) => {
7437 self.last_status = 127;
7438 Err(format!("{}: {}", cmd_name, e))
7439 }
7440 }
7441 } else if let ShellCommand::Pipeline(cmds, _negated) = cmd {
7442 if let Some(last) = cmds.last() {
7445 return self.execute_command_capture(last);
7446 }
7447 Ok(String::new())
7448 } else {
7449 let _ = self.execute_command(cmd);
7452 Ok(String::new())
7453 }
7454 }
7455
7456 fn evaluate_arithmetic(&mut self, expr: &str) -> String {
7458 let expr = self.expand_string(expr);
7459 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
7460 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
7461 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
7462
7463 let mut evaluator = MathEval::new(&expr)
7464 .with_string_variables(&self.variables)
7465 .with_force_float(force_float)
7466 .with_c_precedences(c_prec)
7467 .with_octal_zeroes(octal);
7468
7469 match evaluator.evaluate() {
7470 Ok(result) => {
7471 for (k, v) in evaluator.extract_string_variables() {
7472 self.variables.insert(k.clone(), v.clone());
7473 env::set_var(&k, &v);
7474 }
7475 match result {
7476 crate::math::MathNum::Integer(i) => i.to_string(),
7477 crate::math::MathNum::Float(f) => {
7478 if f.fract() == 0.0 && f.abs() < i64::MAX as f64 {
7479 (f as i64).to_string()
7480 } else {
7481 f.to_string()
7482 }
7483 }
7484 crate::math::MathNum::Unset => "0".to_string(),
7485 }
7486 }
7487 Err(_) => "0".to_string(),
7488 }
7489 }
7490
7491 fn eval_arith_expr(&mut self, expr: &str) -> i64 {
7492 let expr_expanded = self.expand_string(expr);
7493 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
7494 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
7495
7496 let mut evaluator = MathEval::new(&expr_expanded)
7497 .with_string_variables(&self.variables)
7498 .with_c_precedences(c_prec)
7499 .with_octal_zeroes(octal);
7500
7501 match evaluator.evaluate() {
7502 Ok(result) => {
7503 for (k, v) in evaluator.extract_string_variables() {
7504 self.variables.insert(k.clone(), v.clone());
7505 env::set_var(&k, &v);
7506 }
7507 result.to_int()
7508 }
7509 Err(_) => 0,
7510 }
7511 }
7512
7513 fn eval_arith_expr_float(&mut self, expr: &str) -> f64 {
7514 let expr_expanded = self.expand_string(expr);
7515 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
7516 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
7517 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
7518
7519 let mut evaluator = MathEval::new(&expr_expanded)
7520 .with_string_variables(&self.variables)
7521 .with_force_float(force_float)
7522 .with_c_precedences(c_prec)
7523 .with_octal_zeroes(octal);
7524
7525 match evaluator.evaluate() {
7526 Ok(result) => {
7527 for (k, v) in evaluator.extract_string_variables() {
7528 self.variables.insert(k.clone(), v.clone());
7529 env::set_var(&k, &v);
7530 }
7531 result.to_float()
7532 }
7533 Err(_) => 0.0,
7534 }
7535 }
7536
7537 fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
7538 if pattern == "*" {
7540 return true;
7541 }
7542 if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
7543 glob::Pattern::new(pattern)
7545 .map(|p| p.matches(value))
7546 .unwrap_or(false)
7547 } else {
7548 value == pattern
7549 }
7550 }
7551
7552 fn eval_cond_expr(&mut self, expr: &CondExpr) -> bool {
7553 match expr {
7554 CondExpr::FileExists(w) => std::path::Path::new(&self.expand_word(w)).exists(),
7555 CondExpr::FileRegular(w) => std::path::Path::new(&self.expand_word(w)).is_file(),
7556 CondExpr::FileDirectory(w) => std::path::Path::new(&self.expand_word(w)).is_dir(),
7557 CondExpr::FileSymlink(w) => std::path::Path::new(&self.expand_word(w)).is_symlink(),
7558 CondExpr::FileReadable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
7559 CondExpr::FileWritable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
7560 CondExpr::FileExecutable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
7561 CondExpr::FileNonEmpty(w) => std::fs::metadata(&self.expand_word(w))
7562 .map(|m| m.len() > 0)
7563 .unwrap_or(false),
7564 CondExpr::StringEmpty(w) => self.expand_word(w).is_empty(),
7565 CondExpr::StringNonEmpty(w) => !self.expand_word(w).is_empty(),
7566 CondExpr::StringEqual(a, b) => {
7567 let left = self.expand_word(a);
7568 let right = self.expand_word(b);
7569 if right.contains('*') || right.contains('?') || right.contains('[') {
7571 crate::glob::pattern_match(&right, &left, true, true)
7572 } else {
7573 left == right
7574 }
7575 }
7576 CondExpr::StringNotEqual(a, b) => {
7577 let left = self.expand_word(a);
7578 let right = self.expand_word(b);
7579 if right.contains('*') || right.contains('?') || right.contains('[') {
7580 !crate::glob::pattern_match(&right, &left, true, true)
7581 } else {
7582 left != right
7583 }
7584 }
7585 CondExpr::StringMatch(a, b) => {
7586 let val = self.expand_word(a);
7587 let pattern = self.expand_word(b);
7588 if let Some(re) = cached_regex(&pattern) {
7589 if let Some(caps) = re.captures(&val) {
7590 if let Some(m) = caps.get(0) {
7592 self.variables.insert("MATCH".to_string(), m.as_str().to_string());
7593 }
7594 let mut match_arr = Vec::new();
7596 for i in 1..caps.len() {
7597 if let Some(g) = caps.get(i) {
7598 match_arr.push(g.as_str().to_string());
7599 }
7600 }
7601 if !match_arr.is_empty() {
7602 self.arrays.insert("match".to_string(), match_arr);
7603 }
7604 true
7605 } else {
7606 self.variables.remove("MATCH");
7607 self.arrays.remove("match");
7608 false
7609 }
7610 } else {
7611 false
7612 }
7613 }
7614 CondExpr::StringLess(a, b) => self.expand_word(a) < self.expand_word(b),
7615 CondExpr::StringGreater(a, b) => self.expand_word(a) > self.expand_word(b),
7616 CondExpr::NumEqual(a, b) => {
7617 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7618 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7619 a_val == b_val
7620 }
7621 CondExpr::NumNotEqual(a, b) => {
7622 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7623 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7624 a_val != b_val
7625 }
7626 CondExpr::NumLess(a, b) => {
7627 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7628 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7629 a_val < b_val
7630 }
7631 CondExpr::NumLessEqual(a, b) => {
7632 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7633 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7634 a_val <= b_val
7635 }
7636 CondExpr::NumGreater(a, b) => {
7637 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7638 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7639 a_val > b_val
7640 }
7641 CondExpr::NumGreaterEqual(a, b) => {
7642 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
7643 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
7644 a_val >= b_val
7645 }
7646 CondExpr::Not(inner) => !self.eval_cond_expr(inner),
7647 CondExpr::And(a, b) => self.eval_cond_expr(a) && self.eval_cond_expr(b),
7648 CondExpr::Or(a, b) => self.eval_cond_expr(a) || self.eval_cond_expr(b),
7649 }
7650 }
7651
7652 fn builtin_cd(&mut self, args: &[String]) -> i32 {
7659 let mut quiet = false;
7663 let mut use_cdpath = false;
7664 let mut logical = true; let mut positional_args: Vec<&str> = Vec::new();
7666
7667 for arg in args {
7668 if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
7669 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
7671 positional_args.push(arg);
7672 continue;
7673 }
7674 for ch in arg[1..].chars() {
7675 match ch {
7676 'q' => quiet = true,
7677 's' => use_cdpath = true,
7678 'L' => logical = true,
7679 'P' => logical = false,
7680 _ => {
7681 eprintln!("cd: bad option: -{}", ch);
7682 return 1;
7683 }
7684 }
7685 }
7686 } else if arg.starts_with('+')
7687 && arg.len() > 1
7688 && arg[1..].chars().all(|c| c.is_ascii_digit())
7689 {
7690 positional_args.push(arg);
7692 } else {
7693 positional_args.push(arg);
7694 }
7695 }
7696
7697 if positional_args.len() == 2 {
7699 if let Ok(cwd) = env::current_dir() {
7700 let cwd_str = cwd.to_string_lossy();
7701 let old = positional_args[0];
7702 let new = positional_args[1];
7703 if cwd_str.contains(old) {
7704 let new_path = cwd_str.replace(old, new);
7705 if !quiet {
7706 println!("{}", new_path);
7707 }
7708 positional_args = vec![];
7709 return self.do_cd(&new_path, quiet, use_cdpath, logical);
7710 }
7711 }
7712 }
7713
7714 let path_arg = positional_args.first().map(|s| *s).unwrap_or("~");
7715
7716 if path_arg.starts_with('+') || path_arg.starts_with('-') {
7718 if let Ok(n) = path_arg[1..].parse::<usize>() {
7719 let idx = if path_arg.starts_with('+') {
7720 n
7721 } else {
7722 self.dir_stack.len().saturating_sub(n)
7723 };
7724 if let Some(dir) = self.dir_stack.get(idx) {
7725 let dir_path = dir.to_string_lossy().to_string();
7726 return self.do_cd(&dir_path, quiet, use_cdpath, logical);
7727 } else {
7728 eprintln!("cd: no such entry in dir stack");
7729 return 1;
7730 }
7731 }
7732 }
7733
7734 self.do_cd(path_arg, quiet, use_cdpath, logical)
7735 }
7736
7737 fn do_cd(&mut self, path_arg: &str, quiet: bool, use_cdpath: bool, physical: bool) -> i32 {
7738 let path = if path_arg == "~" || path_arg.is_empty() {
7739 dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
7740 } else if path_arg.starts_with("~/") {
7741 dirs::home_dir()
7742 .unwrap_or_else(|| PathBuf::from("."))
7743 .join(&path_arg[2..])
7744 } else if path_arg == "-" {
7745 if let Ok(oldpwd) = env::var("OLDPWD") {
7746 if !quiet {
7747 println!("{}", oldpwd);
7748 }
7749 PathBuf::from(oldpwd)
7750 } else {
7751 eprintln!("cd: OLDPWD not set");
7752 return 1;
7753 }
7754 } else if use_cdpath && !path_arg.starts_with('/') && !path_arg.starts_with('.') {
7755 let cdpath = env::var("CDPATH").unwrap_or_default();
7757 let mut found = None;
7758 for dir in cdpath.split(':') {
7759 let candidate = if dir.is_empty() {
7760 PathBuf::from(path_arg)
7761 } else {
7762 PathBuf::from(dir).join(path_arg)
7763 };
7764 if candidate.is_dir() {
7765 found = Some(candidate);
7766 break;
7767 }
7768 }
7769 found.unwrap_or_else(|| PathBuf::from(path_arg))
7770 } else {
7771 PathBuf::from(path_arg)
7772 };
7773
7774 if let Ok(cwd) = env::current_dir() {
7775 env::set_var("OLDPWD", &cwd);
7776 }
7777
7778 let target = if !physical {
7780 if let Ok(resolved) = path.canonicalize() {
7781 resolved
7782 } else {
7783 path.clone()
7784 }
7785 } else {
7786 path.clone()
7787 };
7788
7789 match env::set_current_dir(&target) {
7790 Ok(_) => {
7791 if let Ok(cwd) = env::current_dir() {
7792 env::set_var("PWD", &cwd);
7793 self.variables
7794 .insert("PWD".to_string(), cwd.to_string_lossy().to_string());
7795 }
7796 0
7797 }
7798 Err(e) => {
7799 eprintln!("cd: {}: {}", path.display(), e);
7800 1
7801 }
7802 }
7803 }
7804
7805 fn builtin_pwd(&mut self, _redirects: &[Redirect]) -> i32 {
7806 match env::current_dir() {
7807 Ok(path) => {
7808 println!("{}", path.display());
7809 0
7810 }
7811 Err(e) => {
7812 eprintln!("pwd: {}", e);
7813 1
7814 }
7815 }
7816 }
7817
7818 fn builtin_echo(&mut self, args: &[String], _redirects: &[Redirect]) -> i32 {
7819 let mut newline = true;
7820 let mut interpret_escapes = false;
7821 let mut start = 0;
7822
7823 for (i, arg) in args.iter().enumerate() {
7824 match arg.as_str() {
7825 "-n" => {
7826 newline = false;
7827 start = i + 1;
7828 }
7829 "-e" => {
7830 interpret_escapes = true;
7831 start = i + 1;
7832 }
7833 "-E" => {
7834 interpret_escapes = false;
7835 start = i + 1;
7836 }
7837 _ => break,
7838 }
7839 }
7840
7841 let output = args[start..].join(" ");
7842 if interpret_escapes {
7843 print!("{}", output.replace("\\n", "\n").replace("\\t", "\t"));
7844 } else {
7845 print!("{}", output);
7846 }
7847
7848 if newline {
7849 println!();
7850 }
7851 0
7852 }
7853
7854 fn builtin_export(&mut self, args: &[String]) -> i32 {
7855 for arg in args {
7856 if let Some((key, value)) = arg.split_once('=') {
7857 self.variables.insert(key.to_string(), value.to_string());
7858 env::set_var(key, value);
7859 } else {
7860 let val = self.get_variable(arg);
7862 env::set_var(arg, &val);
7863 }
7864 }
7865 0
7866 }
7867
7868 fn builtin_unset(&mut self, args: &[String]) -> i32 {
7869 for arg in args {
7870 env::remove_var(arg);
7871 self.variables.remove(arg);
7872 }
7873 0
7874 }
7875
7876 fn builtin_source(&mut self, args: &[String]) -> i32 {
7877 if args.is_empty() {
7878 eprintln!("source: filename argument required");
7879 return 1;
7880 }
7881
7882 let path = &args[0];
7883
7884 let abs_path = if path.starts_with('/') {
7886 path.clone()
7887 } else if path.starts_with("~/") {
7888 if let Some(home) = dirs::home_dir() {
7889 home.join(&path[2..]).to_string_lossy().to_string()
7890 } else {
7891 path.clone()
7892 }
7893 } else {
7894 std::env::current_dir()
7895 .map(|cwd| cwd.join(path).to_string_lossy().to_string())
7896 .unwrap_or_else(|_| path.clone())
7897 };
7898
7899 let saved_zero = self.variables.get("0").cloned();
7901 self.variables.insert("0".to_string(), abs_path.clone());
7902
7903 let result;
7904
7905 if self.posix_mode {
7906 result = match std::fs::read_to_string(&abs_path) {
7908 Ok(content) => match self.execute_script(&content) {
7909 Ok(status) => status,
7910 Err(e) => { eprintln!("source: {}: {}", path, e); 1 }
7911 },
7912 Err(e) => { eprintln!("source: {}: {}", path, e); 1 }
7913 };
7914 } else {
7915 let file_path = std::path::Path::new(&abs_path);
7917
7918 if let Some(ref cache) = self.plugin_cache {
7920 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
7921 if let Some(plugin_id) = cache.check(&abs_path, mt_s, mt_ns) {
7922 if let Ok(delta) = cache.load(plugin_id) {
7923 let t0 = std::time::Instant::now();
7924 self.replay_plugin_delta(&delta);
7925 tracing::info!(
7926 path = %abs_path,
7927 replay_us = t0.elapsed().as_micros() as u64,
7928 funcs = delta.functions.len(),
7929 aliases = delta.aliases.len(),
7930 vars = delta.variables.len() + delta.exports.len(),
7931 "source: cache hit, replayed"
7932 );
7933 if let Some(z) = saved_zero { self.variables.insert("0".to_string(), z); }
7935 else { self.variables.remove("0"); }
7936 return 0;
7937 }
7938 }
7939 }
7940 }
7941
7942 let snapshot = self.snapshot_state();
7944 let t0 = std::time::Instant::now();
7945 tracing::debug!(path = %abs_path, "source: cache miss, executing via AST-cached path");
7946 result = match self.execute_script_file(&abs_path) {
7947 Ok(status) => status,
7948 Err(e) => {
7949 tracing::warn!(path = %abs_path, error = %e, "source: execution failed");
7950 eprintln!("source: {}: {}", path, e);
7951 1
7952 }
7953 };
7954 let source_ms = t0.elapsed().as_millis() as u64;
7955
7956 if result == 0 {
7958 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
7959 let delta = self.diff_state(&snapshot);
7960 let store_path = abs_path.clone();
7961 tracing::info!(
7962 path = %abs_path, source_ms,
7963 funcs = delta.functions.len(),
7964 aliases = delta.aliases.len(),
7965 vars = delta.variables.len() + delta.exports.len(),
7966 "source: caching delta on worker"
7967 );
7968 let cache_db_path = crate::plugin_cache::default_cache_path();
7969 self.worker_pool.submit(move || {
7970 match crate::plugin_cache::PluginCache::open(&cache_db_path) {
7971 Ok(cache) => {
7972 if let Err(e) = cache.store(&store_path, mt_s, mt_ns, source_ms, &delta) {
7973 tracing::error!(path = %store_path, error = %e, "plugin_cache: store failed");
7974 } else {
7975 tracing::debug!(path = %store_path, "plugin_cache: stored");
7976 }
7977 }
7978 Err(e) => tracing::error!(error = %e, "plugin_cache: open for write failed"),
7979 }
7980 });
7981 }
7982 }
7983 }
7984
7985 let final_result = if let Some(ret) = self.returning.take() {
7987 ret
7988 } else {
7989 result
7990 };
7991
7992 if let Some(z) = saved_zero {
7994 self.variables.insert("0".to_string(), z);
7995 } else {
7996 self.variables.remove("0");
7997 }
7998
7999 final_result
8000 }
8001
8002 fn snapshot_state(&self) -> PluginSnapshot {
8004 PluginSnapshot {
8005 functions: self.functions.keys().cloned().collect(),
8006 aliases: self.aliases.keys().cloned().collect(),
8007 global_aliases: self.global_aliases.keys().cloned().collect(),
8008 suffix_aliases: self.suffix_aliases.keys().cloned().collect(),
8009 variables: self.variables.clone(),
8010 arrays: self.arrays.keys().cloned().collect(),
8011 assoc_arrays: self.assoc_arrays.keys().cloned().collect(),
8012 fpath: self.fpath.clone(),
8013 options: self.options.clone(),
8014 hooks: self.hook_functions.clone(),
8015 autoloads: self.autoload_pending.keys().cloned().collect(),
8016 }
8017 }
8018
8019 fn diff_state(&self, snap: &PluginSnapshot) -> crate::plugin_cache::PluginDelta {
8021 use crate::plugin_cache::{AliasKind, PluginDelta};
8022 let mut delta = PluginDelta::default();
8023
8024 for (name, body) in &self.functions {
8026 if !snap.functions.contains(name) {
8027 if let Ok(bytes) = bincode::serialize(body) {
8028 delta.functions.push((name.clone(), bytes));
8029 }
8030 }
8031 }
8032
8033 for (name, value) in &self.aliases {
8035 if !snap.aliases.contains(name) {
8036 delta.aliases.push((name.clone(), value.clone(), AliasKind::Regular));
8037 }
8038 }
8039 for (name, value) in &self.global_aliases {
8040 if !snap.global_aliases.contains(name) {
8041 delta.aliases.push((name.clone(), value.clone(), AliasKind::Global));
8042 }
8043 }
8044 for (name, value) in &self.suffix_aliases {
8045 if !snap.suffix_aliases.contains(name) {
8046 delta.aliases.push((name.clone(), value.clone(), AliasKind::Suffix));
8047 }
8048 }
8049
8050 for (name, value) in &self.variables {
8052 if name == "0" { continue; } match snap.variables.get(name) {
8054 Some(old) if old == value => {} _ => {
8056 if env::var(name).ok().as_ref() == Some(value) {
8058 delta.exports.push((name.clone(), value.clone()));
8059 } else {
8060 delta.variables.push((name.clone(), value.clone()));
8061 }
8062 }
8063 }
8064 }
8065
8066 for (name, values) in &self.arrays {
8068 if !snap.arrays.contains(name) {
8069 delta.arrays.push((name.clone(), values.clone()));
8070 }
8071 }
8072
8073 for p in &self.fpath {
8075 if !snap.fpath.contains(p) {
8076 delta.fpath_additions.push(p.to_string_lossy().to_string());
8077 }
8078 }
8079
8080 for (name, value) in &self.options {
8082 match snap.options.get(name) {
8083 Some(old) if old == value => {}
8084 _ => delta.options_changed.push((name.clone(), *value)),
8085 }
8086 }
8087
8088 for (hook, funcs) in &self.hook_functions {
8090 let old_funcs = snap.hooks.get(hook);
8091 for f in funcs {
8092 let is_new = old_funcs.map_or(true, |old| !old.contains(f));
8093 if is_new {
8094 delta.hooks.push((hook.clone(), f.clone()));
8095 }
8096 }
8097 }
8098
8099 for (name, flags) in &self.autoload_pending {
8101 if !snap.autoloads.contains(name) {
8102 delta.autoloads.push((name.clone(), format!("{:?}", flags)));
8103 }
8104 }
8105
8106 delta
8107 }
8108
8109 fn replay_plugin_delta(&mut self, delta: &crate::plugin_cache::PluginDelta) {
8111 use crate::plugin_cache::AliasKind;
8112
8113 for (name, value, kind) in &delta.aliases {
8115 match kind {
8116 AliasKind::Regular => { self.aliases.insert(name.clone(), value.clone()); }
8117 AliasKind::Global => { self.global_aliases.insert(name.clone(), value.clone()); }
8118 AliasKind::Suffix => { self.suffix_aliases.insert(name.clone(), value.clone()); }
8119 }
8120 }
8121
8122 for (name, value) in &delta.variables {
8124 self.variables.insert(name.clone(), value.clone());
8125 }
8126
8127 for (name, value) in &delta.exports {
8129 self.variables.insert(name.clone(), value.clone());
8130 env::set_var(name, value);
8131 }
8132
8133 for (name, values) in &delta.arrays {
8135 self.arrays.insert(name.clone(), values.clone());
8136 }
8137
8138 for p in &delta.fpath_additions {
8140 let pb = PathBuf::from(p);
8141 if !self.fpath.contains(&pb) {
8142 self.fpath.push(pb);
8143 }
8144 }
8145
8146 for (cmd, func) in &delta.completions {
8148 if let Some(ref mut comps) = self.assoc_arrays.get_mut("_comps") {
8149 comps.insert(cmd.clone(), func.clone());
8150 }
8151 }
8152
8153 for (name, enabled) in &delta.options_changed {
8155 self.options.insert(name.clone(), *enabled);
8156 }
8157
8158 for (hook, func) in &delta.hooks {
8160 self.hook_functions
8161 .entry(hook.clone())
8162 .or_insert_with(Vec::new)
8163 .push(func.clone());
8164 }
8165
8166 for (name, bytes) in &delta.functions {
8168 if let Ok(ast) = bincode::deserialize::<crate::parser::ShellCommand>(bytes) {
8169 self.functions.insert(name.clone(), ast);
8170 }
8171 }
8172 }
8173
8174 fn builtin_exit(&mut self, args: &[String]) -> i32 {
8175 let code = args
8176 .first()
8177 .and_then(|s| s.parse::<i32>().ok())
8178 .unwrap_or(self.last_status);
8179 std::process::exit(code);
8180 }
8181
8182 fn builtin_return(&mut self, args: &[String]) -> i32 {
8183 let status = args
8184 .first()
8185 .and_then(|s| s.parse::<i32>().ok())
8186 .unwrap_or(self.last_status);
8187 self.returning = Some(status);
8188 status
8189 }
8190
8191 fn builtin_test(&mut self, args: &[String]) -> i32 {
8192 if args.is_empty() {
8193 return 1;
8194 }
8195
8196 let args: Vec<&str> = args
8198 .iter()
8199 .map(|s| s.as_str())
8200 .filter(|&s| s != "]")
8201 .collect();
8202
8203 let mut meta_cache: HashMap<String, Option<std::fs::Metadata>> = HashMap::new();
8206 for arg in &args {
8207 if !arg.starts_with('-') && !arg.starts_with('!') && *arg != "(" && *arg != ")" {
8208 let path_str = arg.to_string();
8209 if !meta_cache.contains_key(&path_str) {
8210 meta_cache.insert(path_str, std::fs::metadata(arg).ok());
8211 }
8212 }
8213 }
8214
8215 let get_meta = |path: &str| -> Option<std::fs::Metadata> {
8217 meta_cache.get(path).cloned().unwrap_or_else(|| std::fs::metadata(path).ok())
8218 };
8219
8220 match args.as_slice() {
8221 ["-z", s] => {
8223 if s.is_empty() {
8224 0
8225 } else {
8226 1
8227 }
8228 }
8229 ["-n", s] => {
8230 if !s.is_empty() {
8231 0
8232 } else {
8233 1
8234 }
8235 }
8236
8237 ["-a", path] | ["-e", path] => {
8239 if std::path::Path::new(path).exists() {
8240 0
8241 } else {
8242 1
8243 }
8244 }
8245 ["-f", path] => {
8246 if std::path::Path::new(path).is_file() {
8247 0
8248 } else {
8249 1
8250 }
8251 }
8252 ["-d", path] => {
8253 if std::path::Path::new(path).is_dir() {
8254 0
8255 } else {
8256 1
8257 }
8258 }
8259 ["-b", path] => {
8260 use std::os::unix::fs::FileTypeExt;
8261 if std::fs::symlink_metadata(path)
8262 .map(|m| m.file_type().is_block_device())
8263 .unwrap_or(false)
8264 {
8265 0
8266 } else {
8267 1
8268 }
8269 }
8270 ["-c", path] => {
8271 use std::os::unix::fs::FileTypeExt;
8272 if std::fs::symlink_metadata(path)
8273 .map(|m| m.file_type().is_char_device())
8274 .unwrap_or(false)
8275 {
8276 0
8277 } else {
8278 1
8279 }
8280 }
8281 ["-p", path] => {
8282 use std::os::unix::fs::FileTypeExt;
8283 if std::fs::symlink_metadata(path)
8284 .map(|m| m.file_type().is_fifo())
8285 .unwrap_or(false)
8286 {
8287 0
8288 } else {
8289 1
8290 }
8291 }
8292 ["-S", path] => {
8293 use std::os::unix::fs::FileTypeExt;
8294 if std::fs::symlink_metadata(path)
8295 .map(|m| m.file_type().is_socket())
8296 .unwrap_or(false)
8297 {
8298 0
8299 } else {
8300 1
8301 }
8302 }
8303 ["-h", path] | ["-L", path] => {
8304 if std::path::Path::new(path).is_symlink() {
8305 0
8306 } else {
8307 1
8308 }
8309 }
8310
8311 ["-r", path] => {
8313 use std::os::unix::fs::MetadataExt;
8314 if let Some(meta) = get_meta(path) {
8315 let mode = meta.mode();
8316 let uid = unsafe { libc::geteuid() };
8317 let gid = unsafe { libc::getegid() };
8318 let readable = if meta.uid() == uid {
8319 mode & 0o400 != 0
8320 } else if meta.gid() == gid {
8321 mode & 0o040 != 0
8322 } else {
8323 mode & 0o004 != 0
8324 };
8325 if readable { 0 } else { 1 }
8326 } else {
8327 1
8328 }
8329 }
8330 ["-w", path] => {
8331 use std::os::unix::fs::MetadataExt;
8332 if let Some(meta) = get_meta(path) {
8333 let mode = meta.mode();
8334 let uid = unsafe { libc::geteuid() };
8335 let gid = unsafe { libc::getegid() };
8336 let writable = if meta.uid() == uid {
8337 mode & 0o200 != 0
8338 } else if meta.gid() == gid {
8339 mode & 0o020 != 0
8340 } else {
8341 mode & 0o002 != 0
8342 };
8343 if writable { 0 } else { 1 }
8344 } else {
8345 1
8346 }
8347 }
8348 ["-x", path] => {
8349 use std::os::unix::fs::MetadataExt;
8350 if let Some(meta) = get_meta(path) {
8351 let mode = meta.mode();
8352 let uid = unsafe { libc::geteuid() };
8353 let gid = unsafe { libc::getegid() };
8354 let executable = if meta.uid() == uid {
8355 mode & 0o100 != 0
8356 } else if meta.gid() == gid {
8357 mode & 0o010 != 0
8358 } else {
8359 mode & 0o001 != 0
8360 };
8361 if executable { 0 } else { 1 }
8362 } else {
8363 1
8364 }
8365 }
8366
8367 ["-g", path] => {
8369 use std::os::unix::fs::MetadataExt;
8370 if get_meta(path).map(|m| m.mode() & 0o2000 != 0).unwrap_or(false) { 0 } else { 1 }
8371 }
8372 ["-k", path] => {
8373 use std::os::unix::fs::MetadataExt;
8374 if get_meta(path).map(|m| m.mode() & 0o1000 != 0).unwrap_or(false) { 0 } else { 1 }
8375 }
8376 ["-u", path] => {
8377 use std::os::unix::fs::MetadataExt;
8378 if get_meta(path)
8379 .map(|m| m.mode() & 0o4000 != 0)
8380 .unwrap_or(false)
8381 {
8382 0
8383 } else {
8384 1
8385 }
8386 }
8387
8388 ["-s", path] => {
8390 if get_meta(path).map(|m| m.len() > 0).unwrap_or(false) { 0 } else { 1 }
8391 }
8392
8393 ["-O", path] => {
8395 use std::os::unix::fs::MetadataExt;
8396 if get_meta(path).map(|m| m.uid() == unsafe { libc::geteuid() }).unwrap_or(false) { 0 } else { 1 }
8397 }
8398 ["-G", path] => {
8399 use std::os::unix::fs::MetadataExt;
8400 if get_meta(path).map(|m| m.gid() == unsafe { libc::getegid() }).unwrap_or(false) { 0 } else { 1 }
8401 }
8402
8403 ["-N", path] => {
8405 use std::os::unix::fs::MetadataExt;
8406 if let Some(meta) = get_meta(path) {
8407 if meta.mtime() > meta.atime() {
8408 0
8409 } else {
8410 1
8411 }
8412 } else {
8413 1
8414 }
8415 }
8416
8417 ["-t", fd] => {
8419 if let Ok(fd_num) = fd.parse::<i32>() {
8420 if unsafe { libc::isatty(fd_num) } == 1 {
8421 0
8422 } else {
8423 1
8424 }
8425 } else {
8426 1
8427 }
8428 }
8429
8430 ["-v", varname] => {
8432 if self.variables.contains_key(*varname) || std::env::var(varname).is_ok() {
8433 0
8434 } else {
8435 1
8436 }
8437 }
8438
8439 ["-o", opt] => {
8441 let (name, _) = Self::normalize_option_name(opt);
8442 if self.options.get(&name).copied().unwrap_or(false) {
8443 0
8444 } else {
8445 1
8446 }
8447 }
8448
8449 [a, "=", b] | [a, "==", b] => {
8451 if a == b {
8452 0
8453 } else {
8454 1
8455 }
8456 }
8457 [a, "!=", b] => {
8458 if a != b {
8459 0
8460 } else {
8461 1
8462 }
8463 }
8464 [a, "<", b] => {
8465 if *a < *b {
8466 0
8467 } else {
8468 1
8469 }
8470 }
8471 [a, ">", b] => {
8472 if *a > *b {
8473 0
8474 } else {
8475 1
8476 }
8477 }
8478
8479 [a, "-eq", b] => {
8481 let a: i64 = a.parse().unwrap_or(0);
8482 let b: i64 = b.parse().unwrap_or(0);
8483 if a == b {
8484 0
8485 } else {
8486 1
8487 }
8488 }
8489 [a, "-ne", b] => {
8490 let a: i64 = a.parse().unwrap_or(0);
8491 let b: i64 = b.parse().unwrap_or(0);
8492 if a != b {
8493 0
8494 } else {
8495 1
8496 }
8497 }
8498 [a, "-lt", b] => {
8499 let a: i64 = a.parse().unwrap_or(0);
8500 let b: i64 = b.parse().unwrap_or(0);
8501 if a < b {
8502 0
8503 } else {
8504 1
8505 }
8506 }
8507 [a, "-le", b] => {
8508 let a: i64 = a.parse().unwrap_or(0);
8509 let b: i64 = b.parse().unwrap_or(0);
8510 if a <= b {
8511 0
8512 } else {
8513 1
8514 }
8515 }
8516 [a, "-gt", b] => {
8517 let a: i64 = a.parse().unwrap_or(0);
8518 let b: i64 = b.parse().unwrap_or(0);
8519 if a > b {
8520 0
8521 } else {
8522 1
8523 }
8524 }
8525 [a, "-ge", b] => {
8526 let a: i64 = a.parse().unwrap_or(0);
8527 let b: i64 = b.parse().unwrap_or(0);
8528 if a >= b {
8529 0
8530 } else {
8531 1
8532 }
8533 }
8534
8535 [f1, "-nt", f2] => {
8537 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
8538 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
8539 match (m1, m2) {
8540 (Some(t1), Some(t2)) => {
8541 if t1 > t2 {
8542 0
8543 } else {
8544 1
8545 }
8546 }
8547 (Some(_), None) => 0,
8548 _ => 1,
8549 }
8550 }
8551 [f1, "-ot", f2] => {
8552 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
8553 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
8554 match (m1, m2) {
8555 (Some(t1), Some(t2)) => {
8556 if t1 < t2 {
8557 0
8558 } else {
8559 1
8560 }
8561 }
8562 (None, Some(_)) => 0,
8563 _ => 1,
8564 }
8565 }
8566 [f1, "-ef", f2] => {
8567 use std::os::unix::fs::MetadataExt;
8568 let m1 = std::fs::metadata(f1).ok();
8569 let m2 = std::fs::metadata(f2).ok();
8570 match (m1, m2) {
8571 (Some(a), Some(b)) => {
8572 if a.dev() == b.dev() && a.ino() == b.ino() {
8573 0
8574 } else {
8575 1
8576 }
8577 }
8578 _ => 1,
8579 }
8580 }
8581
8582 [s] => {
8584 if !s.is_empty() {
8585 0
8586 } else {
8587 1
8588 }
8589 }
8590
8591 _ => 1,
8592 }
8593 }
8594
8595 fn builtin_local(&mut self, args: &[String]) -> i32 {
8596 self.builtin_typeset(args)
8597 }
8598
8599 fn builtin_declare(&mut self, args: &[String]) -> i32 {
8600 self.builtin_typeset(args)
8601 }
8602
8603 fn builtin_typeset(&mut self, args: &[String]) -> i32 {
8604 if self.local_scope_depth > 0 {
8607 for arg in args {
8608 if arg.starts_with('-') || arg.starts_with('+') {
8609 continue;
8610 }
8611 let name = arg.split('=').next().unwrap_or(arg);
8612 if !name.is_empty() {
8613 let old_val = self.variables.get(name).cloned();
8614 self.local_save_stack.push((name.to_string(), old_val));
8615 }
8616 }
8617 }
8618
8619 let mut is_array = false; let mut is_assoc = false; let mut is_export = false; let mut is_integer = false; let mut is_readonly = false; let mut is_lower = false; let mut is_upper = false; let mut is_left_pad = false; let mut is_right_pad = false; let mut is_zero_pad = false; let mut is_float = false; let mut is_float_exp = false; let mut is_function = false; let mut is_global = false; let mut is_tied = false; let mut is_hidden = false; let mut is_hide_val = false; let mut is_trace = false; let mut print_mode = false; let mut pattern_match = false; let mut list_mode = false; let mut plus_mode = false; let mut width: Option<usize> = None;
8647 let mut precision: Option<usize> = None;
8648 let mut var_args: Vec<String> = Vec::new();
8649
8650 let mut i = 0;
8651 while i < args.len() {
8652 let arg = &args[i];
8653
8654 if arg == "--" {
8655 i += 1;
8656 while i < args.len() {
8657 var_args.push(args[i].clone());
8658 i += 1;
8659 }
8660 break;
8661 }
8662
8663 if arg == "+" {
8664 plus_mode = true;
8665 i += 1;
8666 continue;
8667 }
8668
8669 if arg.starts_with('+') && arg.len() > 1 {
8670 plus_mode = true;
8671 for c in arg[1..].chars() {
8672 match c {
8673 'a' => is_array = false,
8674 'A' => is_assoc = false,
8675 'x' => is_export = false,
8676 'i' => is_integer = false,
8677 'r' => is_readonly = false,
8678 'l' => is_lower = false,
8679 'u' => is_upper = false,
8680 'L' => is_left_pad = false,
8681 'R' => is_right_pad = false,
8682 'Z' => is_zero_pad = false,
8683 'F' => is_float = false,
8684 'E' => is_float_exp = false,
8685 'f' => is_function = false,
8686 'g' => is_global = false,
8687 'T' => is_tied = false,
8688 'H' => is_hidden = false,
8689 'h' => is_hide_val = false,
8690 't' => is_trace = false,
8691 'p' => print_mode = false,
8692 'm' => pattern_match = false,
8693 _ => {}
8694 }
8695 }
8696 } else if arg.starts_with('-') && arg.len() > 1 {
8697 let mut chars = arg[1..].chars().peekable();
8698 while let Some(c) = chars.next() {
8699 match c {
8700 'a' => is_array = true,
8701 'A' => is_assoc = true,
8702 'x' => is_export = true,
8703 'i' => is_integer = true,
8704 'r' => is_readonly = true,
8705 'l' => is_lower = true,
8706 'u' => is_upper = true,
8707 'L' => {
8708 is_left_pad = true;
8709 let rest: String = chars.clone().collect();
8711 if !rest.is_empty()
8712 && rest
8713 .chars()
8714 .next()
8715 .map(|c| c.is_ascii_digit())
8716 .unwrap_or(false)
8717 {
8718 let num: String =
8719 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
8720 width = num.parse().ok();
8721 }
8722 }
8723 'R' => {
8724 is_right_pad = true;
8725 let rest: String = chars.clone().collect();
8726 if !rest.is_empty()
8727 && rest
8728 .chars()
8729 .next()
8730 .map(|c| c.is_ascii_digit())
8731 .unwrap_or(false)
8732 {
8733 let num: String =
8734 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
8735 width = num.parse().ok();
8736 }
8737 }
8738 'Z' => {
8739 is_zero_pad = true;
8740 let rest: String = chars.clone().collect();
8741 if !rest.is_empty()
8742 && rest
8743 .chars()
8744 .next()
8745 .map(|c| c.is_ascii_digit())
8746 .unwrap_or(false)
8747 {
8748 let num: String =
8749 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
8750 width = num.parse().ok();
8751 }
8752 }
8753 'F' => {
8754 is_float = true;
8755 let rest: String = chars.clone().collect();
8756 if !rest.is_empty()
8757 && rest
8758 .chars()
8759 .next()
8760 .map(|c| c.is_ascii_digit())
8761 .unwrap_or(false)
8762 {
8763 let num: String =
8764 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
8765 precision = num.parse().ok();
8766 }
8767 }
8768 'E' => {
8769 is_float_exp = true;
8770 let rest: String = chars.clone().collect();
8771 if !rest.is_empty()
8772 && rest
8773 .chars()
8774 .next()
8775 .map(|c| c.is_ascii_digit())
8776 .unwrap_or(false)
8777 {
8778 let num: String =
8779 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
8780 precision = num.parse().ok();
8781 }
8782 }
8783 'f' => is_function = true,
8784 'g' => is_global = true,
8785 'T' => is_tied = true,
8786 'H' => is_hidden = true,
8787 'h' => is_hide_val = true,
8788 't' => is_trace = true,
8789 'p' => print_mode = true,
8790 'm' => pattern_match = true,
8791 _ => {}
8792 }
8793 }
8794 } else {
8795 var_args.push(arg.clone());
8796 }
8797 i += 1;
8798 }
8799
8800 let _ = is_global;
8801 let _ = is_tied;
8802 let _ = is_hidden;
8803 let _ = is_hide_val;
8804 let _ = is_trace;
8805 let _ = pattern_match;
8806 let _ = precision;
8807
8808 if is_function && var_args.is_empty() {
8810 let mut func_names: Vec<_> = self.functions.keys().cloned().collect();
8811 func_names.sort();
8812 for name in &func_names {
8813 if let Some(func) = self.functions.get(name) {
8814 if print_mode {
8815 let body = crate::text::getpermtext(func);
8816 println!("{} () {{\n\t{}\n}}", name, body.trim());
8817 } else {
8818 let body = crate::text::getpermtext(func);
8819 println!("{} () {{\n\t{}\n}}", name, body.trim());
8820 }
8821 }
8822 }
8823 return 0;
8824 }
8825
8826 if is_function {
8828 for name in &var_args {
8829 if let Some(func) = self.functions.get(name) {
8830 if print_mode {
8831 let body = crate::text::getpermtext(func);
8832 println!("{} () {{\n\t{}\n}}", name, body.trim());
8833 } else {
8834 let body = crate::text::getpermtext(func);
8835 println!("{} () {{\n\t{}\n}}", name, body.trim());
8836 }
8837 }
8838 }
8839 return 0;
8840 }
8841
8842 if var_args.is_empty() {
8844 list_mode = true;
8845 }
8846
8847 if list_mode {
8848 let mut sorted_names: Vec<_> = self.variables.keys().cloned().collect();
8849 sorted_names.sort();
8850 for name in &sorted_names {
8851 let val = self.variables.get(name).cloned().unwrap_or_default();
8852 let mut attrs = String::new();
8853 if is_export || env::var(name).is_ok() {
8854 attrs.push('x');
8855 }
8856 let is_arr = self.arrays.contains_key(name);
8857 let is_hash = self.assoc_arrays.contains_key(name);
8858 if is_arr {
8859 attrs.push('a');
8860 }
8861 if is_hash {
8862 attrs.push('A');
8863 }
8864 if print_mode {
8865 let prefix = if attrs.is_empty() {
8867 "typeset".to_string()
8868 } else {
8869 format!("typeset -{}", attrs)
8870 };
8871 if is_hash {
8872 if let Some(assoc) = self.assoc_arrays.get(name) {
8873 let mut pairs: Vec<_> = assoc.iter().collect();
8874 pairs.sort_by_key(|(k, _)| (*k).clone());
8875 let formatted: Vec<String> = pairs
8876 .iter()
8877 .map(|(k, v)| {
8878 format!("[{}]={}", shell_quote_value(k), shell_quote_value(v))
8879 })
8880 .collect();
8881 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
8882 }
8883 } else if is_arr {
8884 if let Some(arr) = self.arrays.get(name) {
8885 let formatted: Vec<String> =
8886 arr.iter().map(|v| shell_quote_value(v)).collect();
8887 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
8888 }
8889 } else {
8890 println!("{} {}={}", prefix, name, shell_quote_value(&val));
8891 }
8892 } else if is_hide_val {
8893 println!("{}={}", name, "*".repeat(val.len().min(8)));
8894 } else {
8895 println!("{}={}", name, val);
8896 }
8897 }
8898 return 0;
8899 }
8900
8901 for arg in var_args {
8903 if let Some(eq_pos) = arg.find('=') {
8905 let name = &arg[..eq_pos];
8906 let rest = &arg[eq_pos + 1..];
8907
8908 if rest.starts_with('(') {
8909 let mut elements = Vec::new();
8911 let current = rest[1..].to_string(); if let Some(close_pos) = current.find(')') {
8915 let content = ¤t[..close_pos];
8916 if !content.is_empty() {
8917 elements.extend(content.split_whitespace().map(|s| s.to_string()));
8918 }
8919 } else {
8920 if !current.is_empty() {
8922 let trimmed = current.trim_end_matches(')');
8923 elements.extend(trimmed.split_whitespace().map(|s| s.to_string()));
8924 }
8925 }
8926
8927 if is_assoc {
8929 let mut assoc = std::collections::HashMap::new();
8930 let mut iter = elements.iter();
8931 while let Some(key) = iter.next() {
8932 if let Some(val) = iter.next() {
8933 assoc.insert(key.clone(), val.clone());
8934 }
8935 }
8936 self.assoc_arrays.insert(name.to_string(), assoc);
8937 } else {
8938 self.arrays.insert(name.to_string(), elements);
8939 }
8940 self.variables.insert(name.to_string(), String::new());
8941 } else {
8942 let mut value = rest.to_string();
8944
8945 if is_integer {
8946 value = self.evaluate_arithmetic(&value).to_string();
8948 }
8949 if is_lower {
8950 value = value.to_lowercase();
8951 }
8952 if is_upper {
8953 value = value.to_uppercase();
8954 }
8955 if let Some(w) = width {
8956 if is_left_pad {
8957 value = format!("{:<width$}", value, width = w);
8958 value.truncate(w);
8959 } else if is_right_pad || is_zero_pad {
8960 let pad_char = if is_zero_pad { '0' } else { ' ' };
8961 if value.len() < w {
8962 value = format!(
8963 "{}{}",
8964 pad_char.to_string().repeat(w - value.len()),
8965 value
8966 );
8967 }
8968 if value.len() > w {
8969 value = value[value.len() - w..].to_string();
8970 }
8971 }
8972 }
8973 if is_float || is_float_exp {
8974 if let Ok(f) = value.parse::<f64>() {
8975 let prec = precision.unwrap_or(10);
8976 value = if is_float_exp {
8977 format!("{:.prec$e}", f, prec = prec)
8978 } else {
8979 format!("{:.prec$}", f, prec = prec)
8980 };
8981 }
8982 }
8983
8984 self.variables.insert(name.to_string(), value.clone());
8985
8986 if is_export {
8987 env::set_var(name, &value);
8988 }
8989 }
8990 } else if is_array || is_assoc {
8991 if is_assoc {
8993 self.assoc_arrays
8994 .insert(arg.clone(), std::collections::HashMap::new());
8995 } else {
8996 self.arrays.insert(arg.clone(), Vec::new());
8997 }
8998 self.variables.insert(arg.clone(), String::new());
8999 } else {
9000 self.variables.insert(arg.clone(), String::new());
9001 if is_export {
9002 env::set_var(&arg, "");
9003 }
9004 }
9005
9006 if is_readonly {
9008 let name = if let Some(eq_pos) = arg.find('=') {
9009 arg[..eq_pos].to_string()
9010 } else {
9011 arg.clone()
9012 };
9013 self.readonly_vars.insert(name);
9014 }
9015 }
9016 0
9017 }
9018
9019 fn builtin_read(&mut self, args: &[String]) -> i32 {
9020 use std::io::{BufRead, Read as IoRead};
9023
9024 let mut raw_mode = false; let mut silent = false; let mut to_history = false; let mut prompt_str: Option<String> = None; let mut use_array = false; let mut timeout: Option<u64> = None; let mut delimiter = '\n'; let mut nchars: Option<usize> = None; let mut fd = 0; let mut quiet = false; let mut var_names: Vec<String> = Vec::new();
9035
9036 let mut i = 0;
9037 while i < args.len() {
9038 let arg = &args[i];
9039
9040 if arg == "--" {
9041 i += 1;
9042 while i < args.len() {
9043 var_names.push(args[i].clone());
9044 i += 1;
9045 }
9046 break;
9047 }
9048
9049 if arg.starts_with('-') && arg.len() > 1 {
9050 let mut chars = arg[1..].chars().peekable();
9051 while let Some(ch) = chars.next() {
9052 match ch {
9053 'r' => raw_mode = true,
9054 's' => silent = true,
9055 'z' => to_history = true,
9056 'A' => use_array = true,
9057 'c' | 'l' | 'n' | 'e' | 'E' => {} 'q' => quiet = true,
9059 't' => {
9060 let rest: String = chars.collect();
9061 if !rest.is_empty() {
9062 timeout = rest.parse().ok();
9063 } else {
9064 i += 1;
9065 if i < args.len() {
9066 timeout = args[i].parse().ok();
9067 }
9068 }
9069 break;
9070 }
9071 'd' => {
9072 let rest: String = chars.collect();
9073 if !rest.is_empty() {
9074 delimiter = rest.chars().next().unwrap_or('\n');
9075 } else {
9076 i += 1;
9077 if i < args.len() {
9078 delimiter = args[i].chars().next().unwrap_or('\n');
9079 }
9080 }
9081 break;
9082 }
9083 'k' => {
9084 let rest: String = chars.collect();
9085 if !rest.is_empty() {
9086 nchars = Some(rest.parse().unwrap_or(1));
9087 } else if i + 1 < args.len()
9088 && args[i + 1].chars().all(|c| c.is_ascii_digit())
9089 {
9090 i += 1;
9091 nchars = Some(args[i].parse().unwrap_or(1));
9092 } else {
9093 nchars = Some(1);
9094 }
9095 break;
9096 }
9097 'u' => {
9098 let rest: String = chars.collect();
9099 if !rest.is_empty() {
9100 fd = rest.parse().unwrap_or(0);
9101 } else {
9102 i += 1;
9103 if i < args.len() {
9104 fd = args[i].parse().unwrap_or(0);
9105 }
9106 }
9107 break;
9108 }
9109 'p' => {
9110 let rest: String = chars.collect();
9111 if !rest.is_empty() {
9112 prompt_str = Some(rest);
9113 } else {
9114 i += 1;
9115 if i < args.len() {
9116 prompt_str = Some(args[i].clone());
9117 }
9118 }
9119 break;
9120 }
9121 _ => {}
9122 }
9123 }
9124 } else {
9125 if let Some(pos) = arg.find('?') {
9126 var_names.push(arg[..pos].to_string());
9127 prompt_str = Some(arg[pos + 1..].to_string());
9128 } else {
9129 var_names.push(arg.clone());
9130 }
9131 }
9132 i += 1;
9133 }
9134
9135 if var_names.is_empty() {
9136 var_names.push("REPLY".to_string());
9137 }
9138
9139 if let Some(ref p) = prompt_str {
9140 eprint!("{}", p);
9141 let _ = std::io::stderr().flush();
9142 }
9143
9144 let _ = to_history;
9145 let _ = fd;
9146 let _ = silent;
9147
9148 let input = if let Some(n) = nchars {
9149 let mut buf = vec![0u8; n];
9150 let stdin = io::stdin();
9151 if let Some(_t) = timeout {
9152 }
9154 match stdin.lock().read_exact(&mut buf) {
9155 Ok(_) => String::from_utf8_lossy(&buf).to_string(),
9156 Err(_) => return 1,
9157 }
9158 } else {
9159 let stdin = io::stdin();
9160 let mut input = String::new();
9161 if delimiter == '\n' {
9162 match stdin.lock().read_line(&mut input) {
9163 Ok(0) => return 1,
9164 Ok(_) => {}
9165 Err(_) => return 1,
9166 }
9167 } else {
9168 let mut byte = [0u8; 1];
9169 loop {
9170 match stdin.lock().read_exact(&mut byte) {
9171 Ok(_) => {
9172 let c = byte[0] as char;
9173 if c == delimiter {
9174 break;
9175 }
9176 input.push(c);
9177 }
9178 Err(_) => break,
9179 }
9180 }
9181 }
9182 input
9183 .trim_end_matches('\n')
9184 .trim_end_matches('\r')
9185 .to_string()
9186 };
9187
9188 let processed = if raw_mode {
9189 input
9190 } else {
9191 input.replace("\\\n", "")
9192 };
9193
9194 if quiet {
9195 return if processed.is_empty() { 1 } else { 0 };
9196 }
9197
9198 if use_array {
9199 let var = &var_names[0];
9200 let words: Vec<String> = processed.split_whitespace().map(String::from).collect();
9201 self.arrays.insert(var.clone(), words);
9202 } else if var_names.len() == 1 {
9203 let var = &var_names[0];
9204 env::set_var(var, &processed);
9205 self.variables.insert(var.clone(), processed);
9206 } else {
9207 let ifs = self
9208 .variables
9209 .get("IFS")
9210 .map(|s| s.as_str())
9211 .unwrap_or(" \t\n");
9212 let words: Vec<&str> = processed
9213 .split(|c| ifs.contains(c))
9214 .filter(|s| !s.is_empty())
9215 .collect();
9216
9217 for (j, var) in var_names.iter().enumerate() {
9218 if j < words.len() {
9219 if j == var_names.len() - 1 && words.len() > var_names.len() {
9220 let remaining = words[j..].join(" ");
9221 env::set_var(var, &remaining);
9222 self.variables.insert(var.clone(), remaining);
9223 } else {
9224 env::set_var(var, words[j]);
9225 self.variables.insert(var.clone(), words[j].to_string());
9226 }
9227 } else {
9228 env::set_var(var, "");
9229 self.variables.insert(var.clone(), String::new());
9230 }
9231 }
9232 }
9233
9234 0
9235 }
9236
9237 fn builtin_shift(&mut self, args: &[String]) -> i32 {
9238 let mut from_end = false;
9244 let mut count = 1usize;
9245 let mut array_names: Vec<String> = Vec::new();
9246
9247 let mut i = 0;
9248 while i < args.len() {
9249 let arg = &args[i];
9250 if arg == "-p" {
9251 from_end = true;
9252 } else if arg.chars().all(|c| c.is_ascii_digit()) {
9253 count = arg.parse().unwrap_or(1);
9254 } else {
9255 array_names.push(arg.clone());
9256 }
9257 i += 1;
9258 }
9259
9260 if array_names.is_empty() {
9261 if from_end {
9263 for _ in 0..count {
9264 if !self.positional_params.is_empty() {
9265 self.positional_params.pop();
9266 }
9267 }
9268 } else {
9269 for _ in 0..count.min(self.positional_params.len()) {
9270 self.positional_params.remove(0);
9271 }
9272 }
9273 } else {
9274 for name in array_names {
9276 if let Some(arr) = self.arrays.get_mut(&name) {
9277 if from_end {
9278 for _ in 0..count {
9279 if !arr.is_empty() {
9280 arr.pop();
9281 }
9282 }
9283 } else {
9284 for _ in 0..count {
9285 if !arr.is_empty() {
9286 arr.remove(0);
9287 }
9288 }
9289 }
9290 }
9291 }
9292 }
9293
9294 0
9295 }
9296
9297 #[tracing::instrument(level = "debug", skip(self))]
9298 fn builtin_eval(&mut self, args: &[String]) -> i32 {
9299 let code = args.join(" ");
9300 match self.execute_script(&code) {
9301 Ok(status) => status,
9302 Err(e) => {
9303 eprintln!("eval: {}", e);
9304 1
9305 }
9306 }
9307 }
9308
9309 fn builtin_autoload(&mut self, args: &[String]) -> i32 {
9310 let mut functions = Vec::new();
9314 let mut no_alias = false; let mut zsh_style = false; let mut ksh_style = false; let mut execute_now = false; let mut resolve = false; let mut trace = false; let mut use_caller_dir = false; let _list_mode = false;
9322
9323 let mut i = 0;
9324 while i < args.len() {
9325 let arg = &args[i];
9326
9327 if arg == "--" {
9328 i += 1;
9329 break;
9330 }
9331
9332 if arg.starts_with('+') {
9333 let flags = &arg[1..];
9334 for c in flags.chars() {
9335 match c {
9336 'U' => no_alias = false,
9337 'z' => zsh_style = false,
9338 'k' => ksh_style = false,
9339 't' => trace = false,
9340 'd' => use_caller_dir = false,
9341 _ => {}
9342 }
9343 }
9344 } else if arg.starts_with('-') {
9345 let flags = &arg[1..];
9346 if flags.is_empty() {
9347 i += 1;
9349 break;
9350 }
9351 for c in flags.chars() {
9352 match c {
9353 'U' => no_alias = true,
9354 'z' => zsh_style = true,
9355 'k' => ksh_style = true,
9356 'X' => execute_now = true,
9357 'r' | 'R' => resolve = true,
9358 't' => trace = true,
9359 'T' => {} 'W' => {} 'd' => use_caller_dir = true,
9362 'w' => {} 'm' => {} _ => {}
9365 }
9366 }
9367 } else {
9368 functions.push(arg.clone());
9369 }
9370 i += 1;
9371 }
9372
9373 while i < args.len() {
9375 functions.push(args[i].clone());
9376 i += 1;
9377 }
9378
9379 if functions.is_empty() && !execute_now {
9381 for (name, _) in &self.autoload_pending {
9382 if no_alias && zsh_style {
9383 println!("autoload -Uz {}", name);
9384 } else if no_alias {
9385 println!("autoload -U {}", name);
9386 } else {
9387 println!("autoload {}", name);
9388 }
9389 }
9390 return 0;
9391 }
9392
9393 if execute_now {
9397 for func_name in &functions {
9398 if let Some(loaded) = self.load_autoload_function(func_name) {
9400 let body = match loaded {
9402 ShellCommand::FunctionDef(_, body) => (*body).clone(),
9403 other => other,
9404 };
9405 self.functions.insert(func_name.clone(), body);
9407 self.autoload_pending.remove(func_name);
9409 } else {
9410 eprintln!(
9411 "autoload: {}: function definition file not found",
9412 func_name
9413 );
9414 return 1;
9415 }
9416 }
9417 return 0;
9418 }
9419
9420 for func_name in &functions {
9422 let mut flags = AutoloadFlags::empty();
9424 if no_alias {
9425 flags |= AutoloadFlags::NO_ALIAS;
9426 }
9427 if zsh_style {
9428 flags |= AutoloadFlags::ZSH_STYLE;
9429 }
9430 if ksh_style {
9431 flags |= AutoloadFlags::KSH_STYLE;
9432 }
9433 if trace {
9434 flags |= AutoloadFlags::TRACE;
9435 }
9436 if use_caller_dir {
9437 flags |= AutoloadFlags::USE_CALLER_DIR;
9438 }
9439
9440 self.autoload_pending.insert(func_name.clone(), flags);
9441
9442 let autoload_opts = if zsh_style && no_alias {
9445 "-XUz"
9446 } else if zsh_style {
9447 "-Xz"
9448 } else if no_alias {
9449 "-XU"
9450 } else {
9451 "-X"
9452 };
9453
9454 let stub = ShellCommand::List(vec![
9456 (
9457 ShellCommand::Simple(SimpleCommand {
9458 assignments: vec![],
9459 words: vec![
9460 ShellWord::Literal("builtin".to_string()),
9461 ShellWord::Literal("autoload".to_string()),
9462 ShellWord::Literal(autoload_opts.to_string()),
9463 ShellWord::Literal(func_name.clone()),
9464 ],
9465 redirects: vec![],
9466 }),
9467 ListOp::And,
9468 ),
9469 (
9470 ShellCommand::Simple(SimpleCommand {
9471 assignments: vec![],
9472 words: vec![
9473 ShellWord::Literal(func_name.clone()),
9474 ShellWord::DoubleQuoted(vec![ShellWord::Variable("@".to_string())]),
9475 ],
9476 redirects: vec![],
9477 }),
9478 ListOp::Semi,
9479 ),
9480 ]);
9481
9482 self.functions.insert(func_name.clone(), stub);
9483
9484 if resolve {
9486 if self.find_function_file(func_name).is_none() {
9487 eprintln!(
9488 "autoload: {}: function definition file not found",
9489 func_name
9490 );
9491 }
9492 }
9493 }
9494
9495 if functions.len() >= 4 && !resolve && !execute_now {
9499 let fpath_dirs: Vec<PathBuf> = self.fpath.clone();
9500 let names: Vec<String> = functions.clone();
9501 let pool = std::sync::Arc::clone(&self.worker_pool);
9502
9503 tracing::debug!(
9504 count = names.len(),
9505 fpath_dirs = fpath_dirs.len(),
9506 "batch autoload: pre-resolving fpath lookups on worker pool"
9507 );
9508
9509 let resolved = std::sync::Arc::new(parking_lot::Mutex::new(
9512 HashMap::<String, PathBuf>::with_capacity(names.len()),
9513 ));
9514
9515 for name in names {
9516 let dirs = fpath_dirs.clone();
9517 let resolved = std::sync::Arc::clone(&resolved);
9518 pool.submit(move || {
9519 for dir in &dirs {
9520 let path = dir.join(&name);
9521 if path.exists() && path.is_file() {
9522 let _ = std::fs::read(&path);
9525 resolved.lock().insert(name.clone(), path);
9526 tracing::trace!(func = %name, "autoload batch: pre-resolved");
9527 break;
9528 }
9529 }
9530 });
9531 }
9532 }
9533
9534 0
9535 }
9536
9537 fn find_function_file(&self, name: &str) -> Option<PathBuf> {
9539 for dir in &self.fpath {
9540 let path = dir.join(name);
9541 if path.exists() && path.is_file() {
9542 return Some(path);
9543 }
9544 }
9545 None
9546 }
9547
9548 fn load_autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
9550 if !self.zsh_compat {
9553 if let Some(ref cache) = self.compsys_cache {
9554 if let Ok(Some(bc_blob)) = cache.get_autoload_bytecode(name) {
9556 if let Ok(chunk) = bincode::deserialize::<fusevm::Chunk>(&bc_blob) {
9558 if !chunk.ops.is_empty() {
9559 tracing::trace!(name, bytes = bc_blob.len(), ops = chunk.ops.len(), "autoload: bytecode cache hit → VM");
9560 let mut vm = fusevm::VM::new(chunk);
9562 let _ = vm.run();
9563 self.last_status = vm.last_status;
9564 return Some(ShellCommand::Simple(crate::parser::SimpleCommand {
9566 assignments: Vec::new(),
9567 words: Vec::new(),
9568 redirects: Vec::new(),
9569 }));
9570 }
9571 }
9572 if let Ok(commands) = bincode::deserialize::<Vec<ShellCommand>>(&bc_blob) {
9574 if !commands.is_empty() {
9575 tracing::trace!(name, bytes = bc_blob.len(), "autoload: legacy AST cache hit");
9576 return Some(self.wrap_autoload_commands(name, commands));
9577 }
9578 }
9579 }
9580
9581 if let Ok(Some(body)) = cache.get_autoload_body(name) {
9583 let mut parser = ShellParser::new(&body);
9584 if let Ok(commands) = parser.parse_script() {
9585 if !commands.is_empty() {
9586 let compiler = crate::shell_compiler::ShellCompiler::new();
9588 let chunk = compiler.compile(&commands);
9589 if let Ok(blob) = bincode::serialize(&chunk) {
9590 let _ = cache.set_autoload_bytecode(name, &blob);
9591 tracing::trace!(name, bytes = blob.len(), "autoload: bytecodes compiled and cached");
9592 }
9593 return Some(self.wrap_autoload_commands(name, commands));
9594 }
9595 }
9596 }
9597 }
9598 }
9599
9600 if !self.functions.contains_key(name) {
9602 for dir in &self.fpath.clone() {
9604 let zwc_path = dir.with_extension("zwc");
9606 if zwc_path.exists() {
9607 let prefixed_name = format!(
9609 "{}/{}",
9610 dir.file_name().and_then(|n| n.to_str()).unwrap_or(""),
9611 name
9612 );
9613 if let Some(func) = self.load_function_from_zwc(&zwc_path, &prefixed_name) {
9614 return Some(func);
9615 }
9616 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
9618 return Some(func);
9619 }
9620 }
9621 let func_zwc = dir.join(format!("{}.zwc", name));
9623 if func_zwc.exists() {
9624 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
9625 return Some(func);
9626 }
9627 }
9628 }
9629 }
9630
9631 let path = self.find_function_file(name)?;
9633
9634 let content = std::fs::read_to_string(&path).ok()?;
9636
9637 let mut parser = ShellParser::new(&content);
9639
9640 if let Ok(commands) = parser.parse_script() {
9641 if commands.is_empty() {
9642 return None;
9643 }
9644
9645 if commands.len() == 1 {
9647 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
9648 if fn_name == name {
9649 return Some(commands[0].clone());
9650 }
9651 }
9652 }
9653
9654 let body = if commands.len() == 1 {
9657 commands.into_iter().next().unwrap()
9658 } else {
9659 let list_cmds: Vec<(ShellCommand, ListOp)> =
9661 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
9662 ShellCommand::List(list_cmds)
9663 };
9664
9665 return Some(ShellCommand::FunctionDef(name.to_string(), Box::new(body)));
9666 }
9667
9668 None
9669 }
9670
9671 fn wrap_autoload_commands(&self, name: &str, commands: Vec<ShellCommand>) -> ShellCommand {
9673 if commands.len() == 1 {
9675 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
9676 if fn_name == name {
9677 return commands.into_iter().next().unwrap();
9678 }
9679 }
9680 }
9681 let body = if commands.len() == 1 {
9683 commands.into_iter().next().unwrap()
9684 } else {
9685 let list_cmds: Vec<(ShellCommand, ListOp)> =
9686 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
9687 ShellCommand::List(list_cmds)
9688 };
9689 ShellCommand::FunctionDef(name.to_string(), Box::new(body))
9690 }
9691
9692 pub fn maybe_autoload(&mut self, name: &str) -> bool {
9694 if self.autoload_pending.contains_key(name) {
9695 if let Some(func) = self.load_autoload_function(name) {
9696 let to_store = match func {
9698 ShellCommand::FunctionDef(_, body) => (*body).clone(),
9699 other => other,
9700 };
9701 self.functions.insert(name.to_string(), to_store);
9702 self.autoload_pending.remove(name);
9703 return true;
9704 }
9705 }
9706 false
9707 }
9708
9709 fn builtin_jobs(&mut self, args: &[String]) -> i32 {
9710 let mut long_format = false;
9719 let mut pids_only = false;
9720 let mut show_dir = false;
9721 let mut running_only = false;
9722 let mut stopped_only = false;
9723 let mut job_ids: Vec<usize> = Vec::new();
9724
9725 for arg in args {
9726 if arg.starts_with('-') {
9727 for c in arg[1..].chars() {
9728 match c {
9729 'l' => long_format = true,
9730 'p' => pids_only = true,
9731 'd' => show_dir = true,
9732 'r' => running_only = true,
9733 's' => stopped_only = true,
9734 'Z' => {} _ => {}
9736 }
9737 }
9738 } else if arg.starts_with('%') {
9739 if let Ok(id) = arg[1..].parse::<usize>() {
9740 job_ids.push(id);
9741 }
9742 } else if let Ok(id) = arg.parse::<usize>() {
9743 job_ids.push(id);
9744 }
9745 }
9746
9747 for job in self.jobs.reap_finished() {
9749 if !running_only && !stopped_only {
9750 if pids_only {
9751 println!("{}", job.pid);
9752 } else {
9753 println!("[{}] Done {}", job.id, job.command);
9754 }
9755 }
9756 }
9757
9758 for job in self.jobs.list() {
9760 if !job_ids.is_empty() && !job_ids.contains(&job.id) {
9762 continue;
9763 }
9764
9765 if running_only && job.state != JobState::Running {
9767 continue;
9768 }
9769 if stopped_only && job.state != JobState::Stopped {
9770 continue;
9771 }
9772
9773 if pids_only {
9774 println!("{}", job.pid);
9775 continue;
9776 }
9777
9778 let marker = if job.is_current { "+" } else { "-" };
9779 let state = match job.state {
9780 JobState::Running => "running",
9781 JobState::Stopped => "suspended",
9782 JobState::Done => "done",
9783 };
9784
9785 if long_format {
9786 println!(
9787 "[{}]{} {:6} {} {}",
9788 job.id, marker, job.pid, state, job.command
9789 );
9790 } else {
9791 println!("[{}]{} {} {}", job.id, marker, state, job.command);
9792 }
9793
9794 if show_dir {
9795 if let Ok(cwd) = env::current_dir() {
9796 println!(" (pwd: {})", cwd.display());
9797 }
9798 }
9799 }
9800 0
9801 }
9802
9803 fn builtin_fg(&mut self, args: &[String]) -> i32 {
9804 let job_id = if let Some(arg) = args.first() {
9805 let s = arg.trim_start_matches('%');
9807 match s.parse::<usize>() {
9808 Ok(id) => Some(id),
9809 Err(_) => {
9810 eprintln!("fg: {}: no such job", arg);
9811 return 1;
9812 }
9813 }
9814 } else {
9815 self.jobs.current().map(|j| j.id)
9816 };
9817
9818 let Some(id) = job_id else {
9819 eprintln!("fg: no current job");
9820 return 1;
9821 };
9822
9823 let Some(job) = self.jobs.get(id) else {
9824 eprintln!("fg: %{}: no such job", id);
9825 return 1;
9826 };
9827
9828 let pid = job.pid;
9829 let cmd = job.command.clone();
9830 println!("{}", cmd);
9831
9832 if let Err(e) = continue_job(pid) {
9834 eprintln!("fg: {}", e);
9835 return 1;
9836 }
9837
9838 match wait_for_job(pid) {
9840 Ok(status) => {
9841 self.jobs.remove(id);
9842 status
9843 }
9844 Err(e) => {
9845 eprintln!("fg: {}", e);
9846 1
9847 }
9848 }
9849 }
9850
9851 fn builtin_bg(&mut self, args: &[String]) -> i32 {
9852 let job_id = if let Some(arg) = args.first() {
9853 let s = arg.trim_start_matches('%');
9854 match s.parse::<usize>() {
9855 Ok(id) => Some(id),
9856 Err(_) => {
9857 eprintln!("bg: {}: no such job", arg);
9858 return 1;
9859 }
9860 }
9861 } else {
9862 self.jobs.current().map(|j| j.id)
9863 };
9864
9865 let Some(id) = job_id else {
9866 eprintln!("bg: no current job");
9867 return 1;
9868 };
9869
9870 let Some(job) = self.jobs.get_mut(id) else {
9871 eprintln!("bg: %{}: no such job", id);
9872 return 1;
9873 };
9874
9875 let pid = job.pid;
9876 let cmd = job.command.clone();
9877
9878 if let Err(e) = continue_job(pid) {
9879 eprintln!("bg: {}", e);
9880 return 1;
9881 }
9882
9883 job.state = JobState::Running;
9884 println!("[{}] {} &", id, cmd);
9885 0
9886 }
9887
9888 fn builtin_kill(&mut self, args: &[String]) -> i32 {
9889 use crate::jobs::send_signal;
9892 use nix::sys::signal::Signal;
9893
9894 if args.is_empty() {
9895 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
9896 eprintln!(" kill -l [sig ...]");
9897 return 1;
9898 }
9899
9900 let signal_map: &[(&str, i32, Signal)] = &[
9902 ("HUP", 1, Signal::SIGHUP),
9903 ("INT", 2, Signal::SIGINT),
9904 ("QUIT", 3, Signal::SIGQUIT),
9905 ("ILL", 4, Signal::SIGILL),
9906 ("TRAP", 5, Signal::SIGTRAP),
9907 ("ABRT", 6, Signal::SIGABRT),
9908 ("BUS", 7, Signal::SIGBUS),
9909 ("FPE", 8, Signal::SIGFPE),
9910 ("KILL", 9, Signal::SIGKILL),
9911 ("USR1", 10, Signal::SIGUSR1),
9912 ("SEGV", 11, Signal::SIGSEGV),
9913 ("USR2", 12, Signal::SIGUSR2),
9914 ("PIPE", 13, Signal::SIGPIPE),
9915 ("ALRM", 14, Signal::SIGALRM),
9916 ("TERM", 15, Signal::SIGTERM),
9917 ("CHLD", 17, Signal::SIGCHLD),
9918 ("CONT", 18, Signal::SIGCONT),
9919 ("STOP", 19, Signal::SIGSTOP),
9920 ("TSTP", 20, Signal::SIGTSTP),
9921 ("TTIN", 21, Signal::SIGTTIN),
9922 ("TTOU", 22, Signal::SIGTTOU),
9923 ("URG", 23, Signal::SIGURG),
9924 ("XCPU", 24, Signal::SIGXCPU),
9925 ("XFSZ", 25, Signal::SIGXFSZ),
9926 ("VTALRM", 26, Signal::SIGVTALRM),
9927 ("PROF", 27, Signal::SIGPROF),
9928 ("WINCH", 28, Signal::SIGWINCH),
9929 ("IO", 29, Signal::SIGIO),
9930 ("SYS", 31, Signal::SIGSYS),
9931 ];
9932
9933 let mut sig = Signal::SIGTERM;
9934 let mut pids: Vec<String> = Vec::new();
9935 let mut list_mode = false;
9936 let mut list_args: Vec<String> = Vec::new();
9937
9938 let mut i = 0;
9939 while i < args.len() {
9940 let arg = &args[i];
9941
9942 if arg == "-l" || arg == "-L" {
9943 list_mode = true;
9944 list_args = args[i + 1..].to_vec();
9946 break;
9947 } else if arg == "-s" {
9948 i += 1;
9950 if i >= args.len() {
9951 eprintln!("kill: -s requires an argument");
9952 return 1;
9953 }
9954 let sig_name = args[i].to_uppercase();
9955 let sig_name = sig_name.strip_prefix("SIG").unwrap_or(&sig_name);
9956 if let Some((_, _, s)) = signal_map.iter().find(|(name, _, _)| *name == sig_name) {
9957 sig = *s;
9958 } else {
9959 eprintln!("kill: invalid signal: {}", args[i]);
9960 return 1;
9961 }
9962 } else if arg == "-n" {
9963 i += 1;
9965 if i >= args.len() {
9966 eprintln!("kill: -n requires an argument");
9967 return 1;
9968 }
9969 let num: i32 = match args[i].parse() {
9970 Ok(n) => n,
9971 Err(_) => {
9972 eprintln!("kill: invalid signal number: {}", args[i]);
9973 return 1;
9974 }
9975 };
9976 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
9977 sig = *s;
9978 } else {
9979 eprintln!("kill: invalid signal number: {}", num);
9980 return 1;
9981 }
9982 } else if arg.starts_with('-') && arg.len() > 1 {
9983 let sig_str = &arg[1..];
9985 let sig_upper = sig_str.to_uppercase();
9986 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
9987
9988 if let Ok(num) = sig_str.parse::<i32>() {
9990 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
9991 sig = *s;
9992 } else {
9993 eprintln!("kill: invalid signal: {}", arg);
9994 return 1;
9995 }
9996 } else if let Some((_, _, s)) =
9997 signal_map.iter().find(|(name, _, _)| *name == sig_name)
9998 {
9999 sig = *s;
10000 } else {
10001 eprintln!("kill: invalid signal: {}", arg);
10002 return 1;
10003 }
10004 } else {
10005 pids.push(arg.clone());
10006 }
10007 i += 1;
10008 }
10009
10010 if list_mode {
10012 if list_args.is_empty() {
10013 for (name, num, _) in signal_map {
10015 println!("{:2}) SIG{}", num, name);
10016 }
10017 } else {
10018 for arg in &list_args {
10020 if let Ok(num) = arg.parse::<i32>() {
10021 if let Some((name, _, _)) = signal_map.iter().find(|(_, n, _)| *n == num) {
10023 println!("{}", name);
10024 } else {
10025 eprintln!("kill: unknown signal: {}", num);
10026 }
10027 } else {
10028 let sig_upper = arg.to_uppercase();
10030 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
10031 if let Some((_, num, _)) =
10032 signal_map.iter().find(|(name, _, _)| *name == sig_name)
10033 {
10034 println!("{}", num);
10035 } else {
10036 eprintln!("kill: unknown signal: {}", arg);
10037 }
10038 }
10039 }
10040 }
10041 return 0;
10042 }
10043
10044 if pids.is_empty() {
10045 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
10046 return 1;
10047 }
10048
10049 let mut status = 0;
10050 for arg in &pids {
10051 if arg.starts_with('%') {
10053 let id: usize = match arg[1..].parse() {
10054 Ok(id) => id,
10055 Err(_) => {
10056 eprintln!("kill: {}: no such job", arg);
10057 status = 1;
10058 continue;
10059 }
10060 };
10061 if let Some(job) = self.jobs.get(id) {
10062 if let Err(e) = send_signal(job.pid, sig) {
10063 eprintln!("kill: {}", e);
10064 status = 1;
10065 }
10066 } else {
10067 eprintln!("kill: {}: no such job", arg);
10068 status = 1;
10069 }
10070 } else {
10071 let pid: u32 = match arg.parse() {
10073 Ok(p) => p,
10074 Err(_) => {
10075 eprintln!("kill: {}: invalid pid", arg);
10076 status = 1;
10077 continue;
10078 }
10079 };
10080 if let Err(e) = send_signal(pid as i32, sig) {
10081 eprintln!("kill: {}", e);
10082 status = 1;
10083 }
10084 }
10085 }
10086 status
10087 }
10088
10089 fn builtin_disown(&mut self, args: &[String]) -> i32 {
10090 if args.is_empty() {
10091 if let Some(job) = self.jobs.current() {
10093 let id = job.id;
10094 self.jobs.remove(id);
10095 }
10096 return 0;
10097 }
10098
10099 for arg in args {
10100 let s = arg.trim_start_matches('%');
10101 if let Ok(id) = s.parse::<usize>() {
10102 self.jobs.remove(id);
10103 } else {
10104 eprintln!("disown: {}: no such job", arg);
10105 }
10106 }
10107 0
10108 }
10109
10110 fn builtin_wait(&mut self, args: &[String]) -> i32 {
10111 if args.is_empty() {
10112 let ids: Vec<usize> = self.jobs.list().iter().map(|j| j.id).collect();
10114 for id in ids {
10115 if let Some(mut job) = self.jobs.remove(id) {
10116 if let Some(ref mut child) = job.child {
10117 let _ = wait_for_child(child);
10118 }
10119 }
10120 }
10121 return 0;
10122 }
10123
10124 let mut status = 0;
10125 for arg in args {
10126 if arg.starts_with('%') {
10127 let id: usize = match arg[1..].parse() {
10128 Ok(id) => id,
10129 Err(_) => {
10130 eprintln!("wait: {}: no such job", arg);
10131 status = 127;
10132 continue;
10133 }
10134 };
10135 if let Some(mut job) = self.jobs.remove(id) {
10136 if let Some(ref mut child) = job.child {
10137 match wait_for_child(child) {
10138 Ok(s) => status = s,
10139 Err(e) => {
10140 eprintln!("wait: {}", e);
10141 status = 127;
10142 }
10143 }
10144 }
10145 } else {
10146 eprintln!("wait: {}: no such job", arg);
10147 status = 127;
10148 }
10149 } else {
10150 let pid: u32 = match arg.parse() {
10151 Ok(p) => p,
10152 Err(_) => {
10153 eprintln!("wait: {}: invalid pid", arg);
10154 status = 127;
10155 continue;
10156 }
10157 };
10158 match wait_for_job(pid as i32) {
10159 Ok(s) => status = s,
10160 Err(e) => {
10161 eprintln!("wait: {}", e);
10162 status = 127;
10163 }
10164 }
10165 }
10166 }
10167 status
10168 }
10169
10170 fn builtin_suspend(&self, args: &[String]) -> i32 {
10171 let mut force = false;
10172 for arg in args {
10173 if arg == "-f" {
10174 force = true;
10175 }
10176 }
10177
10178 #[cfg(unix)]
10179 {
10180 use nix::sys::signal::{kill, Signal};
10181 use nix::unistd::getppid;
10182
10183 let ppid = getppid();
10185 if !force && ppid == nix::unistd::Pid::from_raw(1) {
10186 eprintln!("suspend: cannot suspend a login shell");
10187 return 1;
10188 }
10189
10190 let pid = nix::unistd::getpid();
10192 if let Err(e) = kill(pid, Signal::SIGTSTP) {
10193 eprintln!("suspend: {}", e);
10194 return 1;
10195 }
10196 0
10197 }
10198
10199 #[cfg(not(unix))]
10200 {
10201 eprintln!("suspend: not supported on this platform");
10202 1
10203 }
10204 }
10205}
10206
10207impl Default for ShellExecutor {
10208 fn default() -> Self {
10209 Self::new()
10210 }
10211}
10212
10213#[cfg(test)]
10214mod tests {
10215 use super::*;
10216
10217 #[test]
10218 fn test_simple_echo() {
10219 let mut exec = ShellExecutor::new();
10220 let status = exec.execute_script("true").unwrap();
10221 assert_eq!(status, 0);
10222 }
10223
10224 #[test]
10225 fn test_if_true() {
10226 let mut exec = ShellExecutor::new();
10227 let status = exec.execute_script("if true; then true; fi").unwrap();
10228 assert_eq!(status, 0);
10229 }
10230
10231 #[test]
10232 fn test_if_false() {
10233 let mut exec = ShellExecutor::new();
10234 let status = exec
10235 .execute_script("if false; then true; else false; fi")
10236 .unwrap();
10237 assert_eq!(status, 1);
10238 }
10239
10240 #[test]
10241 fn test_for_loop() {
10242 let mut exec = ShellExecutor::new();
10243 exec.execute_script("for i in a b c; do true; done")
10244 .unwrap();
10245 assert_eq!(exec.last_status, 0);
10246 }
10247
10248 #[test]
10249 fn test_and_list() {
10250 let mut exec = ShellExecutor::new();
10251 let status = exec.execute_script("true && true").unwrap();
10252 assert_eq!(status, 0);
10253
10254 let status = exec.execute_script("true && false").unwrap();
10255 assert_eq!(status, 1);
10256 }
10257
10258 #[test]
10259 fn test_or_list() {
10260 let mut exec = ShellExecutor::new();
10261 let status = exec.execute_script("false || true").unwrap();
10262 assert_eq!(status, 0);
10263 }
10264}
10265
10266impl ShellExecutor {
10267 fn builtin_history(&self, args: &[String]) -> i32 {
10268 let Some(ref engine) = self.history else {
10269 eprintln!("history: history engine not available");
10270 return 1;
10271 };
10272
10273 let mut count = 20usize;
10275 let mut show_all = false;
10276 let mut search_query = None;
10277
10278 let mut i = 0;
10279 while i < args.len() {
10280 match args[i].as_str() {
10281 "-c" | "--clear" => {
10282 eprintln!("history: clear not supported in this mode");
10284 return 1;
10285 }
10286 "-a" | "--all" => show_all = true,
10287 "-n" => {
10288 if i + 1 < args.len() {
10289 i += 1;
10290 count = args[i].parse().unwrap_or(20);
10291 }
10292 }
10293 s if s.starts_with('-') && s[1..].chars().all(|c| c.is_ascii_digit()) => {
10294 count = s[1..].parse().unwrap_or(20);
10295 }
10296 s if s.chars().all(|c| c.is_ascii_digit()) => {
10297 count = s.parse().unwrap_or(20);
10298 }
10299 s => {
10300 search_query = Some(s.to_string());
10301 }
10302 }
10303 i += 1;
10304 }
10305
10306 if show_all {
10307 count = 10000;
10308 }
10309
10310 let entries = if let Some(ref q) = search_query {
10311 engine.search(q, count)
10312 } else {
10313 engine.recent(count)
10314 };
10315
10316 match entries {
10317 Ok(entries) => {
10318 for entry in entries.into_iter().rev() {
10320 println!("{:>6} {}", entry.id, entry.command);
10321 }
10322 0
10323 }
10324 Err(e) => {
10325 eprintln!("history: {}", e);
10326 1
10327 }
10328 }
10329 }
10330
10331 fn builtin_fc(&mut self, args: &[String]) -> i32 {
10337 let Some(ref engine) = self.history else {
10338 eprintln!("fc: history engine not available");
10339 return 1;
10340 };
10341
10342 let mut list_mode = false;
10344 let mut no_numbers = false;
10345 let mut reverse = false;
10346 let mut show_time = false;
10347 let mut show_duration = false;
10348 let mut editor: Option<String> = None;
10349 let mut read_file = false;
10350 let mut write_file = false;
10351 let mut append_file = false;
10352 let mut substitute_mode = false;
10353 let mut positional: Vec<&str> = Vec::new();
10354 let mut substitutions: Vec<(String, String)> = Vec::new();
10355
10356 let mut i = 0;
10357 while i < args.len() {
10358 let arg = &args[i];
10359 if arg == "--" {
10360 i += 1;
10361 while i < args.len() {
10362 positional.push(&args[i]);
10363 i += 1;
10364 }
10365 break;
10366 }
10367 if arg.starts_with('-') && arg.len() > 1 {
10368 let chars: Vec<char> = arg[1..].chars().collect();
10369 let mut j = 0;
10370 while j < chars.len() {
10371 match chars[j] {
10372 'l' => list_mode = true,
10373 'n' => no_numbers = true,
10374 'r' => reverse = true,
10375 'd' | 'f' | 'E' | 'i' => show_time = true,
10376 'D' => show_duration = true,
10377 'R' => read_file = true,
10378 'W' => write_file = true,
10379 'A' => append_file = true,
10380 's' => substitute_mode = true,
10381 'e' => {
10382 if j + 1 < chars.len() {
10383 editor = Some(chars[j + 1..].iter().collect());
10384 break;
10385 } else {
10386 i += 1;
10387 if i < args.len() {
10388 editor = Some(args[i].clone());
10389 }
10390 }
10391 }
10392 't' => {
10393 show_time = true;
10394 if j + 1 < chars.len() {
10395 break;
10396 } else {
10397 i += 1;
10398 }
10399 }
10400 'p' | 'P' | 'a' | 'I' | 'L' | 'm' => {} _ => {
10402 if chars[j].is_ascii_digit() {
10403 positional.push(arg);
10404 break;
10405 }
10406 }
10407 }
10408 j += 1;
10409 }
10410 } else if arg.contains('=') && !list_mode {
10411 if let Some((old, new)) = arg.split_once('=') {
10412 substitutions.push((old.to_string(), new.to_string()));
10413 }
10414 } else {
10415 positional.push(arg);
10416 }
10417 i += 1;
10418 }
10419
10420 if read_file || write_file || append_file {
10423 let filename = positional.first().map(|s| *s).unwrap_or("~/.zsh_history");
10424 let path = if filename.starts_with("~/") {
10425 dirs::home_dir()
10426 .map(|h| h.join(&filename[2..]))
10427 .unwrap_or_else(|| std::path::PathBuf::from(filename))
10428 } else {
10429 std::path::PathBuf::from(filename)
10430 };
10431
10432 if read_file {
10433 if let Ok(contents) = std::fs::read_to_string(&path) {
10435 for line in contents.lines() {
10436 if !line.is_empty() && !line.starts_with('#') && !line.starts_with(':') {
10437 let _ = engine.add(line, None);
10438 }
10439 }
10440 } else {
10441 eprintln!("fc: cannot read {}", path.display());
10442 return 1;
10443 }
10444 } else if write_file || append_file {
10445 let mode = if append_file {
10447 std::fs::OpenOptions::new()
10448 .create(true)
10449 .append(true)
10450 .open(&path)
10451 } else {
10452 std::fs::File::create(&path)
10453 };
10454 match mode {
10455 Ok(mut file) => {
10456 use std::io::Write;
10457 if let Ok(entries) = engine.recent(10000) {
10458 for entry in entries.iter().rev() {
10459 let _ = writeln!(file, ": {}:0;{}", entry.timestamp, entry.command);
10460 }
10461 }
10462 }
10463 Err(e) => {
10464 eprintln!("fc: cannot write {}: {}", path.display(), e);
10465 return 1;
10466 }
10467 }
10468 }
10469 return 0;
10470 }
10471
10472 if list_mode || args.is_empty() {
10474 let (first, last) = match positional.len() {
10475 0 => (-16i64, -1i64),
10476 1 => {
10477 let n = positional[0].parse::<i64>().unwrap_or(-16);
10478 (n, -1)
10479 }
10480 _ => {
10481 let f = positional[0].parse::<i64>().unwrap_or(-16);
10482 let l = positional[1].parse::<i64>().unwrap_or(-1);
10483 (f, l)
10484 }
10485 };
10486
10487 let count = if first < 0 { (-first) as usize } else { 16 };
10488 match engine.recent(count.max(100)) {
10489 Ok(mut entries) => {
10490 if reverse {
10491 entries.reverse();
10492 }
10493 for entry in entries.iter().rev().take(count) {
10494 if no_numbers {
10495 println!("{}", entry.command);
10496 } else if show_time {
10497 println!(
10498 "{:>6} {:>10} {}",
10499 entry.id, entry.timestamp, entry.command
10500 );
10501 } else if show_duration {
10502 println!(
10503 "{:>6} {:>5} {}",
10504 entry.id,
10505 entry.duration_ms.unwrap_or(0),
10506 entry.command
10507 );
10508 } else {
10509 println!("{:>6} {}", entry.id, entry.command);
10510 }
10511 }
10512 0
10513 }
10514 Err(e) => {
10515 eprintln!("fc: {}", e);
10516 1
10517 }
10518 }
10519 } else if substitute_mode || !substitutions.is_empty() {
10520 match engine.get_by_offset(0) {
10522 Ok(Some(entry)) => {
10523 let mut cmd = entry.command.clone();
10524 for (old, new) in &substitutions {
10525 cmd = cmd.replace(old, new);
10526 }
10527 println!("{}", cmd);
10528 self.execute_script(&cmd).unwrap_or(1)
10529 }
10530 Ok(None) => {
10531 eprintln!("fc: no command to re-execute");
10532 1
10533 }
10534 Err(e) => {
10535 eprintln!("fc: {}", e);
10536 1
10537 }
10538 }
10539 } else if editor.as_deref() == Some("-") {
10540 match engine.get_by_offset(0) {
10542 Ok(Some(entry)) => {
10543 println!("{}", entry.command);
10544 self.execute_script(&entry.command).unwrap_or(1)
10545 }
10546 Ok(None) => {
10547 eprintln!("fc: no command to re-execute");
10548 1
10549 }
10550 Err(e) => {
10551 eprintln!("fc: {}", e);
10552 1
10553 }
10554 }
10555 } else if let Some(arg) = positional.first() {
10556 if arg.starts_with('-') || arg.starts_with('+') {
10557 let n: usize = arg[1..].parse().unwrap_or(1);
10559 let offset = if arg.starts_with('-') { n - 1 } else { n };
10560 match engine.get_by_offset(offset) {
10561 Ok(Some(entry)) => {
10562 println!("{}", entry.command);
10563 self.execute_script(&entry.command).unwrap_or(1)
10564 }
10565 Ok(None) => {
10566 eprintln!("fc: event not found");
10567 1
10568 }
10569 Err(e) => {
10570 eprintln!("fc: {}", e);
10571 1
10572 }
10573 }
10574 } else {
10575 match engine.search_prefix(arg, 1) {
10577 Ok(entries) if !entries.is_empty() => {
10578 println!("{}", entries[0].command);
10579 self.execute_script(&entries[0].command).unwrap_or(1)
10580 }
10581 Ok(_) => {
10582 eprintln!("fc: event not found: {}", arg);
10583 1
10584 }
10585 Err(e) => {
10586 eprintln!("fc: {}", e);
10587 1
10588 }
10589 }
10590 }
10591 } else {
10592 match engine.get_by_offset(0) {
10594 Ok(Some(entry)) => {
10595 println!("{}", entry.command);
10596 self.execute_script(&entry.command).unwrap_or(1)
10597 }
10598 Ok(None) => {
10599 eprintln!("fc: no command to re-execute");
10600 1
10601 }
10602 Err(e) => {
10603 eprintln!("fc: {}", e);
10604 1
10605 }
10606 }
10607 }
10608 }
10609
10610 fn builtin_trap(&mut self, args: &[String]) -> i32 {
10611 if args.is_empty() {
10612 for (sig, action) in &self.traps {
10614 println!("trap -- '{}' {}", action, sig);
10615 }
10616 return 0;
10617 }
10618
10619 if args.len() == 1 && args[0] == "-l" {
10621 let signals = [
10622 "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
10623 "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN",
10624 "TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS",
10625 ];
10626 for (i, sig) in signals.iter().enumerate() {
10627 print!("{:2}) SIG{:<8}", i + 1, sig);
10628 if (i + 1) % 5 == 0 {
10629 println!();
10630 }
10631 }
10632 println!();
10633 return 0;
10634 }
10635
10636 if args.len() >= 1 && args[0] == "-p" {
10638 let signals = if args.len() > 1 {
10639 &args[1..]
10640 } else {
10641 &[] as &[String]
10642 };
10643 if signals.is_empty() {
10644 for (sig, action) in &self.traps {
10645 println!("trap -- '{}' {}", action, sig);
10646 }
10647 } else {
10648 for sig in signals {
10649 if let Some(action) = self.traps.get(sig) {
10650 println!("trap -- '{}' {}", action, sig);
10651 }
10652 }
10653 }
10654 return 0;
10655 }
10656
10657 if args.len() == 1 {
10661 let sig = &args[0];
10663 if let Some(action) = self.traps.get(sig) {
10664 println!("trap -- '{}' {}", action, sig);
10665 }
10666 return 0;
10667 }
10668
10669 let action = &args[0];
10670 let signals = &args[1..];
10671
10672 for sig in signals {
10673 let sig_upper = sig.to_uppercase();
10674 let sig_name = if sig_upper.starts_with("SIG") {
10675 sig_upper[3..].to_string()
10676 } else {
10677 sig_upper.clone()
10678 };
10679
10680 if action.is_empty() || action == "-" {
10681 self.traps.remove(&sig_name);
10683 } else {
10684 self.traps.insert(sig_name, action.clone());
10685 }
10686 }
10687
10688 0
10689 }
10690
10691 pub fn run_trap(&mut self, signal: &str) {
10693 if let Some(action) = self.traps.get(signal).cloned() {
10694 let _ = self.execute_script(&action);
10695 }
10696 }
10697
10698 fn builtin_alias(&mut self, args: &[String]) -> i32 {
10699 let mut is_global = false;
10708 let mut is_suffix = false;
10709 let mut list_form = false;
10710 let mut pattern_match = false;
10711 let mut print_global = false;
10712 let mut print_suffix = false;
10713 let mut print_regular = false;
10714 let mut positional_args = Vec::new();
10715
10716 let mut i = 0;
10717 while i < args.len() {
10718 let arg = &args[i];
10719 if arg.starts_with('+') && arg.len() > 1 {
10720 for ch in arg[1..].chars() {
10722 match ch {
10723 'g' => print_global = true,
10724 's' => print_suffix = true,
10725 'r' => print_regular = true,
10726 'L' => list_form = true,
10727 'm' => pattern_match = true,
10728 _ => {}
10729 }
10730 }
10731 } else if arg.starts_with('-') && arg != "-" {
10732 for ch in arg[1..].chars() {
10733 match ch {
10734 'g' => is_global = true,
10735 's' => is_suffix = true,
10736 'L' => list_form = true,
10737 'm' => pattern_match = true,
10738 'r' => {} _ => {
10740 eprintln!("zshrs: alias: bad option: -{}", ch);
10741 return 1;
10742 }
10743 }
10744 }
10745 } else {
10746 positional_args.push(arg.clone());
10747 }
10748 i += 1;
10749 }
10750
10751 if print_global || print_suffix || print_regular {
10753 if print_regular {
10754 for (name, value) in &self.aliases {
10755 if list_form {
10756 println!("alias {}='{}'", name, value);
10757 } else {
10758 println!("{}='{}'", name, value);
10759 }
10760 }
10761 }
10762 if print_global {
10763 for (name, value) in &self.global_aliases {
10764 if list_form {
10765 println!("alias -g {}='{}'", name, value);
10766 } else {
10767 println!("{}='{}'", name, value);
10768 }
10769 }
10770 }
10771 if print_suffix {
10772 for (name, value) in &self.suffix_aliases {
10773 if list_form {
10774 println!("alias -s {}='{}'", name, value);
10775 } else {
10776 println!("{}='{}'", name, value);
10777 }
10778 }
10779 }
10780 return 0;
10781 }
10782
10783 if positional_args.is_empty() {
10784 let prefix = if is_suffix {
10786 "alias -s "
10787 } else if is_global {
10788 "alias -g "
10789 } else {
10790 "alias "
10791 };
10792 let alias_map: Vec<(String, String)> = if is_suffix {
10793 self.suffix_aliases
10794 .iter()
10795 .map(|(k, v)| (k.clone(), v.clone()))
10796 .collect()
10797 } else if is_global {
10798 self.global_aliases
10799 .iter()
10800 .map(|(k, v)| (k.clone(), v.clone()))
10801 .collect()
10802 } else {
10803 self.aliases
10804 .iter()
10805 .map(|(k, v)| (k.clone(), v.clone()))
10806 .collect()
10807 };
10808 for (name, value) in alias_map {
10809 if list_form {
10810 println!("{}{}='{}'", prefix, name, value);
10811 } else {
10812 println!("{}='{}'", name, value);
10813 }
10814 }
10815 return 0;
10816 }
10817
10818 for arg in &positional_args {
10819 if let Some(eq_pos) = arg.find('=') {
10820 let name = &arg[..eq_pos];
10822 let value = &arg[eq_pos + 1..];
10823 if is_suffix {
10824 self.suffix_aliases
10825 .insert(name.to_string(), value.to_string());
10826 } else if is_global {
10827 self.global_aliases
10828 .insert(name.to_string(), value.to_string());
10829 } else {
10830 self.aliases.insert(name.to_string(), value.to_string());
10831 }
10832 } else if pattern_match {
10833 let pattern = arg.replace("*", ".*").replace("?", ".");
10835 let re = regex::Regex::new(&format!("^{}$", pattern));
10836
10837 let alias_map: &HashMap<String, String> = if is_suffix {
10838 &self.suffix_aliases
10839 } else if is_global {
10840 &self.global_aliases
10841 } else {
10842 &self.aliases
10843 };
10844
10845 let prefix = if is_suffix {
10846 "alias -s "
10847 } else if is_global {
10848 "alias -g "
10849 } else {
10850 "alias "
10851 };
10852
10853 for (name, value) in alias_map {
10854 let matches = if let Ok(ref r) = re {
10855 r.is_match(name)
10856 } else {
10857 name.contains(arg.as_str())
10858 };
10859 if matches {
10860 if list_form {
10861 println!("{}{}='{}'", prefix, name, value);
10862 } else {
10863 println!("{}='{}'", name, value);
10864 }
10865 }
10866 }
10867 } else {
10868 let value = if is_suffix {
10870 self.suffix_aliases.get(arg.as_str()).cloned()
10871 } else if is_global {
10872 self.global_aliases.get(arg.as_str()).cloned()
10873 } else {
10874 self.aliases.get(arg.as_str()).cloned()
10875 };
10876 if let Some(v) = value {
10877 println!("{}='{}'", arg, v);
10878 } else {
10879 eprintln!("zshrs: alias: {}: not found", arg);
10880 return 1;
10881 }
10882 }
10883 }
10884 0
10885 }
10886
10887 fn builtin_unalias(&mut self, args: &[String]) -> i32 {
10888 if args.is_empty() {
10889 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
10890 return 1;
10891 }
10892
10893 let mut is_global = false;
10894 let mut is_suffix = false;
10895 let mut remove_all = false;
10896 let mut positional_args = Vec::new();
10897
10898 for arg in args {
10899 if arg.starts_with('-') && arg != "-" {
10900 for ch in arg[1..].chars() {
10901 match ch {
10902 'a' => remove_all = true,
10903 'g' => is_global = true,
10904 's' => is_suffix = true,
10905 'm' => {} _ => {
10907 eprintln!("zshrs: unalias: bad option: -{}", ch);
10908 return 1;
10909 }
10910 }
10911 }
10912 } else {
10913 positional_args.push(arg.clone());
10914 }
10915 }
10916
10917 if remove_all {
10918 if is_suffix {
10919 self.suffix_aliases.clear();
10920 } else if is_global {
10921 self.global_aliases.clear();
10922 } else {
10923 self.aliases.clear();
10925 self.global_aliases.clear();
10926 self.suffix_aliases.clear();
10927 }
10928 return 0;
10929 }
10930
10931 if positional_args.is_empty() {
10932 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
10933 return 1;
10934 }
10935
10936 for name in positional_args {
10937 let removed = if is_suffix {
10938 self.suffix_aliases.remove(&name).is_some()
10939 } else if is_global {
10940 self.global_aliases.remove(&name).is_some()
10941 } else {
10942 self.aliases.remove(&name).is_some()
10943 };
10944 if !removed {
10945 eprintln!("zshrs: unalias: {}: not found", name);
10946 return 1;
10947 }
10948 }
10949 0
10950 }
10951
10952 fn builtin_set(&mut self, args: &[String]) -> i32 {
10953 if args.is_empty() {
10954 let mut vars: Vec<_> = self.variables.iter().collect();
10956 vars.sort_by_key(|(k, _)| *k);
10957 for (k, v) in vars {
10958 println!("{}={}", k, shell_quote(v));
10959 }
10960 let mut arrs: Vec<_> = self.arrays.iter().collect();
10962 arrs.sort_by_key(|(k, _)| *k);
10963 for (k, v) in arrs {
10964 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
10965 println!("{}=( {} )", k, quoted.join(" "));
10966 }
10967 return 0;
10968 }
10969
10970 if args.len() == 1 && args[0] == "+" {
10972 let mut names: Vec<_> = self.variables.keys().collect();
10973 names.extend(self.arrays.keys());
10974 names.sort();
10975 names.dedup();
10976 for name in names {
10977 println!("{}", name);
10978 }
10979 return 0;
10980 }
10981
10982 let mut iter = args.iter().peekable();
10983 let mut set_array: Option<bool> = None; let mut array_name: Option<String> = None;
10985 let mut sort_asc = false;
10986 let mut sort_desc = false;
10987
10988 while let Some(arg) = iter.next() {
10989 match arg.as_str() {
10990 "-o" => {
10991 if iter.peek().is_none()
10993 || iter
10994 .peek()
10995 .map(|s| s.starts_with('-') || s.starts_with('+'))
10996 .unwrap_or(false)
10997 {
10998 self.print_options_table();
10999 continue;
11000 }
11001 if let Some(opt) = iter.next() {
11002 let (name, enable) = Self::normalize_option_name(opt);
11003 self.options.insert(name, enable);
11004 }
11005 }
11006 "+o" => {
11007 if iter.peek().is_none()
11009 || iter
11010 .peek()
11011 .map(|s| s.starts_with('-') || s.starts_with('+'))
11012 .unwrap_or(false)
11013 {
11014 self.print_options_reentrant();
11015 continue;
11016 }
11017 if let Some(opt) = iter.next() {
11018 let (name, enable) = Self::normalize_option_name(opt);
11019 self.options.insert(name, !enable);
11020 }
11021 }
11022 "-A" => {
11023 set_array = Some(true);
11024 if let Some(name) = iter.next() {
11025 if !name.starts_with('-') && !name.starts_with('+') {
11026 array_name = Some(name.clone());
11027 }
11028 }
11029 if array_name.is_none() {
11030 let mut arrs: Vec<_> = self.arrays.iter().collect();
11032 arrs.sort_by_key(|(k, _)| *k);
11033 for (k, v) in arrs {
11034 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
11035 println!("{}=( {} )", k, quoted.join(" "));
11036 }
11037 return 0;
11038 }
11039 }
11040 "+A" => {
11041 set_array = Some(false);
11042 if let Some(name) = iter.next() {
11043 if !name.starts_with('-') && !name.starts_with('+') {
11044 array_name = Some(name.clone());
11045 }
11046 }
11047 if array_name.is_none() {
11048 let mut names: Vec<_> = self.arrays.keys().collect();
11050 names.sort();
11051 for name in names {
11052 println!("{}", name);
11053 }
11054 return 0;
11055 }
11056 }
11057 "-s" => sort_asc = true,
11058 "+s" => sort_desc = true,
11059 "-e" => {
11060 self.options.insert("errexit".to_string(), true);
11061 }
11062 "+e" => {
11063 self.options.insert("errexit".to_string(), false);
11064 }
11065 "-x" => {
11066 self.options.insert("xtrace".to_string(), true);
11067 }
11068 "+x" => {
11069 self.options.insert("xtrace".to_string(), false);
11070 }
11071 "-u" => {
11072 self.options.insert("nounset".to_string(), true);
11073 }
11074 "+u" => {
11075 self.options.insert("nounset".to_string(), false);
11076 }
11077 "-v" => {
11078 self.options.insert("verbose".to_string(), true);
11079 }
11080 "+v" => {
11081 self.options.insert("verbose".to_string(), false);
11082 }
11083 "-n" => {
11084 self.options.insert("exec".to_string(), false);
11085 }
11086 "+n" => {
11087 self.options.insert("exec".to_string(), true);
11088 }
11089 "-f" => {
11090 self.options.insert("glob".to_string(), false);
11091 }
11092 "+f" => {
11093 self.options.insert("glob".to_string(), true);
11094 }
11095 "-m" => {
11096 self.options.insert("monitor".to_string(), true);
11097 }
11098 "+m" => {
11099 self.options.insert("monitor".to_string(), false);
11100 }
11101 "-C" => {
11102 self.options.insert("clobber".to_string(), false);
11103 }
11104 "+C" => {
11105 self.options.insert("clobber".to_string(), true);
11106 }
11107 "-b" => {
11108 self.options.insert("notify".to_string(), true);
11109 }
11110 "+b" => {
11111 self.options.insert("notify".to_string(), false);
11112 }
11113 "--" => {
11114 let remaining: Vec<String> = iter.cloned().collect();
11115 if let Some(ref name) = array_name {
11116 let mut values = remaining;
11117 if sort_asc {
11118 values.sort();
11119 } else if sort_desc {
11120 values.sort();
11121 values.reverse();
11122 }
11123 if set_array == Some(true) {
11124 self.arrays.insert(name.clone(), values);
11125 } else {
11126 let arr = self.arrays.entry(name.clone()).or_default();
11128 for (i, v) in values.into_iter().enumerate() {
11129 if i < arr.len() {
11130 arr[i] = v;
11131 } else {
11132 arr.push(v);
11133 }
11134 }
11135 }
11136 } else if remaining.is_empty() {
11137 self.positional_params.clear();
11139 } else {
11140 let mut values = remaining;
11141 if sort_asc {
11142 values.sort();
11143 } else if sort_desc {
11144 values.sort();
11145 values.reverse();
11146 }
11147 self.positional_params = values;
11148 }
11149 return 0;
11150 }
11151 _ => {
11152 if arg.starts_with('-') && arg.len() > 1 {
11154 for c in arg[1..].chars() {
11155 match c {
11156 'e' => {
11157 self.options.insert("errexit".to_string(), true);
11158 }
11159 'x' => {
11160 self.options.insert("xtrace".to_string(), true);
11161 }
11162 'u' => {
11163 self.options.insert("nounset".to_string(), true);
11164 }
11165 'v' => {
11166 self.options.insert("verbose".to_string(), true);
11167 }
11168 'n' => {
11169 self.options.insert("exec".to_string(), false);
11170 }
11171 'f' => {
11172 self.options.insert("glob".to_string(), false);
11173 }
11174 'm' => {
11175 self.options.insert("monitor".to_string(), true);
11176 }
11177 'C' => {
11178 self.options.insert("clobber".to_string(), false);
11179 }
11180 'b' => {
11181 self.options.insert("notify".to_string(), true);
11182 }
11183 _ => {
11184 eprintln!("zshrs: set: -{}: invalid option", c);
11185 return 1;
11186 }
11187 }
11188 }
11189 continue;
11190 }
11191 if arg.starts_with('+') && arg.len() > 1 {
11192 for c in arg[1..].chars() {
11193 match c {
11194 'e' => {
11195 self.options.insert("errexit".to_string(), false);
11196 }
11197 'x' => {
11198 self.options.insert("xtrace".to_string(), false);
11199 }
11200 'u' => {
11201 self.options.insert("nounset".to_string(), false);
11202 }
11203 'v' => {
11204 self.options.insert("verbose".to_string(), false);
11205 }
11206 'n' => {
11207 self.options.insert("exec".to_string(), true);
11208 }
11209 'f' => {
11210 self.options.insert("glob".to_string(), true);
11211 }
11212 'm' => {
11213 self.options.insert("monitor".to_string(), false);
11214 }
11215 'C' => {
11216 self.options.insert("clobber".to_string(), true);
11217 }
11218 'b' => {
11219 self.options.insert("notify".to_string(), false);
11220 }
11221 _ => {
11222 eprintln!("zshrs: set: +{}: invalid option", c);
11223 return 1;
11224 }
11225 }
11226 }
11227 continue;
11228 }
11229 let mut values: Vec<String> =
11231 std::iter::once(arg.clone()).chain(iter.cloned()).collect();
11232 if sort_asc {
11233 values.sort();
11234 } else if sort_desc {
11235 values.sort();
11236 values.reverse();
11237 }
11238 if let Some(ref name) = array_name {
11239 if set_array == Some(true) {
11240 self.arrays.insert(name.clone(), values);
11241 } else {
11242 let arr = self.arrays.entry(name.clone()).or_default();
11243 for (i, v) in values.into_iter().enumerate() {
11244 if i < arr.len() {
11245 arr[i] = v;
11246 } else {
11247 arr.push(v);
11248 }
11249 }
11250 }
11251 } else {
11252 self.positional_params = values;
11253 }
11254 return 0;
11255 }
11256 }
11257 }
11258 0
11259 }
11260
11261 fn default_on_options() -> &'static [&'static str] {
11262 &[
11263 "aliases",
11264 "alwayslastprompt",
11265 "appendhistory",
11266 "autolist",
11267 "automenu",
11268 "autoparamkeys",
11269 "autoparamslash",
11270 "autoremoveslash",
11271 "badpattern",
11272 "banghist",
11273 "bareglobqual",
11274 "beep",
11275 "bgnice",
11276 "caseglob",
11277 "casematch",
11278 "checkjobs",
11279 "checkrunningjobs",
11280 "clobber",
11281 "debugbeforecmd",
11282 "equals",
11283 "evallineno",
11284 "exec",
11285 "flowcontrol",
11286 "functionargzero",
11287 "glob",
11288 "globalexport",
11289 "globalrcs",
11290 "hashcmds",
11291 "hashdirs",
11292 "hashlistall",
11293 "histbeep",
11294 "histsavebycopy",
11295 "hup",
11296 "interactive",
11297 "listambiguous",
11298 "listbeep",
11299 "listtypes",
11300 "monitor",
11301 "multibyte",
11302 "multifuncdef",
11303 "multios",
11304 "nomatch",
11305 "notify",
11306 "promptcr",
11307 "promptpercent",
11308 "promptsp",
11309 "rcs",
11310 "shinstdin",
11311 "shortloops",
11312 "unset",
11313 "zle",
11314 ]
11315 }
11316
11317 fn print_options_table(&self) {
11318 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
11319 opts.sort();
11320 let defaults_on = Self::default_on_options();
11321 for &opt in &opts {
11322 let enabled = self.options.get(opt).copied().unwrap_or(false);
11323 let is_default_on = defaults_on.contains(&opt);
11324 let (display_name, display_state) = if is_default_on {
11327 (format!("no{}", opt), if enabled { "off" } else { "on" })
11328 } else {
11329 (opt.to_string(), if enabled { "on" } else { "off" })
11330 };
11331 println!("{:<22}{}", display_name, display_state);
11332 }
11333 }
11334
11335 fn print_options_reentrant(&self) {
11336 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
11337 opts.sort();
11338 let defaults_on = Self::default_on_options();
11339 for &opt in &opts {
11340 let enabled = self.options.get(opt).copied().unwrap_or(false);
11341 let is_default_on = defaults_on.contains(&opt);
11342 let (display_name, use_minus) = if is_default_on {
11344 (format!("no{}", opt), !enabled)
11345 } else {
11346 (opt.to_string(), enabled)
11347 };
11348 if use_minus {
11349 println!("set -o {}", display_name);
11350 } else {
11351 println!("set +o {}", display_name);
11352 }
11353 }
11354 }
11355
11356 fn builtin_caller(&self, args: &[String]) -> i32 {
11358 let depth: usize = args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
11359 if depth == 0 {
11362 println!("1 main");
11363 } else {
11364 println!("{} main", depth);
11365 }
11366 0
11367 }
11368
11369 fn builtin_doctor(&self, _args: &[String]) -> i32 {
11371 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
11372 let red = |s: &str| format!("\x1b[31m{}\x1b[0m", s);
11373 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
11374 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
11375 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
11376
11377 println!("{}", bold("zshrs doctor"));
11378 println!("{}", dim(&"=".repeat(60)));
11379 println!();
11380
11381 println!("{}", bold("Environment"));
11383 println!(" version: zshrs {}", env!("CARGO_PKG_VERSION"));
11384 println!(" pid: {}", std::process::id());
11385 let cwd = env::current_dir()
11386 .map(|p| p.to_string_lossy().to_string())
11387 .unwrap_or_else(|_| "?".to_string());
11388 println!(" cwd: {}", cwd);
11389 println!(" shell: {}", env::var("SHELL").unwrap_or_else(|_| "?".to_string()));
11390 println!(" pool size: {}", self.worker_pool.size());
11391 println!(" pool done: {} tasks completed", self.worker_pool.completed());
11392 println!(" pool queue: {} pending", self.worker_pool.queue_depth());
11393 println!();
11394
11395 println!("{}", bold("Config"));
11397 let config_path = crate::config::config_path();
11398 if config_path.exists() {
11399 println!(" {} {}", green("*"), config_path.display());
11400 } else {
11401 println!(" {} {} {}", dim("-"), config_path.display(), dim("(using defaults)"));
11402 }
11403 println!();
11404
11405 println!("{}", bold("PATH"));
11407 let path_var = env::var("PATH").unwrap_or_default();
11408 let path_dirs: Vec<&str> = path_var.split(':').filter(|s| !s.is_empty()).collect();
11409 let path_ok = path_dirs.iter().filter(|d| std::path::Path::new(d).is_dir()).count();
11410 let path_missing = path_dirs.len() - path_ok;
11411 println!(" directories: {} total, {} {}, {} {}",
11412 path_dirs.len(),
11413 path_ok, green("valid"),
11414 path_missing, if path_missing > 0 { red("missing") } else { green("missing") },
11415 );
11416 println!(" hash table: {} entries", self.command_hash.len());
11417 println!();
11418
11419 println!("{}", bold("FPATH"));
11421 println!(" directories: {}", self.fpath.len());
11422 let fpath_ok = self.fpath.iter().filter(|d| d.is_dir()).count();
11423 let fpath_missing = self.fpath.len() - fpath_ok;
11424 if fpath_missing > 0 {
11425 println!(" {} {} missing fpath directories", red("!"), fpath_missing);
11426 }
11427 println!(" functions: {} loaded", self.functions.len());
11428 println!(" autoload: {} pending", self.autoload_pending.len());
11429 println!();
11430
11431 println!("{}", bold("SQLite Caches"));
11433 if let Some(ref engine) = self.history {
11434 let count = engine.count().unwrap_or(0);
11435 println!(" history: {} entries {}", count, green("OK"));
11436 } else {
11437 println!(" history: {}", yellow("not initialized"));
11438 }
11439
11440 if let Some(ref cache) = self.compsys_cache {
11441 let count = compsys::cache_entry_count(cache);
11442 println!(" compsys: {} completions {}", count, green("OK"));
11443
11444 if let Ok(missing) = cache.get_autoloads_missing_bytecode() {
11446 if missing.is_empty() {
11447 println!(" bytecode cache: {}", green("all functions compiled to bytecode"));
11448 } else {
11449 println!(" bytecode cache: {} functions {}", missing.len(), yellow("missing bytecode blobs"));
11450 }
11451 }
11452 } else {
11453 println!(" compsys: {}", yellow("no cache"));
11454 }
11455
11456 if let Some(ref cache) = self.plugin_cache {
11457 let (plugins, functions) = cache.stats();
11458 println!(" plugins: {} plugins, {} cached functions {}", plugins, functions, green("OK"));
11459 } else {
11460 println!(" plugins: {}", yellow("no cache"));
11461 }
11462 println!();
11463
11464 println!("{}", bold("Shell State"));
11466 println!(" aliases: {}", self.aliases.len());
11467 println!(" global: {} aliases", self.global_aliases.len());
11468 println!(" suffix: {} aliases", self.suffix_aliases.len());
11469 println!(" variables: {}", self.variables.len());
11470 println!(" arrays: {}", self.arrays.len());
11471 println!(" assoc: {}", self.assoc_arrays.len());
11472 println!(" options: {} set", self.options.iter().filter(|(_, v)| **v).count());
11473 println!(" traps: {} active", self.traps.len());
11474 println!(" hooks: {} registered", self.hook_functions.values().map(|v| v.len()).sum::<usize>());
11475 println!();
11476
11477 println!("{}", bold("Log"));
11479 let log_path = crate::log::log_path();
11480 if log_path.exists() {
11481 let size = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);
11482 println!(" {} {} bytes", log_path.display(), size);
11483 } else {
11484 println!(" {}", dim("no log file yet"));
11485 }
11486 println!();
11487
11488 println!("{}", bold("Profiling"));
11490 println!(" chrome tracing: {}", if crate::log::profiling_enabled() { green("enabled") } else { dim("disabled") });
11491 println!(" flamegraph: {}", if crate::log::flamegraph_enabled() { green("enabled") } else { dim("disabled") });
11492 println!(" prometheus: {}", if crate::log::prometheus_enabled() { green("enabled") } else { dim("disabled") });
11493 println!();
11494
11495 0
11496 }
11497
11498 fn builtin_dbview(&self, args: &[String]) -> i32 {
11511 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
11512 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
11513 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
11514 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
11515 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
11516
11517 if args.is_empty() {
11518 println!("{}", bold("zshrs SQLite caches"));
11520 println!();
11521
11522 if let Some(ref cache) = self.compsys_cache {
11523 println!(" {} {}", bold("compsys.db"), dim("(completion cache)"));
11524 if let Ok(n) = cache.count_table("autoloads") {
11525 let bc_count = cache.count_table_where("autoloads", "bytecode IS NOT NULL").unwrap_or(0);
11526 println!(" autoloads: {:>6} rows ({} compiled)", n, bc_count);
11527 }
11528 if let Ok(n) = cache.count_table("comps") { println!(" comps: {:>6} rows", n); }
11529 if let Ok(n) = cache.count_table("services") { println!(" services: {:>6} rows", n); }
11530 if let Ok(n) = cache.count_table("patcomps") { println!(" patcomps: {:>6} rows", n); }
11531 if let Ok(n) = cache.count_table("executables") { println!(" executables: {:>6} rows", n); }
11532 if let Ok(n) = cache.count_table("zstyles") { println!(" zstyles: {:>6} rows", n); }
11533 println!();
11534 }
11535
11536 if let Some(ref engine) = self.history {
11537 println!(" {} {}", bold("history.db"), dim("(command history)"));
11538 if let Ok(n) = engine.count() { println!(" entries: {:>6} rows", n); }
11539 println!();
11540 }
11541
11542 if let Some(ref cache) = self.plugin_cache {
11543 let (plugins, functions) = cache.stats();
11544 println!(" {} {}", bold("plugins.db"), dim("(plugin source cache)"));
11545 println!(" plugins: {:>6} rows", plugins);
11546 println!(" functions: {:>6} rows", functions);
11547 println!();
11548 }
11549
11550 println!(" Usage: {} <table> [name] [--count]", cyan("dbview"));
11551 return 0;
11552 }
11553
11554 let table = args[0].as_str();
11555 let filter = args.get(1).map(|s| s.as_str());
11556 let count_only = args.iter().any(|a| a == "--count" || a == "-c");
11557
11558 match table {
11559 "autoloads" => {
11560 let Some(ref cache) = self.compsys_cache else {
11561 eprintln!("dbview: no compsys cache");
11562 return 1;
11563 };
11564
11565 if count_only {
11566 let n = cache.count_table("autoloads").unwrap_or(0);
11567 println!("{}", n);
11568 return 0;
11569 }
11570
11571 if let Some(name) = filter {
11572 match cache.get_autoload(name) {
11574 Ok(Some(stub)) => {
11575 println!("{}", bold(&format!("autoload: {}", name)));
11576 println!(" source: {}", stub.source);
11577 println!(" body: {} bytes", stub.body.as_ref().map(|b| b.len()).unwrap_or(0));
11578 match cache.get_autoload_bytecode(name) {
11579 Ok(Some(blob)) => println!(" bytecode: {} {} bytes", green("YES"), blob.len()),
11580 _ => println!(" bytecode: {}", yellow("NULL")),
11581 }
11582 if let Some(ref body) = stub.body {
11584 println!(" preview:");
11585 for (i, line) in body.lines().take(10).enumerate() {
11586 println!(" {:>3}: {}", i + 1, dim(line));
11587 }
11588 let total = body.lines().count();
11589 if total > 10 {
11590 println!(" {} ({} more lines)", dim("..."), total - 10);
11591 }
11592 }
11593 }
11594 _ => {
11595 eprintln!("dbview: autoload '{}' not found", name);
11596 return 1;
11597 }
11598 }
11599 return 0;
11600 }
11601
11602 let conn = &cache.conn();
11604 match conn.prepare("SELECT name, source, length(body), length(bytecode) FROM autoloads ORDER BY name LIMIT 200") {
11605 Ok(mut stmt) => {
11606 let rows = stmt.query_map([], |row| {
11607 Ok((
11608 row.get::<_, String>(0)?,
11609 row.get::<_, String>(1)?,
11610 row.get::<_, Option<i64>>(2)?,
11611 row.get::<_, Option<i64>>(3)?,
11612 ))
11613 });
11614 if let Ok(rows) = rows {
11615 println!("{:<40} {:>8} {:>8} {}", bold("NAME"), bold("BODY"), bold("BYTECODE"), bold("SOURCE"));
11616 let mut count = 0;
11617 for row in rows.flatten() {
11618 let (name, source, body_len, ast_len) = row;
11619 let ast_str = match ast_len {
11620 Some(n) => green(&format!("{:>8}", n)),
11621 None => yellow(&format!("{:>8}", "NULL")),
11622 };
11623 let body_str = match body_len {
11624 Some(n) => format!("{:>8}", n),
11625 None => dim("NULL").to_string(),
11626 };
11627 let src_short = if source.len() > 50 {
11629 format!("...{}", &source[source.len() - 47..])
11630 } else {
11631 source
11632 };
11633 println!("{:<40} {} {} {}", name, body_str, ast_str, dim(&src_short));
11634 count += 1;
11635 }
11636 println!("\n{} rows shown (LIMIT 200)", count);
11637 }
11638 }
11639 Err(e) => {
11640 eprintln!("dbview: query failed: {}", e);
11641 return 1;
11642 }
11643 }
11644 }
11645
11646 "comps" => {
11647 let Some(ref cache) = self.compsys_cache else {
11648 eprintln!("dbview: no compsys cache");
11649 return 1;
11650 };
11651 if count_only {
11652 println!("{}", cache.count_table("comps").unwrap_or(0));
11653 return 0;
11654 }
11655 let conn = cache.conn();
11656 let query = if let Some(pat) = filter {
11657 format!("SELECT command, function FROM comps WHERE command LIKE '%{}%' ORDER BY command LIMIT 100", pat)
11658 } else {
11659 "SELECT command, function FROM comps ORDER BY command LIMIT 100".to_string()
11660 };
11661 match conn.prepare(&query) {
11662 Ok(mut stmt) => {
11663 println!("{:<40} {}", bold("COMMAND"), bold("FUNCTION"));
11664 let rows = stmt.query_map([], |row| {
11665 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
11666 });
11667 if let Ok(rows) = rows {
11668 for row in rows.flatten() {
11669 println!("{:<40} {}", row.0, cyan(&row.1));
11670 }
11671 }
11672 }
11673 Err(e) => { eprintln!("dbview: {}", e); return 1; }
11674 }
11675 }
11676
11677 "executables" => {
11678 let Some(ref cache) = self.compsys_cache else {
11679 eprintln!("dbview: no compsys cache");
11680 return 1;
11681 };
11682 if count_only {
11683 println!("{}", cache.count_table("executables").unwrap_or(0));
11684 return 0;
11685 }
11686 let conn = cache.conn();
11687 let query = if let Some(pat) = filter {
11688 format!("SELECT name, path FROM executables WHERE name LIKE '%{}%' ORDER BY name LIMIT 100", pat)
11689 } else {
11690 "SELECT name, path FROM executables ORDER BY name LIMIT 100".to_string()
11691 };
11692 match conn.prepare(&query) {
11693 Ok(mut stmt) => {
11694 println!("{:<30} {}", bold("NAME"), bold("PATH"));
11695 let rows = stmt.query_map([], |row| {
11696 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
11697 });
11698 if let Ok(rows) = rows {
11699 for row in rows.flatten() {
11700 println!("{:<30} {}", row.0, dim(&row.1));
11701 }
11702 }
11703 }
11704 Err(e) => { eprintln!("dbview: {}", e); return 1; }
11705 }
11706 }
11707
11708 "history" => {
11709 let Some(ref engine) = self.history else {
11710 eprintln!("dbview: no history engine");
11711 return 1;
11712 };
11713 if count_only {
11714 println!("{}", engine.count().unwrap_or(0));
11715 return 0;
11716 }
11717 if let Some(pat) = filter {
11718 if let Ok(entries) = engine.search(pat, 20) {
11719 for e in entries {
11720 println!(" {} {} {}", dim(&e.timestamp.to_string()), cyan(&e.command), dim(&format!("[{}]", e.exit_code.unwrap_or(0))));
11721 }
11722 }
11723 } else if let Ok(entries) = engine.recent(20) {
11724 for e in entries {
11725 println!(" {} {} {}", dim(&e.timestamp.to_string()), cyan(&e.command), dim(&format!("[{}]", e.exit_code.unwrap_or(0))));
11726 }
11727 }
11728 }
11729
11730 "plugins" => {
11731 let Some(ref cache) = self.plugin_cache else {
11732 eprintln!("dbview: no plugin cache");
11733 return 1;
11734 };
11735 let (plugins, functions) = cache.stats();
11736 println!("{} plugins, {} cached functions", plugins, functions);
11737 }
11738
11739 _ => {
11740 eprintln!("dbview: unknown table '{}'. Available: autoloads, comps, executables, history, plugins", table);
11741 return 1;
11742 }
11743 }
11744
11745 0
11746 }
11747
11748 fn builtin_profile(&mut self, args: &[String]) -> i32 {
11761 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
11762 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
11763 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
11764 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
11765
11766 if args.is_empty() {
11767 println!("Usage: profile {{ commands }}");
11768 println!(" profile -s 'script string'");
11769 println!(" profile -f function_name [args...]");
11770 println!(" profile --clear");
11771 println!(" profile --dump");
11772 return 0;
11773 }
11774
11775 if args[0] == "--clear" {
11776 self.profiler = crate::zprof::Profiler::new();
11777 println!("profile data cleared");
11778 return 0;
11779 }
11780
11781 if args[0] == "--dump" {
11782 let (_, output) = crate::zprof::builtin_zprof(
11783 &mut self.profiler,
11784 &crate::zprof::ZprofOptions { clear: false },
11785 );
11786 if !output.is_empty() {
11787 print!("{}", output);
11788 } else {
11789 println!("{}", dim("no profile data"));
11790 }
11791 return 0;
11792 }
11793
11794 let code = if args[0] == "-s" {
11796 if args.len() < 2 {
11798 eprintln!("profile: -s requires a script string");
11799 return 1;
11800 }
11801 args[1..].join(" ")
11802 } else if args[0] == "-f" {
11803 if args.len() < 2 {
11805 eprintln!("profile: -f requires a function name");
11806 return 1;
11807 }
11808 args[1..].join(" ")
11809 } else {
11810 args.join(" ")
11812 };
11813
11814 let was_enabled = self.profiling_enabled;
11816 self.profiling_enabled = true;
11817 self.profiler = crate::zprof::Profiler::new(); let t0 = std::time::Instant::now();
11820 let result = self.execute_script(&code);
11821 let elapsed = t0.elapsed();
11822 let status = match result {
11823 Ok(s) => s,
11824 Err(e) => {
11825 eprintln!("profile: {}", e);
11826 1
11827 }
11828 };
11829
11830 println!();
11832 println!("{}", bold("profile results"));
11833 println!("{}", dim(&"─".repeat(60)));
11834 let dur_str = if elapsed.as_secs() > 0 {
11835 format!("{:.3}s", elapsed.as_secs_f64())
11836 } else if elapsed.as_millis() > 0 {
11837 format!("{:.3}ms", elapsed.as_secs_f64() * 1000.0)
11838 } else {
11839 format!("{:.1}µs", elapsed.as_secs_f64() * 1_000_000.0)
11840 };
11841 println!(" total: {}", cyan(&dur_str));
11842 println!(" status: {}", status);
11843 println!();
11844
11845 let (_, output) = crate::zprof::builtin_zprof(
11847 &mut self.profiler,
11848 &crate::zprof::ZprofOptions { clear: false },
11849 );
11850 if !output.is_empty() {
11851 println!("{}", bold("function breakdown"));
11852 print!("{}", output);
11853 }
11854
11855 println!();
11857 println!(" {} set ZSHRS_LOG=trace for per-command tracing", yellow("tip:"));
11858 println!(" {} output: {}", dim("log"), dim(&crate::log::log_path().display().to_string()));
11859
11860 self.profiling_enabled = was_enabled;
11861 status
11862 }
11863
11864 fn run_intercepts(
11871 &mut self,
11872 cmd_name: &str,
11873 full_cmd: &str,
11874 args: &[String],
11875 ) -> Option<Result<i32, String>> {
11876 let matching: Vec<Intercept> = self
11878 .intercepts
11879 .iter()
11880 .filter(|i| intercept_matches(&i.pattern, cmd_name, full_cmd))
11881 .cloned()
11882 .collect();
11883
11884 if matching.is_empty() {
11885 return None;
11886 }
11887
11888 self.variables.insert("INTERCEPT_NAME".to_string(), cmd_name.to_string());
11890 self.variables.insert("INTERCEPT_ARGS".to_string(), args.join(" "));
11891 self.variables.insert("INTERCEPT_CMD".to_string(), full_cmd.to_string());
11892
11893 for advice in matching.iter().filter(|i| matches!(i.kind, AdviceKind::Before)) {
11895 let _ = self.execute_advice(&advice.code);
11896 }
11897
11898 let around = matching.iter().find(|i| matches!(i.kind, AdviceKind::Around));
11900
11901 let t0 = std::time::Instant::now();
11902
11903 let result = if let Some(advice) = around {
11904 self.variables.insert("__intercept_proceed".to_string(), "0".to_string());
11907 let advice_result = self.execute_advice(&advice.code);
11908
11909 let proceeded = self.variables.get("__intercept_proceed")
11911 .map(|v| v == "1")
11912 .unwrap_or(false);
11913
11914 if proceeded {
11915 advice_result
11917 } else {
11918 advice_result
11920 }
11921 } else {
11922 let has_after = matching.iter().any(|i| matches!(i.kind, AdviceKind::After));
11927 if !has_after {
11928 return None;
11930 }
11931
11932 self.run_original_command(cmd_name, args)
11934 };
11935
11936 let elapsed = t0.elapsed();
11937
11938 let ms = elapsed.as_secs_f64() * 1000.0;
11940 self.variables.insert("INTERCEPT_MS".to_string(), format!("{:.3}", ms));
11941 self.variables.insert("INTERCEPT_US".to_string(), format!("{:.0}", ms * 1000.0));
11942
11943 for advice in matching.iter().filter(|i| matches!(i.kind, AdviceKind::After)) {
11945 let _ = self.execute_advice(&advice.code);
11946 }
11947
11948 self.variables.remove("INTERCEPT_NAME");
11950 self.variables.remove("INTERCEPT_ARGS");
11951 self.variables.remove("INTERCEPT_CMD");
11952 self.variables.remove("INTERCEPT_MS");
11953 self.variables.remove("INTERCEPT_US");
11954 self.variables.remove("__intercept_proceed");
11955
11956 Some(result)
11957 }
11958
11959 fn execute_advice(&mut self, code: &str) -> Result<i32, String> {
11963 let code = code.trim();
11964 if code.starts_with('@') {
11965 let stryke_code = code.trim_start_matches('@').trim();
11966 if let Some(status) = crate::try_stryke_dispatch(stryke_code) {
11967 self.last_status = status;
11968 return Ok(status);
11969 }
11970 }
11972 self.execute_script(code)
11973 }
11974
11975 fn run_original_command(&mut self, cmd_name: &str, args: &[String]) -> Result<i32, String> {
11976 if let Some(func) = self.functions.get(cmd_name).cloned() {
11978 return self.call_function(&func, args);
11979 }
11980 if self.maybe_autoload(cmd_name) {
11981 if let Some(func) = self.functions.get(cmd_name).cloned() {
11982 return self.call_function(&func, args);
11983 }
11984 }
11985 self.execute_external(cmd_name, &args.to_vec(), &[])
11987 }
11988
11989 fn builtin_intercept(&mut self, args: &[String]) -> i32 {
11999 if args.is_empty() {
12000 println!("Usage: intercept <before|after|around> <pattern> {{ code }}");
12001 println!(" intercept list | remove <id> | clear");
12002 return 0;
12003 }
12004
12005 match args[0].as_str() {
12006 "list" => {
12007 if self.intercepts.is_empty() {
12008 println!("no intercepts registered");
12009 } else {
12010 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12011 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
12012 println!("{:>4} {:<8} {:<20} {}", bold("ID"), bold("KIND"), bold("PATTERN"), bold("CODE"));
12013 for i in &self.intercepts {
12014 let kind = match i.kind {
12015 AdviceKind::Before => "before",
12016 AdviceKind::After => "after",
12017 AdviceKind::Around => "around",
12018 };
12019 let code_preview = if i.code.len() > 40 {
12020 format!("{}...", &i.code[..37])
12021 } else {
12022 i.code.clone()
12023 };
12024 println!("{:>4} {:<8} {:<20} {}", cyan(&i.id.to_string()), kind, i.pattern, code_preview);
12025 }
12026 }
12027 0
12028 }
12029 "clear" => {
12030 let count = self.intercepts.len();
12031 self.intercepts.clear();
12032 println!("cleared {} intercepts", count);
12033 0
12034 }
12035 "remove" => {
12036 if args.len() < 2 {
12037 eprintln!("intercept remove: requires ID");
12038 return 1;
12039 }
12040 if let Ok(id) = args[1].parse::<u32>() {
12041 let before = self.intercepts.len();
12042 self.intercepts.retain(|i| i.id != id);
12043 if self.intercepts.len() < before {
12044 println!("removed intercept {}", id);
12045 0
12046 } else {
12047 eprintln!("intercept: no intercept with ID {}", id);
12048 1
12049 }
12050 } else {
12051 eprintln!("intercept remove: invalid ID");
12052 1
12053 }
12054 }
12055 "before" | "after" | "around" => {
12056 let kind = match args[0].as_str() {
12057 "before" => AdviceKind::Before,
12058 "after" => AdviceKind::After,
12059 "around" => AdviceKind::Around,
12060 _ => unreachable!(),
12061 };
12062
12063 if args.len() < 3 {
12064 eprintln!("intercept {}: requires <pattern> {{ code }}", args[0]);
12065 return 1;
12066 }
12067
12068 let pattern = args[1].clone();
12069 let code = args[2..].join(" ");
12071 let code = code.trim().to_string();
12073 let code = if code.starts_with('{') && code.ends_with('}') {
12074 code[1..code.len() - 1].trim().to_string()
12075 } else {
12076 code
12077 };
12078
12079 let id = self.intercepts.iter().map(|i| i.id).max().unwrap_or(0) + 1;
12080 self.intercepts.push(Intercept {
12081 pattern,
12082 kind: kind.clone(),
12083 code: code.clone(),
12084 id,
12085 });
12086
12087 let kind_str = match kind {
12088 AdviceKind::Before => "before",
12089 AdviceKind::After => "after",
12090 AdviceKind::Around => "around",
12091 };
12092 println!("intercept #{}: {} {} → {}", id, kind_str, self.intercepts.last().unwrap().pattern,
12093 if code.len() > 50 { format!("{}...", &code[..47]) } else { code });
12094 0
12095 }
12096 _ => {
12097 eprintln!("intercept: unknown subcommand '{}'. Use before|after|around|list|remove|clear", args[0]);
12098 1
12099 }
12100 }
12101 }
12102
12103 fn builtin_intercept_proceed(&mut self, _args: &[String]) -> i32 {
12105 self.variables.insert("__intercept_proceed".to_string(), "1".to_string());
12106 let cmd_name = self.variables.get("INTERCEPT_NAME").cloned().unwrap_or_default();
12108 let args_str = self.variables.get("INTERCEPT_ARGS").cloned().unwrap_or_default();
12109 let args: Vec<String> = if args_str.is_empty() {
12110 Vec::new()
12111 } else {
12112 args_str.split_whitespace().map(|s| s.to_string()).collect()
12113 };
12114 match self.run_original_command(&cmd_name, &args) {
12115 Ok(status) => status,
12116 Err(e) => {
12117 eprintln!("intercept_proceed: {}", e);
12118 1
12119 }
12120 }
12121 }
12122
12123 fn builtin_async(&mut self, args: &[String]) -> i32 {
12136 if args.is_empty() {
12137 eprintln!("async: requires a command string");
12138 return 1;
12139 }
12140
12141 let code = args.join(" ");
12142 let id = self.next_async_id;
12143 self.next_async_id += 1;
12144
12145 let (tx, rx) = crossbeam_channel::bounded::<(i32, String)>(1);
12146 let pool = std::sync::Arc::clone(&self.worker_pool);
12147
12148 pool.submit(move || {
12149 use std::process::{Command, Stdio};
12151 let output = Command::new("sh")
12152 .args(["-c", &code])
12153 .stdout(Stdio::piped())
12154 .stderr(Stdio::inherit())
12155 .output();
12156 match output {
12157 Ok(out) => {
12158 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
12159 let status = out.status.code().unwrap_or(1);
12160 let _ = tx.send((status, stdout));
12161 }
12162 Err(_) => {
12163 let _ = tx.send((127, String::new()));
12164 }
12165 }
12166 });
12167
12168 self.async_jobs.insert(id, rx);
12169 println!("{}", id);
12171 0
12172 }
12173
12174 fn builtin_await(&mut self, args: &[String]) -> i32 {
12181 if args.is_empty() {
12182 eprintln!("await: requires a job ID");
12183 return 1;
12184 }
12185
12186 let id: u32 = match args[0].parse() {
12187 Ok(n) => n,
12188 Err(_) => {
12189 eprintln!("await: invalid job ID '{}'", args[0]);
12190 return 1;
12191 }
12192 };
12193
12194 let rx = match self.async_jobs.remove(&id) {
12195 Some(rx) => rx,
12196 None => {
12197 eprintln!("await: no async job with ID {}", id);
12198 return 1;
12199 }
12200 };
12201
12202 match rx.recv() {
12204 Ok((status, stdout)) => {
12205 if !stdout.is_empty() {
12206 print!("{}", stdout);
12207 }
12208 self.last_status = status;
12209 status
12210 }
12211 Err(_) => {
12212 eprintln!("await: job {} died without result", id);
12213 1
12214 }
12215 }
12216 }
12217
12218 fn builtin_pmap(&mut self, args: &[String]) -> i32 {
12227 if args.len() < 2 {
12228 eprintln!("pmap: requires 'command {{}}' followed by arguments");
12229 return 1;
12230 }
12231
12232 let template = &args[0];
12233 let items = &args[1..];
12234
12235 let mut receivers = Vec::with_capacity(items.len());
12237 for item in items {
12238 let cmd = template.replace("{}", item);
12239 let rx = self.worker_pool.submit_with_result(move || {
12240 use std::process::{Command, Stdio};
12241 let output = Command::new("sh")
12242 .args(["-c", &cmd])
12243 .stdout(Stdio::piped())
12244 .stderr(Stdio::inherit())
12245 .output();
12246 match output {
12247 Ok(out) => (
12248 out.status.code().unwrap_or(1),
12249 String::from_utf8_lossy(&out.stdout).to_string(),
12250 ),
12251 Err(_) => (127, String::new()),
12252 }
12253 });
12254 receivers.push(rx);
12255 }
12256
12257 let mut any_fail = false;
12259 for rx in receivers {
12260 if let Ok((status, stdout)) = rx.recv() {
12261 if !stdout.is_empty() {
12262 print!("{}", stdout);
12263 }
12264 if status != 0 {
12265 any_fail = true;
12266 }
12267 }
12268 }
12269
12270 if any_fail { 1 } else { 0 }
12271 }
12272
12273 fn builtin_pgrep(&mut self, args: &[String]) -> i32 {
12280 if args.len() < 2 {
12281 eprintln!("pgrep: requires 'test_command {{}}' followed by arguments");
12282 return 1;
12283 }
12284
12285 let template = &args[0];
12286 let items = &args[1..];
12287
12288 let mut receivers: Vec<(String, crossbeam_channel::Receiver<bool>)> = Vec::with_capacity(items.len());
12289 for item in items {
12290 let cmd = template.replace("{}", item);
12291 let rx = self.worker_pool.submit_with_result(move || {
12292 use std::process::{Command, Stdio};
12293 Command::new("sh")
12294 .args(["-c", &cmd])
12295 .stdout(Stdio::null())
12296 .stderr(Stdio::null())
12297 .status()
12298 .map(|s| s.success())
12299 .unwrap_or(false)
12300 });
12301 receivers.push((item.clone(), rx));
12302 }
12303
12304 for (item, rx) in receivers {
12305 if let Ok(true) = rx.recv() {
12306 println!("{}", item);
12307 }
12308 }
12309
12310 0
12311 }
12312
12313 fn builtin_peach(&mut self, args: &[String]) -> i32 {
12320 if args.len() < 2 {
12321 eprintln!("peach: requires 'command {{}}' followed by arguments");
12322 return 1;
12323 }
12324
12325 let template = &args[0];
12326 let items = &args[1..];
12327
12328 let (tx, rx) = crossbeam_channel::unbounded::<(String, i32, String)>();
12329
12330 for item in items {
12331 let cmd = template.replace("{}", item);
12332 let item_clone = item.clone();
12333 let tx = tx.clone();
12334 self.worker_pool.submit(move || {
12335 use std::process::{Command, Stdio};
12336 let output = Command::new("sh")
12337 .args(["-c", &cmd])
12338 .stdout(Stdio::piped())
12339 .stderr(Stdio::inherit())
12340 .output();
12341 match output {
12342 Ok(out) => {
12343 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
12344 let status = out.status.code().unwrap_or(1);
12345 let _ = tx.send((item_clone, status, stdout));
12346 }
12347 Err(_) => {
12348 let _ = tx.send((item_clone, 127, String::new()));
12349 }
12350 }
12351 });
12352 }
12353 drop(tx);
12354
12355 let mut any_fail = false;
12356 for (_, status, stdout) in rx {
12357 if !stdout.is_empty() {
12358 print!("{}", stdout);
12359 }
12360 if status != 0 {
12361 any_fail = true;
12362 }
12363 }
12364
12365 if any_fail { 1 } else { 0 }
12366 }
12367
12368 fn builtin_barrier(&mut self, args: &[String]) -> i32 {
12375 if args.is_empty() {
12376 eprintln!("barrier: requires commands separated by :::");
12377 return 1;
12378 }
12379
12380 let mut commands: Vec<String> = Vec::new();
12382 let mut current = String::new();
12383 for arg in args {
12384 if arg == ":::" {
12385 if !current.is_empty() {
12386 commands.push(current.trim().to_string());
12387 current.clear();
12388 }
12389 } else {
12390 if !current.is_empty() {
12391 current.push(' ');
12392 }
12393 current.push_str(arg);
12394 }
12395 }
12396 if !current.is_empty() {
12397 commands.push(current.trim().to_string());
12398 }
12399
12400 if commands.is_empty() {
12401 return 0;
12402 }
12403
12404 let mut receivers = Vec::with_capacity(commands.len());
12406 for cmd in &commands {
12407 let cmd = cmd.clone();
12408 let rx = self.worker_pool.submit_with_result(move || {
12409 use std::process::{Command, Stdio};
12410 Command::new("sh")
12411 .args(["-c", &cmd])
12412 .stdout(Stdio::inherit())
12413 .stderr(Stdio::inherit())
12414 .status()
12415 .map(|s| s.code().unwrap_or(1))
12416 .unwrap_or(127)
12417 });
12418 receivers.push(rx);
12419 }
12420
12421 let mut worst = 0i32;
12423 for rx in receivers {
12424 if let Ok(status) = rx.recv() {
12425 if status > worst {
12426 worst = status;
12427 }
12428 }
12429 }
12430
12431 self.last_status = worst;
12432 worst
12433 }
12434
12435 fn builtin_help(&self, args: &[String]) -> i32 {
12437 if args.is_empty() {
12438 println!("zshrs shell builtins:");
12439 println!("");
12440 println!(" alias, bg, bind, break, builtin, cd, command, continue,");
12441 println!(" declare, dirs, disown, echo, enable, eval, exec, exit,");
12442 println!(" export, false, fc, fg, getopts, hash, help, history,");
12443 println!(" jobs, kill, let, local, logout, popd, printf, pushd,");
12444 println!(" pwd, read, readonly, return, set, shift, shopt, source,");
12445 println!(" suspend, test, times, trap, true, type, typeset, ulimit,");
12446 println!(" umask, unalias, unset, wait, whence, where, which");
12447 println!("");
12448 println!("Type 'help name' for more information about 'name'.");
12449 return 0;
12450 }
12451
12452 let cmd = &args[0];
12453 match cmd.as_str() {
12454 "cd" => println!("cd: cd [-L|-P] [dir]\n Change the shell working directory."),
12455 "echo" => println!("echo: echo [-neE] [arg ...]\n Write arguments to standard output."),
12456 "export" => println!("export: export [-fn] [name[=value] ...]\n Set export attribute for shell variables."),
12457 "alias" => println!("alias: alias [-p] [name[=value] ...]\n Define or display aliases."),
12458 "history" => println!("history: history [-c] [-d offset] [n]\n Display or manipulate the history list."),
12459 "jobs" => println!("jobs: jobs [-lnprs] [jobspec ...]\n Display status of jobs."),
12460 "kill" => println!("kill: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ...\n Send a signal to a job."),
12461 "read" => println!("read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]\n Read a line from standard input."),
12462 "set" => println!("set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]\n Set or unset values of shell options and positional parameters."),
12463 "test" | "[" => println!("test: test [expr]\n Evaluate conditional expression."),
12464 "type" => println!("type: type [-afptP] name [name ...]\n Display information about command type."),
12465 _ => println!("{}: no help available", cmd),
12466 }
12467 0
12468 }
12469
12470 fn builtin_readarray(&mut self, args: &[String]) -> i32 {
12472 use std::io::{BufRead, BufReader};
12473
12474 let mut array_name = "MAPFILE".to_string();
12475 let mut delimiter = '\n';
12476 let mut count = 0usize; let mut skip = 0usize;
12478 let mut strip_trailing = false;
12479 let mut callback: Option<String> = None;
12480 let mut callback_quantum = 0usize;
12481
12482 let mut i = 0;
12483 while i < args.len() {
12484 match args[i].as_str() {
12485 "-d" => {
12486 i += 1;
12487 if i < args.len() && !args[i].is_empty() {
12488 delimiter = args[i].chars().next().unwrap_or('\n');
12489 }
12490 }
12491 "-n" => {
12492 i += 1;
12493 if i < args.len() {
12494 count = args[i].parse().unwrap_or(0);
12495 }
12496 }
12497 "-O" => {
12498 i += 1;
12499 }
12501 "-s" => {
12502 i += 1;
12503 if i < args.len() {
12504 skip = args[i].parse().unwrap_or(0);
12505 }
12506 }
12507 "-t" => strip_trailing = true,
12508 "-C" => {
12509 i += 1;
12510 if i < args.len() {
12511 callback = Some(args[i].clone());
12512 }
12513 }
12514 "-c" => {
12515 i += 1;
12516 if i < args.len() {
12517 callback_quantum = args[i].parse().unwrap_or(5000);
12518 }
12519 }
12520 "-u" => {
12521 i += 1;
12522 }
12524 s if !s.starts_with('-') => {
12525 array_name = s.to_string();
12526 }
12527 _ => {}
12528 }
12529 i += 1;
12530 }
12531
12532 let stdin = std::io::stdin();
12533 let reader = BufReader::new(stdin.lock());
12534 let mut lines = Vec::new();
12535 let mut line_count = 0usize;
12536
12537 for line_result in reader.lines() {
12538 if let Ok(mut line) = line_result {
12539 line_count += 1;
12540
12541 if line_count <= skip {
12542 continue;
12543 }
12544
12545 if strip_trailing {
12546 while line.ends_with('\n') || line.ends_with('\r') {
12547 line.pop();
12548 }
12549 }
12550
12551 lines.push(line);
12552
12553 if count > 0 && lines.len() >= count {
12554 break;
12555 }
12556 }
12557 }
12558
12559 self.arrays.insert(array_name, lines);
12560 let _ = (callback, callback_quantum);
12561 0
12562 }
12563
12564 fn builtin_shopt(&mut self, args: &[String]) -> i32 {
12565 if args.is_empty() {
12566 for (opt, val) in &self.options {
12568 println!("shopt {} {}", if *val { "-s" } else { "-u" }, opt);
12569 }
12570 return 0;
12571 }
12572
12573 let mut set = None;
12574 let mut opts = Vec::new();
12575
12576 for arg in args {
12577 match arg.as_str() {
12578 "-s" => set = Some(true),
12579 "-u" => set = Some(false),
12580 "-p" => {
12581 for opt in &opts {
12583 let val = self.options.get(opt).copied().unwrap_or(false);
12584 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
12585 }
12586 return 0;
12587 }
12588 _ => opts.push(arg.clone()),
12589 }
12590 }
12591
12592 if let Some(enable) = set {
12593 for opt in &opts {
12594 self.options.insert(opt.clone(), enable);
12595 }
12596 } else {
12597 for opt in &opts {
12599 let val = self.options.get(opt).copied().unwrap_or(false);
12600 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
12601 }
12602 }
12603 0
12604 }
12605
12606 fn builtin_setopt(&mut self, args: &[String]) -> i32 {
12608 if args.is_empty() {
12609 let defaults_on = Self::default_on_options();
12613 let mut diff_opts: Vec<String> = Vec::new();
12614
12615 for &opt in Self::all_zsh_options() {
12616 let enabled = self.options.get(opt).copied().unwrap_or(false);
12617 let is_default_on = defaults_on.contains(&opt);
12618
12619 if is_default_on && !enabled {
12620 diff_opts.push(format!("no{}", opt));
12622 } else if !is_default_on && enabled {
12623 diff_opts.push(opt.to_string());
12625 }
12626 }
12627 diff_opts.sort();
12628 for opt in diff_opts {
12629 println!("{}", opt);
12630 }
12631 return 0;
12632 }
12633
12634 let mut use_pattern = false;
12635 let mut iter = args.iter().peekable();
12636
12637 while let Some(arg) = iter.next() {
12638 match arg.as_str() {
12639 "-m" => use_pattern = true,
12640 "-o" => {
12641 if let Some(opt) = iter.next() {
12643 let (name, enable) = Self::normalize_option_name(opt);
12644 self.options.insert(name, enable);
12645 }
12646 }
12647 "+o" => {
12648 if let Some(opt) = iter.next() {
12650 let (name, enable) = Self::normalize_option_name(opt);
12651 self.options.insert(name, !enable);
12652 }
12653 }
12654 _ => {
12655 if use_pattern {
12656 for opt in Self::all_zsh_options() {
12658 if Self::option_matches_pattern(opt, arg) {
12659 self.options.insert(opt.to_string(), true);
12660 }
12661 }
12662 } else {
12663 let (name, enable) = Self::normalize_option_name(arg);
12664 self.options.insert(name, enable);
12666 }
12667 }
12668 }
12669 }
12670 0
12671 }
12672
12673 fn builtin_unsetopt(&mut self, args: &[String]) -> i32 {
12675 if args.is_empty() {
12676 let defaults_on = Self::default_on_options();
12680 let mut all_opts: Vec<String> = Vec::new();
12681
12682 for &opt in Self::all_zsh_options() {
12683 let is_default_on = defaults_on.contains(&opt);
12684 if is_default_on {
12685 all_opts.push(format!("no{}", opt));
12686 } else {
12687 all_opts.push(opt.to_string());
12688 }
12689 }
12690 all_opts.sort();
12691 for opt in all_opts {
12692 println!("{}", opt);
12693 }
12694 return 0;
12695 }
12696
12697 let mut use_pattern = false;
12698 let mut iter = args.iter().peekable();
12699
12700 while let Some(arg) = iter.next() {
12701 match arg.as_str() {
12702 "-m" => use_pattern = true,
12703 "-o" => {
12704 if let Some(opt) = iter.next() {
12706 let (name, enable) = Self::normalize_option_name(opt);
12707 self.options.insert(name, !enable);
12708 }
12709 }
12710 "+o" => {
12711 if let Some(opt) = iter.next() {
12713 let (name, enable) = Self::normalize_option_name(opt);
12714 self.options.insert(name, enable);
12715 }
12716 }
12717 _ => {
12718 if use_pattern {
12719 for opt in Self::all_zsh_options() {
12720 if Self::option_matches_pattern(opt, arg) {
12721 self.options.insert(opt.to_string(), false);
12722 }
12723 }
12724 } else {
12725 let (name, enable) = Self::normalize_option_name(arg);
12726 self.options.insert(name, !enable);
12728 }
12729 }
12730 }
12731 }
12732 0
12733 }
12734
12735 fn builtin_getopts(&mut self, args: &[String]) -> i32 {
12736 if args.len() < 2 {
12737 eprintln!("zshrs: getopts: usage: getopts optstring name [arg ...]");
12738 return 1;
12739 }
12740
12741 let optstring = &args[0];
12742 let varname = &args[1];
12743 let opt_args: Vec<&str> = if args.len() > 2 {
12744 args[2..].iter().map(|s| s.as_str()).collect()
12745 } else {
12746 self.positional_params.iter().map(|s| s.as_str()).collect()
12747 };
12748
12749 let optind: usize = self
12751 .variables
12752 .get("OPTIND")
12753 .and_then(|s| s.parse().ok())
12754 .unwrap_or(1);
12755
12756 if optind > opt_args.len() {
12757 self.variables.insert(varname.to_string(), "?".to_string());
12758 return 1;
12759 }
12760
12761 let current_arg = opt_args[optind - 1];
12762
12763 if !current_arg.starts_with('-') || current_arg == "-" {
12764 self.variables.insert(varname.to_string(), "?".to_string());
12765 return 1;
12766 }
12767
12768 if current_arg == "--" {
12769 self.variables
12770 .insert("OPTIND".to_string(), (optind + 1).to_string());
12771 self.variables.insert(varname.to_string(), "?".to_string());
12772 return 1;
12773 }
12774
12775 let optpos: usize = self
12777 .variables
12778 .get("_OPTPOS")
12779 .and_then(|s| s.parse().ok())
12780 .unwrap_or(1);
12781
12782 let opt_char = current_arg.chars().nth(optpos);
12783
12784 if let Some(c) = opt_char {
12785 let opt_idx = optstring.find(c);
12787
12788 match opt_idx {
12789 Some(idx) => {
12790 let takes_arg = optstring.chars().nth(idx + 1) == Some(':');
12792
12793 if takes_arg {
12794 let arg = if optpos + 1 < current_arg.len() {
12796 current_arg[optpos + 1..].to_string()
12798 } else if optind < opt_args.len() {
12799 self.variables
12801 .insert("OPTIND".to_string(), (optind + 2).to_string());
12802 self.variables.remove("_OPTPOS");
12803 opt_args[optind].to_string()
12804 } else {
12805 self.variables.insert(varname.to_string(), "?".to_string());
12807 if !optstring.starts_with(':') {
12808 eprintln!("zshrs: getopts: option requires an argument -- {}", c);
12809 }
12810 self.variables.insert("OPTARG".to_string(), c.to_string());
12811 return 1;
12812 };
12813
12814 self.variables.insert("OPTARG".to_string(), arg);
12815 self.variables
12816 .insert("OPTIND".to_string(), (optind + 1).to_string());
12817 self.variables.remove("_OPTPOS");
12818 } else {
12819 if optpos + 1 < current_arg.len() {
12821 self.variables
12823 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
12824 } else {
12825 self.variables
12827 .insert("OPTIND".to_string(), (optind + 1).to_string());
12828 self.variables.remove("_OPTPOS");
12829 }
12830 }
12831
12832 self.variables.insert(varname.to_string(), c.to_string());
12833 0
12834 }
12835 None => {
12836 if !optstring.starts_with(':') {
12838 eprintln!("zshrs: getopts: illegal option -- {}", c);
12839 }
12840 self.variables.insert(varname.to_string(), "?".to_string());
12841 self.variables.insert("OPTARG".to_string(), c.to_string());
12842
12843 if optpos + 1 < current_arg.len() {
12845 self.variables
12846 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
12847 } else {
12848 self.variables
12849 .insert("OPTIND".to_string(), (optind + 1).to_string());
12850 self.variables.remove("_OPTPOS");
12851 }
12852 0
12853 }
12854 }
12855 } else {
12856 self.variables
12858 .insert("OPTIND".to_string(), (optind + 1).to_string());
12859 self.variables.remove("_OPTPOS");
12860 self.variables.insert(varname.to_string(), "?".to_string());
12861 1
12862 }
12863 }
12864
12865 fn builtin_type(&mut self, args: &[String]) -> i32 {
12866 if args.is_empty() {
12867 return 0;
12868 }
12869
12870 let mut show_all = false;
12871 let mut path_only = false;
12872 let mut silent = false;
12873 let mut show_type = false;
12874 let mut names = Vec::new();
12875
12876 let mut iter = args.iter();
12877 while let Some(arg) = iter.next() {
12878 if arg.starts_with('-') && arg.len() > 1 {
12879 for c in arg[1..].chars() {
12880 match c {
12881 'a' => show_all = true,
12882 'p' => path_only = true,
12883 'P' => path_only = true,
12884 's' => silent = true,
12885 't' => show_type = true,
12886 'f' => {} 'w' => {} _ => {}
12889 }
12890 }
12891 } else {
12892 names.push(arg.clone());
12893 }
12894 }
12895
12896 if names.is_empty() {
12897 return 0;
12898 }
12899
12900 let mut status = 0;
12901 for name in &names {
12902 let mut found_any = false;
12903
12904 if !path_only && self.aliases.contains_key(name) {
12906 found_any = true;
12907 if !silent {
12908 if show_type {
12909 println!("alias");
12910 } else {
12911 println!(
12912 "{} is aliased to `{}'",
12913 name,
12914 self.aliases.get(name).unwrap()
12915 );
12916 }
12917 }
12918 if !show_all {
12919 continue;
12920 }
12921 }
12922
12923 if !path_only && self.functions.contains_key(name) {
12925 found_any = true;
12926 if !silent {
12927 if show_type {
12928 println!("function");
12929 } else {
12930 println!("{} is a shell function", name);
12931 }
12932 }
12933 if !show_all {
12934 continue;
12935 }
12936 }
12937
12938 if !path_only && (self.is_builtin(name) || name == ":" || name == "[") {
12940 found_any = true;
12941 if !silent {
12942 if show_type {
12943 println!("builtin");
12944 } else {
12945 println!("{} is a shell builtin", name);
12946 }
12947 }
12948 if !show_all {
12949 continue;
12950 }
12951 }
12952
12953 if let Ok(path_env) = std::env::var("PATH") {
12955 for dir in path_env.split(':') {
12956 let full_path = format!("{}/{}", dir, name);
12957 if std::path::Path::new(&full_path).exists() {
12958 found_any = true;
12959 if !silent {
12960 if show_type {
12961 println!("file");
12962 } else {
12963 println!("{} is {}", name, full_path);
12964 }
12965 }
12966 if !show_all {
12967 break;
12968 }
12969 }
12970 }
12971 }
12972
12973 if !found_any {
12974 if !silent {
12975 eprintln!("zshrs: type: {}: not found", name);
12976 }
12977 status = 1;
12978 }
12979 }
12980 status
12981 }
12982
12983 fn builtin_hash(&mut self, args: &[String]) -> i32 {
12984 let mut dir_mode = false;
12993 let mut rehash = false;
12994 let mut fill_all = false;
12995 let mut pattern_match = false;
12996 let mut verbose = false;
12997 let mut list_form = false;
12998 let mut names = Vec::new();
12999
13000 let mut i = 0;
13001 while i < args.len() {
13002 let arg = &args[i];
13003 if arg.starts_with('-') && arg.len() > 1 {
13004 for ch in arg[1..].chars() {
13005 match ch {
13006 'd' => dir_mode = true,
13007 'r' => rehash = true,
13008 'f' => fill_all = true,
13009 'm' => pattern_match = true,
13010 'v' => verbose = true,
13011 'L' => list_form = true,
13012 _ => {}
13013 }
13014 }
13015 } else {
13016 names.push(arg.clone());
13017 }
13018 i += 1;
13019 }
13020
13021 if rehash && !dir_mode && names.is_empty() {
13023 self.command_hash.clear();
13024 return 0;
13025 }
13026
13027 if fill_all {
13029 if let Ok(path_var) = env::var("PATH") {
13030 for dir in path_var.split(':') {
13031 if let Ok(entries) = std::fs::read_dir(dir) {
13032 for entry in entries.flatten() {
13033 if let Ok(ft) = entry.file_type() {
13034 if ft.is_file() || ft.is_symlink() {
13035 if let Some(name) = entry.file_name().to_str() {
13036 let path = entry.path().to_string_lossy().to_string();
13037 self.command_hash.insert(name.to_string(), path);
13038 }
13039 }
13040 }
13041 }
13042 }
13043 }
13044 }
13045 return 0;
13046 }
13047
13048 if dir_mode {
13049 if names.is_empty() {
13051 for (name, path) in &self.named_dirs {
13053 if list_form {
13054 println!("hash -d {}={}", name, path.display());
13055 } else if verbose {
13056 println!("{}={}", name, path.display());
13057 } else {
13058 println!("{}={}", name, path.display());
13059 }
13060 }
13061 return 0;
13062 }
13063
13064 if rehash {
13065 if pattern_match {
13067 let to_remove: Vec<String> = self
13069 .named_dirs
13070 .keys()
13071 .filter(|k| {
13072 names.iter().any(|pat| {
13073 let pattern = pat.replace("*", ".*").replace("?", ".");
13074 regex::Regex::new(&format!("^{}$", pattern))
13075 .map(|r| r.is_match(k))
13076 .unwrap_or(false)
13077 })
13078 })
13079 .cloned()
13080 .collect();
13081 for name in to_remove {
13082 self.named_dirs.remove(&name);
13083 }
13084 } else {
13085 for name in &names {
13086 self.named_dirs.remove(name);
13087 }
13088 }
13089 return 0;
13090 }
13091
13092 for name in &names {
13094 if let Some((n, p)) = name.split_once('=') {
13095 self.add_named_dir(n, p);
13096 } else {
13097 eprintln!("hash: -d: {} not in name=value format", name);
13098 return 1;
13099 }
13100 }
13101 return 0;
13102 }
13103
13104 if names.is_empty() {
13106 for (name, path) in &self.command_hash {
13108 if list_form {
13109 println!("hash {}={}", name, path);
13110 } else {
13111 println!("{}={}", name, path);
13112 }
13113 }
13114 return 0;
13115 }
13116
13117 for name in &names {
13118 if let Some((cmd, path)) = name.split_once('=') {
13119 self.command_hash.insert(cmd.to_string(), path.to_string());
13121 if verbose {
13122 println!("{}={}", cmd, path);
13123 }
13124 } else if let Some(path) = self.find_in_path(name) {
13125 self.command_hash.insert(name.clone(), path.clone());
13127 if verbose {
13128 println!("{}={}", name, path);
13129 }
13130 } else {
13131 eprintln!("zshrs: hash: {}: not found", name);
13132 return 1;
13133 }
13134 }
13135 0
13136 }
13137
13138 fn builtin_add_zsh_hook(&mut self, args: &[String]) -> i32 {
13140 if args.len() < 2 {
13142 eprintln!("usage: add-zsh-hook [-d] hook function");
13143 return 1;
13144 }
13145
13146 let (delete, hook, func) = if args[0] == "-d" {
13147 if args.len() < 3 {
13148 eprintln!("usage: add-zsh-hook -d hook function");
13149 return 1;
13150 }
13151 (true, &args[1], &args[2])
13152 } else {
13153 (false, &args[0], &args[1])
13154 };
13155
13156 if delete {
13157 if let Some(funcs) = self.hook_functions.get_mut(hook.as_str()) {
13159 funcs.retain(|f| f != func);
13160 }
13161 } else {
13162 self.add_hook(hook, func);
13164 }
13165 0
13166 }
13167
13168 fn builtin_command(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
13169 let mut use_default_path = false;
13174 let mut print_path = false;
13175 let mut verbose = false;
13176 let mut positional_args: Vec<&str> = Vec::new();
13177
13178 let mut i = 0;
13179 while i < args.len() {
13180 let arg = &args[i];
13181 if arg.starts_with('-') && arg.len() > 1 && positional_args.is_empty() {
13182 for ch in arg[1..].chars() {
13183 match ch {
13184 'p' => use_default_path = true,
13185 'v' => print_path = true,
13186 'V' => verbose = true,
13187 '-' => {
13188 i += 1;
13190 break;
13191 }
13192 _ => {
13193 eprintln!("command: bad option: -{}", ch);
13194 return 1;
13195 }
13196 }
13197 }
13198 } else {
13199 positional_args.push(arg);
13200 }
13201 i += 1;
13202 }
13203
13204 while i < args.len() {
13206 positional_args.push(&args[i]);
13207 i += 1;
13208 }
13209
13210 if positional_args.is_empty() {
13211 return 0;
13212 }
13213
13214 let cmd = positional_args[0];
13215
13216 if print_path || verbose {
13218 let path_var = if use_default_path {
13220 "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
13221 } else {
13222 env::var("PATH").unwrap_or_default()
13223 };
13224
13225 for dir in path_var.split(':') {
13226 let full_path = PathBuf::from(dir).join(cmd);
13227 if full_path.exists() && full_path.is_file() {
13228 if verbose {
13229 println!("{} is {}", cmd, full_path.display());
13230 } else {
13231 println!("{}", full_path.display());
13232 }
13233 return 0;
13234 }
13235 }
13236
13237 if verbose {
13238 eprintln!("{} not found", cmd);
13239 }
13240 return 1;
13241 }
13242
13243 let cmd_args: Vec<String> = positional_args[1..].iter().map(|s| s.to_string()).collect();
13245
13246 if use_default_path {
13247 let old_path = env::var("PATH").ok();
13249 env::set_var("PATH", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin");
13250 let result = self
13251 .execute_external(
13252 cmd,
13253 &cmd_args
13254 .iter()
13255 .map(|s| s.as_str())
13256 .collect::<Vec<_>>()
13257 .join(" ")
13258 .split_whitespace()
13259 .map(String::from)
13260 .collect::<Vec<_>>(),
13261 redirects,
13262 )
13263 .unwrap_or(127);
13264 if let Some(p) = old_path {
13265 env::set_var("PATH", p);
13266 }
13267 result
13268 } else {
13269 self.execute_external(cmd, &cmd_args, redirects)
13270 .unwrap_or(127)
13271 }
13272 }
13273
13274 fn builtin_builtin(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
13275 if args.is_empty() {
13277 return 0;
13278 }
13279
13280 let cmd = &args[0];
13281 let cmd_args = &args[1..];
13282
13283 match cmd.as_str() {
13284 "cd" => self.builtin_cd(cmd_args),
13285 "pwd" => self.builtin_pwd(redirects),
13286 "echo" => self.builtin_echo(cmd_args, redirects),
13287 "export" => self.builtin_export(cmd_args),
13288 "unset" => self.builtin_unset(cmd_args),
13289 "exit" => self.builtin_exit(cmd_args),
13290 "return" => self.builtin_return(cmd_args),
13291 "true" => 0,
13292 "false" => 1,
13293 ":" => 0,
13294 "test" | "[" => self.builtin_test(cmd_args),
13295 "local" => self.builtin_local(cmd_args),
13296 "declare" | "typeset" => self.builtin_declare(cmd_args),
13297 "read" => self.builtin_read(cmd_args),
13298 "shift" => self.builtin_shift(cmd_args),
13299 "eval" => self.builtin_eval(cmd_args),
13300 "alias" => self.builtin_alias(cmd_args),
13301 "unalias" => self.builtin_unalias(cmd_args),
13302 "set" => self.builtin_set(cmd_args),
13303 "shopt" => self.builtin_shopt(cmd_args),
13304 "getopts" => self.builtin_getopts(cmd_args),
13305 "type" => self.builtin_type(cmd_args),
13306 "hash" => self.builtin_hash(cmd_args),
13307 "add-zsh-hook" => self.builtin_add_zsh_hook(cmd_args),
13308 "autoload" => self.builtin_autoload(cmd_args),
13309 "source" | "." => self.builtin_source(cmd_args),
13310 "functions" => self.builtin_functions(cmd_args),
13311 "zle" => self.builtin_zle(cmd_args),
13312 "bindkey" => self.builtin_bindkey(cmd_args),
13313 "setopt" => self.builtin_setopt(cmd_args),
13314 "unsetopt" => self.builtin_unsetopt(cmd_args),
13315 "emulate" => self.builtin_emulate(cmd_args),
13316 "zstyle" => self.builtin_zstyle(cmd_args),
13317 "compadd" => self.builtin_compadd(cmd_args),
13318 "compset" => self.builtin_compset(cmd_args),
13319 "compdef" => self.builtin_compdef(cmd_args),
13320 "compinit" => self.builtin_compinit(cmd_args),
13321 "cdreplay" => self.builtin_cdreplay(cmd_args),
13322 "zmodload" => self.builtin_zmodload(cmd_args),
13323 "zcompile" => self.builtin_zcompile(cmd_args),
13324 "zformat" => self.builtin_zformat(cmd_args),
13325 "zprof" => self.builtin_zprof(cmd_args),
13326 "print" => self.builtin_print(cmd_args),
13327 "printf" => self.builtin_printf(cmd_args),
13328 "command" => self.builtin_command(cmd_args, redirects),
13329 "whence" => self.builtin_whence(cmd_args),
13330 "which" => self.builtin_which(cmd_args),
13331 "where" => self.builtin_where(cmd_args),
13332 "fc" => self.builtin_fc(cmd_args),
13333 "history" => self.builtin_history(cmd_args),
13334 "dirs" => self.builtin_dirs(cmd_args),
13335 "pushd" => self.builtin_pushd(cmd_args),
13336 "popd" => self.builtin_popd(cmd_args),
13337 "bg" => self.builtin_bg(cmd_args),
13338 "fg" => self.builtin_fg(cmd_args),
13339 "jobs" => self.builtin_jobs(cmd_args),
13340 "kill" => self.builtin_kill(cmd_args),
13341 "wait" => self.builtin_wait(cmd_args),
13342 "trap" => self.builtin_trap(cmd_args),
13343 "umask" => self.builtin_umask(cmd_args),
13344 "ulimit" => self.builtin_ulimit(cmd_args),
13345 "times" => self.builtin_times(cmd_args),
13346 "let" => self.builtin_let(cmd_args),
13347 "integer" => self.builtin_integer(cmd_args),
13348 "float" => self.builtin_float(cmd_args),
13349 "readonly" => self.builtin_readonly(cmd_args),
13350 _ => {
13351 eprintln!("zshrs: builtin: {}: not a shell builtin", cmd);
13352 1
13353 }
13354 }
13355 }
13356
13357 fn builtin_let(&mut self, args: &[String]) -> i32 {
13358 if args.is_empty() {
13359 return 1;
13360 }
13361
13362 let mut result = 0i64;
13363 for expr in args {
13364 result = self.evaluate_arithmetic_expr(expr);
13365 }
13366
13367 if result == 0 {
13369 1
13370 } else {
13371 0
13372 }
13373 }
13374
13375 fn builtin_compgen(&self, args: &[String]) -> i32 {
13377 let mut i = 0;
13378 let mut prefix = String::new();
13379 let mut actions = Vec::new();
13380 let mut wordlist = None;
13381 let mut globpat = None;
13382
13383 while i < args.len() {
13384 match args[i].as_str() {
13385 "-W" => {
13386 i += 1;
13387 if i < args.len() {
13388 wordlist = Some(args[i].clone());
13389 }
13390 }
13391 "-G" => {
13392 i += 1;
13393 if i < args.len() {
13394 globpat = Some(args[i].clone());
13395 }
13396 }
13397 "-a" => actions.push("alias"),
13398 "-b" => actions.push("builtin"),
13399 "-c" => actions.push("command"),
13400 "-d" => actions.push("directory"),
13401 "-e" => actions.push("export"),
13402 "-f" => actions.push("file"),
13403 "-j" => actions.push("job"),
13404 "-k" => actions.push("keyword"),
13405 "-u" => actions.push("user"),
13406 "-v" => actions.push("variable"),
13407 s if !s.starts_with('-') => prefix = s.to_string(),
13408 _ => {}
13409 }
13410 i += 1;
13411 }
13412
13413 let mut results = Vec::new();
13414
13415 for action in actions {
13417 match action {
13418 "alias" => {
13419 for name in self.aliases.keys() {
13420 if name.starts_with(&prefix) {
13421 results.push(name.clone());
13422 }
13423 }
13424 }
13425 "builtin" => {
13426 for name in [
13427 "cd", "pwd", "echo", "export", "unset", "source", "exit", "return", "true",
13428 "false", ":", "test", "[", "local", "declare", "jobs", "fg", "bg", "kill",
13429 "disown", "wait", "alias", "unalias", "set", "shopt",
13430 ] {
13431 if name.starts_with(&prefix) {
13432 results.push(name.to_string());
13433 }
13434 }
13435 }
13436 "directory" => {
13437 if let Ok(entries) = std::fs::read_dir(".") {
13438 for entry in entries.flatten() {
13439 if let Ok(ft) = entry.file_type() {
13440 if ft.is_dir() {
13441 let name = entry.file_name().to_string_lossy().to_string();
13442 if name.starts_with(&prefix) {
13443 results.push(name);
13444 }
13445 }
13446 }
13447 }
13448 }
13449 }
13450 "file" => {
13451 if let Ok(entries) = std::fs::read_dir(".") {
13452 for entry in entries.flatten() {
13453 let name = entry.file_name().to_string_lossy().to_string();
13454 if name.starts_with(&prefix) {
13455 results.push(name);
13456 }
13457 }
13458 }
13459 }
13460 "variable" => {
13461 for name in self.variables.keys() {
13462 if name.starts_with(&prefix) {
13463 results.push(name.clone());
13464 }
13465 }
13466 for name in std::env::vars().map(|(k, _)| k) {
13467 if name.starts_with(&prefix) && !results.contains(&name) {
13468 results.push(name);
13469 }
13470 }
13471 }
13472 _ => {}
13473 }
13474 }
13475
13476 if let Some(words) = wordlist {
13478 for word in words.split_whitespace() {
13479 if word.starts_with(&prefix) {
13480 results.push(word.to_string());
13481 }
13482 }
13483 }
13484
13485 if let Some(_pattern) = globpat {
13487 let full_pattern = format!("{}*", prefix);
13488 if let Ok(paths) = glob::glob(&full_pattern) {
13489 for path in paths.flatten() {
13490 results.push(path.to_string_lossy().to_string());
13491 }
13492 }
13493 }
13494
13495 results.sort();
13496 results.dedup();
13497 for r in results {
13498 println!("{}", r);
13499 }
13500 0
13501 }
13502
13503 fn builtin_complete(&mut self, args: &[String]) -> i32 {
13505 if args.is_empty() {
13506 for (cmd, spec) in &self.completions {
13508 let mut parts = vec!["complete".to_string()];
13509 for action in &spec.actions {
13510 parts.push(format!("-{}", action));
13511 }
13512 if let Some(ref w) = spec.wordlist {
13513 parts.push("-W".to_string());
13514 parts.push(format!("'{}'", w));
13515 }
13516 if let Some(ref f) = spec.function {
13517 parts.push("-F".to_string());
13518 parts.push(f.clone());
13519 }
13520 if let Some(ref c) = spec.command {
13521 parts.push("-C".to_string());
13522 parts.push(c.clone());
13523 }
13524 parts.push(cmd.clone());
13525 println!("{}", parts.join(" "));
13526 }
13527 return 0;
13528 }
13529
13530 let mut spec = CompSpec::default();
13531 let mut commands = Vec::new();
13532 let mut i = 0;
13533
13534 while i < args.len() {
13535 match args[i].as_str() {
13536 "-W" => {
13537 i += 1;
13538 if i < args.len() {
13539 spec.wordlist = Some(args[i].clone());
13540 }
13541 }
13542 "-F" => {
13543 i += 1;
13544 if i < args.len() {
13545 spec.function = Some(args[i].clone());
13546 }
13547 }
13548 "-C" => {
13549 i += 1;
13550 if i < args.len() {
13551 spec.command = Some(args[i].clone());
13552 }
13553 }
13554 "-G" => {
13555 i += 1;
13556 if i < args.len() {
13557 spec.globpat = Some(args[i].clone());
13558 }
13559 }
13560 "-P" => {
13561 i += 1;
13562 if i < args.len() {
13563 spec.prefix = Some(args[i].clone());
13564 }
13565 }
13566 "-S" => {
13567 i += 1;
13568 if i < args.len() {
13569 spec.suffix = Some(args[i].clone());
13570 }
13571 }
13572 "-a" => spec.actions.push("a".to_string()),
13573 "-b" => spec.actions.push("b".to_string()),
13574 "-c" => spec.actions.push("c".to_string()),
13575 "-d" => spec.actions.push("d".to_string()),
13576 "-e" => spec.actions.push("e".to_string()),
13577 "-f" => spec.actions.push("f".to_string()),
13578 "-j" => spec.actions.push("j".to_string()),
13579 "-r" => {
13580 i += 1;
13582 while i < args.len() {
13583 self.completions.remove(&args[i]);
13584 i += 1;
13585 }
13586 return 0;
13587 }
13588 s if !s.starts_with('-') => commands.push(s.to_string()),
13589 _ => {}
13590 }
13591 i += 1;
13592 }
13593
13594 for cmd in commands {
13595 self.completions.insert(cmd, spec.clone());
13596 }
13597 0
13598 }
13599
13600 fn builtin_compopt(&mut self, args: &[String]) -> i32 {
13602 let _ = args;
13604 0
13605 }
13606
13607 fn builtin_compadd(&mut self, args: &[String]) -> i32 {
13609 let _ = args;
13612 0
13613 }
13614
13615 fn builtin_compset(&mut self, args: &[String]) -> i32 {
13617 let _ = args;
13619 0
13620 }
13621
13622 fn builtin_compdef(&mut self, args: &[String]) -> i32 {
13627 if let Some(cache) = &mut self.compsys_cache {
13628 compsys::compdef::compdef_execute(cache, args)
13629 } else {
13630 self.deferred_compdefs.push(args.to_vec());
13632 0
13633 }
13634 }
13635
13636 #[tracing::instrument(level = "info", skip(self))]
13639 fn builtin_compinit(&mut self, args: &[String]) -> i32 {
13640 let mut quiet = false;
13647 let mut no_dump = false;
13648 let mut dump_file: Option<String> = None;
13649 let mut use_cache = false;
13650 let mut ignore_insecure = false;
13651 let mut use_insecure = false;
13652
13653 let mut i = 0;
13654 while i < args.len() {
13655 match args[i].as_str() {
13656 "-q" => quiet = true,
13657 "-C" => use_cache = true,
13658 "-D" => no_dump = true,
13659 "-d" => {
13660 i += 1;
13661 if i < args.len() {
13662 dump_file = Some(args[i].clone());
13663 }
13664 }
13665 "-u" => use_insecure = true,
13666 "-i" => ignore_insecure = true,
13667 _ => {}
13668 }
13669 i += 1;
13670 }
13671
13672 if !use_insecure && !self.posix_mode {
13674 if let Some(ref cache) = self.plugin_cache {
13675 let insecure = cache.compaudit_cached(&self.fpath);
13676 if !insecure.is_empty() && !ignore_insecure {
13677 if !quiet {
13678 eprintln!("compinit: insecure directories:");
13679 for d in &insecure {
13680 eprintln!(" {}", d);
13681 }
13682 eprintln!("compinit: run with -i to ignore or -u to use anyway");
13683 }
13684 return 1;
13685 }
13686 }
13687 }
13688
13689 if self.zsh_compat {
13691 return self.compinit_compat(quiet, no_dump, dump_file, use_cache);
13692 }
13693
13694 if use_cache {
13698 if let Some(cache) = &self.compsys_cache {
13699 if compsys::cache_is_valid(cache) {
13700 if let Ok(result) = compsys::load_from_cache(cache) {
13702 if !quiet {
13703 tracing::info!(
13704 comps = result.comps.len(),
13705 "compinit: using cached completions"
13706 );
13707 }
13708 self.assoc_arrays.insert("_comps".to_string(), result.comps);
13709 self.assoc_arrays
13710 .insert("_services".to_string(), result.services);
13711 self.assoc_arrays
13712 .insert("_patcomps".to_string(), result.patcomps);
13713
13714 if let Some(ref cache) = self.compsys_cache {
13717 if let Ok(missing) = cache.count_autoloads_missing_bytecode() {
13718 if missing > 0 {
13719 tracing::info!(
13720 count = missing,
13721 "compinit: backfilling bytecode blobs on worker pool"
13722 );
13723 let cache_path = compsys::cache::default_cache_path();
13724 let total_missing = missing;
13725 self.worker_pool.submit(move || {
13726 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
13727 Ok(c) => c,
13728 Err(_) => return,
13729 };
13730 let mut total_cached = 0usize;
13734 loop {
13735 let stubs = match cache.get_autoloads_missing_bytecode_batch(100) {
13736 Ok(s) if !s.is_empty() => s,
13737 _ => break,
13738 };
13739 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(stubs.len());
13740 for (name, body) in &stubs {
13741 let mut parser = crate::parser::ShellParser::new(body);
13742 if let Ok(commands) = parser.parse_script() {
13743 if !commands.is_empty() {
13744 let compiler = crate::shell_compiler::ShellCompiler::new();
13745 let chunk = compiler.compile(&commands);
13746 if let Ok(blob) = bincode::serialize(&chunk) {
13747 batch.push((name.clone(), blob));
13748 }
13749 }
13750 }
13751 }
13752 total_cached += batch.len();
13753 if let Err(e) = cache.set_autoload_bytecodes_bulk(&batch) {
13754 tracing::warn!(error = %e, "compinit: bytecode backfill batch failed");
13755 break;
13756 }
13757 if stubs.len() < 100 {
13759 break;
13760 }
13761 }
13762 tracing::info!(
13763 cached = total_cached,
13764 total = total_missing,
13765 "compinit: bytecode backfill complete"
13766 );
13767 });
13768 }
13769 }
13770 }
13771
13772 return 0;
13773 }
13774 }
13775 }
13776 }
13777
13778 let fpath = self.fpath.clone();
13782 let fpath_count = fpath.len();
13783 let pool_size = self.worker_pool.size();
13784 let (tx, rx) = std::sync::mpsc::channel();
13785 let bg_start = std::time::Instant::now();
13786 tracing::info!(
13787 fpath_dirs = fpath_count,
13788 worker_pool = pool_size,
13789 "compinit: shipping to worker pool"
13790 );
13791 self.worker_pool.submit(move || {
13792 tracing::debug!("compinit-bg: thread started");
13793 let cache_path = compsys::cache::default_cache_path();
13794 if let Some(parent) = cache_path.parent() {
13795 let _ = std::fs::create_dir_all(parent);
13796 }
13797 let _ = std::fs::remove_file(&cache_path);
13799 let _ = std::fs::remove_file(format!("{}-shm", cache_path.display()));
13800 let _ = std::fs::remove_file(format!("{}-wal", cache_path.display()));
13801
13802 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
13803 Ok(c) => c,
13804 Err(e) => {
13805 tracing::error!("compinit: failed to create cache: {}", e);
13806 return;
13807 }
13808 };
13809
13810 let result = match compsys::build_cache_from_fpath(&fpath, &mut cache) {
13811 Ok(r) => r,
13812 Err(e) => {
13813 tracing::error!("compinit: scan failed: {}", e);
13814 return;
13815 }
13816 };
13817
13818 tracing::info!(
13819 functions = result.files_scanned,
13820 comps = result.comps.len(),
13821 dirs = result.dirs_scanned,
13822 ms = result.scan_time_ms,
13823 "compinit: background scan complete"
13824 );
13825
13826 let parse_start = std::time::Instant::now();
13830 let mut parse_ok = 0usize;
13831 let mut parse_fail = 0usize;
13832 let mut no_body = 0usize;
13833 let batch_size = 100;
13834 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(batch_size);
13835
13836 for file in &result.files {
13837 if let Some(ref body) = file.body {
13838 let mut parser = crate::parser::ShellParser::new(body);
13839 match parser.parse_script() {
13840 Ok(commands) if !commands.is_empty() => {
13841 let compiler = crate::shell_compiler::ShellCompiler::new();
13843 let chunk = compiler.compile(&commands);
13844 if let Ok(blob) = bincode::serialize(&chunk) {
13845 batch.push((file.name.clone(), blob));
13846 parse_ok += 1;
13847 if batch.len() >= batch_size {
13848 let _ = cache.set_autoload_bytecodes_bulk(&batch);
13849 batch.clear();
13850 }
13851 }
13852 }
13853 Ok(_) => { parse_fail += 1; }
13854 Err(_) => { parse_fail += 1; }
13855 }
13856 } else {
13857 no_body += 1;
13858 }
13859 }
13860 if !batch.is_empty() {
13862 let _ = cache.set_autoload_bytecodes_bulk(&batch);
13863 batch.clear();
13864 }
13865
13866 tracing::info!(
13867 cached = parse_ok,
13868 failed = parse_fail,
13869 no_body = no_body,
13870 total = result.files.len(),
13871 ms = parse_start.elapsed().as_millis() as u64,
13872 "compinit: bytecode blobs cached"
13873 );
13874
13875 let _ = tx.send(CompInitBgResult { result, cache });
13876 });
13877
13878 self.compinit_pending = Some((rx, bg_start));
13879 0
13880 }
13881
13882 pub fn drain_compinit_bg(&mut self) {
13886 if let Some((rx, start)) = self.compinit_pending.take() {
13887 match rx.try_recv() {
13888 Ok(bg) => {
13889 let comps = bg.result.comps.len();
13890 self.assoc_arrays
13891 .insert("_comps".to_string(), bg.result.comps);
13892 self.assoc_arrays
13893 .insert("_services".to_string(), bg.result.services);
13894 self.assoc_arrays
13895 .insert("_patcomps".to_string(), bg.result.patcomps);
13896 self.compsys_cache = Some(bg.cache);
13897 tracing::info!(
13898 wall_ms = start.elapsed().as_millis() as u64,
13899 comps,
13900 "compinit: background results merged"
13901 );
13902 }
13903 Err(std::sync::mpsc::TryRecvError::Empty) => {
13904 self.compinit_pending = Some((rx, start));
13906 }
13907 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
13908 tracing::warn!("compinit: background thread died without sending results");
13909 }
13910 }
13911 }
13912 }
13913
13914 fn compinit_compat(
13917 &mut self,
13918 quiet: bool,
13919 no_dump: bool,
13920 dump_file: Option<String>,
13921 use_cache: bool,
13922 ) -> i32 {
13923 let zdotdir = self
13924 .variables
13925 .get("ZDOTDIR")
13926 .cloned()
13927 .or_else(|| std::env::var("ZDOTDIR").ok())
13928 .unwrap_or_else(|| std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
13929
13930 let dump_path = dump_file
13931 .map(PathBuf::from)
13932 .unwrap_or_else(|| PathBuf::from(&zdotdir).join(".zcompdump"));
13933
13934 if use_cache && dump_path.exists() {
13936 if compsys::check_dump(&dump_path, &self.fpath, "zshrs-0.1.0") {
13937 if !quiet {
13940 tracing::info!("compinit: .zcompdump valid, rescanning for compat");
13941 }
13942 }
13943 }
13944
13945 let result = compsys::compinit(&self.fpath);
13947
13948 if !quiet {
13949 tracing::info!(
13950 functions = result.files_scanned,
13951 comps = result.comps.len(),
13952 dirs = result.dirs_scanned,
13953 ms = result.scan_time_ms,
13954 "compinit: fpath scan complete"
13955 );
13956 }
13957
13958 if !no_dump {
13960 let _ = compsys::compdump(&result, &dump_path, "zshrs-0.1.0");
13961 }
13962
13963 self.assoc_arrays
13965 .insert("_comps".to_string(), result.comps.clone());
13966 self.assoc_arrays
13967 .insert("_services".to_string(), result.services.clone());
13968 self.assoc_arrays
13969 .insert("_patcomps".to_string(), result.patcomps.clone());
13970
13971 self.compsys_cache = None;
13973
13974 0
13975 }
13976
13977 fn builtin_cdreplay(&mut self, args: &[String]) -> i32 {
13980 let quiet = args.contains(&"-q".to_string());
13981
13982 if self.deferred_compdefs.is_empty() {
13983 return 0;
13984 }
13985
13986 let deferred = std::mem::take(&mut self.deferred_compdefs);
13987 let count = deferred.len();
13988
13989 if let Some(cache) = &mut self.compsys_cache {
13990 for compdef_args in deferred {
13991 compsys::compdef::compdef_execute(cache, &compdef_args);
13992 }
13993 }
13994
13995 if !quiet {
13996 eprintln!("cdreplay: replayed {} compdef calls", count);
13997 }
13998
13999 0
14000 }
14001
14002 fn builtin_zstyle(&mut self, args: &[String]) -> i32 {
14004 if args.is_empty() {
14005 for (pattern, style, values) in self.style_table.list(None) {
14007 println!("zstyle '{}' {} {}", pattern, style, values.join(" "));
14008 }
14009 return 0;
14010 }
14011
14012 if args[0].starts_with('-') {
14014 match args[0].as_str() {
14015 "-d" => {
14016 let pattern = args.get(1).map(|s| s.as_str());
14018 let style = args.get(2).map(|s| s.as_str());
14019 self.style_table.delete(pattern, style);
14020 return 0;
14021 }
14022 "-g" => {
14023 if args.len() >= 4 {
14025 let array_name = &args[1];
14026 let context = &args[2];
14027 let style = &args[3];
14028 if let Some(values) = self.style_table.get(context, style) {
14029 self.arrays.insert(array_name.clone(), values.to_vec());
14030 return 0;
14031 }
14032 }
14033 return 1;
14034 }
14035 "-s" => {
14036 if args.len() >= 4 {
14038 let var_name = &args[1];
14039 let context = &args[2];
14040 let style = &args[3];
14041 let sep = args.get(4).map(|s| s.as_str()).unwrap_or(" ");
14042 if let Some(values) = self.style_table.get(context, style) {
14043 self.variables.insert(var_name.clone(), values.join(sep));
14044 return 0;
14045 }
14046 }
14047 return 1;
14048 }
14049 "-t" => {
14050 if args.len() >= 3 {
14052 let context = &args[1];
14053 let style = &args[2];
14054 return if self.style_table.test_bool(context, style).unwrap_or(false) {
14055 0
14056 } else {
14057 1
14058 };
14059 }
14060 return 1;
14061 }
14062 "-L" => {
14063 for (pattern, style, values) in self.style_table.list(None) {
14065 let values_str = values
14066 .iter()
14067 .map(|v| format!("'{}'", v.replace('\'', "'\\''")))
14068 .collect::<Vec<_>>()
14069 .join(" ");
14070 println!("zstyle '{}' {} {}", pattern, style, values_str);
14071 }
14072 return 0;
14073 }
14074 _ => {}
14075 }
14076 }
14077
14078 if args.len() >= 2 {
14080 let pattern = &args[0];
14081 let style = &args[1];
14082 let values: Vec<String> = args[2..].to_vec();
14083 self.style_table.set(pattern, style, values.clone(), false);
14084
14085 if let Some(cache) = &self.compsys_cache {
14087 let _ = cache.set_zstyle(pattern, style, &values, false);
14088 }
14089
14090 let existing = self
14092 .zstyles
14093 .iter_mut()
14094 .find(|s| s.pattern == *pattern && s.style == *style);
14095 if let Some(s) = existing {
14096 s.values = args[2..].to_vec();
14097 } else {
14098 self.zstyles.push(ZStyle {
14099 pattern: pattern.clone(),
14100 style: style.clone(),
14101 values: args[2..].to_vec(),
14102 });
14103 }
14104 }
14105 0
14106 }
14107
14108 fn builtin_ztie(&mut self, args: &[String]) -> i32 {
14111 use crate::db_gdbm;
14112
14113 let mut db_type: Option<String> = None;
14114 let mut file_path: Option<String> = None;
14115 let mut readonly = false;
14116 let mut param_args: Vec<String> = Vec::new();
14117
14118 let mut i = 0;
14119 while i < args.len() {
14120 match args[i].as_str() {
14121 "-d" => {
14122 if i + 1 < args.len() {
14123 db_type = Some(args[i + 1].clone());
14124 i += 2;
14125 } else {
14126 eprintln!("ztie: -d requires an argument");
14127 return 1;
14128 }
14129 }
14130 "-f" => {
14131 if i + 1 < args.len() {
14132 file_path = Some(args[i + 1].clone());
14133 i += 2;
14134 } else {
14135 eprintln!("ztie: -f requires an argument");
14136 return 1;
14137 }
14138 }
14139 "-r" => {
14140 readonly = true;
14141 i += 1;
14142 }
14143 arg if arg.starts_with('-') => {
14144 eprintln!("ztie: bad option: {}", arg);
14145 return 1;
14146 }
14147 _ => {
14148 param_args.push(args[i].clone());
14149 i += 1;
14150 }
14151 }
14152 }
14153
14154 match db_gdbm::ztie(
14155 ¶m_args,
14156 readonly,
14157 db_type.as_deref(),
14158 file_path.as_deref(),
14159 ) {
14160 Ok(()) => 0,
14161 Err(e) => {
14162 eprintln!("ztie: {}", e);
14163 1
14164 }
14165 }
14166 }
14167
14168 fn builtin_zuntie(&mut self, args: &[String]) -> i32 {
14171 use crate::db_gdbm;
14172
14173 let mut force_unset = false;
14174 let mut param_args: Vec<String> = Vec::new();
14175
14176 for arg in args {
14177 match arg.as_str() {
14178 "-u" => force_unset = true,
14179 a if a.starts_with('-') => {
14180 eprintln!("zuntie: bad option: {}", a);
14181 return 1;
14182 }
14183 _ => param_args.push(arg.clone()),
14184 }
14185 }
14186
14187 if param_args.is_empty() {
14188 eprintln!("zuntie: not enough arguments");
14189 return 1;
14190 }
14191
14192 match db_gdbm::zuntie(¶m_args, force_unset) {
14193 Ok(()) => 0,
14194 Err(e) => {
14195 eprintln!("zuntie: {}", e);
14196 1
14197 }
14198 }
14199 }
14200
14201 fn builtin_zgdbmpath(&mut self, args: &[String]) -> i32 {
14205 use crate::db_gdbm;
14206
14207 if args.is_empty() {
14208 eprintln!(
14209 "zgdbmpath: parameter name (whose path is to be written to $REPLY) is required"
14210 );
14211 return 1;
14212 }
14213
14214 match db_gdbm::zgdbmpath(&args[0]) {
14215 Ok(path) => {
14216 self.variables.insert("REPLY".to_string(), path.clone());
14217 std::env::set_var("REPLY", &path);
14218 0
14219 }
14220 Err(e) => {
14221 eprintln!("zgdbmpath: {}", e);
14222 1
14223 }
14224 }
14225 }
14226
14227 fn builtin_pushd(&mut self, args: &[String]) -> i32 {
14229 let mut quiet = false;
14238 let mut physical = false;
14239 let mut positional_args: Vec<String> = Vec::new();
14240
14241 for arg in args {
14242 if arg.starts_with('-') && arg.len() > 1 {
14243 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
14245 positional_args.push(arg.clone());
14246 continue;
14247 }
14248 for ch in arg[1..].chars() {
14249 match ch {
14250 'q' => quiet = true,
14251 's' => physical = false,
14252 'L' => physical = false,
14253 'P' => physical = true,
14254 _ => {}
14255 }
14256 }
14257 } else if arg.starts_with('+') {
14258 positional_args.push(arg.clone());
14259 } else {
14260 positional_args.push(arg.clone());
14261 }
14262 }
14263
14264 let current = match std::env::current_dir() {
14265 Ok(p) => p,
14266 Err(e) => {
14267 eprintln!("pushd: {}", e);
14268 return 1;
14269 }
14270 };
14271
14272 if positional_args.is_empty() {
14273 if self.dir_stack.is_empty() {
14275 eprintln!("pushd: no other directory");
14276 return 1;
14277 }
14278 let target = self.dir_stack.pop().unwrap();
14279 self.dir_stack.push(current.clone());
14280
14281 let resolved = if physical {
14282 target.canonicalize().unwrap_or(target.clone())
14283 } else {
14284 target.clone()
14285 };
14286
14287 if let Err(e) = std::env::set_current_dir(&resolved) {
14288 eprintln!("pushd: {}: {}", target.display(), e);
14289 self.dir_stack.pop();
14290 self.dir_stack.push(target);
14291 return 1;
14292 }
14293 if !quiet {
14294 self.print_dir_stack();
14295 }
14296 return 0;
14297 }
14298
14299 let arg = &positional_args[0];
14300
14301 if arg.starts_with('+') || arg.starts_with('-') {
14303 if let Ok(n) = arg[1..].parse::<usize>() {
14304 let total = self.dir_stack.len() + 1;
14305 if n >= total {
14306 eprintln!("pushd: {}: directory stack index out of range", arg);
14307 return 1;
14308 }
14309 let rotate_pos = if arg.starts_with('+') { n } else { total - n };
14311 let mut full_stack = vec![current.clone()];
14312 full_stack.extend(self.dir_stack.iter().cloned());
14313 full_stack.rotate_left(rotate_pos);
14314
14315 let target = full_stack.remove(0);
14316 self.dir_stack = full_stack;
14317
14318 let resolved = if physical {
14319 target.canonicalize().unwrap_or(target.clone())
14320 } else {
14321 target.clone()
14322 };
14323
14324 if let Err(e) = std::env::set_current_dir(&resolved) {
14325 eprintln!("pushd: {}: {}", target.display(), e);
14326 return 1;
14327 }
14328 if !quiet {
14329 self.print_dir_stack();
14330 }
14331 return 0;
14332 }
14333 }
14334
14335 let target = PathBuf::from(arg);
14337 let resolved = if physical {
14338 target.canonicalize().unwrap_or(target.clone())
14339 } else {
14340 target.clone()
14341 };
14342
14343 self.dir_stack.push(current);
14344 if let Err(e) = std::env::set_current_dir(&resolved) {
14345 eprintln!("pushd: {}: {}", arg, e);
14346 self.dir_stack.pop();
14347 return 1;
14348 }
14349 if !quiet {
14350 self.print_dir_stack();
14351 }
14352 0
14353 }
14354
14355 fn builtin_popd(&mut self, args: &[String]) -> i32 {
14357 let mut quiet = false;
14364 let mut physical = false;
14365 let mut stack_index: Option<String> = None;
14366
14367 for arg in args {
14368 if arg.starts_with('-') && arg.len() > 1 {
14369 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
14371 stack_index = Some(arg.clone());
14372 continue;
14373 }
14374 for ch in arg[1..].chars() {
14375 match ch {
14376 'q' => quiet = true,
14377 's' => physical = false,
14378 'L' => physical = false,
14379 'P' => physical = true,
14380 _ => {}
14381 }
14382 }
14383 } else if arg.starts_with('+') {
14384 stack_index = Some(arg.clone());
14385 }
14386 }
14387
14388 if self.dir_stack.is_empty() {
14389 eprintln!("popd: directory stack empty");
14390 return 1;
14391 }
14392
14393 if let Some(arg) = stack_index {
14395 if arg.starts_with('+') || arg.starts_with('-') {
14396 if let Ok(n) = arg[1..].parse::<usize>() {
14397 let total = self.dir_stack.len() + 1;
14398 if n >= total {
14399 eprintln!("popd: {}: directory stack index out of range", arg);
14400 return 1;
14401 }
14402 let remove_pos = if arg.starts_with('+') {
14403 n
14404 } else {
14405 total - 1 - n
14406 };
14407 if remove_pos == 0 {
14408 let target = self.dir_stack.remove(0);
14410 let resolved = if physical {
14411 target.canonicalize().unwrap_or(target.clone())
14412 } else {
14413 target.clone()
14414 };
14415 if let Err(e) = std::env::set_current_dir(&resolved) {
14416 eprintln!("popd: {}: {}", target.display(), e);
14417 return 1;
14418 }
14419 } else {
14420 self.dir_stack.remove(remove_pos - 1);
14421 }
14422 if !quiet {
14423 self.print_dir_stack();
14424 }
14425 return 0;
14426 }
14427 }
14428 }
14429
14430 let target = self.dir_stack.pop().unwrap();
14431 let resolved = if physical {
14432 target.canonicalize().unwrap_or(target.clone())
14433 } else {
14434 target.clone()
14435 };
14436 if let Err(e) = std::env::set_current_dir(&resolved) {
14437 eprintln!("popd: {}: {}", target.display(), e);
14438 self.dir_stack.push(target);
14439 return 1;
14440 }
14441 if !quiet {
14442 self.print_dir_stack();
14443 }
14444 0
14445 }
14446
14447 fn builtin_dirs(&mut self, args: &[String]) -> i32 {
14449 let mut clear = false;
14456 let mut full_paths = false;
14457 let mut per_line = false;
14458 let mut verbose = false;
14459 let mut indices: Vec<i32> = Vec::new();
14460
14461 for arg in args {
14462 if arg.starts_with('-') && arg.len() > 1 {
14463 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
14465 if let Ok(n) = arg.parse::<i32>() {
14466 indices.push(n);
14467 continue;
14468 }
14469 }
14470 for ch in arg[1..].chars() {
14471 match ch {
14472 'c' => clear = true,
14473 'l' => full_paths = true,
14474 'p' => per_line = true,
14475 'v' => verbose = true,
14476 _ => {}
14477 }
14478 }
14479 } else if arg.starts_with('+') && arg.len() > 1 {
14480 if let Ok(n) = arg[1..].parse::<i32>() {
14481 indices.push(n);
14482 }
14483 } else {
14484 if let Ok(n) = arg.parse::<i32>() {
14486 indices.push(n);
14487 }
14488 }
14489 }
14490
14491 if clear {
14492 self.dir_stack.clear();
14493 return 0;
14494 }
14495
14496 let current = std::env::current_dir().unwrap_or_default();
14497 let home = dirs::home_dir().unwrap_or_default();
14498
14499 let format_path = |p: &std::path::Path| -> String {
14500 let path_str = p.to_string_lossy().to_string();
14501 if !full_paths {
14502 let home_str = home.to_string_lossy();
14503 if path_str.starts_with(home_str.as_ref()) {
14504 return format!("~{}", &path_str[home_str.len()..]);
14505 }
14506 }
14507 path_str
14508 };
14509
14510 if !indices.is_empty() {
14512 let stack_len = self.dir_stack.len() + 1; for idx in indices {
14514 let actual_idx = if idx >= 0 {
14515 idx as usize
14516 } else {
14517 stack_len.saturating_sub((-idx) as usize)
14518 };
14519
14520 if actual_idx == 0 {
14521 println!("{}", format_path(¤t));
14522 } else if actual_idx <= self.dir_stack.len() {
14523 let stack_idx = self.dir_stack.len() - actual_idx;
14525 if let Some(dir) = self.dir_stack.get(stack_idx) {
14526 println!("{}", format_path(dir));
14527 }
14528 }
14529 }
14530 return 0;
14531 }
14532
14533 if verbose {
14534 println!(" 0 {}", format_path(¤t));
14535 for (i, dir) in self.dir_stack.iter().rev().enumerate() {
14536 println!("{:2} {}", i + 1, format_path(dir));
14537 }
14538 } else if per_line {
14539 println!("{}", format_path(¤t));
14540 for dir in self.dir_stack.iter().rev() {
14541 println!("{}", format_path(dir));
14542 }
14543 } else {
14544 let mut parts = vec![format_path(¤t)];
14545 for dir in self.dir_stack.iter().rev() {
14546 parts.push(format_path(dir));
14547 }
14548 println!("{}", parts.join(" "));
14549 }
14550 0
14551 }
14552
14553 fn print_dir_stack(&self) {
14554 let current = std::env::current_dir().unwrap_or_default();
14555 let mut parts = vec![current.to_string_lossy().to_string()];
14556 for dir in self.dir_stack.iter().rev() {
14557 parts.push(dir.to_string_lossy().to_string());
14558 }
14559 println!("{}", parts.join(" "));
14560 }
14561
14562 fn builtin_printf(&self, args: &[String]) -> i32 {
14564 if args.is_empty() {
14565 eprintln!("printf: usage: printf format [arguments]");
14566 return 1;
14567 }
14568
14569 let format = &args[0];
14570 let format_args = &args[1..];
14571 let mut arg_idx = 0;
14572 let mut output = String::new();
14573 let mut chars = format.chars().peekable();
14574
14575 while let Some(c) = chars.next() {
14576 if c == '\\' {
14577 match chars.next() {
14578 Some('n') => output.push('\n'),
14579 Some('t') => output.push('\t'),
14580 Some('r') => output.push('\r'),
14581 Some('\\') => output.push('\\'),
14582 Some('a') => output.push('\x07'),
14583 Some('b') => output.push('\x08'),
14584 Some('e') | Some('E') => output.push('\x1b'),
14585 Some('f') => output.push('\x0c'),
14586 Some('v') => output.push('\x0b'),
14587 Some('"') => output.push('"'),
14588 Some('\'') => output.push('\''),
14589 Some('0') => {
14590 let mut octal = String::new();
14591 while octal.len() < 3 {
14592 if let Some(&d) = chars.peek() {
14593 if d >= '0' && d <= '7' {
14594 octal.push(d);
14595 chars.next();
14596 } else {
14597 break;
14598 }
14599 } else {
14600 break;
14601 }
14602 }
14603 if octal.is_empty() {
14604 output.push('\0');
14605 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
14606 output.push(val as char);
14607 }
14608 }
14609 Some('x') => {
14610 let mut hex = String::new();
14611 while hex.len() < 2 {
14612 if let Some(&d) = chars.peek() {
14613 if d.is_ascii_hexdigit() {
14614 hex.push(d);
14615 chars.next();
14616 } else {
14617 break;
14618 }
14619 } else {
14620 break;
14621 }
14622 }
14623 if !hex.is_empty() {
14624 if let Ok(val) = u8::from_str_radix(&hex, 16) {
14625 output.push(val as char);
14626 }
14627 }
14628 }
14629 Some('u') => {
14630 let mut hex = String::new();
14631 while hex.len() < 4 {
14632 if let Some(&d) = chars.peek() {
14633 if d.is_ascii_hexdigit() {
14634 hex.push(d);
14635 chars.next();
14636 } else {
14637 break;
14638 }
14639 } else {
14640 break;
14641 }
14642 }
14643 if !hex.is_empty() {
14644 if let Ok(val) = u32::from_str_radix(&hex, 16) {
14645 if let Some(c) = char::from_u32(val) {
14646 output.push(c);
14647 }
14648 }
14649 }
14650 }
14651 Some('U') => {
14652 let mut hex = String::new();
14653 while hex.len() < 8 {
14654 if let Some(&d) = chars.peek() {
14655 if d.is_ascii_hexdigit() {
14656 hex.push(d);
14657 chars.next();
14658 } else {
14659 break;
14660 }
14661 } else {
14662 break;
14663 }
14664 }
14665 if !hex.is_empty() {
14666 if let Ok(val) = u32::from_str_radix(&hex, 16) {
14667 if let Some(c) = char::from_u32(val) {
14668 output.push(c);
14669 }
14670 }
14671 }
14672 }
14673 Some('c') => {
14674 print!("{}", output);
14675 return 0;
14676 }
14677 Some(other) => {
14678 output.push('\\');
14679 output.push(other);
14680 }
14681 None => output.push('\\'),
14682 }
14683 } else if c == '%' {
14684 if chars.peek() == Some(&'%') {
14685 chars.next();
14686 output.push('%');
14687 continue;
14688 }
14689
14690 let mut flags = String::new();
14691 while let Some(&f) = chars.peek() {
14692 if f == '-' || f == '+' || f == ' ' || f == '#' || f == '0' {
14693 flags.push(f);
14694 chars.next();
14695 } else {
14696 break;
14697 }
14698 }
14699
14700 let mut width = String::new();
14701 if chars.peek() == Some(&'*') {
14702 chars.next();
14703 if arg_idx < format_args.len() {
14704 width = format_args[arg_idx].clone();
14705 arg_idx += 1;
14706 }
14707 } else {
14708 while let Some(&d) = chars.peek() {
14709 if d.is_ascii_digit() {
14710 width.push(d);
14711 chars.next();
14712 } else {
14713 break;
14714 }
14715 }
14716 }
14717
14718 let mut precision = String::new();
14719 if chars.peek() == Some(&'.') {
14720 chars.next();
14721 if chars.peek() == Some(&'*') {
14722 chars.next();
14723 if arg_idx < format_args.len() {
14724 precision = format_args[arg_idx].clone();
14725 arg_idx += 1;
14726 }
14727 } else {
14728 while let Some(&d) = chars.peek() {
14729 if d.is_ascii_digit() {
14730 precision.push(d);
14731 chars.next();
14732 } else {
14733 break;
14734 }
14735 }
14736 }
14737 }
14738
14739 let specifier = chars.next().unwrap_or('s');
14740 let arg = if arg_idx < format_args.len() {
14741 let a = &format_args[arg_idx];
14742 arg_idx += 1;
14743 a.clone()
14744 } else {
14745 String::new()
14746 };
14747
14748 let width_val: usize = width.parse().unwrap_or(0);
14749 let prec_val: Option<usize> = if precision.is_empty() {
14750 None
14751 } else {
14752 precision.parse().ok()
14753 };
14754 let left_align = flags.contains('-');
14755 let zero_pad = flags.contains('0') && !left_align;
14756 let plus_sign = flags.contains('+');
14757 let space_sign = flags.contains(' ') && !plus_sign;
14758 let alt_form = flags.contains('#');
14759
14760 match specifier {
14761 's' => {
14762 let mut s = arg;
14763 if let Some(p) = prec_val {
14764 s = s.chars().take(p).collect();
14765 }
14766 if width_val > s.len() {
14767 if left_align {
14768 output.push_str(&s);
14769 output.push_str(&" ".repeat(width_val - s.len()));
14770 } else {
14771 output.push_str(&" ".repeat(width_val - s.len()));
14772 output.push_str(&s);
14773 }
14774 } else {
14775 output.push_str(&s);
14776 }
14777 }
14778 'b' => {
14779 let expanded = self.expand_printf_escapes(&arg);
14780 if let Some(p) = prec_val {
14781 let s: String = expanded.chars().take(p).collect();
14782 output.push_str(&s);
14783 } else {
14784 output.push_str(&expanded);
14785 }
14786 }
14787 'c' => {
14788 if let Some(ch) = arg.chars().next() {
14789 output.push(ch);
14790 }
14791 }
14792 'q' => {
14793 output.push('\'');
14794 for ch in arg.chars() {
14795 if ch == '\'' {
14796 output.push_str("'\\''");
14797 } else {
14798 output.push(ch);
14799 }
14800 }
14801 output.push('\'');
14802 }
14803 'd' | 'i' => {
14804 let val: i64 = if arg.starts_with("0x") || arg.starts_with("0X") {
14805 i64::from_str_radix(&arg[2..], 16).unwrap_or(0)
14806 } else if arg.starts_with("0") && arg.len() > 1 && !arg.contains('.') {
14807 i64::from_str_radix(&arg[1..], 8).unwrap_or(0)
14808 } else if arg.starts_with('\'') || arg.starts_with('"') {
14809 arg.chars().nth(1).map(|c| c as i64).unwrap_or(0)
14810 } else {
14811 arg.parse().unwrap_or(0)
14812 };
14813
14814 let sign = if val < 0 {
14815 "-"
14816 } else if plus_sign {
14817 "+"
14818 } else if space_sign {
14819 " "
14820 } else {
14821 ""
14822 };
14823 let abs_val = val.abs();
14824 let num_str = abs_val.to_string();
14825 let total_len = sign.len() + num_str.len();
14826
14827 if width_val > total_len {
14828 if left_align {
14829 output.push_str(sign);
14830 output.push_str(&num_str);
14831 output.push_str(&" ".repeat(width_val - total_len));
14832 } else if zero_pad {
14833 output.push_str(sign);
14834 output.push_str(&"0".repeat(width_val - total_len));
14835 output.push_str(&num_str);
14836 } else {
14837 output.push_str(&" ".repeat(width_val - total_len));
14838 output.push_str(sign);
14839 output.push_str(&num_str);
14840 }
14841 } else {
14842 output.push_str(sign);
14843 output.push_str(&num_str);
14844 }
14845 }
14846 'u' => {
14847 let val: u64 = if arg.starts_with("0x") || arg.starts_with("0X") {
14848 u64::from_str_radix(&arg[2..], 16).unwrap_or(0)
14849 } else if arg.starts_with("0") && arg.len() > 1 {
14850 u64::from_str_radix(&arg[1..], 8).unwrap_or(0)
14851 } else {
14852 arg.parse().unwrap_or(0)
14853 };
14854 let num_str = val.to_string();
14855 if width_val > num_str.len() {
14856 if left_align {
14857 output.push_str(&num_str);
14858 output.push_str(&" ".repeat(width_val - num_str.len()));
14859 } else if zero_pad {
14860 output.push_str(&"0".repeat(width_val - num_str.len()));
14861 output.push_str(&num_str);
14862 } else {
14863 output.push_str(&" ".repeat(width_val - num_str.len()));
14864 output.push_str(&num_str);
14865 }
14866 } else {
14867 output.push_str(&num_str);
14868 }
14869 }
14870 'o' => {
14871 let val: u64 = arg.parse().unwrap_or(0);
14872 let num_str = format!("{:o}", val);
14873 let prefix = if alt_form && val != 0 { "0" } else { "" };
14874 let total_len = prefix.len() + num_str.len();
14875 if width_val > total_len {
14876 if left_align {
14877 output.push_str(prefix);
14878 output.push_str(&num_str);
14879 output.push_str(&" ".repeat(width_val - total_len));
14880 } else {
14881 output.push_str(&" ".repeat(width_val - total_len));
14882 output.push_str(prefix);
14883 output.push_str(&num_str);
14884 }
14885 } else {
14886 output.push_str(prefix);
14887 output.push_str(&num_str);
14888 }
14889 }
14890 'x' => {
14891 let val: u64 = arg.parse().unwrap_or(0);
14892 let num_str = format!("{:x}", val);
14893 let prefix = if alt_form && val != 0 { "0x" } else { "" };
14894 let total_len = prefix.len() + num_str.len();
14895 if width_val > total_len {
14896 if left_align {
14897 output.push_str(prefix);
14898 output.push_str(&num_str);
14899 output.push_str(&" ".repeat(width_val - total_len));
14900 } else {
14901 output.push_str(&" ".repeat(width_val - total_len));
14902 output.push_str(prefix);
14903 output.push_str(&num_str);
14904 }
14905 } else {
14906 output.push_str(prefix);
14907 output.push_str(&num_str);
14908 }
14909 }
14910 'X' => {
14911 let val: u64 = arg.parse().unwrap_or(0);
14912 let num_str = format!("{:X}", val);
14913 let prefix = if alt_form && val != 0 { "0X" } else { "" };
14914 let total_len = prefix.len() + num_str.len();
14915 if width_val > total_len {
14916 if left_align {
14917 output.push_str(prefix);
14918 output.push_str(&num_str);
14919 output.push_str(&" ".repeat(width_val - total_len));
14920 } else {
14921 output.push_str(&" ".repeat(width_val - total_len));
14922 output.push_str(prefix);
14923 output.push_str(&num_str);
14924 }
14925 } else {
14926 output.push_str(prefix);
14927 output.push_str(&num_str);
14928 }
14929 }
14930 'e' | 'E' => {
14931 let val: f64 = arg.parse().unwrap_or(0.0);
14932 let prec = prec_val.unwrap_or(6);
14933 let formatted = if specifier == 'e' {
14934 format!("{:.prec$e}", val, prec = prec)
14935 } else {
14936 format!("{:.prec$E}", val, prec = prec)
14937 };
14938 if width_val > formatted.len() {
14939 if left_align {
14940 output.push_str(&formatted);
14941 output.push_str(&" ".repeat(width_val - formatted.len()));
14942 } else {
14943 output.push_str(&" ".repeat(width_val - formatted.len()));
14944 output.push_str(&formatted);
14945 }
14946 } else {
14947 output.push_str(&formatted);
14948 }
14949 }
14950 'f' | 'F' => {
14951 let val: f64 = arg.parse().unwrap_or(0.0);
14952 let prec = prec_val.unwrap_or(6);
14953 let sign = if val < 0.0 {
14954 "-"
14955 } else if plus_sign {
14956 "+"
14957 } else if space_sign {
14958 " "
14959 } else {
14960 ""
14961 };
14962 let formatted = format!("{:.prec$}", val.abs(), prec = prec);
14963 let total = sign.len() + formatted.len();
14964 if width_val > total {
14965 if left_align {
14966 output.push_str(sign);
14967 output.push_str(&formatted);
14968 output.push_str(&" ".repeat(width_val - total));
14969 } else if zero_pad {
14970 output.push_str(sign);
14971 output.push_str(&"0".repeat(width_val - total));
14972 output.push_str(&formatted);
14973 } else {
14974 output.push_str(&" ".repeat(width_val - total));
14975 output.push_str(sign);
14976 output.push_str(&formatted);
14977 }
14978 } else {
14979 output.push_str(sign);
14980 output.push_str(&formatted);
14981 }
14982 }
14983 'g' | 'G' => {
14984 let val: f64 = arg.parse().unwrap_or(0.0);
14985 let prec = prec_val.unwrap_or(6).max(1);
14986 let formatted = if specifier == 'g' {
14987 format!("{:.prec$}", val, prec = prec)
14988 } else {
14989 format!("{:.prec$}", val, prec = prec).to_uppercase()
14990 };
14991 output.push_str(&formatted);
14992 }
14993 'a' | 'A' => {
14994 let val: f64 = arg.parse().unwrap_or(0.0);
14995 let formatted = float_to_hex(val, specifier == 'A');
14996 output.push_str(&formatted);
14997 }
14998 _ => {
14999 output.push('%');
15000 output.push(specifier);
15001 }
15002 }
15003 } else {
15004 output.push(c);
15005 }
15006 }
15007
15008 print!("{}", output);
15009 0
15010 }
15011
15012 fn expand_printf_escapes(&self, s: &str) -> String {
15013 let mut result = String::new();
15014 let mut chars = s.chars().peekable();
15015 while let Some(c) = chars.next() {
15016 if c == '\\' {
15017 match chars.next() {
15018 Some('n') => result.push('\n'),
15019 Some('t') => result.push('\t'),
15020 Some('r') => result.push('\r'),
15021 Some('\\') => result.push('\\'),
15022 Some('a') => result.push('\x07'),
15023 Some('b') => result.push('\x08'),
15024 Some('e') | Some('E') => result.push('\x1b'),
15025 Some('f') => result.push('\x0c'),
15026 Some('v') => result.push('\x0b'),
15027 Some('0') => {
15028 let mut octal = String::new();
15029 while octal.len() < 3 {
15030 if let Some(&d) = chars.peek() {
15031 if d >= '0' && d <= '7' {
15032 octal.push(d);
15033 chars.next();
15034 } else {
15035 break;
15036 }
15037 } else {
15038 break;
15039 }
15040 }
15041 if octal.is_empty() {
15042 result.push('\0');
15043 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
15044 result.push(val as char);
15045 }
15046 }
15047 Some('c') => break,
15048 Some(other) => {
15049 result.push('\\');
15050 result.push(other);
15051 }
15052 None => result.push('\\'),
15053 }
15054 } else {
15055 result.push(c);
15056 }
15057 }
15058 result
15059 }
15060
15061 fn evaluate_arithmetic_expr(&mut self, expr: &str) -> i64 {
15062 self.eval_arith_expr(expr)
15063 }
15064
15065 fn builtin_break(&mut self, args: &[String]) -> i32 {
15071 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
15072 self.breaking = levels.max(1);
15073 0
15074 }
15075
15076 fn builtin_continue(&mut self, args: &[String]) -> i32 {
15078 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
15079 self.continuing = levels.max(1);
15080 0
15081 }
15082
15083 fn builtin_disable(&mut self, args: &[String]) -> i32 {
15085 let mut disable_aliases = false;
15086 let mut disable_builtins = false;
15087 let mut disable_functions = false;
15088 let mut names = Vec::new();
15089
15090 let mut iter = args.iter();
15091 while let Some(arg) = iter.next() {
15092 match arg.as_str() {
15093 "-a" => disable_aliases = true,
15094 "-f" => disable_functions = true,
15095 "-r" => disable_builtins = true,
15096 _ if arg.starts_with('-') => {}
15097 _ => names.push(arg.clone()),
15098 }
15099 }
15100
15101 if !disable_aliases && !disable_functions {
15103 disable_builtins = true;
15104 }
15105
15106 for name in names {
15107 if disable_aliases {
15108 self.aliases.remove(&name);
15109 }
15110 if disable_functions {
15111 self.functions.remove(&name);
15112 }
15113 if disable_builtins {
15114 self.options.insert(format!("_disabled_{}", name), true);
15116 }
15117 }
15118 0
15119 }
15120
15121 fn builtin_enable(&mut self, args: &[String]) -> i32 {
15123 for arg in args {
15124 if !arg.starts_with('-') {
15125 self.options.remove(&format!("_disabled_{}", arg));
15126 }
15127 }
15128 0
15129 }
15130
15131 fn builtin_emulate(&mut self, args: &[String]) -> i32 {
15133 let mut local_mode = false;
15136 let mut reset_mode = false;
15137 let mut list_mode = false;
15138 let mut mode: Option<String> = None;
15139 let mut command_arg: Option<String> = None;
15140 let mut extra_set_opts: Vec<String> = Vec::new();
15141 let mut extra_unset_opts: Vec<String> = Vec::new();
15142
15143 let mut i = 0;
15144 while i < args.len() {
15145 let arg = &args[i];
15146
15147 if arg == "-c" {
15148 i += 1;
15150 if i < args.len() {
15151 command_arg = Some(args[i].clone());
15152 } else {
15153 eprintln!("emulate: -c requires an argument");
15154 return 1;
15155 }
15156 } else if arg == "-o" {
15157 i += 1;
15159 if i < args.len() {
15160 extra_set_opts.push(args[i].clone());
15161 } else {
15162 eprintln!("emulate: -o requires an argument");
15163 return 1;
15164 }
15165 } else if arg == "+o" {
15166 i += 1;
15168 if i < args.len() {
15169 extra_unset_opts.push(args[i].clone());
15170 } else {
15171 eprintln!("emulate: +o requires an argument");
15172 return 1;
15173 }
15174 } else if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
15175 for ch in arg[1..].chars() {
15177 match ch {
15178 'L' => local_mode = true,
15179 'R' => reset_mode = true,
15180 'l' => list_mode = true,
15181 _ => {
15182 eprintln!("emulate: bad option: -{}", ch);
15183 return 1;
15184 }
15185 }
15186 }
15187 } else if arg.starts_with('+') && arg.len() > 1 {
15188 for ch in arg[1..].chars() {
15190 extra_unset_opts.push(ch.to_string());
15192 }
15193 } else if mode.is_none() {
15194 mode = Some(arg.clone());
15195 }
15196 i += 1;
15197 }
15198
15199 if local_mode && command_arg.is_some() {
15201 eprintln!("emulate: -L and -c are mutually exclusive");
15202 return 1;
15203 }
15204
15205 if mode.is_none() && !list_mode {
15207 let current = self
15208 .variables
15209 .get("EMULATE")
15210 .cloned()
15211 .unwrap_or_else(|| "zsh".to_string());
15212 println!("{}", current);
15213 return 0;
15214 }
15215
15216 let mode = mode.unwrap_or_else(|| "zsh".to_string());
15217
15218 let (set_opts, unset_opts) = Self::emulate_mode_options(&mode, reset_mode);
15220
15221 if list_mode {
15223 for opt in &set_opts {
15224 println!("{}", opt);
15225 }
15226 for opt in &unset_opts {
15227 println!("no{}", opt);
15228 }
15229 if local_mode {
15230 println!("localoptions");
15231 println!("localpatterns");
15232 println!("localtraps");
15233 }
15234 return 0;
15235 }
15236
15237 let saved_options = if command_arg.is_some() {
15239 Some(self.options.clone())
15240 } else {
15241 None
15242 };
15243 let saved_emulate = if command_arg.is_some() {
15244 self.variables.get("EMULATE").cloned()
15245 } else {
15246 None
15247 };
15248
15249 self.variables.insert("EMULATE".to_string(), mode.clone());
15251
15252 for opt in &set_opts {
15254 let opt_name = opt.to_lowercase().replace('_', "");
15255 self.options.insert(opt_name, true);
15256 }
15257 for opt in &unset_opts {
15258 let opt_name = opt.to_lowercase().replace('_', "");
15259 self.options.insert(opt_name, false);
15260 }
15261
15262 for opt in &extra_set_opts {
15264 let opt_name = opt.to_lowercase().replace('_', "");
15265 self.options.insert(opt_name, true);
15266 }
15267 for opt in &extra_unset_opts {
15268 let opt_name = opt.to_lowercase().replace('_', "");
15269 self.options.insert(opt_name, false);
15270 }
15271
15272 if local_mode {
15274 self.options.insert("localoptions".to_string(), true);
15275 self.options.insert("localpatterns".to_string(), true);
15276 self.options.insert("localtraps".to_string(), true);
15277 }
15278
15279 let result = if let Some(cmd) = command_arg {
15281 let status = self.execute_script(&cmd).unwrap_or(1);
15282
15283 if let Some(opts) = saved_options {
15285 self.options = opts;
15286 }
15287 if let Some(emu) = saved_emulate {
15288 self.variables.insert("EMULATE".to_string(), emu);
15289 } else {
15290 self.variables.remove("EMULATE");
15291 }
15292
15293 status
15294 } else {
15295 0
15296 };
15297
15298 result
15299 }
15300
15301 fn emulate_mode_options(mode: &str, reset: bool) -> (Vec<&'static str>, Vec<&'static str>) {
15303 match mode {
15304 "zsh" => {
15305 if reset {
15306 (
15308 vec![
15309 "aliases",
15310 "alwayslastprompt",
15311 "autolist",
15312 "automenu",
15313 "autoparamslash",
15314 "autoremoveslash",
15315 "banghist",
15316 "bareglobqual",
15317 "completeinword",
15318 "extendedhistory",
15319 "functionargzero",
15320 "glob",
15321 "hashcmds",
15322 "hashdirs",
15323 "histexpand",
15324 "histignoredups",
15325 "interactivecomments",
15326 "listambiguous",
15327 "listtypes",
15328 "multios",
15329 "nomatch",
15330 "notify",
15331 "promptpercent",
15332 "promptsubst",
15333 ],
15334 vec![
15335 "ksharrays",
15336 "kshglob",
15337 "shwordsplit",
15338 "shglob",
15339 "posixbuiltins",
15340 "posixidentifiers",
15341 "posixstrings",
15342 "bsdecho",
15343 "ignorebraces",
15344 ],
15345 )
15346 } else {
15347 (vec!["functionargzero"], vec!["ksharrays", "shwordsplit"])
15349 }
15350 }
15351 "sh" => {
15352 let set = vec![
15353 "ksharrays",
15354 "shwordsplit",
15355 "posixbuiltins",
15356 "shglob",
15357 "shfileexpansion",
15358 "globsubst",
15359 "interactivecomments",
15360 "rmstarsilent",
15361 "bsdecho",
15362 "ignorebraces",
15363 ];
15364 let unset = vec![
15365 "badpattern",
15366 "banghist",
15367 "bgnice",
15368 "equals",
15369 "functionargzero",
15370 "globalexport",
15371 "multios",
15372 "nomatch",
15373 "notify",
15374 "promptpercent",
15375 ];
15376 (set, unset)
15377 }
15378 "ksh" => {
15379 let set = vec![
15380 "ksharrays",
15381 "kshglob",
15382 "shwordsplit",
15383 "posixbuiltins",
15384 "kshoptionprint",
15385 "localoptions",
15386 "promptbang",
15387 "promptsubst",
15388 "singlelinezle",
15389 "interactivecomments",
15390 ];
15391 let unset = vec![
15392 "badpattern",
15393 "banghist",
15394 "bgnice",
15395 "equals",
15396 "functionargzero",
15397 "globalexport",
15398 "multios",
15399 "nomatch",
15400 "notify",
15401 "promptpercent",
15402 ];
15403 (set, unset)
15404 }
15405 "csh" => {
15406 (vec!["cshnullglob", "cshjunkiequotes"], vec!["nomatch"])
15408 }
15409 "bash" => {
15410 let set = vec![
15411 "ksharrays",
15412 "shwordsplit",
15413 "interactivecomments",
15414 "shfileexpansion",
15415 "globsubst",
15416 ];
15417 let unset = vec![
15418 "badpattern",
15419 "banghist",
15420 "functionargzero",
15421 "multios",
15422 "nomatch",
15423 "notify",
15424 "promptpercent",
15425 ];
15426 (set, unset)
15427 }
15428 _ => (vec![], vec![]),
15429 }
15430 }
15431
15432 fn builtin_exec(&mut self, args: &[String]) -> i32 {
15434 let mut clear_env = false;
15440 let mut login_shell = false;
15441 let mut argv0: Option<String> = None;
15442 let mut cmd_args: Vec<String> = Vec::new();
15443
15444 let mut i = 0;
15445 while i < args.len() {
15446 let arg = &args[i];
15447
15448 if arg == "-c" && cmd_args.is_empty() {
15449 clear_env = true;
15450 } else if arg == "-l" && cmd_args.is_empty() {
15451 login_shell = true;
15452 } else if arg == "-a" && cmd_args.is_empty() {
15453 i += 1;
15454 if i < args.len() {
15455 argv0 = Some(args[i].clone());
15456 }
15457 } else if arg.starts_with('-') && cmd_args.is_empty() {
15458 for ch in arg[1..].chars() {
15460 match ch {
15461 'c' => clear_env = true,
15462 'l' => login_shell = true,
15463 'a' => {
15464 i += 1;
15465 if i < args.len() {
15466 argv0 = Some(args[i].clone());
15467 }
15468 }
15469 _ => {}
15470 }
15471 }
15472 } else {
15473 cmd_args.push(arg.clone());
15474 }
15475 i += 1;
15476 }
15477
15478 if cmd_args.is_empty() {
15479 if clear_env {
15481 for (key, _) in env::vars() {
15482 env::remove_var(&key);
15483 }
15484 }
15485 return 0;
15486 }
15487
15488 let cmd = &cmd_args[0];
15489 let rest_args: Vec<&str> = cmd_args[1..].iter().map(|s| s.as_str()).collect();
15490
15491 let effective_argv0 = if let Some(a0) = argv0 {
15493 a0
15494 } else if login_shell {
15495 format!("-{}", cmd)
15496 } else {
15497 cmd.clone()
15498 };
15499
15500 use std::os::unix::process::CommandExt;
15501 let mut command = std::process::Command::new(cmd);
15502 command.arg0(&effective_argv0);
15503 command.args(&rest_args);
15504
15505 if clear_env {
15506 command.env_clear();
15507 }
15508
15509 let err = command.exec();
15510 eprintln!("exec: {}: {}", cmd, err);
15511 1
15512 }
15513
15514 fn builtin_float(&mut self, args: &[String]) -> i32 {
15516 for arg in args {
15517 if arg.starts_with('-') {
15518 continue;
15519 }
15520 if let Some(eq_pos) = arg.find('=') {
15521 let name = &arg[..eq_pos];
15522 let value = &arg[eq_pos + 1..];
15523 let float_val: f64 = value.parse().unwrap_or(0.0);
15524 self.variables
15525 .insert(name.to_string(), float_val.to_string());
15526 self.options.insert(format!("_float_{}", name), true);
15527 } else {
15528 self.variables.insert(arg.clone(), "0.0".to_string());
15529 self.options.insert(format!("_float_{}", arg), true);
15530 }
15531 }
15532 0
15533 }
15534
15535 fn builtin_integer(&mut self, args: &[String]) -> i32 {
15537 for arg in args {
15538 if arg.starts_with('-') {
15539 continue;
15540 }
15541 if let Some(eq_pos) = arg.find('=') {
15542 let name = &arg[..eq_pos];
15543 let value = &arg[eq_pos + 1..];
15544 let int_val: i64 = value.parse().unwrap_or(0);
15545 self.variables.insert(name.to_string(), int_val.to_string());
15546 self.options.insert(format!("_integer_{}", name), true);
15547 } else {
15548 self.variables.insert(arg.clone(), "0".to_string());
15549 self.options.insert(format!("_integer_{}", arg), true);
15550 }
15551 }
15552 0
15553 }
15554
15555 fn builtin_functions(&self, args: &[String]) -> i32 {
15557 let mut list_only = false;
15558 let mut show_trace = false;
15559 let mut names: Vec<&str> = Vec::new();
15560
15561 for arg in args {
15562 match arg.as_str() {
15563 "-l" => list_only = true,
15564 "-t" => show_trace = true,
15565 _ if arg.starts_with('-') => {}
15566 _ => names.push(arg),
15567 }
15568 }
15569
15570 if names.is_empty() {
15571 let mut func_names: Vec<_> = self.functions.keys().collect();
15573 func_names.sort();
15574 for name in func_names {
15575 if list_only {
15576 println!("{}", name);
15577 } else if let Some(func) = self.functions.get(name) {
15578 let body = crate::text::getpermtext(func);
15579 println!("{} () {{\n\t{}\n}}", name, body.trim());
15580 }
15581 }
15582 } else {
15583 for name in names {
15585 if let Some(func) = self.functions.get(name) {
15586 if show_trace {
15587 println!("functions -t {}", name);
15588 } else {
15589 let body = crate::text::getpermtext(func);
15590 println!("{} () {{\n\t{}\n}}", name, body.trim());
15591 }
15592 } else {
15593 eprintln!("functions: no such function: {}", name);
15594 return 1;
15595 }
15596 }
15597 }
15598 0
15599 }
15600
15601 fn builtin_print(&mut self, args: &[String]) -> i32 {
15603 let mut no_newline = false;
15606 let mut one_per_line = false;
15607 let mut interpret_escapes = true; let mut raw_mode = false;
15609 let mut prompt_expand = false;
15610 let mut fd: i32 = 1; let mut columns = 0usize;
15612 let mut null_terminate = false;
15613 let mut push_to_stack = false;
15614 let mut add_to_history = false;
15615 let mut sort_asc = false;
15616 let mut sort_desc = false;
15617 let mut named_dir_subst = false;
15618 let mut store_var: Option<String> = None;
15619 let mut format_string: Option<String> = None;
15620 let mut output_args: Vec<String> = Vec::new();
15621
15622 let mut i = 0;
15623 while i < args.len() {
15624 let arg = &args[i];
15625
15626 if arg == "--" {
15627 i += 1;
15628 while i < args.len() {
15629 output_args.push(args[i].clone());
15630 i += 1;
15631 }
15632 break;
15633 }
15634
15635 if arg.starts_with('-')
15636 && arg.len() > 1
15637 && !arg
15638 .chars()
15639 .nth(1)
15640 .map(|c| c.is_ascii_digit())
15641 .unwrap_or(false)
15642 {
15643 let mut chars = arg[1..].chars().peekable();
15644 while let Some(ch) = chars.next() {
15645 match ch {
15646 'n' => no_newline = true,
15647 'l' => one_per_line = true,
15648 'r' => {
15649 raw_mode = true;
15650 interpret_escapes = false;
15651 }
15652 'R' => {
15653 raw_mode = true;
15654 interpret_escapes = false;
15655 }
15656 'e' => interpret_escapes = true,
15657 'E' => interpret_escapes = false,
15658 'P' => prompt_expand = true,
15659 'N' => null_terminate = true,
15660 'z' => push_to_stack = true,
15661 's' => add_to_history = true,
15662 'o' => sort_asc = true,
15663 'O' => sort_desc = true,
15664 'D' => named_dir_subst = true,
15665 'c' => columns = 1,
15666 'a' | 'b' | 'i' | 'm' | 'p' | 'S' | 'x' | 'X' => {} 'u' => {
15668 let rest: String = chars.collect();
15670 if !rest.is_empty() {
15671 fd = rest.parse().unwrap_or(1);
15672 } else {
15673 i += 1;
15674 if i < args.len() {
15675 fd = args[i].parse().unwrap_or(1);
15676 }
15677 }
15678 break;
15679 }
15680 'C' => {
15681 let rest: String = chars.collect();
15683 if !rest.is_empty() {
15684 columns = rest.parse().unwrap_or(0);
15685 } else {
15686 i += 1;
15687 if i < args.len() {
15688 columns = args[i].parse().unwrap_or(0);
15689 }
15690 }
15691 break;
15692 }
15693 'v' => {
15694 let rest: String = chars.collect();
15696 if !rest.is_empty() {
15697 store_var = Some(rest);
15698 } else {
15699 i += 1;
15700 if i < args.len() {
15701 store_var = Some(args[i].clone());
15702 }
15703 }
15704 break;
15705 }
15706 'f' => {
15707 let rest: String = chars.collect();
15709 if !rest.is_empty() {
15710 format_string = Some(rest);
15711 } else {
15712 i += 1;
15713 if i < args.len() {
15714 format_string = Some(args[i].clone());
15715 }
15716 }
15717 break;
15718 }
15719 _ => {}
15720 }
15721 }
15722 } else {
15723 output_args.push(arg.clone());
15724 }
15725 i += 1;
15726 }
15727
15728 let _ = push_to_stack; let _ = fd; if sort_asc {
15733 output_args.sort();
15734 } else if sort_desc {
15735 output_args.sort_by(|a, b| b.cmp(a));
15736 }
15737
15738 if let Some(fmt) = format_string {
15740 let output = self.printf_format(&fmt, &output_args);
15741 if let Some(var) = store_var {
15742 self.variables.insert(var, output);
15743 } else {
15744 print!("{}", output);
15745 }
15746 return 0;
15747 }
15748
15749 let processed: Vec<String> = output_args
15751 .iter()
15752 .map(|s| {
15753 let mut result = s.clone();
15754 if prompt_expand {
15755 result = self.expand_prompt_string(&result);
15756 }
15757 if interpret_escapes && !raw_mode {
15758 result = self.expand_printf_escapes(&result);
15759 }
15760 if named_dir_subst {
15761 if let Ok(home) = env::var("HOME") {
15763 if result.starts_with(&home) {
15764 result = format!("~{}", &result[home.len()..]);
15765 }
15766 }
15767 for (name, path) in &self.named_dirs {
15769 let path_str = path.to_string_lossy();
15770 if result.starts_with(path_str.as_ref()) {
15771 result = format!("~{}{}", name, &result[path_str.len()..]);
15772 break;
15773 }
15774 }
15775 }
15776 result
15777 })
15778 .collect();
15779
15780 let separator = if one_per_line { "\n" } else { " " };
15782 let terminator = if null_terminate {
15783 "\0"
15784 } else if no_newline {
15785 ""
15786 } else {
15787 "\n"
15788 };
15789
15790 let output = if one_per_line {
15792 processed.join("\n")
15793 } else if columns > 0 {
15794 let mut result = String::new();
15796 let num_items = processed.len();
15797 let rows = (num_items + columns - 1) / columns;
15798 for row in 0..rows {
15799 let mut row_items = Vec::new();
15800 for col in 0..columns {
15801 let idx = row + col * rows;
15802 if idx < num_items {
15803 row_items.push(processed[idx].as_str());
15804 }
15805 }
15806 result.push_str(&row_items.join("\t"));
15807 if row < rows - 1 {
15808 result.push('\n');
15809 }
15810 }
15811 result
15812 } else {
15813 processed.join(separator)
15814 };
15815
15816 if add_to_history {
15818 if let Some(ref mut engine) = self.history {
15819 let _ = engine.add(&output, None);
15820 }
15821 }
15822
15823 if let Some(var) = store_var {
15825 self.variables.insert(var, output);
15826 } else {
15827 print!("{}{}", output, terminator);
15828 }
15829
15830 0
15831 }
15832
15833 fn printf_format(&self, format: &str, args: &[String]) -> String {
15834 let mut result = String::new();
15835 let mut arg_idx = 0;
15836 let mut chars = format.chars().peekable();
15837
15838 while let Some(ch) = chars.next() {
15839 if ch == '%' {
15840 if chars.peek() == Some(&'%') {
15841 chars.next();
15842 result.push('%');
15843 continue;
15844 }
15845
15846 let mut spec = String::from("%");
15848
15849 while let Some(&c) = chars.peek() {
15851 if c == '-' || c == '+' || c == ' ' || c == '#' || c == '0' {
15852 spec.push(c);
15853 chars.next();
15854 } else {
15855 break;
15856 }
15857 }
15858
15859 while let Some(&c) = chars.peek() {
15861 if c.is_ascii_digit() {
15862 spec.push(c);
15863 chars.next();
15864 } else {
15865 break;
15866 }
15867 }
15868
15869 if chars.peek() == Some(&'.') {
15871 spec.push('.');
15872 chars.next();
15873 while let Some(&c) = chars.peek() {
15874 if c.is_ascii_digit() {
15875 spec.push(c);
15876 chars.next();
15877 } else {
15878 break;
15879 }
15880 }
15881 }
15882
15883 if let Some(conv) = chars.next() {
15885 let arg = args.get(arg_idx).map(|s| s.as_str()).unwrap_or("");
15886 arg_idx += 1;
15887
15888 match conv {
15889 's' => result.push_str(arg),
15890 'd' | 'i' => {
15891 let n: i64 = arg.parse().unwrap_or(0);
15892 result.push_str(&n.to_string());
15893 }
15894 'u' => {
15895 let n: u64 = arg.parse().unwrap_or(0);
15896 result.push_str(&n.to_string());
15897 }
15898 'x' => {
15899 let n: i64 = arg.parse().unwrap_or(0);
15900 result.push_str(&format!("{:x}", n));
15901 }
15902 'X' => {
15903 let n: i64 = arg.parse().unwrap_or(0);
15904 result.push_str(&format!("{:X}", n));
15905 }
15906 'o' => {
15907 let n: i64 = arg.parse().unwrap_or(0);
15908 result.push_str(&format!("{:o}", n));
15909 }
15910 'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
15911 let n: f64 = arg.parse().unwrap_or(0.0);
15912 result.push_str(&format!("{}", n));
15913 }
15914 'c' => {
15915 if let Some(c) = arg.chars().next() {
15916 result.push(c);
15917 }
15918 }
15919 'b' => {
15920 result.push_str(&self.expand_printf_escapes(arg));
15921 }
15922 'n' => result.push('\n'),
15923 _ => {
15924 result.push('%');
15925 result.push(conv);
15926 }
15927 }
15928 }
15929 } else {
15930 result.push(ch);
15931 }
15932 }
15933
15934 result
15935 }
15936
15937 fn builtin_whence(&self, args: &[String]) -> i32 {
15939 let mut verbose = false;
15952 let mut csh_style = false;
15953 let mut word_type = false;
15954 let mut skip_functions = false;
15955 let mut path_only = false;
15956 let mut show_all = false;
15957 let mut pattern_mode = false;
15958 let mut show_symlink = false;
15959 let mut show_symlink_steps = false;
15960 let mut tab_expand: Option<usize> = None;
15961 let mut names: Vec<&str> = Vec::new();
15962
15963 let mut i = 0;
15964 while i < args.len() {
15965 let arg = &args[i];
15966
15967 if arg == "--" {
15968 i += 1;
15969 while i < args.len() {
15970 names.push(&args[i]);
15971 i += 1;
15972 }
15973 break;
15974 }
15975
15976 if arg.starts_with('-') && arg.len() > 1 {
15977 let mut chars = arg[1..].chars().peekable();
15978 while let Some(ch) = chars.next() {
15979 match ch {
15980 'v' => verbose = true,
15981 'c' => csh_style = true,
15982 'w' => word_type = true,
15983 'f' => skip_functions = true,
15984 'p' => path_only = true,
15985 'a' => show_all = true,
15986 'm' => pattern_mode = true,
15987 's' => show_symlink = true,
15988 'S' => show_symlink_steps = true,
15989 'x' => {
15990 let rest: String = chars.collect();
15992 if !rest.is_empty() {
15993 tab_expand = rest.parse().ok();
15994 } else {
15995 i += 1;
15996 if i < args.len() {
15997 tab_expand = args[i].parse().ok();
15998 }
15999 }
16000 break;
16001 }
16002 _ => {}
16003 }
16004 }
16005 } else {
16006 names.push(arg);
16007 }
16008 i += 1;
16009 }
16010
16011 let _ = csh_style; let _ = pattern_mode; let _ = tab_expand;
16014
16015 let mut status = 0;
16016 for name in names {
16017 let mut found = false;
16018 let mut word = "none";
16019
16020 if !path_only {
16021 if self.is_reserved_word(name) {
16023 found = true;
16024 word = "reserved";
16025 if word_type {
16026 println!("{}: {}", name, word);
16027 } else if verbose {
16028 println!("{} is a reserved word", name);
16029 } else {
16030 println!("{}", name);
16031 }
16032 if !show_all {
16033 continue;
16034 }
16035 }
16036
16037 if let Some(alias_val) = self.aliases.get(name) {
16039 found = true;
16040 word = "alias";
16041 if word_type {
16042 println!("{}: {}", name, word);
16043 } else if verbose {
16044 println!("{} is an alias for {}", name, alias_val);
16045 } else {
16046 println!("{}", alias_val);
16047 }
16048 if !show_all {
16049 continue;
16050 }
16051 }
16052
16053 if !skip_functions && self.functions.contains_key(name) {
16055 found = true;
16056 word = "function";
16057 if word_type {
16058 println!("{}: {}", name, word);
16059 } else if verbose {
16060 println!("{} is a shell function", name);
16061 } else {
16062 println!("{}", name);
16063 }
16064 if !show_all {
16065 continue;
16066 }
16067 }
16068
16069 if self.is_builtin(name) {
16071 found = true;
16072 word = "builtin";
16073 if word_type {
16074 println!("{}: {}", name, word);
16075 } else if verbose {
16076 println!("{} is a shell builtin", name);
16077 } else {
16078 println!("{}", name);
16079 }
16080 if !show_all {
16081 continue;
16082 }
16083 }
16084
16085 if let Some(path) = self.named_dirs.get(name) {
16088 found = true;
16089 word = "hashed";
16090 if word_type {
16091 println!("{}: {}", name, word);
16092 } else if verbose {
16093 println!("{} is hashed ({})", name, path.display());
16094 } else {
16095 println!("{}", path.display());
16096 }
16097 if !show_all {
16098 continue;
16099 }
16100 }
16101 }
16102
16103 if let Some(path) = self.find_in_path(name) {
16105 found = true;
16106 word = "command";
16107
16108 let display_path = if show_symlink || show_symlink_steps {
16110 let p = std::path::Path::new(&path);
16111 if show_symlink_steps {
16112 let mut current = p.to_path_buf();
16113 let mut steps = vec![path.clone()];
16114 while let Ok(target) = std::fs::read_link(¤t) {
16115 let resolved = if target.is_absolute() {
16116 target.clone()
16117 } else {
16118 current
16119 .parent()
16120 .unwrap_or(std::path::Path::new("/"))
16121 .join(&target)
16122 };
16123 steps.push(resolved.to_string_lossy().to_string());
16124 current = resolved;
16125 }
16126 steps.join(" -> ")
16127 } else {
16128 match p.canonicalize() {
16129 Ok(resolved) => format!("{} -> {}", path, resolved.display()),
16130 Err(_) => path.clone(),
16131 }
16132 }
16133 } else {
16134 path.clone()
16135 };
16136
16137 if word_type {
16138 println!("{}: {}", name, word);
16139 } else if verbose {
16140 println!("{} is {}", name, display_path);
16141 } else {
16142 println!("{}", display_path);
16143 }
16144 }
16145
16146 if !found {
16147 if word_type {
16148 println!("{}: none", name);
16149 } else if verbose {
16150 println!("{} not found", name);
16151 }
16152 status = 1;
16153 }
16154 }
16155 status
16156 }
16157
16158 fn is_reserved_word(&self, name: &str) -> bool {
16159 matches!(
16160 name,
16161 "if" | "then"
16162 | "else"
16163 | "elif"
16164 | "fi"
16165 | "case"
16166 | "esac"
16167 | "for"
16168 | "select"
16169 | "while"
16170 | "until"
16171 | "do"
16172 | "done"
16173 | "in"
16174 | "function"
16175 | "time"
16176 | "coproc"
16177 | "{"
16178 | "}"
16179 | "!"
16180 | "[["
16181 | "]]"
16182 | "(("
16183 | "))"
16184 )
16185 }
16186
16187 fn builtin_where(&self, args: &[String]) -> i32 {
16189 let mut new_args = vec!["-a".to_string(), "-v".to_string()];
16191 new_args.extend(args.iter().cloned());
16192 self.builtin_whence(&new_args)
16193 }
16194
16195 fn builtin_which(&self, args: &[String]) -> i32 {
16197 let mut new_args = vec!["-c".to_string()];
16199 new_args.extend(args.iter().cloned());
16200 self.builtin_whence(&new_args)
16201 }
16202
16203 fn is_builtin(&self, name: &str) -> bool {
16206 BUILTIN_SET.contains(name) || name.starts_with('_')
16207 }
16208
16209 fn find_in_path(&self, name: &str) -> Option<String> {
16211 if let Some(path) = self.command_hash.get(name) {
16213 return Some(path.clone());
16214 }
16215 let path_var = env::var("PATH").unwrap_or_default();
16217 for dir in path_var.split(':') {
16218 let full_path = format!("{}/{}", dir, name);
16219 if std::path::Path::new(&full_path).exists() {
16220 return Some(full_path);
16221 }
16222 }
16223 None
16224 }
16225
16226 fn builtin_ulimit(&self, args: &[String]) -> i32 {
16228 use libc::{getrlimit, rlimit, setrlimit};
16229 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
16230 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
16231
16232 let mut resource = RLIMIT_FSIZE; let mut hard = false;
16234 let mut soft = true;
16235 let mut value: Option<u64> = None;
16236
16237 let mut iter = args.iter();
16238 while let Some(arg) = iter.next() {
16239 match arg.as_str() {
16240 "-H" => {
16241 hard = true;
16242 soft = false;
16243 }
16244 "-S" => {
16245 soft = true;
16246 hard = false;
16247 }
16248 "-a" => {
16249 self.print_all_limits(soft);
16251 return 0;
16252 }
16253 "-c" => resource = RLIMIT_CORE,
16254 "-d" => resource = RLIMIT_DATA,
16255 "-f" => resource = RLIMIT_FSIZE,
16256 "-n" => resource = RLIMIT_NOFILE,
16257 "-s" => resource = RLIMIT_STACK,
16258 "-t" => resource = RLIMIT_CPU,
16259 "-u" => resource = RLIMIT_NPROC,
16260 "-v" => resource = RLIMIT_AS,
16261 "-m" => resource = RLIMIT_RSS,
16262 "unlimited" => value = Some(libc::RLIM_INFINITY as u64),
16263 _ if !arg.starts_with('-') => {
16264 value = arg.parse().ok();
16265 }
16266 _ => {}
16267 }
16268 }
16269
16270 let mut rlim = rlimit {
16271 rlim_cur: 0,
16272 rlim_max: 0,
16273 };
16274 unsafe {
16275 if getrlimit(resource, &mut rlim) != 0 {
16276 eprintln!("ulimit: cannot get limit");
16277 return 1;
16278 }
16279 }
16280
16281 if let Some(v) = value {
16282 if soft {
16284 rlim.rlim_cur = v as libc::rlim_t;
16285 }
16286 if hard {
16287 rlim.rlim_max = v as libc::rlim_t;
16288 }
16289 unsafe {
16290 if setrlimit(resource, &rlim) != 0 {
16291 eprintln!("ulimit: cannot set limit");
16292 return 1;
16293 }
16294 }
16295 } else {
16296 let limit = if hard { rlim.rlim_max } else { rlim.rlim_cur };
16298 if limit == libc::RLIM_INFINITY as libc::rlim_t {
16299 println!("unlimited");
16300 } else {
16301 println!("{}", limit);
16302 }
16303 }
16304 0
16305 }
16306
16307 fn print_all_limits(&self, soft: bool) {
16308 use libc::{getrlimit, rlimit};
16309 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
16310 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
16311
16312 let limits = [
16313 (RLIMIT_CORE, "core file size", "blocks", 512),
16314 (RLIMIT_DATA, "data seg size", "kbytes", 1024),
16315 (RLIMIT_FSIZE, "file size", "blocks", 512),
16316 (RLIMIT_NOFILE, "open files", "", 1),
16317 (RLIMIT_STACK, "stack size", "kbytes", 1024),
16318 (RLIMIT_CPU, "cpu time", "seconds", 1),
16319 (RLIMIT_NPROC, "max user processes", "", 1),
16320 (RLIMIT_AS, "virtual memory", "kbytes", 1024),
16321 (RLIMIT_RSS, "max memory size", "kbytes", 1024),
16322 ];
16323
16324 for (resource, name, unit, divisor) in limits {
16325 let mut rlim = rlimit {
16326 rlim_cur: 0,
16327 rlim_max: 0,
16328 };
16329 unsafe {
16330 if getrlimit(resource, &mut rlim) == 0 {
16331 let limit = if soft { rlim.rlim_cur } else { rlim.rlim_max };
16332 let unit_str = if unit.is_empty() {
16333 ""
16334 } else {
16335 &format!("({})", unit)
16336 };
16337 if limit == libc::RLIM_INFINITY as libc::rlim_t {
16338 println!("{:25} {} unlimited", name, unit_str);
16339 } else {
16340 println!("{:25} {} {}", name, unit_str, limit / divisor);
16341 }
16342 }
16343 }
16344 }
16345 }
16346
16347 fn builtin_limit(&self, args: &[String]) -> i32 {
16349 if args.is_empty() {
16351 use libc::{getrlimit, rlimit, RLIM_INFINITY};
16353 let resources = [
16354 (libc::RLIMIT_CPU, "cputime", 1, "seconds"),
16355 (libc::RLIMIT_FSIZE, "filesize", 1024, "kB"),
16356 (libc::RLIMIT_DATA, "datasize", 1024, "kB"),
16357 (libc::RLIMIT_STACK, "stacksize", 1024, "kB"),
16358 (libc::RLIMIT_CORE, "coredumpsize", 1024, "kB"),
16359 (libc::RLIMIT_RSS, "memoryuse", 1024, "kB"),
16360 #[cfg(target_os = "linux")]
16361 (libc::RLIMIT_NPROC, "maxproc", 1, ""),
16362 (libc::RLIMIT_NOFILE, "descriptors", 1, ""),
16363 ];
16364 for (res, name, divisor, unit) in resources {
16365 let mut rl: rlimit = unsafe { std::mem::zeroed() };
16366 unsafe { getrlimit(res, &mut rl); }
16367 let val = if rl.rlim_cur == RLIM_INFINITY as u64 {
16368 "unlimited".to_string()
16369 } else {
16370 let v = rl.rlim_cur as u64 / divisor;
16371 if unit.is_empty() { format!("{}", v) } else { format!("{}{}", v, unit) }
16372 };
16373 println!("{:<16}{}", name, val);
16374 }
16375 return 0;
16376 }
16377 self.builtin_ulimit(args)
16378 }
16379
16380 fn builtin_unlimit(&self, args: &[String]) -> i32 {
16382 let mut new_args = args.to_vec();
16383 new_args.push("unlimited".to_string());
16384 self.builtin_ulimit(&new_args)
16385 }
16386
16387 fn builtin_umask(&self, args: &[String]) -> i32 {
16389 use libc::umask;
16390
16391 let mut symbolic = false;
16392 let mut value: Option<&str> = None;
16393
16394 for arg in args {
16395 match arg.as_str() {
16396 "-S" => symbolic = true,
16397 _ if !arg.starts_with('-') => value = Some(arg),
16398 _ => {}
16399 }
16400 }
16401
16402 if let Some(v) = value {
16403 if let Ok(mask) = u32::from_str_radix(v, 8) {
16405 unsafe {
16406 umask(mask as libc::mode_t);
16407 }
16408 } else {
16409 eprintln!("umask: invalid mask: {}", v);
16410 return 1;
16411 }
16412 } else {
16413 let mask = unsafe {
16415 let m = umask(0);
16416 umask(m);
16417 m
16418 };
16419 if symbolic {
16420 let u = 7 - ((mask >> 6) & 7);
16421 let g = 7 - ((mask >> 3) & 7);
16422 let o = 7 - (mask & 7);
16423 println!(
16424 "u={}{}{}g={}{}{}o={}{}{}",
16425 if u & 4 != 0 { "r" } else { "" },
16426 if u & 2 != 0 { "w" } else { "" },
16427 if u & 1 != 0 { "x" } else { "" },
16428 if g & 4 != 0 { "r" } else { "" },
16429 if g & 2 != 0 { "w" } else { "" },
16430 if g & 1 != 0 { "x" } else { "" },
16431 if o & 4 != 0 { "r" } else { "" },
16432 if o & 2 != 0 { "w" } else { "" },
16433 if o & 1 != 0 { "x" } else { "" },
16434 );
16435 } else {
16436 println!("{:04o}", mask);
16437 }
16438 }
16439 0
16440 }
16441
16442 fn builtin_rehash(&mut self, args: &[String]) -> i32 {
16444 let mut rehash_dirs = false;
16450 let mut force = false;
16451 let mut verbose = false;
16452
16453 for arg in args {
16454 if arg.starts_with('-') {
16455 for ch in arg[1..].chars() {
16456 match ch {
16457 'd' => rehash_dirs = true,
16458 'f' => force = true,
16459 'v' => verbose = true,
16460 _ => {}
16461 }
16462 }
16463 }
16464 }
16465
16466 if rehash_dirs {
16467 self.named_dirs.clear();
16470 if let Ok(home) = env::var("HOME") {
16471 self.named_dirs.insert(String::new(), PathBuf::from(&home)); }
16473 return 0;
16474 }
16475
16476 self.command_hash.clear();
16478
16479 if force {
16480 if let Ok(path_var) = env::var("PATH") {
16483 let dirs: Vec<String> = path_var
16484 .split(':')
16485 .filter(|s| !s.is_empty())
16486 .map(|s| s.to_string())
16487 .collect();
16488
16489 let (tx, rx) = std::sync::mpsc::channel::<Vec<(String, String)>>();
16490
16491 for dir in dirs {
16492 let tx = tx.clone();
16493 self.worker_pool.submit(move || {
16494 let mut batch = Vec::new();
16495 if let Ok(entries) = std::fs::read_dir(&dir) {
16496 for entry in entries.flatten() {
16497 if let Ok(ft) = entry.file_type() {
16498 if ft.is_file() || ft.is_symlink() {
16499 if let Some(name) = entry.file_name().to_str() {
16500 let path =
16501 entry.path().to_string_lossy().to_string();
16502 batch.push((name.to_string(), path));
16503 }
16504 }
16505 }
16506 }
16507 }
16508 let _ = tx.send(batch);
16509 });
16510 }
16511 drop(tx);
16512
16513 for batch in rx {
16514 for (name, path) in batch {
16515 if verbose {
16516 println!("{}={}", name, path);
16517 }
16518 self.command_hash.insert(name, path);
16519 }
16520 }
16521 }
16522 }
16523
16524 0
16525 }
16526
16527 fn builtin_unhash(&mut self, args: &[String]) -> i32 {
16529 let mut remove_aliases = false;
16530 let mut remove_functions = false;
16531 let mut remove_dirs = false;
16532 let mut names: Vec<&str> = Vec::new();
16533
16534 for arg in args {
16535 match arg.as_str() {
16536 "-a" => remove_aliases = true,
16537 "-f" => remove_functions = true,
16538 "-d" => remove_dirs = true,
16539 "-m" => {} _ if arg.starts_with('-') => {}
16541 _ => names.push(arg),
16542 }
16543 }
16544
16545 for name in names {
16546 if remove_aliases {
16547 self.aliases.remove(name);
16548 }
16549 if remove_functions {
16550 self.functions.remove(name);
16551 }
16552 if remove_dirs {
16553 }
16555 }
16556 0
16557 }
16558
16559 fn builtin_times(&self, _args: &[String]) -> i32 {
16561 use libc::{getrusage, rusage, RUSAGE_CHILDREN, RUSAGE_SELF};
16562
16563 let mut self_usage: rusage = unsafe { std::mem::zeroed() };
16564 let mut child_usage: rusage = unsafe { std::mem::zeroed() };
16565
16566 unsafe {
16567 getrusage(RUSAGE_SELF, &mut self_usage);
16568 getrusage(RUSAGE_CHILDREN, &mut child_usage);
16569 }
16570
16571 let self_user =
16572 self_usage.ru_utime.tv_sec as f64 + self_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
16573 let self_sys =
16574 self_usage.ru_stime.tv_sec as f64 + self_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
16575 let child_user =
16576 child_usage.ru_utime.tv_sec as f64 + child_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
16577 let child_sys =
16578 child_usage.ru_stime.tv_sec as f64 + child_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
16579
16580 println!("{:.3}s {:.3}s", self_user, self_sys);
16581 println!("{:.3}s {:.3}s", child_user, child_sys);
16582 0
16583 }
16584
16585 fn builtin_zmodload(&mut self, args: &[String]) -> i32 {
16587 let mut list_loaded = false;
16588 let mut unload = false;
16589 let mut modules: Vec<&str> = Vec::new();
16590
16591 for arg in args {
16592 match arg.as_str() {
16593 "-l" | "-L" => list_loaded = true,
16594 "-u" => unload = true,
16595 "-a" | "-b" | "-c" | "-d" | "-e" | "-f" | "-i" | "-p" | "-s" => {}
16596 _ if arg.starts_with('-') => {}
16597 _ => modules.push(arg),
16598 }
16599 }
16600
16601 if list_loaded || modules.is_empty() {
16602 println!("zsh/complete");
16604 println!("zsh/complist");
16605 println!("zsh/parameter");
16606 println!("zsh/zutil");
16607 return 0;
16608 }
16609
16610 for module in modules {
16611 if unload {
16612 self.options.remove(&format!("_module_{}", module));
16614 } else {
16615 self.options.insert(format!("_module_{}", module), true);
16617 }
16618 }
16619 0
16620 }
16621
16622 fn builtin_r(&mut self, args: &[String]) -> i32 {
16624 let mut fc_args = vec!["-e".to_string(), "-".to_string()];
16625 fc_args.extend(args.iter().cloned());
16626 self.builtin_fc(&fc_args)
16627 }
16628
16629 fn builtin_ttyctl(&self, args: &[String]) -> i32 {
16631 for arg in args {
16632 match arg.as_str() {
16633 "-f" => {
16634 }
16637 "-u" => {
16638 }
16640 _ => {}
16641 }
16642 }
16643 0
16644 }
16645
16646 fn builtin_noglob(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
16648 if args.is_empty() {
16649 return 0;
16650 }
16651
16652 let saved = self.options.get("noglob").cloned();
16654 self.options.insert("noglob".to_string(), true);
16655
16656 let status = self.builtin_command(args, redirects);
16658
16659 if let Some(v) = saved {
16661 self.options.insert("noglob".to_string(), v);
16662 } else {
16663 self.options.remove("noglob");
16664 }
16665
16666 status
16667 }
16668
16669 fn builtin_zstat(&self, args: &[String]) -> i32 {
16675 use std::os::unix::fs::MetadataExt;
16676 use std::os::unix::fs::PermissionsExt;
16677
16678 let mut show_all = true;
16679 let mut symbolic_mode = false;
16680 let mut show_link = false;
16681 let mut _as_array = false;
16682 let mut _array_name = String::new();
16683 let mut format_time = String::new();
16684 let mut elements: Vec<String> = Vec::new();
16685 let mut files: Vec<&str> = Vec::new();
16686
16687 let mut iter = args.iter().peekable();
16688 while let Some(arg) = iter.next() {
16689 match arg.as_str() {
16690 "-s" => symbolic_mode = true,
16691 "-L" => show_link = true,
16692 "-N" => {} "-n" => {} "-o" => show_all = false,
16695 "-A" => {
16696 _as_array = true;
16697 if let Some(name) = iter.next() {
16698 _array_name = name.clone();
16699 }
16700 }
16701 "-F" => {
16702 if let Some(fmt) = iter.next() {
16703 format_time = fmt.clone();
16704 }
16705 }
16706 s if s.starts_with('+') => {
16707 elements.push(s[1..].to_string());
16708 show_all = false;
16709 }
16710 s if !s.starts_with('-') => files.push(s),
16711 _ => {}
16712 }
16713 }
16714
16715 if files.is_empty() {
16716 eprintln!("zstat: no files specified");
16717 return 1;
16718 }
16719
16720 for file in files {
16721 let meta = if show_link {
16722 std::fs::symlink_metadata(file)
16723 } else {
16724 std::fs::metadata(file)
16725 };
16726
16727 let meta = match meta {
16728 Ok(m) => m,
16729 Err(e) => {
16730 eprintln!("zstat: {}: {}", file, e);
16731 return 1;
16732 }
16733 };
16734
16735 let output_element = |name: &str, value: &str| {
16736 if _as_array {
16737 println!("{}={}", name, value);
16739 } else if show_all || elements.contains(&name.to_string()) {
16740 println!("{}: {}", name, value);
16741 }
16742 };
16743
16744 output_element("device", &meta.dev().to_string());
16745 output_element("inode", &meta.ino().to_string());
16746
16747 if symbolic_mode {
16748 let mode = meta.permissions().mode();
16749 let mode_str = format!(
16750 "{}{}{}{}{}{}{}{}{}{}",
16751 match mode & 0o170000 {
16752 0o040000 => 'd',
16753 0o120000 => 'l',
16754 0o100000 => '-',
16755 0o060000 => 'b',
16756 0o020000 => 'c',
16757 0o010000 => 'p',
16758 0o140000 => 's',
16759 _ => '?',
16760 },
16761 if mode & 0o400 != 0 { 'r' } else { '-' },
16762 if mode & 0o200 != 0 { 'w' } else { '-' },
16763 if mode & 0o4000 != 0 {
16764 's'
16765 } else if mode & 0o100 != 0 {
16766 'x'
16767 } else {
16768 '-'
16769 },
16770 if mode & 0o040 != 0 { 'r' } else { '-' },
16771 if mode & 0o020 != 0 { 'w' } else { '-' },
16772 if mode & 0o2000 != 0 {
16773 's'
16774 } else if mode & 0o010 != 0 {
16775 'x'
16776 } else {
16777 '-'
16778 },
16779 if mode & 0o004 != 0 { 'r' } else { '-' },
16780 if mode & 0o002 != 0 { 'w' } else { '-' },
16781 if mode & 0o1000 != 0 {
16782 't'
16783 } else if mode & 0o001 != 0 {
16784 'x'
16785 } else {
16786 '-'
16787 },
16788 );
16789 output_element("mode", &mode_str);
16790 } else {
16791 output_element("mode", &format!("{:o}", meta.permissions().mode()));
16792 }
16793
16794 output_element("nlink", &meta.nlink().to_string());
16795 output_element("uid", &meta.uid().to_string());
16796 output_element("gid", &meta.gid().to_string());
16797 output_element("rdev", &meta.rdev().to_string());
16798 output_element("size", &meta.len().to_string());
16799
16800 let format_timestamp = |secs: i64| -> String {
16801 if format_time.is_empty() {
16802 secs.to_string()
16803 } else {
16804 chrono::DateTime::from_timestamp(secs, 0)
16805 .map(|dt| dt.format(&format_time).to_string())
16806 .unwrap_or_else(|| secs.to_string())
16807 }
16808 };
16809
16810 output_element("atime", &format_timestamp(meta.atime()));
16811 output_element("mtime", &format_timestamp(meta.mtime()));
16812 output_element("ctime", &format_timestamp(meta.ctime()));
16813 output_element("blksize", &meta.blksize().to_string());
16814 output_element("blocks", &meta.blocks().to_string());
16815
16816 if show_link && meta.file_type().is_symlink() {
16817 if let Ok(target) = std::fs::read_link(file) {
16818 output_element("link", &target.to_string_lossy());
16819 }
16820 }
16821 }
16822
16823 0
16824 }
16825
16826 fn builtin_strftime(&self, args: &[String]) -> i32 {
16828 let mut format = "%c".to_string();
16829 let mut timestamp: Option<i64> = None;
16830 let mut to_var = false;
16831 let mut var_name = String::new();
16832
16833 let mut iter = args.iter();
16834 while let Some(arg) = iter.next() {
16835 match arg.as_str() {
16836 "-s" => {
16837 to_var = true;
16838 if let Some(name) = iter.next() {
16839 var_name = name.clone();
16840 }
16841 }
16842 "-r" => {
16843 if let Some(ts_str) = iter.next() {
16845 timestamp = ts_str.parse().ok();
16846 }
16847 }
16848 s if !s.starts_with('-') => {
16849 if format == "%c" {
16850 format = s.to_string();
16851 } else if timestamp.is_none() {
16852 timestamp = s.parse().ok();
16853 }
16854 }
16855 _ => {}
16856 }
16857 }
16858
16859 let ts = timestamp.unwrap_or_else(|| chrono::Local::now().timestamp());
16860
16861 let result = chrono::DateTime::from_timestamp(ts, 0)
16862 .map(|dt: chrono::DateTime<chrono::Utc>| {
16863 dt.with_timezone(&chrono::Local).format(&format).to_string()
16864 })
16865 .unwrap_or_else(|| "invalid timestamp".to_string());
16866
16867 if to_var && !var_name.is_empty() {
16868 println!("{}={}", var_name, result);
16870 } else {
16871 println!("{}", result);
16872 }
16873
16874 0
16875 }
16876
16877 fn builtin_zsleep(&self, args: &[String]) -> i32 {
16879 if args.is_empty() {
16880 eprintln!("zsleep: missing argument");
16881 return 1;
16882 }
16883
16884 let secs: f64 = match args[0].parse() {
16885 Ok(s) => s,
16886 Err(_) => {
16887 eprintln!("zsleep: invalid number: {}", args[0]);
16888 return 1;
16889 }
16890 };
16891
16892 std::thread::sleep(std::time::Duration::from_secs_f64(secs));
16893 0
16894 }
16895
16896 fn builtin_zsystem(&mut self, args: &[String]) -> i32 {
16899 if args.is_empty() {
16900 eprintln!("zsystem: subcommand expected");
16901 return 1;
16902 }
16903 match args[0].as_str() {
16904 "flock" => self.builtin_zsystem_flock(&args[1..]),
16905 "supports" => self.builtin_zsystem_supports(&args[1..]),
16906 _ => {
16907 eprintln!("zsystem: unknown subcommand: {}", args[0]);
16908 1
16909 }
16910 }
16911 }
16912
16913 fn builtin_zsystem_supports(&self, args: &[String]) -> i32 {
16915 if args.is_empty() {
16916 eprintln!("zsystem: supports: not enough arguments");
16917 return 255;
16918 }
16919 if args.len() > 1 {
16920 eprintln!("zsystem: supports: too many arguments");
16921 return 255;
16922 }
16923 match args[0].as_str() {
16924 "supports" | "flock" => 0,
16925 _ => 1,
16926 }
16927 }
16928
16929 fn builtin_zsystem_flock(&mut self, args: &[String]) -> i32 {
16931 #[cfg(unix)]
16932 {
16933 use std::os::unix::io::AsRawFd;
16934
16935 let mut cloexec = true;
16936 let mut readlock = false;
16937 let mut timeout: Option<f64> = None;
16938 let mut fdvar: Option<String> = None;
16939 let mut file: Option<&str> = None;
16940
16941 let mut i = 0;
16942 while i < args.len() {
16943 let arg = &args[i];
16944 if arg == "--" {
16945 i += 1;
16946 if i < args.len() {
16947 file = Some(&args[i]);
16948 }
16949 break;
16950 }
16951 if !arg.starts_with('-') {
16952 file = Some(arg);
16953 break;
16954 }
16955 let mut chars = arg[1..].chars().peekable();
16956 while let Some(c) = chars.next() {
16957 match c {
16958 'e' => cloexec = false,
16959 'r' => readlock = true,
16960 'u' => return 0,
16961 'f' => {
16962 let rest: String = chars.collect();
16963 if !rest.is_empty() {
16964 fdvar = Some(rest);
16965 } else {
16966 i += 1;
16967 if i < args.len() {
16968 fdvar = Some(args[i].clone());
16969 } else {
16970 eprintln!("zsystem: flock: option f requires a variable name");
16971 return 1;
16972 }
16973 }
16974 break;
16975 }
16976 't' => {
16977 let rest: String = chars.collect();
16978 let val = if !rest.is_empty() {
16979 rest
16980 } else {
16981 i += 1;
16982 if i < args.len() {
16983 args[i].clone()
16984 } else {
16985 eprintln!(
16986 "zsystem: flock: option t requires a numeric timeout"
16987 );
16988 return 1;
16989 }
16990 };
16991 match val.parse::<f64>() {
16992 Ok(t) => timeout = Some(t),
16993 Err(_) => {
16994 eprintln!("zsystem: flock: invalid timeout value: '{}'", val);
16995 return 1;
16996 }
16997 }
16998 break;
16999 }
17000 'i' => {
17001 let rest: String = chars.collect();
17002 if rest.is_empty() {
17003 i += 1;
17004 if i >= args.len() {
17005 eprintln!("zsystem: flock: option i requires a numeric retry interval");
17006 return 1;
17007 }
17008 }
17009 break;
17010 }
17011 _ => {
17012 eprintln!("zsystem: flock: unknown option: -{}", c);
17013 return 1;
17014 }
17015 }
17016 }
17017 i += 1;
17018 }
17019
17020 let filepath = match file {
17021 Some(f) => f,
17022 None => {
17023 eprintln!("zsystem: flock: not enough arguments");
17024 return 1;
17025 }
17026 };
17027
17028 use std::fs::OpenOptions;
17029 let file_handle = match OpenOptions::new()
17030 .read(true)
17031 .write(!readlock)
17032 .create(true)
17033 .truncate(false)
17034 .open(filepath)
17035 {
17036 Ok(f) => f,
17037 Err(e) => {
17038 eprintln!("zsystem: flock: {}: {}", filepath, e);
17039 return 1;
17040 }
17041 };
17042
17043 let lock_type = if readlock {
17044 libc::F_RDLCK as i16
17045 } else {
17046 libc::F_WRLCK as i16
17047 };
17048
17049 let mut flock = libc::flock {
17050 l_type: lock_type,
17051 l_whence: libc::SEEK_SET as i16,
17052 l_start: 0,
17053 l_len: 0,
17054 l_pid: 0,
17055 };
17056
17057 let cmd = if timeout.is_some() {
17058 libc::F_SETLK
17059 } else {
17060 libc::F_SETLKW
17061 };
17062 let start = std::time::Instant::now();
17063 let timeout_duration = timeout.map(|t| std::time::Duration::from_secs_f64(t));
17064
17065 loop {
17066 let ret = unsafe { libc::fcntl(file_handle.as_raw_fd(), cmd, &mut flock) };
17067 if ret == 0 {
17068 if let Some(ref var) = fdvar {
17069 let fd = file_handle.as_raw_fd();
17070 std::mem::forget(file_handle);
17071 self.variables.insert(var.clone(), fd.to_string());
17072 } else {
17073 std::mem::forget(file_handle);
17074 }
17075 let _ = cloexec;
17076 return 0;
17077 }
17078 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
17079 if errno != libc::EACCES && errno != libc::EAGAIN {
17080 eprintln!(
17081 "zsystem: flock: {}: {}",
17082 filepath,
17083 std::io::Error::last_os_error()
17084 );
17085 return 1;
17086 }
17087 if let Some(td) = timeout_duration {
17088 if start.elapsed() >= td {
17089 return 2;
17090 }
17091 std::thread::sleep(std::time::Duration::from_millis(100));
17092 } else {
17093 eprintln!(
17094 "zsystem: flock: {}: {}",
17095 filepath,
17096 std::io::Error::last_os_error()
17097 );
17098 return 1;
17099 }
17100 }
17101 }
17102 #[cfg(not(unix))]
17103 {
17104 eprintln!("zsystem: flock: not supported on this platform");
17105 1
17106 }
17107 }
17108
17109 fn builtin_sync(&self, _args: &[String]) -> i32 {
17112 #[cfg(unix)]
17113 unsafe {
17114 libc::sync();
17115 }
17116 0
17117 }
17118
17119 fn builtin_mkdir(&self, args: &[String]) -> i32 {
17122 let mut mode: u32 = 0o777;
17123 let mut parents = false;
17124 let mut dirs: Vec<&str> = Vec::new();
17125
17126 let mut i = 0;
17127 while i < args.len() {
17128 let arg = &args[i];
17129 if arg == "-p" {
17130 parents = true;
17131 } else if arg == "-m" && i + 1 < args.len() {
17132 i += 1;
17133 mode = u32::from_str_radix(&args[i], 8).unwrap_or(0o777);
17134 } else if arg.starts_with("-m") {
17135 mode = u32::from_str_radix(&arg[2..], 8).unwrap_or(0o777);
17136 } else if !arg.starts_with('-') || arg == "-" || arg == "--" {
17137 if arg == "--" {
17138 dirs.extend(args[i + 1..].iter().map(|s| s.as_str()));
17139 break;
17140 }
17141 dirs.push(arg);
17142 }
17143 i += 1;
17144 }
17145
17146 let mut err = 0;
17147 for dir in dirs {
17148 let path = std::path::Path::new(dir);
17149 let result = if parents {
17150 std::fs::create_dir_all(path)
17151 } else {
17152 std::fs::create_dir(path)
17153 };
17154 if let Err(e) = result {
17155 eprintln!("mkdir: cannot create directory '{}': {}", dir, e);
17156 err = 1;
17157 } else {
17158 #[cfg(unix)]
17159 {
17160 use std::os::unix::fs::PermissionsExt;
17161 let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode));
17162 }
17163 }
17164 }
17165 err
17166 }
17167
17168 fn builtin_rmdir(&self, args: &[String]) -> i32 {
17171 let mut err = 0;
17172 for arg in args {
17173 if arg.starts_with('-') {
17174 continue;
17175 }
17176 if let Err(e) = std::fs::remove_dir(arg) {
17177 eprintln!("rmdir: cannot remove '{}': {}", arg, e);
17178 err = 1;
17179 }
17180 }
17181 err
17182 }
17183
17184 fn builtin_ln(&self, args: &[String]) -> i32 {
17187 let mut symbolic = false;
17188 let mut force = false;
17189 let mut no_deref = false;
17190 let mut files: Vec<&str> = Vec::new();
17191
17192 for arg in args {
17193 match arg.as_str() {
17194 "-s" => symbolic = true,
17195 "-f" => force = true,
17196 "-n" | "-h" => no_deref = true,
17197 s if !s.starts_with('-') => files.push(s),
17198 _ => {}
17199 }
17200 }
17201
17202 if files.len() < 2 {
17203 if files.len() == 1 {
17204 let src = files[0];
17205 let target = std::path::Path::new(src)
17206 .file_name()
17207 .map(|n| n.to_string_lossy().to_string())
17208 .unwrap_or_else(|| src.to_string());
17209 files.push(Box::leak(target.into_boxed_str()));
17210 } else {
17211 eprintln!("ln: missing file operand");
17212 return 1;
17213 }
17214 }
17215
17216 let target = files.pop().unwrap();
17217 let target_path = std::path::Path::new(target);
17218 let is_dir = !no_deref && target_path.is_dir();
17219
17220 for src in files {
17221 let dest = if is_dir {
17222 format!(
17223 "{}/{}",
17224 target,
17225 std::path::Path::new(src)
17226 .file_name()
17227 .map(|n| n.to_string_lossy().to_string())
17228 .unwrap_or_else(|| src.to_string())
17229 )
17230 } else {
17231 target.to_string()
17232 };
17233
17234 let dest_path = std::path::Path::new(&dest);
17235 if force && dest_path.exists() {
17236 let _ = std::fs::remove_file(&dest);
17237 }
17238
17239 let result = if symbolic {
17240 #[cfg(unix)]
17241 {
17242 std::os::unix::fs::symlink(src, &dest)
17243 }
17244 #[cfg(not(unix))]
17245 {
17246 Err(std::io::Error::new(
17247 std::io::ErrorKind::Unsupported,
17248 "symlinks not supported",
17249 ))
17250 }
17251 } else {
17252 std::fs::hard_link(src, &dest)
17253 };
17254
17255 if let Err(e) = result {
17256 eprintln!("ln: cannot create link '{}' -> '{}': {}", dest, src, e);
17257 return 1;
17258 }
17259 }
17260 0
17261 }
17262
17263 fn builtin_mv(&self, args: &[String]) -> i32 {
17266 let mut force = false;
17267 let mut interactive = false;
17268 let mut verbose = false;
17269 let mut files: Vec<&str> = Vec::new();
17270
17271 for arg in args {
17272 match arg.as_str() {
17273 "-f" => force = true,
17274 "-i" => interactive = true,
17275 "-v" => verbose = true,
17276 s if !s.starts_with('-') => files.push(s),
17277 _ => {}
17278 }
17279 }
17280
17281 if files.len() < 2 {
17282 eprintln!("mv: missing file operand");
17283 return 1;
17284 }
17285
17286 let target = files.pop().unwrap();
17287 let target_path = std::path::Path::new(target);
17288 let is_dir = target_path.is_dir();
17289
17290 for src in files {
17291 let dest = if is_dir {
17292 format!(
17293 "{}/{}",
17294 target,
17295 std::path::Path::new(src)
17296 .file_name()
17297 .map(|n| n.to_string_lossy().to_string())
17298 .unwrap_or_else(|| src.to_string())
17299 )
17300 } else {
17301 target.to_string()
17302 };
17303
17304 let dest_path = std::path::Path::new(&dest);
17305 if dest_path.exists() && !force {
17306 if interactive {
17307 eprint!("mv: overwrite '{}'? ", dest);
17308 let mut response = String::new();
17309 if std::io::stdin().read_line(&mut response).is_err()
17310 || !response.trim().eq_ignore_ascii_case("y")
17311 {
17312 continue;
17313 }
17314 } else {
17315 eprintln!("mv: cannot overwrite '{}': File exists", dest);
17316 return 1;
17317 }
17318 }
17319
17320 if let Err(e) = std::fs::rename(src, &dest) {
17321 eprintln!("mv: cannot move '{}' to '{}': {}", src, dest, e);
17322 return 1;
17323 }
17324
17325 if verbose {
17326 println!("'{}' -> '{}'", src, dest);
17327 }
17328 }
17329 0
17330 }
17331
17332 fn builtin_cp(&self, args: &[String]) -> i32 {
17335 let mut recursive = false;
17336 let mut force = false;
17337 let mut interactive = false;
17338 let mut preserve = false;
17339 let mut verbose = false;
17340 let mut files: Vec<&str> = Vec::new();
17341
17342 for arg in args {
17343 match arg.as_str() {
17344 "-r" | "-R" => recursive = true,
17345 "-f" => force = true,
17346 "-i" => interactive = true,
17347 "-p" => preserve = true,
17348 "-v" => verbose = true,
17349 s if !s.starts_with('-') => files.push(s),
17350 _ => {}
17351 }
17352 }
17353
17354 let _ = preserve; if files.len() < 2 {
17357 eprintln!("cp: missing file operand");
17358 return 1;
17359 }
17360
17361 let target = files.pop().unwrap();
17362 let target_path = std::path::Path::new(target);
17363 let is_dir = target_path.is_dir();
17364
17365 for src in files {
17366 let src_path = std::path::Path::new(src);
17367 let dest = if is_dir {
17368 format!(
17369 "{}/{}",
17370 target,
17371 src_path
17372 .file_name()
17373 .map(|n| n.to_string_lossy().to_string())
17374 .unwrap_or_else(|| src.to_string())
17375 )
17376 } else {
17377 target.to_string()
17378 };
17379
17380 let dest_path = std::path::Path::new(&dest);
17381 if dest_path.exists() && !force {
17382 if interactive {
17383 eprint!("cp: overwrite '{}'? ", dest);
17384 let mut response = String::new();
17385 if std::io::stdin().read_line(&mut response).is_err()
17386 || !response.trim().eq_ignore_ascii_case("y")
17387 {
17388 continue;
17389 }
17390 }
17391 }
17392
17393 let result = if src_path.is_dir() {
17394 if recursive {
17395 Self::copy_dir_recursive(src_path, dest_path)
17396 } else {
17397 eprintln!("cp: -r not specified; omitting directory '{}'", src);
17398 continue;
17399 }
17400 } else {
17401 std::fs::copy(src, &dest).map(|_| ())
17402 };
17403
17404 if let Err(e) = result {
17405 eprintln!("cp: cannot copy '{}' to '{}': {}", src, dest, e);
17406 return 1;
17407 }
17408
17409 if verbose {
17410 println!("'{}' -> '{}'", src, dest);
17411 }
17412 }
17413 0
17414 }
17415
17416 fn copy_dir_recursive(src: &std::path::Path, dest: &std::path::Path) -> std::io::Result<()> {
17417 if !dest.exists() {
17418 std::fs::create_dir_all(dest)?;
17419 }
17420 for entry in std::fs::read_dir(src)? {
17421 let entry = entry?;
17422 let file_type = entry.file_type()?;
17423 let src_path = entry.path();
17424 let dest_path = dest.join(entry.file_name());
17425
17426 if file_type.is_dir() {
17427 Self::copy_dir_recursive(&src_path, &dest_path)?;
17428 } else {
17429 std::fs::copy(&src_path, &dest_path)?;
17430 }
17431 }
17432 Ok(())
17433 }
17434
17435 fn builtin_rm(&self, args: &[String]) -> i32 {
17437 let mut recursive = false;
17438 let mut force = false;
17439 let mut interactive = false;
17440 let mut verbose = false;
17441 let mut files: Vec<&str> = Vec::new();
17442
17443 for arg in args {
17444 match arg.as_str() {
17445 "-r" | "-R" => recursive = true,
17446 "-f" => force = true,
17447 "-i" => interactive = true,
17448 "-v" => verbose = true,
17449 "-rf" | "-fr" => {
17450 recursive = true;
17451 force = true;
17452 }
17453 s if !s.starts_with('-') => files.push(s),
17454 _ => {}
17455 }
17456 }
17457
17458 for file in files {
17459 let path = std::path::Path::new(file);
17460
17461 if !path.exists() {
17462 if !force {
17463 eprintln!("rm: cannot remove '{}': No such file or directory", file);
17464 return 1;
17465 }
17466 continue;
17467 }
17468
17469 if interactive {
17470 let file_type = if path.is_dir() { "directory" } else { "file" };
17471 eprint!("rm: remove {} '{}'? ", file_type, file);
17472 let mut response = String::new();
17473 if std::io::stdin().read_line(&mut response).is_err()
17474 || !response.trim().eq_ignore_ascii_case("y")
17475 {
17476 continue;
17477 }
17478 }
17479
17480 let result = if path.is_dir() {
17481 if recursive {
17482 std::fs::remove_dir_all(path)
17483 } else {
17484 eprintln!("rm: cannot remove '{}': Is a directory", file);
17485 return 1;
17486 }
17487 } else {
17488 std::fs::remove_file(path)
17489 };
17490
17491 if let Err(e) = result {
17492 if !force {
17493 eprintln!("rm: cannot remove '{}': {}", file, e);
17494 return 1;
17495 }
17496 } else if verbose {
17497 println!("removed '{}'", file);
17498 }
17499 }
17500 0
17501 }
17502
17503 #[cfg(unix)]
17505 fn builtin_chown(&self, args: &[String]) -> i32 {
17506 use std::os::unix::fs::MetadataExt;
17507
17508 let mut recursive = false;
17509 let mut positional: Vec<&str> = Vec::new();
17510
17511 for arg in args {
17512 match arg.as_str() {
17513 "-R" => recursive = true,
17514 "-h" => {} s if !s.starts_with('-') => positional.push(s),
17516 _ => {}
17517 }
17518 }
17519
17520 if positional.len() < 2 {
17521 eprintln!("chown: missing operand");
17522 return 1;
17523 }
17524
17525 let owner_spec = positional[0];
17526 let files = &positional[1..];
17527
17528 let (user, group) = if let Some(colon_pos) = owner_spec.find(':') {
17530 (&owner_spec[..colon_pos], Some(&owner_spec[colon_pos + 1..]))
17531 } else {
17532 (owner_spec, None)
17533 };
17534
17535 let uid: u32 = if user.is_empty() {
17536 u32::MAX
17537 } else if let Ok(id) = user.parse() {
17538 id
17539 } else {
17540 unsafe {
17542 let c_user = std::ffi::CString::new(user).unwrap();
17543 let pw = libc::getpwnam(c_user.as_ptr());
17544 if pw.is_null() {
17545 eprintln!("chown: invalid user: '{}'", user);
17546 return 1;
17547 }
17548 (*pw).pw_uid
17549 }
17550 };
17551
17552 let gid: u32 = match group {
17553 Some(g) if !g.is_empty() => {
17554 if let Ok(id) = g.parse() {
17555 id
17556 } else {
17557 unsafe {
17558 let c_group = std::ffi::CString::new(g).unwrap();
17559 let gr = libc::getgrnam(c_group.as_ptr());
17560 if gr.is_null() {
17561 eprintln!("chown: invalid group: '{}'", g);
17562 return 1;
17563 }
17564 (*gr).gr_gid
17565 }
17566 }
17567 }
17568 _ => u32::MAX,
17569 };
17570
17571 fn do_chown(path: &std::path::Path, uid: u32, gid: u32, recursive: bool) -> i32 {
17572 let c_path = match std::ffi::CString::new(path.to_string_lossy().as_bytes()) {
17573 Ok(p) => p,
17574 Err(_) => return 1,
17575 };
17576
17577 let ret = unsafe { libc::chown(c_path.as_ptr(), uid, gid) };
17578 if ret != 0 {
17579 eprintln!(
17580 "chown: changing ownership of '{}': {}",
17581 path.display(),
17582 std::io::Error::last_os_error()
17583 );
17584 return 1;
17585 }
17586
17587 if recursive && path.is_dir() {
17588 if let Ok(entries) = std::fs::read_dir(path) {
17589 for entry in entries.flatten() {
17590 if do_chown(&entry.path(), uid, gid, true) != 0 {
17591 return 1;
17592 }
17593 }
17594 }
17595 }
17596 0
17597 }
17598
17599 for file in files {
17600 if do_chown(std::path::Path::new(file), uid, gid, recursive) != 0 {
17601 return 1;
17602 }
17603 }
17604 0
17605 }
17606
17607 #[cfg(not(unix))]
17608 fn builtin_chown(&self, _args: &[String]) -> i32 {
17609 eprintln!("chown: not supported on this platform");
17610 1
17611 }
17612
17613 fn builtin_chmod(&self, args: &[String]) -> i32 {
17615 let mut recursive = false;
17616 let mut positional: Vec<&str> = Vec::new();
17617
17618 for arg in args {
17619 match arg.as_str() {
17620 "-R" => recursive = true,
17621 s if !s.starts_with('-') => positional.push(s),
17622 _ => {}
17623 }
17624 }
17625
17626 if positional.len() < 2 {
17627 eprintln!("chmod: missing operand");
17628 return 1;
17629 }
17630
17631 let mode_spec = positional[0];
17632 let files = &positional[1..];
17633
17634 let mode: Option<u32> = u32::from_str_radix(mode_spec, 8).ok();
17636
17637 if mode.is_none() {
17638 eprintln!("chmod: symbolic mode not implemented, use octal");
17640 return 1;
17641 }
17642
17643 let mode = mode.unwrap();
17644
17645 fn do_chmod(path: &std::path::Path, mode: u32, recursive: bool) -> i32 {
17646 #[cfg(unix)]
17647 {
17648 use std::os::unix::fs::PermissionsExt;
17649 if let Err(e) =
17650 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
17651 {
17652 eprintln!("chmod: changing permissions of '{}': {}", path.display(), e);
17653 return 1;
17654 }
17655
17656 if recursive && path.is_dir() {
17657 if let Ok(entries) = std::fs::read_dir(path) {
17658 for entry in entries.flatten() {
17659 if do_chmod(&entry.path(), mode, true) != 0 {
17660 return 1;
17661 }
17662 }
17663 }
17664 }
17665 }
17666 #[cfg(not(unix))]
17667 {
17668 let _ = (path, mode, recursive);
17669 }
17670 0
17671 }
17672
17673 for file in files {
17674 if do_chmod(std::path::Path::new(file), mode, recursive) != 0 {
17675 return 1;
17676 }
17677 }
17678 0
17679 }
17680
17681 fn builtin_zfiles(&self, cmd: &str, args: &[String]) -> i32 {
17683 let mut force = false;
17684 let mut verbose = false;
17685 let mut files: Vec<&str> = Vec::new();
17686
17687 for arg in args {
17688 match arg.as_str() {
17689 "-f" => force = true,
17690 "-v" => verbose = true,
17691 "-i" => {} s if !s.starts_with('-') => files.push(s),
17693 _ => {}
17694 }
17695 }
17696
17697 if files.len() < 2 {
17698 eprintln!("{}: missing operand", cmd);
17699 return 1;
17700 }
17701
17702 let target = files.pop().unwrap();
17703 let target_is_dir = std::path::Path::new(target).is_dir();
17704
17705 for src in files {
17706 let dest = if target_is_dir {
17707 format!(
17708 "{}/{}",
17709 target,
17710 std::path::Path::new(src)
17711 .file_name()
17712 .map(|n| n.to_string_lossy().to_string())
17713 .unwrap_or_else(|| src.to_string())
17714 )
17715 } else {
17716 target.to_string()
17717 };
17718
17719 if !force && std::path::Path::new(&dest).exists() {
17720 eprintln!("{}: '{}' already exists", cmd, dest);
17721 continue;
17722 }
17723
17724 let result = match cmd {
17725 "zln" => {
17726 #[cfg(unix)]
17727 {
17728 std::os::unix::fs::symlink(src, &dest)
17729 }
17730 #[cfg(not(unix))]
17731 {
17732 Err(std::io::Error::new(
17733 std::io::ErrorKind::Unsupported,
17734 "symlinks not supported",
17735 ))
17736 }
17737 }
17738 "zcp" => std::fs::copy(src, &dest).map(|_| ()),
17739 "zmv" => std::fs::rename(src, &dest),
17740 _ => Ok(()),
17741 };
17742
17743 match result {
17744 Ok(()) => {
17745 if verbose {
17746 println!("{} -> {}", src, dest);
17747 }
17748 }
17749 Err(e) => {
17750 eprintln!("{}: {}: {}", cmd, src, e);
17751 return 1;
17752 }
17753 }
17754 }
17755
17756 0
17757 }
17758
17759 fn builtin_coproc(&mut self, args: &[String]) -> i32 {
17761 if args.is_empty() {
17763 println!("(no coprocesses)");
17765 return 0;
17766 }
17767
17768 let cmd = args.join(" ");
17770 match std::process::Command::new("sh")
17771 .arg("-c")
17772 .arg(&cmd)
17773 .stdin(std::process::Stdio::piped())
17774 .stdout(std::process::Stdio::piped())
17775 .spawn()
17776 {
17777 Ok(child) => {
17778 println!("[coproc] {}", child.id());
17779 0
17780 }
17781 Err(e) => {
17782 eprintln!("coproc: {}", e);
17783 1
17784 }
17785 }
17786 }
17787
17788 fn builtin_zparseopts(&mut self, args: &[String]) -> i32 {
17790 let mut remove_parsed = false; let mut keep_going = false; let mut fail_on_error = false; let mut keep_values = false; let mut _map_names = false; let mut array_name: Option<String> = None; let mut assoc_name: Option<String> = None; let mut specs: Vec<String> = Vec::new();
17798
17799 let mut iter = args.iter().peekable();
17800
17801 while let Some(arg) = iter.next() {
17803 match arg.as_str() {
17804 "-D" => remove_parsed = true,
17805 "-E" => keep_going = true,
17806 "-F" => fail_on_error = true,
17807 "-K" => keep_values = true,
17808 "-M" => _map_names = true,
17809 "-a" => {
17810 if let Some(name) = iter.next() {
17811 array_name = Some(name.clone());
17812 }
17813 }
17814 "-A" => {
17815 if let Some(name) = iter.next() {
17816 assoc_name = Some(name.clone());
17817 }
17818 }
17819 "-" | "--" => break,
17820 s if !s.starts_with('-') || s.contains('=') || s.contains(':') => {
17821 specs.push(s.to_string());
17822 }
17823 _ => specs.push(arg.clone()),
17824 }
17825 }
17826
17827 for arg in iter {
17829 specs.push(arg.clone());
17830 }
17831
17832 #[derive(Clone)]
17834 struct OptSpec {
17835 name: String,
17836 takes_arg: bool,
17837 optional_arg: bool,
17838 #[allow(dead_code)]
17839 append: bool,
17840 target_array: Option<String>,
17841 }
17842
17843 let mut opt_specs: Vec<OptSpec> = Vec::new();
17844 for spec in &specs {
17845 let mut s = spec.as_str();
17846 let mut target = None;
17847
17848 if let Some(eq_pos) = s.rfind('=') {
17850 if !s[eq_pos + 1..].contains(':') {
17851 target = Some(s[eq_pos + 1..].to_string());
17852 s = &s[..eq_pos];
17853 }
17854 }
17855
17856 let append = s.ends_with('+') || s.contains("+:");
17857 let s = s.trim_end_matches('+');
17858
17859 let (name, takes_arg, optional_arg) = if s.ends_with("::") {
17860 (s.trim_end_matches(':').trim_end_matches(':'), true, true)
17861 } else if s.ends_with(':') {
17862 (s.trim_end_matches(':'), true, false)
17863 } else {
17864 (s, false, false)
17865 };
17866
17867 opt_specs.push(OptSpec {
17868 name: name.to_string(),
17869 takes_arg,
17870 optional_arg,
17871 append,
17872 target_array: target,
17873 });
17874 }
17875
17876 let positionals: Vec<String> = (1..=99)
17878 .map(|i| self.get_variable(&i.to_string()))
17879 .take_while(|v| !v.is_empty())
17880 .collect();
17881
17882 let mut results: Vec<(String, Option<String>)> = Vec::new();
17884 let mut i = 0;
17885 let mut parsed_count = 0;
17886
17887 while i < positionals.len() {
17888 let arg = &positionals[i];
17889
17890 if arg == "-" || arg == "--" {
17891 parsed_count = i + 1;
17892 break;
17893 }
17894
17895 if !arg.starts_with('-') {
17896 if !keep_going {
17897 break;
17898 }
17899 i += 1;
17900 continue;
17901 }
17902
17903 let opt_name = arg.trim_start_matches('-');
17905 let mut matched = false;
17906
17907 for spec in &opt_specs {
17908 if opt_name == spec.name || opt_name.starts_with(&format!("{}=", spec.name)) {
17909 matched = true;
17910
17911 if spec.takes_arg {
17912 let arg_value = if opt_name.contains('=') {
17913 Some(opt_name.splitn(2, '=').nth(1).unwrap_or("").to_string())
17914 } else if i + 1 < positionals.len()
17915 && (!positionals[i + 1].starts_with('-') || spec.optional_arg)
17916 {
17917 i += 1;
17918 Some(positionals[i].clone())
17919 } else if spec.optional_arg {
17920 None
17921 } else if fail_on_error {
17922 eprintln!("zparseopts: missing argument for option: {}", spec.name);
17923 return 1;
17924 } else {
17925 None
17926 };
17927 results.push((format!("-{}", spec.name), arg_value));
17928 } else {
17929 results.push((format!("-{}", spec.name), None));
17930 }
17931 break;
17932 }
17933 }
17934
17935 if !matched && !keep_going {
17936 break;
17937 }
17938
17939 i += 1;
17940 parsed_count = i;
17941 }
17942
17943 if let Some(arr_name) = &array_name {
17945 let mut arr_values: Vec<String> = Vec::new();
17946 for (opt, val) in &results {
17947 arr_values.push(opt.clone());
17948 if let Some(v) = val {
17949 arr_values.push(v.clone());
17950 }
17951 }
17952 self.arrays.insert(arr_name.clone(), arr_values);
17953 }
17954
17955 if let Some(assoc) = &assoc_name {
17957 let mut map: HashMap<String, String> = HashMap::new();
17958 for (opt, val) in &results {
17959 map.insert(opt.clone(), val.clone().unwrap_or_default());
17960 }
17961 self.assoc_arrays.insert(assoc.clone(), map);
17962 }
17963
17964 for spec in &opt_specs {
17966 if let Some(target) = &spec.target_array {
17967 let values: Vec<String> = results
17968 .iter()
17969 .filter(|(opt, _)| opt.trim_start_matches('-') == spec.name)
17970 .flat_map(|(opt, val)| {
17971 let mut v = vec![opt.clone()];
17972 if let Some(arg) = val {
17973 v.push(arg.clone());
17974 }
17975 v
17976 })
17977 .collect();
17978 if !values.is_empty() || !keep_values {
17979 self.arrays.insert(target.clone(), values);
17980 }
17981 }
17982 }
17983
17984 if remove_parsed && parsed_count > 0 {
17986 for i in 1..=parsed_count {
17987 self.variables.remove(&i.to_string());
17988 std::env::remove_var(i.to_string());
17989 }
17990 let remaining: Vec<String> = ((parsed_count + 1)..=99)
17992 .map(|i| self.get_variable(&i.to_string()))
17993 .take_while(|v| !v.is_empty())
17994 .collect();
17995 for (i, val) in remaining.iter().enumerate() {
17996 self.variables.insert((i + 1).to_string(), val.clone());
17997 }
17998 }
17999
18000 0
18001 }
18002
18003 fn builtin_readonly(&mut self, args: &[String]) -> i32 {
18005 if args.is_empty() {
18006 for name in &self.readonly_vars {
18008 if let Some(val) = self.variables.get(name) {
18009 println!("readonly {}={}", name, val);
18010 }
18011 }
18012 return 0;
18013 }
18014
18015 for arg in args {
18016 if arg == "-p" {
18017 for name in &self.readonly_vars {
18018 if let Some(val) = self.variables.get(name) {
18019 println!("declare -r {}=\"{}\"", name, val);
18020 }
18021 }
18022 } else if let Some(eq_pos) = arg.find('=') {
18023 let name = &arg[..eq_pos];
18024 let value = &arg[eq_pos + 1..];
18025 self.variables.insert(name.to_string(), value.to_string());
18026 self.readonly_vars.insert(name.to_string());
18027 } else {
18028 self.readonly_vars.insert(arg.clone());
18029 }
18030 }
18031 0
18032 }
18033
18034 fn builtin_unfunction(&mut self, args: &[String]) -> i32 {
18036 for name in args {
18037 if self.functions.remove(name).is_none() {
18038 eprintln!("unfunction: no such function: {}", name);
18039 }
18040 }
18041 0
18042 }
18043
18044 fn builtin_getln(&mut self, args: &[String]) -> i32 {
18046 if args.is_empty() {
18047 eprintln!("getln: missing variable name");
18048 return 1;
18049 }
18050 let mut line = String::new();
18052 if std::io::stdin().read_line(&mut line).is_ok() {
18053 let line = line.trim_end_matches('\n');
18054 self.variables.insert(args[0].clone(), line.to_string());
18055 0
18056 } else {
18057 1
18058 }
18059 }
18060
18061 fn builtin_pushln(&mut self, args: &[String]) -> i32 {
18063 for arg in args {
18064 println!("{}", arg);
18065 }
18066 0
18067 }
18068
18069 fn builtin_bindkey(&mut self, args: &[String]) -> i32 {
18071 use crate::zle::{zle, KeymapName};
18072
18073 if args.is_empty() {
18074 let zle = zle();
18076 for (keys, widget) in zle
18077 .keymaps
18078 .get(&KeymapName::Main)
18079 .map(|km| km.list_bindings().collect::<Vec<_>>())
18080 .unwrap_or_default()
18081 {
18082 println!("\"{}\" {}", keys, widget);
18083 }
18084 return 0;
18085 }
18086
18087 let mut iter = args.iter().peekable();
18088 let mut keymap = KeymapName::Main;
18089 let mut list_mode = false;
18090 let mut list_all = false;
18091 let mut remove = false;
18092
18093 while let Some(arg) = iter.next() {
18094 match arg.as_str() {
18095 "-l" => {
18096 list_mode = true;
18097 }
18098 "-L" => {
18099 list_mode = true;
18100 list_all = true;
18101 }
18102 "-la" | "-lL" => {
18103 list_mode = true;
18104 list_all = true;
18105 }
18106 "-M" => {
18107 if let Some(name) = iter.next() {
18108 if let Some(km) = KeymapName::from_str(name) {
18109 keymap = km;
18110 }
18111 }
18112 }
18113 "-r" => {
18114 remove = true;
18115 }
18116 "-A" => {
18117 return 0;
18119 }
18120 "-N" => {
18121 return 0;
18123 }
18124 "-e" => {
18125 keymap = KeymapName::Emacs;
18126 }
18127 "-v" => {
18128 keymap = KeymapName::ViInsert;
18129 }
18130 "-a" => {
18131 keymap = KeymapName::ViCommand;
18132 }
18133 key if !key.starts_with('-') => {
18134 if let Some(widget) = iter.next() {
18136 let mut zle = zle();
18137 if remove {
18138 zle.unbind_key(keymap, key);
18139 } else {
18140 zle.bind_key(keymap, key, widget);
18141 }
18142 }
18143 return 0;
18144 }
18145 _ => {}
18146 }
18147 }
18148
18149 if list_mode {
18150 let zle = zle();
18151 if list_all {
18152 for km_name in &[
18153 KeymapName::Emacs,
18154 KeymapName::ViInsert,
18155 KeymapName::ViCommand,
18156 ] {
18157 println!("{}", km_name.as_str());
18158 }
18159 } else {
18160 if let Some(km) = zle.keymaps.get(&keymap) {
18161 for (keys, widget) in km.list_bindings() {
18162 println!("bindkey \"{}\" {}", keys, widget);
18163 }
18164 }
18165 }
18166 }
18167
18168 0
18169 }
18170
18171 fn builtin_zle(&mut self, args: &[String]) -> i32 {
18173 use crate::zle::zle;
18174
18175 if args.is_empty() {
18176 return 0;
18177 }
18178
18179 let mut iter = args.iter().peekable();
18180
18181 while let Some(arg) = iter.next() {
18182 match arg.as_str() {
18183 "-l" => {
18184 let zle = zle();
18186 let mut widgets: Vec<&str> = zle.list_widgets();
18187 widgets.sort();
18188 for w in widgets {
18189 println!("{}", w);
18190 }
18191 return 0;
18192 }
18193 "-la" | "-lL" => {
18194 let zle = zle();
18196 let mut widgets: Vec<&str> = zle.list_widgets();
18197 widgets.sort();
18198 for w in widgets {
18199 println!("{}", w);
18200 }
18201 return 0;
18202 }
18203 "-N" => {
18204 if let Some(widget_name) = iter.next() {
18206 let func_name = iter
18207 .next()
18208 .map(|s| s.as_str())
18209 .unwrap_or(widget_name.as_str());
18210 let mut zle = zle();
18211 zle.define_widget(widget_name, func_name);
18212 }
18213 return 0;
18214 }
18215 "-D" => {
18216 return 0;
18218 }
18219 "-A" => {
18220 return 0;
18222 }
18223 "-R" => {
18224 return 0;
18226 }
18227 "-U" => {
18228 return 0;
18230 }
18231 "-K" => {
18232 return 0;
18234 }
18235 "-F" => {
18236 return 0;
18238 }
18239 "-M" => {
18240 return 0;
18242 }
18243 "-I" => {
18244 return 0;
18246 }
18247 "-f" => {
18248 if let Some(name) = iter.next() {
18250 let zle = zle();
18251 return if zle.get_widget(name).is_some() { 0 } else { 1 };
18252 }
18253 return 1;
18254 }
18255 widget_name if !widget_name.starts_with('-') => {
18256 let mut zle = zle();
18258 match zle.execute_widget(widget_name, None) {
18259 crate::zle::WidgetResult::Ok => return 0,
18260 crate::zle::WidgetResult::Error(e) => {
18261 eprintln!("zle: {}", e);
18262 return 1;
18263 }
18264 crate::zle::WidgetResult::CallFunction(func) => {
18265 drop(zle);
18267 if let Some(f) = self.functions.get(&func).cloned() {
18268 return self.call_function(&f, &[]).unwrap_or(1);
18269 }
18270 return 1;
18271 }
18272 _ => return 0,
18273 }
18274 }
18275 _ => {}
18276 }
18277 }
18278
18279 0
18280 }
18281
18282 fn builtin_sched(&mut self, args: &[String]) -> i32 {
18284 use std::time::{Duration, SystemTime};
18285
18286 if args.is_empty() {
18287 if self.scheduled_commands.is_empty() {
18289 return 0;
18290 }
18291 let now = SystemTime::now();
18292 for cmd in &self.scheduled_commands {
18293 let remaining = cmd.run_at.duration_since(now).unwrap_or(Duration::ZERO);
18294 println!("{:3} +{:5} {}", cmd.id, remaining.as_secs(), cmd.command);
18295 }
18296 return 0;
18297 }
18298
18299 let mut i = 0;
18300 while i < args.len() {
18301 match args[i].as_str() {
18302 "-" => {
18303 i += 1;
18305 if i >= args.len() {
18306 eprintln!("sched: -: need item number");
18307 return 1;
18308 }
18309 if let Ok(id) = args[i].parse::<u32>() {
18310 self.scheduled_commands.retain(|c| c.id != id);
18311 return 0;
18312 } else {
18313 eprintln!("sched: invalid item number");
18314 return 1;
18315 }
18316 }
18317 "+" => {
18318 i += 1;
18320 if i >= args.len() {
18321 eprintln!("sched: +: need time");
18322 return 1;
18323 }
18324 let secs: u64 = args[i].parse().unwrap_or(0);
18325 i += 1;
18326 let command = args[i..].join(" ");
18327
18328 let id = self.scheduled_commands.len() as u32 + 1;
18329 self.scheduled_commands.push(ScheduledCommand {
18330 id,
18331 run_at: SystemTime::now() + Duration::from_secs(secs),
18332 command,
18333 });
18334 return 0;
18335 }
18336 time_str => {
18337 let parts: Vec<&str> = time_str.split(':').collect();
18339 if parts.len() >= 2 {
18340 let hour: u32 = parts[0].parse().unwrap_or(0);
18341 let min: u32 = parts[1].parse().unwrap_or(0);
18342 let sec: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
18343
18344 let now = SystemTime::now();
18346 let target_secs = (hour * 3600 + min * 60 + sec) as u64;
18347 let _day_secs = 86400u64;
18348
18349 let run_at = now + Duration::from_secs(target_secs);
18351
18352 i += 1;
18353 let command = args[i..].join(" ");
18354
18355 let id = self.scheduled_commands.len() as u32 + 1;
18356 self.scheduled_commands.push(ScheduledCommand {
18357 id,
18358 run_at,
18359 command,
18360 });
18361 return 0;
18362 } else {
18363 eprintln!("sched: invalid time format");
18364 return 1;
18365 }
18366 }
18367 }
18368 }
18369 0
18370 }
18371
18372 fn builtin_zcompile(&mut self, args: &[String]) -> i32 {
18374 use crate::zwc::{ZwcBuilder, ZwcFile};
18375
18376 let mut list_mode = false; let mut compile_current = false; let mut compile_auto = false; let mut files: Vec<String> = Vec::new();
18380
18381 let mut i = 0;
18382 while i < args.len() {
18383 let arg = &args[i];
18384 if arg.starts_with('-') && arg.len() > 1 {
18385 for c in arg[1..].chars() {
18386 match c {
18387 't' => list_mode = true,
18388 'c' => compile_current = true,
18389 'a' => compile_auto = true,
18390 'U' | 'M' | 'R' | 'm' | 'z' | 'k' => {} _ => {
18392 eprintln!("zcompile: unknown option: -{}", c);
18393 return 1;
18394 }
18395 }
18396 }
18397 } else {
18398 files.push(arg.clone());
18399 }
18400 i += 1;
18401 }
18402
18403 if files.is_empty() {
18404 eprintln!("zcompile: not enough arguments");
18405 return 1;
18406 }
18407
18408 if list_mode {
18410 let zwc_path = if files[0].ends_with(".zwc") {
18411 files[0].clone()
18412 } else {
18413 format!("{}.zwc", files[0])
18414 };
18415
18416 match ZwcFile::load(&zwc_path) {
18417 Ok(zwc) => {
18418 println!("zwc file for zshrs-{}", env!("CARGO_PKG_VERSION"));
18419 if files.len() > 1 {
18420 for name in &files[1..] {
18422 if zwc.get_function(name).is_some() {
18423 println!("{}", name);
18424 } else {
18425 eprintln!("zcompile: function not found: {}", name);
18426 return 1;
18427 }
18428 }
18429 } else {
18430 for name in zwc.list_functions() {
18432 println!("{}", name);
18433 }
18434 }
18435 return 0;
18436 }
18437 Err(e) => {
18438 eprintln!("zcompile: can't read zwc file: {}: {}", zwc_path, e);
18439 return 1;
18440 }
18441 }
18442 }
18443
18444 if compile_current || compile_auto {
18446 let zwc_path = if files[0].ends_with(".zwc") {
18447 files[0].clone()
18448 } else {
18449 format!("{}.zwc", files[0])
18450 };
18451
18452 let mut builder = ZwcBuilder::new();
18453
18454 if files.len() > 1 {
18455 for name in &files[1..] {
18457 if let Some(func) = self.functions.get(name) {
18458 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
18460 builder.add_source(name, &source);
18461 } else if compile_auto && self.autoload_pending.contains_key(name) {
18462 if let Some(path) = self.find_function_file(name) {
18464 if let Err(e) = builder.add_file(&path) {
18465 eprintln!("zcompile: can't read {}: {}", name, e);
18466 return 1;
18467 }
18468 }
18469 } else {
18470 eprintln!("zcompile: no such function: {}", name);
18471 return 1;
18472 }
18473 }
18474 } else {
18475 for (name, func) in &self.functions {
18477 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
18478 builder.add_source(name, &source);
18479 }
18480 }
18481
18482 if let Err(e) = builder.write(&zwc_path) {
18483 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
18484 return 1;
18485 }
18486 return 0;
18487 }
18488
18489 let zwc_path = if files[0].ends_with(".zwc") {
18491 files[0].clone()
18492 } else {
18493 format!("{}.zwc", files[0])
18494 };
18495
18496 let mut builder = ZwcBuilder::new();
18497
18498 let source_files = if files.len() == 1 {
18500 let path = std::path::Path::new(&files[0]);
18502 if path.is_dir() {
18503 match std::fs::read_dir(path) {
18505 Ok(entries) => {
18506 for entry in entries.flatten() {
18507 let p = entry.path();
18508 if p.is_file() && !p.extension().map_or(false, |e| e == "zwc") {
18509 if let Err(e) = builder.add_file(&p) {
18510 eprintln!("zcompile: can't read {:?}: {}", p, e);
18511 }
18512 }
18513 }
18514 }
18515 Err(e) => {
18516 eprintln!("zcompile: can't read directory: {}", e);
18517 return 1;
18518 }
18519 }
18520 vec![]
18521 } else {
18522 vec![files[0].clone()]
18523 }
18524 } else {
18525 files[1..].to_vec()
18526 };
18527
18528 for file in &source_files {
18529 let path = std::path::Path::new(file);
18530 if let Err(e) = builder.add_file(path) {
18531 eprintln!("zcompile: can't read {}: {}", file, e);
18532 return 1;
18533 }
18534 }
18535
18536 if let Err(e) = builder.write(&zwc_path) {
18537 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
18538 return 1;
18539 }
18540
18541 0
18542 }
18543
18544 fn builtin_zformat(&self, args: &[String]) -> i32 {
18546 if args.len() < 2 {
18547 eprintln!("zformat: not enough arguments");
18548 return 1;
18549 }
18550
18551 match args[0].as_str() {
18552 "-f" => {
18553 if args.len() < 3 {
18555 return 1;
18556 }
18557 let _var_name = &args[1];
18558 let format = &args[2];
18559 let specs: HashMap<char, &str> = args[3..]
18560 .iter()
18561 .filter_map(|s| {
18562 let mut chars = s.chars();
18563 let key = chars.next()?;
18564 if chars.next() == Some(':') {
18565 Some((key, &s[2..]))
18566 } else {
18567 None
18568 }
18569 })
18570 .collect();
18571
18572 let mut result = String::new();
18573 let mut chars = format.chars().peekable();
18574 while let Some(c) = chars.next() {
18575 if c == '%' {
18576 if let Some(&spec_char) = chars.peek() {
18577 if let Some(replacement) = specs.get(&spec_char) {
18578 result.push_str(replacement);
18579 chars.next();
18580 continue;
18581 }
18582 }
18583 }
18584 result.push(c);
18585 }
18586 println!("{}", result);
18587 }
18588 "-a" => {
18589 if args.len() < 4 {
18592 eprintln!("zformat -a: need array, separator, and specs");
18593 return 1;
18594 }
18595 let _array_name = &args[1];
18596 let sep = &args[2];
18597
18598 let mut results = Vec::new();
18599 for spec in &args[3..] {
18600 let parts: Vec<&str> = spec.splitn(3, ':').collect();
18601 if parts.len() >= 2 {
18602 let text = parts[0];
18603 let value = parts[1];
18604 let cond = parts.get(2).copied();
18605
18606 if let Some(c) = cond {
18608 if c.is_empty() || c == "0" {
18609 continue;
18610 }
18611 }
18612
18613 if !value.is_empty() {
18614 results.push(format!("{}{}{}", text, sep, value));
18615 }
18616 }
18617 }
18618
18619 for r in results {
18620 println!("{}", r);
18621 }
18622 }
18623 _ => {
18624 eprintln!("zformat: unknown option: {}", args[0]);
18625 return 1;
18626 }
18627 }
18628 0
18629 }
18630
18631 fn builtin_vared(&mut self, args: &[String]) -> i32 {
18633 if args.is_empty() {
18634 eprintln!("vared: not enough arguments");
18635 return 1;
18636 }
18637
18638 let mut var_name = String::new();
18639 let mut prompt = String::new();
18640 let mut rprompt = String::new();
18641 let mut _history = false; let mut i = 0;
18643
18644 while i < args.len() {
18645 match args[i].as_str() {
18646 "-p" if i + 1 < args.len() => {
18647 i += 1;
18648 prompt = args[i].clone();
18649 }
18650 "-r" if i + 1 < args.len() => {
18651 i += 1;
18652 rprompt = args[i].clone();
18653 }
18654 "-h" => _history = true,
18655 "-c" => {} "-e" => {} "-M" | "-m" => {
18658 i += 1;
18659 } "-a" | "-A" => {
18661 i += 1;
18662 } s if !s.starts_with('-') => {
18664 var_name = s.to_string();
18665 }
18666 _ => {}
18667 }
18668 i += 1;
18669 }
18670
18671 if var_name.is_empty() {
18672 eprintln!("vared: not enough arguments");
18673 return 1;
18674 }
18675
18676 let current = self.get_variable(&var_name);
18678
18679 if !prompt.is_empty() {
18681 eprint!("{}", prompt);
18682 }
18683 print!("{}", current);
18684 if !rprompt.is_empty() {
18685 eprint!("{}", rprompt);
18686 }
18687
18688 let mut input = String::new();
18689 if std::io::stdin().read_line(&mut input).is_ok() {
18690 let value = input.trim_end_matches('\n').to_string();
18691 self.variables.insert(var_name, value);
18692 return 0;
18693 }
18694 1
18695 }
18696
18697 fn builtin_echotc(&self, args: &[String]) -> i32 {
18699 if args.is_empty() {
18700 eprintln!("echotc: not enough arguments");
18701 return 1;
18702 }
18703
18704 match args[0].as_str() {
18706 "cl" => print!("\x1b[H\x1b[2J"), "cd" => print!("\x1b[J"), "ce" => print!("\x1b[K"), "cm" => {
18710 if args.len() >= 3 {
18712 if let (Ok(row), Ok(col)) = (args[1].parse::<u32>(), args[2].parse::<u32>()) {
18713 print!("\x1b[{};{}H", row + 1, col + 1);
18714 }
18715 }
18716 }
18717 "up" => print!("\x1b[A"), "do" => print!("\x1b[B"), "le" => print!("\x1b[D"), "nd" => print!("\x1b[C"), "ho" => print!("\x1b[H"), "vi" => print!("\x1b[?25l"), "ve" => print!("\x1b[?25h"), "so" => print!("\x1b[7m"), "se" => print!("\x1b[27m"), "us" => print!("\x1b[4m"), "ue" => print!("\x1b[24m"), "md" => print!("\x1b[1m"), "me" => print!("\x1b[0m"), "mr" => print!("\x1b[7m"), "AF" | "setaf" => {
18732 if args.len() >= 2 {
18734 if let Ok(color) = args[1].parse::<u32>() {
18735 print!("\x1b[38;5;{}m", color);
18736 }
18737 }
18738 }
18739 "AB" | "setab" => {
18740 if args.len() >= 2 {
18742 if let Ok(color) = args[1].parse::<u32>() {
18743 print!("\x1b[48;5;{}m", color);
18744 }
18745 }
18746 }
18747 "Co" | "colors" => {
18748 println!("256");
18750 }
18751 "co" | "cols" => {
18752 println!(
18754 "{}",
18755 std::env::var("COLUMNS")
18756 .ok()
18757 .and_then(|s| s.parse().ok())
18758 .unwrap_or(80u16)
18759 );
18760 }
18761 "li" | "lines" => {
18762 println!(
18764 "{}",
18765 std::env::var("LINES")
18766 .ok()
18767 .and_then(|s| s.parse().ok())
18768 .unwrap_or(24u16)
18769 );
18770 }
18771 cap => {
18772 eprintln!("echotc: unknown capability: {}", cap);
18773 return 1;
18774 }
18775 }
18776 use std::io::Write;
18777 let _ = std::io::stdout().flush();
18778 0
18779 }
18780
18781 fn builtin_echoti(&self, args: &[String]) -> i32 {
18783 self.builtin_echotc(args)
18786 }
18787
18788 fn builtin_zpty(&mut self, args: &[String]) -> i32 {
18790 use std::io::{Read, Write};
18791 use std::process::{Command, Stdio};
18792
18793 if args.is_empty() {
18794 if self.zptys.is_empty() {
18796 return 0;
18797 }
18798 for (name, state) in &self.zptys {
18799 println!("{}: {} (pid {})", name, state.cmd, state.pid);
18800 }
18801 return 0;
18802 }
18803
18804 let mut i = 0;
18805 while i < args.len() {
18806 match args[i].as_str() {
18807 "-d" => {
18808 i += 1;
18810 if i >= args.len() {
18811 eprintln!("zpty: -d requires pty name");
18812 return 1;
18813 }
18814 let name = &args[i];
18815 if let Some(mut state) = self.zptys.remove(name) {
18816 if let Some(ref mut child) = state.child {
18817 let _ = child.kill();
18818 }
18819 return 0;
18820 } else {
18821 eprintln!("zpty: no such pty: {}", name);
18822 return 1;
18823 }
18824 }
18825 "-w" => {
18826 i += 1;
18828 if i >= args.len() {
18829 eprintln!("zpty: -w requires pty name");
18830 return 1;
18831 }
18832 let name = args[i].clone();
18833 i += 1;
18834 let data = args[i..].join(" ") + "\n";
18835
18836 if let Some(state) = self.zptys.get_mut(&name) {
18837 if let Some(ref mut stdin) = state.stdin {
18838 if stdin.write_all(data.as_bytes()).is_ok() {
18839 let _ = stdin.flush();
18840 return 0;
18841 }
18842 }
18843 eprintln!("zpty: write failed");
18844 return 1;
18845 } else {
18846 eprintln!("zpty: no such pty: {}", name);
18847 return 1;
18848 }
18849 }
18850 "-r" => {
18851 i += 1;
18853 if i >= args.len() {
18854 eprintln!("zpty: -r requires pty name");
18855 return 1;
18856 }
18857 let name = args[i].clone();
18858 i += 1;
18859 let var_name = if i < args.len() {
18860 args[i].clone()
18861 } else {
18862 "REPLY".to_string()
18863 };
18864
18865 if let Some(state) = self.zptys.get_mut(&name) {
18866 if let Some(ref mut stdout) = state.stdout {
18867 let mut buf = vec![0u8; 4096];
18868 match stdout.read(&mut buf) {
18869 Ok(n) => {
18870 let data = String::from_utf8_lossy(&buf[..n]).to_string();
18871 self.variables.insert(var_name, data);
18872 return 0;
18873 }
18874 Err(_) => return 1,
18875 }
18876 }
18877 return 1;
18878 } else {
18879 eprintln!("zpty: no such pty: {}", name);
18880 return 1;
18881 }
18882 }
18883 "-t" => {
18884 i += 1;
18886 if i >= args.len() {
18887 return 1;
18888 }
18889 let name = &args[i];
18890 if self.zptys.contains_key(name) {
18891 return 0; }
18893 return 1;
18894 }
18895 "-L" => {
18896 for (name, state) in &self.zptys {
18898 println!("zpty {} {}", name, state.cmd);
18899 }
18900 return 0;
18901 }
18902 "-b" | "-e" => {
18903 i += 1;
18905 continue;
18906 }
18907 name if !name.starts_with('-') => {
18908 i += 1;
18910 if i >= args.len() {
18911 eprintln!("zpty: command required");
18912 return 1;
18913 }
18914 let cmd_str = args[i..].join(" ");
18915
18916 match Command::new("sh")
18917 .arg("-c")
18918 .arg(&cmd_str)
18919 .stdin(Stdio::piped())
18920 .stdout(Stdio::piped())
18921 .stderr(Stdio::piped())
18922 .spawn()
18923 {
18924 Ok(mut child) => {
18925 let pid = child.id();
18926 let stdin = child.stdin.take();
18927 let stdout = child.stdout.take();
18928
18929 self.zptys.insert(
18930 name.to_string(),
18931 ZptyState {
18932 pid,
18933 cmd: cmd_str,
18934 stdin,
18935 stdout,
18936 child: Some(child),
18937 },
18938 );
18939 return 0;
18940 }
18941 Err(e) => {
18942 eprintln!("zpty: failed to start: {}", e);
18943 return 1;
18944 }
18945 }
18946 }
18947 _ => {
18948 i += 1;
18949 }
18950 }
18951 i += 1;
18952 }
18953 0
18954 }
18955
18956 fn builtin_zprof(&mut self, args: &[String]) -> i32 {
18958 use crate::zprof::ZprofOptions;
18959
18960 let options = ZprofOptions {
18961 clear: args.iter().any(|a| a == "-c"),
18962 };
18963
18964 let (status, output) = crate::zprof::builtin_zprof(&mut self.profiler, &options);
18965 if !output.is_empty() {
18966 print!("{}", output);
18967 }
18968 status
18969 }
18970
18971 fn builtin_zsocket(&mut self, args: &[String]) -> i32 {
18973 use std::os::unix::net::{UnixListener, UnixStream};
18974
18975 if args.is_empty() {
18976 if self.unix_sockets.is_empty() {
18978 return 0;
18979 }
18980 for (fd, state) in &self.unix_sockets {
18981 let path = state
18982 .path
18983 .as_ref()
18984 .map(|p| p.display().to_string())
18985 .unwrap_or_default();
18986 let status = if state.listening {
18987 "listening"
18988 } else {
18989 "connected"
18990 };
18991 println!("{}: {} ({})", fd, path, status);
18992 }
18993 return 0;
18994 }
18995
18996 let mut i = 0;
18997 let mut verbose = false;
18998 let mut var_name = "REPLY".to_string();
18999
19000 while i < args.len() {
19001 match args[i].as_str() {
19002 "-v" => {
19003 verbose = true;
19004 i += 1;
19005 if i < args.len() && !args[i].starts_with('-') {
19006 var_name = args[i].clone();
19007 }
19008 }
19009 "-l" => {
19010 i += 1;
19012 if i >= args.len() {
19013 eprintln!("zsocket: -l requires path");
19014 return 1;
19015 }
19016 let path = PathBuf::from(&args[i]);
19017
19018 let _ = std::fs::remove_file(&path);
19020
19021 match UnixListener::bind(&path) {
19022 Ok(listener) => {
19023 let fd = self.next_fd;
19024 self.next_fd += 1;
19025
19026 self.unix_sockets.insert(
19027 fd,
19028 UnixSocketState {
19029 path: Some(path),
19030 listening: true,
19031 stream: None,
19032 listener: Some(listener),
19033 },
19034 );
19035
19036 if verbose {
19037 self.variables.insert(var_name.clone(), fd.to_string());
19038 }
19039 println!("{}", fd);
19040 return 0;
19041 }
19042 Err(e) => {
19043 eprintln!("zsocket: bind failed: {}", e);
19044 return 1;
19045 }
19046 }
19047 }
19048 "-a" => {
19049 i += 1;
19051 if i >= args.len() {
19052 eprintln!("zsocket: -a requires fd");
19053 return 1;
19054 }
19055 let listen_fd: i32 = args[i].parse().unwrap_or(-1);
19056
19057 if let Some(state) = self.unix_sockets.get(&listen_fd) {
19058 if let Some(ref listener) = state.listener {
19059 match listener.accept() {
19060 Ok((stream, _addr)) => {
19061 let new_fd = self.next_fd;
19062 self.next_fd += 1;
19063
19064 self.unix_sockets.insert(
19065 new_fd,
19066 UnixSocketState {
19067 path: None,
19068 listening: false,
19069 stream: Some(stream),
19070 listener: None,
19071 },
19072 );
19073
19074 if verbose {
19075 self.variables.insert(var_name.clone(), new_fd.to_string());
19076 }
19077 println!("{}", new_fd);
19078 return 0;
19079 }
19080 Err(e) => {
19081 eprintln!("zsocket: accept failed: {}", e);
19082 return 1;
19083 }
19084 }
19085 }
19086 }
19087 eprintln!("zsocket: invalid fd");
19088 return 1;
19089 }
19090 "-d" => {
19091 i += 1;
19093 if i >= args.len() {
19094 eprintln!("zsocket: -d requires fd");
19095 return 1;
19096 }
19097 let fd: i32 = args[i].parse().unwrap_or(-1);
19098
19099 if let Some(state) = self.unix_sockets.remove(&fd) {
19100 if let Some(path) = state.path {
19101 let _ = std::fs::remove_file(path);
19102 }
19103 return 0;
19104 }
19105 eprintln!("zsocket: no such fd");
19106 return 1;
19107 }
19108 path if !path.starts_with('-') => {
19109 match UnixStream::connect(path) {
19111 Ok(stream) => {
19112 let fd = self.next_fd;
19113 self.next_fd += 1;
19114
19115 self.unix_sockets.insert(
19116 fd,
19117 UnixSocketState {
19118 path: Some(PathBuf::from(path)),
19119 listening: false,
19120 stream: Some(stream),
19121 listener: None,
19122 },
19123 );
19124
19125 if verbose {
19126 self.variables.insert(var_name.clone(), fd.to_string());
19127 }
19128 println!("{}", fd);
19129 return 0;
19130 }
19131 Err(e) => {
19132 eprintln!("zsocket: connect failed: {}", e);
19133 return 1;
19134 }
19135 }
19136 }
19137 _ => {}
19138 }
19139 i += 1;
19140 }
19141 0
19142 }
19143
19144 fn builtin_ztcp(&mut self, args: &[String]) -> i32 {
19146 self.builtin_zsocket(args)
19148 }
19149
19150 fn builtin_zregexparse(&mut self, args: &[String]) -> i32 {
19152 if args.len() < 2 {
19153 eprintln!("zregexparse: usage: zregexparse var pattern [string]");
19154 return 1;
19155 }
19156
19157 let var_name = &args[0];
19158 let pattern = &args[1];
19159 let string = if args.len() > 2 {
19160 args[2].clone()
19161 } else {
19162 self.variables.get("REPLY").cloned().unwrap_or_default()
19163 };
19164
19165 match regex::Regex::new(pattern) {
19166 Ok(re) => {
19167 if let Some(captures) = re.captures(&string) {
19168 if let Some(m) = captures.get(0) {
19170 self.variables
19171 .insert(var_name.clone(), m.as_str().to_string());
19172 }
19173
19174 let mut match_array = Vec::new();
19176 let mut mbegin_array = Vec::new();
19177 let mut mend_array = Vec::new();
19178
19179 for (i, cap) in captures.iter().enumerate() {
19180 if let Some(c) = cap {
19181 match_array.push(c.as_str().to_string());
19182 mbegin_array.push((c.start() + 1).to_string());
19183 mend_array.push(c.end().to_string());
19184 self.variables
19185 .insert(format!("match[{}]", i), c.as_str().to_string());
19186 }
19187 }
19188 self.arrays.insert("match".to_string(), match_array);
19189 self.arrays.insert("mbegin".to_string(), mbegin_array);
19190 self.arrays.insert("mend".to_string(), mend_array);
19191
19192 if let Some(m) = captures.get(0) {
19194 self.variables
19195 .insert("MBEGIN".to_string(), (m.start() + 1).to_string());
19196 self.variables
19197 .insert("MEND".to_string(), m.end().to_string());
19198 }
19199
19200 0
19201 } else {
19202 1
19203 }
19204 }
19205 Err(e) => {
19206 eprintln!("zregexparse: invalid regex: {}", e);
19207 2
19208 }
19209 }
19210 }
19211
19212 fn builtin_clone(&mut self, args: &[String]) -> i32 {
19214 use std::process::Command;
19215
19216 let mut cmd =
19219 Command::new(std::env::current_exe().unwrap_or_else(|_| PathBuf::from("zshrs")));
19220
19221 if !args.is_empty() {
19222 cmd.arg("-c").arg(args.join(" "));
19224 }
19225
19226 for (k, v) in &self.variables {
19228 cmd.env(k, v);
19229 }
19230
19231 match cmd.spawn() {
19232 Ok(mut child) => match child.wait() {
19233 Ok(status) => status.code().unwrap_or(0),
19234 Err(_) => 1,
19235 },
19236 Err(e) => {
19237 eprintln!("clone: failed to spawn subshell: {}", e);
19238 1
19239 }
19240 }
19241 }
19242
19243 fn builtin_log(&mut self, args: &[String]) -> i32 {
19245 self.builtin_exit(args)
19246 }
19247
19248 fn builtin_comparguments(&mut self, _args: &[String]) -> i32 {
19252 0
19254 }
19255
19256 fn builtin_compcall(&mut self, _args: &[String]) -> i32 {
19258 0
19260 }
19261
19262 fn builtin_compctl(&mut self, args: &[String]) -> i32 {
19264 if args.is_empty() {
19265 println!("compctl: old-style completion system");
19266 println!("Use the new completion system (compsys) instead");
19267 return 0;
19268 }
19269 0
19271 }
19272
19273 fn builtin_compdescribe(&mut self, _args: &[String]) -> i32 {
19275 0
19276 }
19277
19278 fn builtin_compfiles(&mut self, _args: &[String]) -> i32 {
19280 0
19281 }
19282
19283 fn builtin_compgroups(&mut self, _args: &[String]) -> i32 {
19285 0
19286 }
19287
19288 fn builtin_compquote(&mut self, _args: &[String]) -> i32 {
19290 0
19291 }
19292
19293 fn builtin_comptags(&mut self, args: &[String]) -> i32 {
19295 if args.is_empty() {
19296 return 1;
19297 }
19298 match args[0].as_str() {
19299 "-i" => {
19300 0
19302 }
19303 "-S" => {
19304 0
19306 }
19307 _ => 1,
19308 }
19309 }
19310
19311 fn builtin_comptry(&mut self, _args: &[String]) -> i32 {
19313 1 }
19315
19316 fn builtin_compvalues(&mut self, _args: &[String]) -> i32 {
19318 0
19319 }
19320
19321 fn builtin_cap(&self, args: &[String]) -> i32 {
19323 if args.is_empty() {
19326 println!("cap: display/set capabilities");
19327 println!(" getcap file... - display capabilities");
19328 println!(" setcap caps file - set capabilities");
19329 return 0;
19330 }
19331
19332 #[cfg(target_os = "linux")]
19333 {
19334 let status = std::process::Command::new(&args[0])
19337 .args(&args[1..])
19338 .status();
19339 return status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
19340 }
19341
19342 #[cfg(not(target_os = "linux"))]
19343 {
19344 eprintln!("cap: capabilities not supported on this platform");
19345 1
19346 }
19347 }
19348
19349 fn builtin_zcurses(&mut self, args: &[String]) -> i32 {
19351 if args.is_empty() {
19352 eprintln!("zcurses: requires subcommand");
19353 return 1;
19354 }
19355
19356 match args[0].as_str() {
19357 "init" => {
19358 println!("zcurses: would initialize curses");
19360 0
19361 }
19362 "end" => {
19363 println!("zcurses: would end curses");
19365 0
19366 }
19367 "addwin" => {
19368 0
19370 }
19371 "delwin" => {
19372 0
19374 }
19375 "refresh" => {
19376 0
19378 }
19379 "move" => {
19380 0
19382 }
19383 "clear" => {
19384 0
19386 }
19387 "char" | "string" => {
19388 0
19390 }
19391 "border" => {
19392 0
19394 }
19395 "attr" => {
19396 0
19398 }
19399 "color" => {
19400 0
19402 }
19403 "scroll" => {
19404 0
19406 }
19407 "input" => {
19408 0
19410 }
19411 "mouse" => {
19412 0
19414 }
19415 "querychar" => {
19416 0
19418 }
19419 "resize" => {
19420 0
19422 }
19423 cmd => {
19424 eprintln!("zcurses: unknown subcommand: {}", cmd);
19425 1
19426 }
19427 }
19428 }
19429
19430 fn builtin_sysread(&mut self, args: &[String]) -> i32 {
19432 use std::io::Read;
19433
19434 let mut fd = 0i32; let mut count: Option<usize> = None;
19436 let mut var_name = "REPLY".to_string();
19437 let mut i = 0;
19438
19439 while i < args.len() {
19440 match args[i].as_str() {
19441 "-c" if i + 1 < args.len() => {
19442 i += 1;
19443 count = args[i].parse().ok();
19444 }
19445 "-i" if i + 1 < args.len() => {
19446 i += 1;
19447 fd = args[i].parse().unwrap_or(0);
19448 }
19449 "-o" if i + 1 < args.len() => {
19450 i += 1;
19451 var_name = args[i].clone();
19452 }
19453 "-t" if i + 1 < args.len() => {
19454 i += 1;
19455 }
19457 _ => {
19458 var_name = args[i].clone();
19459 }
19460 }
19461 i += 1;
19462 }
19463
19464 let mut buffer = vec![0u8; count.unwrap_or(8192)];
19465
19466 if fd == 0 {
19468 match std::io::stdin().read(&mut buffer) {
19469 Ok(n) => {
19470 buffer.truncate(n);
19471 let s = String::from_utf8_lossy(&buffer).to_string();
19472 self.variables.insert(var_name, s);
19473 0
19474 }
19475 Err(_) => 1,
19476 }
19477 } else {
19478 eprintln!("sysread: only fd 0 (stdin) supported");
19479 1
19480 }
19481 }
19482
19483 fn builtin_syswrite(&mut self, args: &[String]) -> i32 {
19485 use std::io::Write;
19486
19487 let mut fd = 1i32; let mut data = String::new();
19489 let mut i = 0;
19490
19491 while i < args.len() {
19492 match args[i].as_str() {
19493 "-o" if i + 1 < args.len() => {
19494 i += 1;
19495 fd = args[i].parse().unwrap_or(1);
19496 }
19497 "-c" if i + 1 < args.len() => {
19498 i += 1;
19499 }
19501 _ => {
19502 data = args[i].clone();
19503 }
19504 }
19505 i += 1;
19506 }
19507
19508 match fd {
19509 1 => {
19510 let _ = std::io::stdout().write_all(data.as_bytes());
19511 let _ = std::io::stdout().flush();
19512 0
19513 }
19514 2 => {
19515 let _ = std::io::stderr().write_all(data.as_bytes());
19516 let _ = std::io::stderr().flush();
19517 0
19518 }
19519 _ => {
19520 eprintln!("syswrite: only fd 1 (stdout) and 2 (stderr) supported");
19521 1
19522 }
19523 }
19524 }
19525
19526 fn builtin_syserror(&self, args: &[String]) -> i32 {
19528 let errno = if args.is_empty() {
19529 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
19531 } else {
19532 args[0].parse().unwrap_or(0)
19533 };
19534
19535 let err = std::io::Error::from_raw_os_error(errno);
19536 println!("{}", err);
19537 0
19538 }
19539
19540 fn builtin_sysopen(&mut self, args: &[String]) -> i32 {
19542 use std::fs::OpenOptions;
19543
19544 let mut filename = String::new();
19545 let mut var_name = "REPLY".to_string();
19546 let mut read = false;
19547 let mut write = false;
19548 let mut append = false;
19549 let mut create = false;
19550 let mut truncate = false;
19551
19552 let mut i = 0;
19553 while i < args.len() {
19554 match args[i].as_str() {
19555 "-r" => read = true,
19556 "-w" => write = true,
19557 "-a" => append = true,
19558 "-c" => create = true,
19559 "-t" => truncate = true,
19560 "-u" => {
19561 i += 1;
19562 if i < args.len() {
19563 var_name = args[i].clone();
19564 }
19565 }
19566 "-o" => {
19567 i += 1;
19568 }
19570 s if !s.starts_with('-') => {
19571 filename = s.to_string();
19572 }
19573 _ => {}
19574 }
19575 i += 1;
19576 }
19577
19578 if filename.is_empty() {
19579 eprintln!("sysopen: need filename");
19580 return 1;
19581 }
19582
19583 if !read && !write && !append {
19585 read = true;
19586 }
19587
19588 let file = OpenOptions::new()
19589 .read(read)
19590 .write(write || append || truncate)
19591 .append(append)
19592 .create(create || write)
19593 .truncate(truncate)
19594 .open(&filename);
19595
19596 match file {
19597 Ok(f) => {
19598 let fd = self.next_fd;
19599 self.next_fd += 1;
19600 self.open_fds.insert(fd, f);
19601 self.variables.insert(var_name, fd.to_string());
19602 0
19603 }
19604 Err(e) => {
19605 eprintln!("sysopen: {}: {}", filename, e);
19606 1
19607 }
19608 }
19609 }
19610
19611 fn builtin_sysseek(&mut self, args: &[String]) -> i32 {
19613 use std::io::{Seek, SeekFrom};
19614
19615 let mut fd = -1i32;
19616 let mut offset = 0i64;
19617 let mut whence = SeekFrom::Start(0);
19618
19619 let mut i = 0;
19620 while i < args.len() {
19621 match args[i].as_str() {
19622 "-u" => {
19623 i += 1;
19624 if i < args.len() {
19625 fd = args[i].parse().unwrap_or(-1);
19626 }
19627 }
19628 "-w" => {
19629 i += 1;
19630 if i < args.len() {
19631 whence = match args[i].as_str() {
19632 "start" | "set" | "0" => SeekFrom::Start(offset as u64),
19633 "current" | "cur" | "1" => SeekFrom::Current(offset),
19634 "end" | "2" => SeekFrom::End(offset),
19635 _ => SeekFrom::Start(offset as u64),
19636 };
19637 }
19638 }
19639 s if !s.starts_with('-') => {
19640 offset = s.parse().unwrap_or(0);
19641 }
19642 _ => {}
19643 }
19644 i += 1;
19645 }
19646
19647 if fd < 0 {
19648 eprintln!("sysseek: need fd (-u)");
19649 return 1;
19650 }
19651
19652 whence = match whence {
19654 SeekFrom::Start(_) => SeekFrom::Start(offset as u64),
19655 SeekFrom::Current(_) => SeekFrom::Current(offset),
19656 SeekFrom::End(_) => SeekFrom::End(offset),
19657 };
19658
19659 if let Some(file) = self.open_fds.get_mut(&fd) {
19660 match file.seek(whence) {
19661 Ok(pos) => {
19662 self.variables.insert("REPLY".to_string(), pos.to_string());
19663 0
19664 }
19665 Err(e) => {
19666 eprintln!("sysseek: {}", e);
19667 1
19668 }
19669 }
19670 } else {
19671 eprintln!("sysseek: bad fd: {}", fd);
19672 1
19673 }
19674 }
19675
19676 fn builtin_private(&mut self, args: &[String]) -> i32 {
19678 self.builtin_local(args)
19680 }
19681
19682 fn builtin_zattr(&self, cmd: &str, args: &[String]) -> i32 {
19684 match cmd {
19685 "zgetattr" => {
19686 if args.len() < 2 {
19687 eprintln!("zgetattr: need file and attribute name");
19688 return 1;
19689 }
19690 #[cfg(target_os = "macos")]
19691 {
19692 let output = std::process::Command::new("xattr")
19694 .arg("-p")
19695 .arg(&args[1])
19696 .arg(&args[0])
19697 .output();
19698 if let Ok(out) = output {
19699 print!("{}", String::from_utf8_lossy(&out.stdout));
19700 return if out.status.success() { 0 } else { 1 };
19701 }
19702 }
19703 #[cfg(target_os = "linux")]
19704 {
19705 let output = std::process::Command::new("getfattr")
19706 .arg("-n")
19707 .arg(&args[1])
19708 .arg(&args[0])
19709 .output();
19710 if let Ok(out) = output {
19711 print!("{}", String::from_utf8_lossy(&out.stdout));
19712 return if out.status.success() { 0 } else { 1 };
19713 }
19714 }
19715 1
19716 }
19717 "zsetattr" => {
19718 if args.len() < 3 {
19719 eprintln!("zsetattr: need file, attribute name, and value");
19720 return 1;
19721 }
19722 #[cfg(target_os = "macos")]
19723 {
19724 let status = std::process::Command::new("xattr")
19725 .arg("-w")
19726 .arg(&args[1])
19727 .arg(&args[2])
19728 .arg(&args[0])
19729 .status();
19730 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
19731 }
19732 #[cfg(target_os = "linux")]
19733 {
19734 let status = std::process::Command::new("setfattr")
19735 .arg("-n")
19736 .arg(&args[1])
19737 .arg("-v")
19738 .arg(&args[2])
19739 .arg(&args[0])
19740 .status();
19741 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
19742 }
19743 #[allow(unreachable_code)]
19744 1
19745 }
19746 "zdelattr" => {
19747 if args.len() < 2 {
19748 eprintln!("zdelattr: need file and attribute name");
19749 return 1;
19750 }
19751 #[cfg(target_os = "macos")]
19752 {
19753 let status = std::process::Command::new("xattr")
19754 .arg("-d")
19755 .arg(&args[1])
19756 .arg(&args[0])
19757 .status();
19758 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
19759 }
19760 #[cfg(target_os = "linux")]
19761 {
19762 let status = std::process::Command::new("setfattr")
19763 .arg("-x")
19764 .arg(&args[1])
19765 .arg(&args[0])
19766 .status();
19767 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
19768 }
19769 #[allow(unreachable_code)]
19770 1
19771 }
19772 "zlistattr" => {
19773 if args.is_empty() {
19774 eprintln!("zlistattr: need file");
19775 return 1;
19776 }
19777 #[cfg(target_os = "macos")]
19778 {
19779 let output = std::process::Command::new("xattr").arg(&args[0]).output();
19780 if let Ok(out) = output {
19781 print!("{}", String::from_utf8_lossy(&out.stdout));
19782 return if out.status.success() { 0 } else { 1 };
19783 }
19784 }
19785 #[cfg(target_os = "linux")]
19786 {
19787 let output = std::process::Command::new("getfattr")
19788 .arg("-d")
19789 .arg(&args[0])
19790 .output();
19791 if let Ok(out) = output {
19792 print!("{}", String::from_utf8_lossy(&out.stdout));
19793 return if out.status.success() { 0 } else { 1 };
19794 }
19795 }
19796 1
19797 }
19798 _ => 1,
19799 }
19800 }
19801
19802 fn builtin_zftp(&mut self, args: &[String]) -> i32 {
19804 if args.is_empty() {
19805 println!("zftp: FTP client");
19806 println!(" zftp open host [port]");
19807 println!(" zftp login [user [password]]");
19808 println!(" zftp cd dir");
19809 println!(" zftp get file [localfile]");
19810 println!(" zftp put file [remotefile]");
19811 println!(" zftp ls [dir]");
19812 println!(" zftp close");
19813 return 0;
19814 }
19815
19816 match args[0].as_str() {
19817 "open" => {
19818 if args.len() < 2 {
19819 eprintln!("zftp open: need hostname");
19820 return 1;
19821 }
19822 println!("zftp: would connect to {}", args[1]);
19824 0
19825 }
19826 "login" => {
19827 println!("zftp: would login");
19829 0
19830 }
19831 "cd" => {
19832 if args.len() < 2 {
19833 eprintln!("zftp cd: need directory");
19834 return 1;
19835 }
19836 println!("zftp: would cd to {}", args[1]);
19837 0
19838 }
19839 "get" => {
19840 if args.len() < 2 {
19841 eprintln!("zftp get: need filename");
19842 return 1;
19843 }
19844 println!("zftp: would download {}", args[1]);
19845 0
19846 }
19847 "put" => {
19848 if args.len() < 2 {
19849 eprintln!("zftp put: need filename");
19850 return 1;
19851 }
19852 println!("zftp: would upload {}", args[1]);
19853 0
19854 }
19855 "ls" => {
19856 println!("zftp: would list directory");
19857 0
19858 }
19859 "close" | "quit" => {
19860 println!("zftp: would close connection");
19861 0
19862 }
19863 "params" => {
19864 println!("ZFTP_HOST=");
19866 println!("ZFTP_PORT=21");
19867 println!("ZFTP_USER=");
19868 println!("ZFTP_PWD=");
19869 println!("ZFTP_TYPE=A");
19870 0
19871 }
19872 cmd => {
19873 eprintln!("zftp: unknown command: {}", cmd);
19874 1
19875 }
19876 }
19877 }
19878
19879 fn builtin_promptinit(&mut self, _args: &[String]) -> i32 {
19881 self.arrays.insert(
19882 "prompt_themes".to_string(),
19883 vec![
19884 "adam1".to_string(),
19885 "adam2".to_string(),
19886 "bart".to_string(),
19887 "bigfade".to_string(),
19888 "clint".to_string(),
19889 "default".to_string(),
19890 "elite".to_string(),
19891 "elite2".to_string(),
19892 "fade".to_string(),
19893 "fire".to_string(),
19894 "minimal".to_string(),
19895 "off".to_string(),
19896 "oliver".to_string(),
19897 "pws".to_string(),
19898 "redhat".to_string(),
19899 "restore".to_string(),
19900 "suse".to_string(),
19901 "walters".to_string(),
19902 "zefram".to_string(),
19903 ],
19904 );
19905 self.variables
19906 .insert("prompt_theme".to_string(), "default".to_string());
19907 0
19908 }
19909
19910 fn builtin_prompt(&mut self, args: &[String]) -> i32 {
19912 if args.is_empty() {
19913 let theme = self
19914 .variables
19915 .get("prompt_theme")
19916 .cloned()
19917 .unwrap_or_else(|| "default".to_string());
19918 println!("Current prompt theme: {}", theme);
19919 return 0;
19920 }
19921 match args[0].as_str() {
19922 "-l" | "--list" => {
19923 println!("Available prompt themes:");
19924 if let Some(themes) = self.arrays.get("prompt_themes") {
19925 for theme in themes {
19926 println!(" {}", theme);
19927 }
19928 }
19929 }
19930 "-p" | "--preview" => {
19931 let theme = args.get(1).map(|s| s.as_str()).unwrap_or("default");
19932 self.apply_prompt_theme(theme, true);
19933 }
19934 "-h" | "--help" => {
19935 println!("prompt [options] [theme]");
19936 println!(" -l, --list List available themes");
19937 println!(" -p, --preview Preview a theme");
19938 println!(" -s, --setup Set up a theme");
19939 }
19940 _ => {
19941 let theme = if args[0].starts_with('-') {
19942 args.get(1).map(|s| s.as_str()).unwrap_or("default")
19943 } else {
19944 args[0].as_str()
19945 };
19946 self.apply_prompt_theme(theme, false);
19947 }
19948 }
19949 0
19950 }
19951
19952 fn apply_prompt_theme(&mut self, theme: &str, preview: bool) {
19953 let (ps1, rps1) = match theme {
19954 "minimal" => ("%# ", ""),
19955 "off" => ("$ ", ""),
19956 "adam1" => (
19957 "%B%F{cyan}%n@%m %F{blue}%~%f%b %# ",
19958 "%F{yellow}%D{%H:%M}%f",
19959 ),
19960 "redhat" => ("[%n@%m %~]$ ", ""),
19961 _ => ("%n@%m %~ %# ", ""),
19962 };
19963 if preview {
19964 println!("PS1={:?}", ps1);
19965 println!("RPS1={:?}", rps1);
19966 } else {
19967 self.variables.insert("PS1".to_string(), ps1.to_string());
19968 self.variables.insert("RPS1".to_string(), rps1.to_string());
19969 self.variables
19970 .insert("prompt_theme".to_string(), theme.to_string());
19971 }
19972 }
19973
19974 fn builtin_pcre_compile(&mut self, args: &[String]) -> i32 {
19976 use crate::pcre::{pcre_compile, PcreCompileOptions};
19977
19978 let mut pattern = String::new();
19979 let mut options = PcreCompileOptions::default();
19980
19981 for arg in args {
19982 match arg.as_str() {
19983 "-a" => options.anchored = true,
19984 "-i" => options.caseless = true,
19985 "-m" => options.multiline = true,
19986 "-s" => options.dotall = true,
19987 "-x" => options.extended = true,
19988 s if !s.starts_with('-') => pattern = s.to_string(),
19989 _ => {}
19990 }
19991 }
19992
19993 if pattern.is_empty() {
19994 eprintln!("pcre_compile: no pattern specified");
19995 return 1;
19996 }
19997
19998 match pcre_compile(&pattern, &options, &mut self.pcre_state) {
19999 Ok(()) => 0,
20000 Err(e) => {
20001 eprintln!("pcre_compile: {}", e);
20002 1
20003 }
20004 }
20005 }
20006
20007 fn builtin_pcre_match(&mut self, args: &[String]) -> i32 {
20009 use crate::pcre::{pcre_match, PcreMatchOptions};
20010
20011 let mut var_name = "MATCH".to_string();
20012 let mut array_name = "match".to_string();
20013 let mut string = String::new();
20014 let mut i = 0;
20015
20016 while i < args.len() {
20017 match args[i].as_str() {
20018 "-v" => {
20019 i += 1;
20020 if i < args.len() {
20021 var_name = args[i].clone();
20022 }
20023 }
20024 "-a" => {
20025 i += 1;
20026 if i < args.len() {
20027 array_name = args[i].clone();
20028 }
20029 }
20030 s if !s.starts_with('-') => string = s.to_string(),
20031 _ => {}
20032 }
20033 i += 1;
20034 }
20035
20036 let options = PcreMatchOptions {
20037 match_var: Some(var_name.clone()),
20038 array_var: Some(array_name.clone()),
20039 ..Default::default()
20040 };
20041
20042 match pcre_match(&string, &options, &self.pcre_state) {
20043 Ok(result) => {
20044 if result.matched {
20045 if let Some(m) = result.full_match {
20046 self.variables.insert(var_name, m);
20047 }
20048 let matches: Vec<String> =
20049 result.captures.into_iter().filter_map(|c| c).collect();
20050 self.arrays.insert(array_name, matches);
20051 0
20052 } else {
20053 1
20054 }
20055 }
20056 Err(e) => {
20057 eprintln!("pcre_match: {}", e);
20058 1
20059 }
20060 }
20061 }
20062
20063 fn builtin_pcre_study(&mut self, _args: &[String]) -> i32 {
20065 use crate::pcre::pcre_study;
20066
20067 match pcre_study(&self.pcre_state) {
20068 Ok(()) => 0,
20069 Err(e) => {
20070 eprintln!("pcre_study: {}", e);
20071 1
20072 }
20073 }
20074 }
20075
20076 pub fn zfork(&mut self, flags: ForkFlags) -> std::io::Result<ForkResult> {
20083 let can_background = self.options.get("monitor").copied().unwrap_or(false);
20085
20086 unsafe {
20087 match libc::fork() {
20088 -1 => Err(std::io::Error::last_os_error()),
20089 0 => {
20090 if !flags.contains(ForkFlags::NOJOB) && can_background {
20092 let pid = libc::getpid();
20094 if flags.contains(ForkFlags::NEWGRP) {
20095 libc::setpgid(0, 0);
20096 }
20097 if flags.contains(ForkFlags::FGTTY) {
20098 libc::tcsetpgrp(0, pid);
20099 }
20100 }
20101
20102 if !flags.contains(ForkFlags::KEEPSIGS) {
20104 self.reset_signals();
20105 }
20106
20107 Ok(ForkResult::Child)
20108 }
20109 pid => {
20110 if !flags.contains(ForkFlags::NOJOB) {
20112 self.add_child_process(pid);
20114 }
20115 Ok(ForkResult::Parent(pid))
20116 }
20117 }
20118 }
20119 }
20120
20121 fn add_child_process(&mut self, pid: i32) {
20123 self.variables.insert("!".to_string(), pid.to_string());
20125 }
20126
20127 fn reset_signals(&self) {
20129 unsafe {
20130 libc::signal(libc::SIGINT, libc::SIG_DFL);
20131 libc::signal(libc::SIGQUIT, libc::SIG_DFL);
20132 libc::signal(libc::SIGTERM, libc::SIG_DFL);
20133 libc::signal(libc::SIGTSTP, libc::SIG_DFL);
20134 libc::signal(libc::SIGTTIN, libc::SIG_DFL);
20135 libc::signal(libc::SIGTTOU, libc::SIG_DFL);
20136 libc::signal(libc::SIGCHLD, libc::SIG_DFL);
20137 }
20138 }
20139
20140 pub fn zexecve(&self, cmd: &str, args: &[String]) -> ! {
20143 use std::ffi::CString;
20144 use std::os::unix::ffi::OsStrExt;
20145
20146 let c_cmd = CString::new(cmd).expect("CString::new failed");
20147
20148 let c_args: Vec<CString> = std::iter::once(c_cmd.clone())
20150 .chain(args.iter().map(|s| CString::new(s.as_str()).unwrap()))
20151 .collect();
20152
20153 let c_argv: Vec<*const libc::c_char> = c_args
20154 .iter()
20155 .map(|s| s.as_ptr())
20156 .chain(std::iter::once(std::ptr::null()))
20157 .collect();
20158
20159 let env_vars: Vec<CString> = std::env::vars()
20161 .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
20162 .collect();
20163
20164 let c_envp: Vec<*const libc::c_char> = env_vars
20165 .iter()
20166 .map(|s| s.as_ptr())
20167 .chain(std::iter::once(std::ptr::null()))
20168 .collect();
20169
20170 unsafe {
20171 libc::execve(c_cmd.as_ptr(), c_argv.as_ptr(), c_envp.as_ptr());
20172 eprintln!(
20174 "zshrs: exec failed: {}: {}",
20175 cmd,
20176 std::io::Error::last_os_error()
20177 );
20178 std::process::exit(127);
20179 }
20180 }
20181
20182 pub fn entersubsh(&mut self, flags: SubshellFlags) {
20185 let level = self
20187 .get_variable("ZSH_SUBSHELL")
20188 .parse::<i32>()
20189 .unwrap_or(0);
20190 self.variables
20191 .insert("ZSH_SUBSHELL".to_string(), (level + 1).to_string());
20192
20193 if flags.contains(SubshellFlags::NOMONITOR) {
20195 self.options.insert("monitor".to_string(), false);
20196 }
20197
20198 if !flags.contains(SubshellFlags::KEEPFDS) {
20200 self.close_extra_fds();
20201 }
20202
20203 if !flags.contains(SubshellFlags::KEEPTRAPS) {
20205 self.reset_traps();
20206 }
20207 }
20208
20209 fn close_extra_fds(&self) {
20211 for fd in 10..256 {
20213 unsafe {
20214 libc::close(fd);
20215 }
20216 }
20217 }
20218
20219 fn reset_traps(&mut self) {
20221 self.traps.clear();
20222 }
20223
20224 pub fn doshfunc(
20227 &mut self,
20228 name: &str,
20229 func: &ShellCommand,
20230 args: &[String],
20231 ) -> Result<i32, String> {
20232 let old_argv = self.positional_params.clone();
20234 let old_funcstack = self.arrays.get("funcstack").cloned();
20235 let old_funcsourcetrace = self.arrays.get("funcsourcetrace").cloned();
20236
20237 self.positional_params = args.to_vec();
20239
20240 let mut funcstack = old_funcstack.clone().unwrap_or_default();
20242 funcstack.insert(0, name.to_string());
20243 self.arrays.insert("funcstack".to_string(), funcstack);
20244
20245 let result = self.execute_command(func);
20247
20248 self.positional_params = old_argv;
20250 if let Some(fs) = old_funcstack {
20251 self.arrays.insert("funcstack".to_string(), fs);
20252 } else {
20253 self.arrays.remove("funcstack");
20254 }
20255 if let Some(fst) = old_funcsourcetrace {
20256 self.arrays.insert("funcsourcetrace".to_string(), fst);
20257 }
20258
20259 result
20260 }
20261
20262 pub fn execarith(&mut self, expr: &str) -> i32 {
20265 let result = self.eval_arith_expr(expr);
20266 if result == 0 {
20267 1
20268 } else {
20269 0
20270 }
20271 }
20272
20273 pub fn execcond(&mut self, cond: &CondExpr) -> i32 {
20276 if self.eval_cond_expr(cond) {
20277 0
20278 } else {
20279 1
20280 }
20281 }
20282
20283 pub fn exectime(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
20286 use std::time::Instant;
20287
20288 let start = Instant::now();
20289 let result = self.execute_command(cmd);
20290 let elapsed = start.elapsed();
20291
20292 let user_time = elapsed.as_secs_f64() * 0.7; let sys_time = elapsed.as_secs_f64() * 0.1;
20295 let real_time = elapsed.as_secs_f64();
20296
20297 eprintln!(
20298 "{:.2}s user {:.2}s system {:.0}% cpu {:.3} total",
20299 user_time,
20300 sys_time,
20301 ((user_time + sys_time) / real_time * 100.0).min(100.0),
20302 real_time
20303 );
20304
20305 result
20306 }
20307
20308 pub fn findcmd(&self, name: &str, do_hash: bool) -> Option<String> {
20311 if do_hash {
20313 if let Some(path) = self.command_hash.get(name) {
20314 if std::path::Path::new(path).exists() {
20315 return Some(path.clone());
20316 }
20317 }
20318 }
20319
20320 if let Ok(path_var) = std::env::var("PATH") {
20322 for dir in path_var.split(':') {
20323 let full_path = format!("{}/{}", dir, name);
20324 if std::path::Path::new(&full_path).is_file() {
20325 return Some(full_path);
20326 }
20327 }
20328 }
20329
20330 None
20331 }
20332
20333 pub fn hashcmd(&mut self, name: &str, path: &str) {
20336 self.command_hash.insert(name.to_string(), path.to_string());
20337 }
20338
20339 pub fn iscom(&self, name: &str) -> bool {
20342 if self.is_builtin_cmd(name) {
20344 return true;
20345 }
20346
20347 if self.functions.contains_key(name) {
20349 return true;
20350 }
20351
20352 if self.aliases.contains_key(name) {
20354 return true;
20355 }
20356
20357 self.findcmd(name, true).is_some()
20359 }
20360
20361 fn is_builtin_cmd(&self, name: &str) -> bool {
20363 BUILTIN_SET.contains(name)
20364 }
20365
20366 pub fn closem(&self, exceptions: &[i32]) {
20369 for fd in 3..256 {
20370 if !exceptions.contains(&fd) {
20371 unsafe {
20372 libc::close(fd);
20373 }
20374 }
20375 }
20376 }
20377
20378 pub fn mpipe(&self) -> std::io::Result<(i32, i32)> {
20381 let mut fds = [0i32; 2];
20382 let result = unsafe { libc::pipe(fds.as_mut_ptr()) };
20383 if result == -1 {
20384 Err(std::io::Error::last_os_error())
20385 } else {
20386 Ok((fds[0], fds[1]))
20387 }
20388 }
20389
20390 pub fn addfd(&self, fd: i32, target_fd: i32, mode: RedirMode) -> std::io::Result<()> {
20393 match mode {
20394 RedirMode::Dup => {
20395 if fd != target_fd {
20396 unsafe {
20397 if libc::dup2(fd, target_fd) == -1 {
20398 return Err(std::io::Error::last_os_error());
20399 }
20400 }
20401 }
20402 }
20403 RedirMode::Close => unsafe {
20404 libc::close(target_fd);
20405 },
20406 }
20407 Ok(())
20408 }
20409
20410 pub fn gethere(&mut self, terminator: &str, strip_tabs: bool) -> String {
20413 let mut content = String::new();
20414
20415 if strip_tabs {
20419 content = content
20420 .lines()
20421 .map(|line| line.trim_start_matches('\t'))
20422 .collect::<Vec<_>>()
20423 .join("\n");
20424 }
20425
20426 content
20427 }
20428
20429 pub fn getherestr(&mut self, word: &str) -> String {
20432 let expanded = self.expand_string(word);
20433 format!("{}\n", expanded)
20434 }
20435
20436 pub fn resolvebuiltin(&self, name: &str) -> Option<BuiltinType> {
20439 if self.is_builtin_cmd(name) {
20440 Some(BuiltinType::Normal)
20441 } else {
20442 None
20444 }
20445 }
20446
20447 pub fn cancd(&self, path_str: &str) -> bool {
20450 use std::os::unix::fs::PermissionsExt;
20451
20452 let path = std::path::Path::new(path_str);
20453 if !path.is_dir() {
20454 return false;
20455 }
20456
20457 if let Ok(meta) = path.metadata() {
20458 let mode = meta.permissions().mode();
20459 let uid = unsafe { libc::getuid() };
20461 let gid = unsafe { libc::getgid() };
20462 let file_uid = meta.uid();
20463 let file_gid = meta.gid();
20464
20465 if uid == file_uid {
20466 return (mode & 0o100) != 0;
20467 } else if gid == file_gid {
20468 return (mode & 0o010) != 0;
20469 } else {
20470 return (mode & 0o001) != 0;
20471 }
20472 }
20473
20474 false
20475 }
20476
20477 pub fn commandnotfound(&mut self, name: &str, args: &[String]) -> i32 {
20480 if self.functions.contains_key("command_not_found_handler") {
20482 let mut handler_args = vec![name.to_string()];
20483 handler_args.extend(args.iter().cloned());
20484
20485 if let Some(func) = self.functions.get("command_not_found_handler").cloned() {
20486 if let Ok(code) = self.doshfunc("command_not_found_handler", &func, &handler_args) {
20487 return code;
20488 }
20489 }
20490 }
20491
20492 eprintln!("zshrs: command not found: {}", name);
20493 127
20494 }
20495}
20496
20497use std::os::unix::fs::MetadataExt;
20498
20499bitflags::bitflags! {
20500 #[derive(Debug, Clone, Copy, Default)]
20502 pub struct ForkFlags: u32 {
20503 const NOJOB = 1 << 0; const NEWGRP = 1 << 1; const FGTTY = 1 << 2; const KEEPSIGS = 1 << 3; }
20508}
20509
20510bitflags::bitflags! {
20511 #[derive(Debug, Clone, Copy, Default)]
20513 pub struct SubshellFlags: u32 {
20514 const NOMONITOR = 1 << 0; const KEEPFDS = 1 << 1; const KEEPTRAPS = 1 << 2; }
20518}
20519
20520#[derive(Debug)]
20522pub enum ForkResult {
20523 Parent(i32), Child,
20525}
20526
20527#[derive(Debug, Clone, Copy)]
20529pub enum RedirMode {
20530 Dup,
20531 Close,
20532}
20533
20534#[derive(Debug, Clone, Copy)]
20536pub enum BuiltinType {
20537 Normal,
20538 Disabled,
20539}