1use crate::history::HistoryEngine;
8use crate::math::MathEval;
9use crate::pcre::PcreState;
10use crate::prompt::{expand_prompt, PromptContext};
11use crate::tcp::TcpSessions;
12use crate::zftp::Zftp;
13use crate::zprof::Profiler;
14use crate::zutil::StyleTable;
15use compsys::cache::CompsysCache;
16use compsys::CompInitResult;
17use parking_lot::Mutex;
18use std::collections::HashSet;
19
20#[derive(Debug, Clone)]
22pub enum AdviceKind {
23 Before,
25 After,
27 Around,
29}
30
31#[derive(Debug, Clone)]
33pub struct Intercept {
34 pub pattern: String,
36 pub kind: AdviceKind,
38 pub code: String,
40 pub id: u32,
42}
43
44pub struct CompInitBgResult {
46 pub result: CompInitResult,
47 pub cache: CompsysCache,
48}
49use std::io::Write;
50use std::sync::LazyLock;
51
52struct PluginSnapshot {
54 functions: std::collections::HashSet<String>,
55 aliases: std::collections::HashSet<String>,
56 global_aliases: std::collections::HashSet<String>,
57 suffix_aliases: std::collections::HashSet<String>,
58 variables: HashMap<String, String>,
59 arrays: std::collections::HashSet<String>,
60 assoc_arrays: std::collections::HashSet<String>,
61 fpath: Vec<PathBuf>,
62 options: HashMap<String, bool>,
63 hooks: HashMap<String, Vec<String>>,
64 autoloads: std::collections::HashSet<String>,
65}
66
67static REGEX_CACHE: LazyLock<Mutex<std::collections::HashMap<String, regex::Regex>>> =
69 LazyLock::new(|| Mutex::new(std::collections::HashMap::with_capacity(64)));
70
71use std::cell::RefCell;
76
77thread_local! {
80 static CURRENT_EXECUTOR: RefCell<Option<*mut ShellExecutor>> = const { RefCell::new(None) };
81}
82
83struct ExecutorContext;
85
86impl ExecutorContext {
87 fn enter(executor: &mut ShellExecutor) -> Self {
88 CURRENT_EXECUTOR.with(|cell| {
89 *cell.borrow_mut() = Some(executor as *mut ShellExecutor);
90 });
91 ExecutorContext
92 }
93}
94
95impl Drop for ExecutorContext {
96 fn drop(&mut self) {
97 CURRENT_EXECUTOR.with(|cell| {
98 *cell.borrow_mut() = None;
99 });
100 }
101}
102
103#[inline]
107fn with_executor<F, R>(f: F) -> R
108where
109 F: FnOnce(&mut ShellExecutor) -> R,
110{
111 CURRENT_EXECUTOR.with(|cell| {
112 let ptr = cell
113 .borrow()
114 .expect("with_executor called outside VM context");
115 let executor = unsafe { &mut *ptr };
118 f(executor)
119 })
120}
121
122fn register_builtins(vm: &mut fusevm::VM) {
124 use fusevm::shell_builtins::*;
125 use fusevm::Value;
126
127 vm.register_builtin(BUILTIN_CD, |vm, argc| {
129 let args = pop_args(vm, argc);
130 let status = with_executor(|exec| exec.builtin_cd(&args));
131 Value::Status(status)
132 });
133
134 vm.register_builtin(BUILTIN_PWD, |vm, argc| {
135 let _args = pop_args(vm, argc);
136 let status = with_executor(|exec| exec.builtin_pwd(&[]));
137 Value::Status(status)
138 });
139
140 vm.register_builtin(BUILTIN_ECHO, |vm, argc| {
141 let args = pop_args(vm, argc);
142 let status = with_executor(|exec| exec.builtin_echo(&args, &[]));
143 Value::Status(status)
144 });
145
146 vm.register_builtin(BUILTIN_PRINT, |vm, argc| {
147 let args = pop_args(vm, argc);
148 let status = with_executor(|exec| exec.builtin_print(&args));
149 Value::Status(status)
150 });
151
152 vm.register_builtin(BUILTIN_PRINTF, |vm, argc| {
153 let args = pop_args(vm, argc);
154 let status = with_executor(|exec| exec.builtin_printf(&args));
155 Value::Status(status)
156 });
157
158 vm.register_builtin(BUILTIN_EXPORT, |vm, argc| {
159 let args = pop_args(vm, argc);
160 let status = with_executor(|exec| exec.builtin_export(&args));
161 Value::Status(status)
162 });
163
164 vm.register_builtin(BUILTIN_UNSET, |vm, argc| {
165 let args = pop_args(vm, argc);
166 let status = with_executor(|exec| exec.builtin_unset(&args));
167 Value::Status(status)
168 });
169
170 vm.register_builtin(BUILTIN_SOURCE, |vm, argc| {
171 let args = pop_args(vm, argc);
172 let status = with_executor(|exec| exec.builtin_source(&args));
173 Value::Status(status)
174 });
175
176 vm.register_builtin(BUILTIN_EXIT, |vm, argc| {
177 let args = pop_args(vm, argc);
178 let status = with_executor(|exec| exec.builtin_exit(&args));
179 Value::Status(status)
180 });
181
182 vm.register_builtin(BUILTIN_RETURN, |vm, argc| {
183 let args = pop_args(vm, argc);
184 let status = with_executor(|exec| exec.builtin_return(&args));
185 Value::Status(status)
186 });
187
188 vm.register_builtin(BUILTIN_TRUE, |_vm, _argc| Value::Status(0));
189 vm.register_builtin(BUILTIN_FALSE, |_vm, _argc| Value::Status(1));
190 vm.register_builtin(BUILTIN_COLON, |_vm, _argc| Value::Status(0));
191
192 vm.register_builtin(BUILTIN_TEST, |vm, argc| {
193 let args = pop_args(vm, argc);
194 let status = with_executor(|exec| exec.builtin_test(&args));
195 Value::Status(status)
196 });
197
198 vm.register_builtin(BUILTIN_LOCAL, |vm, argc| {
200 let args = pop_args(vm, argc);
201 let status = with_executor(|exec| exec.builtin_local(&args));
202 Value::Status(status)
203 });
204
205 vm.register_builtin(BUILTIN_TYPESET, |vm, argc| {
206 let args = pop_args(vm, argc);
207 let status = with_executor(|exec| exec.builtin_declare(&args));
208 Value::Status(status)
209 });
210
211 vm.register_builtin(BUILTIN_READONLY, |vm, argc| {
212 let args = pop_args(vm, argc);
213 let status = with_executor(|exec| exec.builtin_readonly(&args));
214 Value::Status(status)
215 });
216
217 vm.register_builtin(BUILTIN_INTEGER, |vm, argc| {
218 let args = pop_args(vm, argc);
219 let status = with_executor(|exec| exec.builtin_integer(&args));
220 Value::Status(status)
221 });
222
223 vm.register_builtin(BUILTIN_FLOAT, |vm, argc| {
224 let args = pop_args(vm, argc);
225 let status = with_executor(|exec| exec.builtin_float(&args));
226 Value::Status(status)
227 });
228
229 vm.register_builtin(BUILTIN_READ, |vm, argc| {
231 let args = pop_args(vm, argc);
232 let status = with_executor(|exec| exec.builtin_read(&args));
233 Value::Status(status)
234 });
235
236 vm.register_builtin(BUILTIN_BREAK, |vm, argc| {
238 let args = pop_args(vm, argc);
239 let status = with_executor(|exec| exec.builtin_break(&args));
240 Value::Status(status)
241 });
242
243 vm.register_builtin(BUILTIN_CONTINUE, |vm, argc| {
244 let args = pop_args(vm, argc);
245 let status = with_executor(|exec| exec.builtin_continue(&args));
246 Value::Status(status)
247 });
248
249 vm.register_builtin(BUILTIN_SHIFT, |vm, argc| {
250 let args = pop_args(vm, argc);
251 let status = with_executor(|exec| exec.builtin_shift(&args));
252 Value::Status(status)
253 });
254
255 vm.register_builtin(BUILTIN_EVAL, |vm, argc| {
256 let args = pop_args(vm, argc);
257 let status = with_executor(|exec| exec.builtin_eval(&args));
258 Value::Status(status)
259 });
260
261 vm.register_builtin(BUILTIN_EXEC, |vm, argc| {
262 let args = pop_args(vm, argc);
263 let status = with_executor(|exec| exec.builtin_exec(&args));
264 Value::Status(status)
265 });
266
267 vm.register_builtin(BUILTIN_COMMAND, |vm, argc| {
268 let args = pop_args(vm, argc);
269 let status = with_executor(|exec| exec.builtin_command(&args, &[]));
270 Value::Status(status)
271 });
272
273 vm.register_builtin(BUILTIN_BUILTIN, |vm, argc| {
274 let args = pop_args(vm, argc);
275 let status = with_executor(|exec| exec.builtin_builtin(&args, &[]));
276 Value::Status(status)
277 });
278
279 vm.register_builtin(BUILTIN_LET, |vm, argc| {
280 let args = pop_args(vm, argc);
281 let status = with_executor(|exec| exec.builtin_let(&args));
282 Value::Status(status)
283 });
284
285 vm.register_builtin(BUILTIN_JOBS, |vm, argc| {
287 let args = pop_args(vm, argc);
288 let status = with_executor(|exec| exec.builtin_jobs(&args));
289 Value::Status(status)
290 });
291
292 vm.register_builtin(BUILTIN_FG, |vm, argc| {
293 let args = pop_args(vm, argc);
294 let status = with_executor(|exec| exec.builtin_fg(&args));
295 Value::Status(status)
296 });
297
298 vm.register_builtin(BUILTIN_BG, |vm, argc| {
299 let args = pop_args(vm, argc);
300 let status = with_executor(|exec| exec.builtin_bg(&args));
301 Value::Status(status)
302 });
303
304 vm.register_builtin(BUILTIN_KILL, |vm, argc| {
305 let args = pop_args(vm, argc);
306 let status = with_executor(|exec| exec.builtin_kill(&args));
307 Value::Status(status)
308 });
309
310 vm.register_builtin(BUILTIN_DISOWN, |vm, argc| {
311 let args = pop_args(vm, argc);
312 let status = with_executor(|exec| exec.builtin_disown(&args));
313 Value::Status(status)
314 });
315
316 vm.register_builtin(BUILTIN_WAIT, |vm, argc| {
317 let args = pop_args(vm, argc);
318 let status = with_executor(|exec| exec.builtin_wait(&args));
319 Value::Status(status)
320 });
321
322 vm.register_builtin(BUILTIN_SUSPEND, |vm, argc| {
323 let args = pop_args(vm, argc);
324 let status = with_executor(|exec| exec.builtin_suspend(&args));
325 Value::Status(status)
326 });
327
328 vm.register_builtin(BUILTIN_HISTORY, |vm, argc| {
330 let args = pop_args(vm, argc);
331 let status = with_executor(|exec| exec.builtin_history(&args));
332 Value::Status(status)
333 });
334
335 vm.register_builtin(BUILTIN_FC, |vm, argc| {
336 let args = pop_args(vm, argc);
337 let status = with_executor(|exec| exec.builtin_fc(&args));
338 Value::Status(status)
339 });
340
341 vm.register_builtin(BUILTIN_R, |vm, argc| {
342 let args = pop_args(vm, argc);
343 let status = with_executor(|exec| exec.builtin_r(&args));
344 Value::Status(status)
345 });
346
347 vm.register_builtin(BUILTIN_ALIAS, |vm, argc| {
349 let args = pop_args(vm, argc);
350 let status = with_executor(|exec| exec.builtin_alias(&args));
351 Value::Status(status)
352 });
353
354 vm.register_builtin(BUILTIN_UNALIAS, |vm, argc| {
355 let args = pop_args(vm, argc);
356 let status = with_executor(|exec| exec.builtin_unalias(&args));
357 Value::Status(status)
358 });
359
360 vm.register_builtin(BUILTIN_SET, |vm, argc| {
362 let args = pop_args(vm, argc);
363 let status = with_executor(|exec| exec.builtin_set(&args));
364 Value::Status(status)
365 });
366
367 vm.register_builtin(BUILTIN_SETOPT, |vm, argc| {
368 let args = pop_args(vm, argc);
369 let status = with_executor(|exec| exec.builtin_setopt(&args));
370 Value::Status(status)
371 });
372
373 vm.register_builtin(BUILTIN_UNSETOPT, |vm, argc| {
374 let args = pop_args(vm, argc);
375 let status = with_executor(|exec| exec.builtin_unsetopt(&args));
376 Value::Status(status)
377 });
378
379 vm.register_builtin(BUILTIN_SHOPT, |vm, argc| {
380 let args = pop_args(vm, argc);
381 let status = with_executor(|exec| exec.builtin_shopt(&args));
382 Value::Status(status)
383 });
384
385 vm.register_builtin(BUILTIN_EMULATE, |vm, argc| {
386 let args = pop_args(vm, argc);
387 let status = with_executor(|exec| exec.builtin_emulate(&args));
388 Value::Status(status)
389 });
390
391 vm.register_builtin(BUILTIN_GETOPTS, |vm, argc| {
392 let args = pop_args(vm, argc);
393 let status = with_executor(|exec| exec.builtin_getopts(&args));
394 Value::Status(status)
395 });
396
397 vm.register_builtin(BUILTIN_AUTOLOAD, |vm, argc| {
399 let args = pop_args(vm, argc);
400 let status = with_executor(|exec| exec.builtin_autoload(&args));
401 Value::Status(status)
402 });
403
404 vm.register_builtin(BUILTIN_FUNCTIONS, |vm, argc| {
405 let args = pop_args(vm, argc);
406 let status = with_executor(|exec| exec.builtin_functions(&args));
407 Value::Status(status)
408 });
409
410 vm.register_builtin(BUILTIN_UNFUNCTION, |vm, argc| {
411 let args = pop_args(vm, argc);
412 let status = with_executor(|exec| exec.builtin_unfunction(&args));
413 Value::Status(status)
414 });
415
416 vm.register_builtin(BUILTIN_TRAP, |vm, argc| {
418 let args = pop_args(vm, argc);
419 let status = with_executor(|exec| exec.builtin_trap(&args));
420 Value::Status(status)
421 });
422
423 vm.register_builtin(BUILTIN_PUSHD, |vm, argc| {
425 let args = pop_args(vm, argc);
426 let status = with_executor(|exec| exec.builtin_pushd(&args));
427 Value::Status(status)
428 });
429
430 vm.register_builtin(BUILTIN_POPD, |vm, argc| {
431 let args = pop_args(vm, argc);
432 let status = with_executor(|exec| exec.builtin_popd(&args));
433 Value::Status(status)
434 });
435
436 vm.register_builtin(BUILTIN_DIRS, |vm, argc| {
437 let args = pop_args(vm, argc);
438 let status = with_executor(|exec| exec.builtin_dirs(&args));
439 Value::Status(status)
440 });
441
442 vm.register_builtin(BUILTIN_TYPE, |vm, argc| {
444 let args = pop_args(vm, argc);
445 let status = with_executor(|exec| exec.builtin_type(&args));
446 Value::Status(status)
447 });
448
449 vm.register_builtin(BUILTIN_WHENCE, |vm, argc| {
450 let args = pop_args(vm, argc);
451 let status = with_executor(|exec| exec.builtin_whence(&args));
452 Value::Status(status)
453 });
454
455 vm.register_builtin(BUILTIN_WHERE, |vm, argc| {
456 let args = pop_args(vm, argc);
457 let status = with_executor(|exec| exec.builtin_where(&args));
458 Value::Status(status)
459 });
460
461 vm.register_builtin(BUILTIN_WHICH, |vm, argc| {
462 let args = pop_args(vm, argc);
463 let status = with_executor(|exec| exec.builtin_which(&args));
464 Value::Status(status)
465 });
466
467 vm.register_builtin(BUILTIN_HASH, |vm, argc| {
468 let args = pop_args(vm, argc);
469 let status = with_executor(|exec| exec.builtin_hash(&args));
470 Value::Status(status)
471 });
472
473 vm.register_builtin(BUILTIN_REHASH, |vm, argc| {
474 let args = pop_args(vm, argc);
475 let status = with_executor(|exec| exec.builtin_rehash(&args));
476 Value::Status(status)
477 });
478
479 vm.register_builtin(BUILTIN_UNHASH, |vm, argc| {
480 let args = pop_args(vm, argc);
481 let status = with_executor(|exec| exec.builtin_unhash(&args));
482 Value::Status(status)
483 });
484
485 vm.register_builtin(BUILTIN_COMPGEN, |vm, argc| {
487 let args = pop_args(vm, argc);
488 let status = with_executor(|exec| exec.builtin_compgen(&args));
489 Value::Status(status)
490 });
491
492 vm.register_builtin(BUILTIN_COMPLETE, |vm, argc| {
493 let args = pop_args(vm, argc);
494 let status = with_executor(|exec| exec.builtin_complete(&args));
495 Value::Status(status)
496 });
497
498 vm.register_builtin(BUILTIN_COMPOPT, |vm, argc| {
499 let args = pop_args(vm, argc);
500 let status = with_executor(|exec| exec.builtin_compopt(&args));
501 Value::Status(status)
502 });
503
504 vm.register_builtin(BUILTIN_COMPADD, |vm, argc| {
505 let args = pop_args(vm, argc);
506 let status = with_executor(|exec| exec.builtin_compadd(&args));
507 Value::Status(status)
508 });
509
510 vm.register_builtin(BUILTIN_COMPSET, |vm, argc| {
511 let args = pop_args(vm, argc);
512 let status = with_executor(|exec| exec.builtin_compset(&args));
513 Value::Status(status)
514 });
515
516 vm.register_builtin(BUILTIN_COMPDEF, |vm, argc| {
517 let args = pop_args(vm, argc);
518 let status = with_executor(|exec| exec.builtin_compdef(&args));
519 Value::Status(status)
520 });
521
522 vm.register_builtin(BUILTIN_COMPINIT, |vm, argc| {
523 let args = pop_args(vm, argc);
524 let status = with_executor(|exec| exec.builtin_compinit(&args));
525 Value::Status(status)
526 });
527
528 vm.register_builtin(BUILTIN_CDREPLAY, |vm, argc| {
529 let args = pop_args(vm, argc);
530 let status = with_executor(|exec| exec.builtin_cdreplay(&args));
531 Value::Status(status)
532 });
533
534 vm.register_builtin(BUILTIN_ZSTYLE, |vm, argc| {
536 let args = pop_args(vm, argc);
537 let status = with_executor(|exec| exec.builtin_zstyle(&args));
538 Value::Status(status)
539 });
540
541 vm.register_builtin(BUILTIN_ZMODLOAD, |vm, argc| {
542 let args = pop_args(vm, argc);
543 let status = with_executor(|exec| exec.builtin_zmodload(&args));
544 Value::Status(status)
545 });
546
547 vm.register_builtin(BUILTIN_BINDKEY, |vm, argc| {
548 let args = pop_args(vm, argc);
549 let status = with_executor(|exec| exec.builtin_bindkey(&args));
550 Value::Status(status)
551 });
552
553 vm.register_builtin(BUILTIN_ZLE, |vm, argc| {
554 let args = pop_args(vm, argc);
555 let status = with_executor(|exec| exec.builtin_zle(&args));
556 Value::Status(status)
557 });
558
559 vm.register_builtin(BUILTIN_VARED, |vm, argc| {
560 let args = pop_args(vm, argc);
561 let status = with_executor(|exec| exec.builtin_vared(&args));
562 Value::Status(status)
563 });
564
565 vm.register_builtin(BUILTIN_ZCOMPILE, |vm, argc| {
566 let args = pop_args(vm, argc);
567 let status = with_executor(|exec| exec.builtin_zcompile(&args));
568 Value::Status(status)
569 });
570
571 vm.register_builtin(BUILTIN_ZFORMAT, |vm, argc| {
572 let args = pop_args(vm, argc);
573 let status = with_executor(|exec| exec.builtin_zformat(&args));
574 Value::Status(status)
575 });
576
577 vm.register_builtin(BUILTIN_ZPARSEOPTS, |vm, argc| {
578 let args = pop_args(vm, argc);
579 let status = with_executor(|exec| exec.builtin_zparseopts(&args));
580 Value::Status(status)
581 });
582
583 vm.register_builtin(BUILTIN_ZREGEXPARSE, |vm, argc| {
584 let args = pop_args(vm, argc);
585 let status = with_executor(|exec| exec.builtin_zregexparse(&args));
586 Value::Status(status)
587 });
588
589 vm.register_builtin(BUILTIN_ULIMIT, |vm, argc| {
591 let args = pop_args(vm, argc);
592 let status = with_executor(|exec| exec.builtin_ulimit(&args));
593 Value::Status(status)
594 });
595
596 vm.register_builtin(BUILTIN_LIMIT, |vm, argc| {
597 let args = pop_args(vm, argc);
598 let status = with_executor(|exec| exec.builtin_limit(&args));
599 Value::Status(status)
600 });
601
602 vm.register_builtin(BUILTIN_UNLIMIT, |vm, argc| {
603 let args = pop_args(vm, argc);
604 let status = with_executor(|exec| exec.builtin_unlimit(&args));
605 Value::Status(status)
606 });
607
608 vm.register_builtin(BUILTIN_UMASK, |vm, argc| {
609 let args = pop_args(vm, argc);
610 let status = with_executor(|exec| exec.builtin_umask(&args));
611 Value::Status(status)
612 });
613
614 vm.register_builtin(BUILTIN_TIMES, |vm, argc| {
616 let args = pop_args(vm, argc);
617 let status = with_executor(|exec| exec.builtin_times(&args));
618 Value::Status(status)
619 });
620
621 vm.register_builtin(BUILTIN_CALLER, |vm, argc| {
622 let args = pop_args(vm, argc);
623 let status = with_executor(|exec| exec.builtin_caller(&args));
624 Value::Status(status)
625 });
626
627 vm.register_builtin(BUILTIN_HELP, |vm, argc| {
628 let args = pop_args(vm, argc);
629 let status = with_executor(|exec| exec.builtin_help(&args));
630 Value::Status(status)
631 });
632
633 vm.register_builtin(BUILTIN_ENABLE, |vm, argc| {
634 let args = pop_args(vm, argc);
635 let status = with_executor(|exec| exec.builtin_enable(&args));
636 Value::Status(status)
637 });
638
639 vm.register_builtin(BUILTIN_DISABLE, |vm, argc| {
640 let args = pop_args(vm, argc);
641 let status = with_executor(|exec| exec.builtin_disable(&args));
642 Value::Status(status)
643 });
644
645 vm.register_builtin(BUILTIN_NOGLOB, |vm, argc| {
646 let args = pop_args(vm, argc);
647 let status = with_executor(|exec| exec.builtin_noglob(&args, &[]));
648 Value::Status(status)
649 });
650
651 vm.register_builtin(BUILTIN_TTYCTL, |vm, argc| {
652 let args = pop_args(vm, argc);
653 let status = with_executor(|exec| exec.builtin_ttyctl(&args));
654 Value::Status(status)
655 });
656
657 vm.register_builtin(BUILTIN_SYNC, |vm, argc| {
658 let args = pop_args(vm, argc);
659 let status = with_executor(|exec| exec.builtin_sync(&args));
660 Value::Status(status)
661 });
662
663 vm.register_builtin(BUILTIN_MKDIR, |vm, argc| {
664 let args = pop_args(vm, argc);
665 let status = with_executor(|exec| exec.builtin_mkdir(&args));
666 Value::Status(status)
667 });
668
669 vm.register_builtin(BUILTIN_STRFTIME, |vm, argc| {
670 let args = pop_args(vm, argc);
671 let status = with_executor(|exec| exec.builtin_strftime(&args));
672 Value::Status(status)
673 });
674
675 vm.register_builtin(BUILTIN_ZSLEEP, |vm, argc| {
676 let args = pop_args(vm, argc);
677 let status = with_executor(|exec| exec.builtin_zsleep(&args));
678 Value::Status(status)
679 });
680
681 vm.register_builtin(BUILTIN_ZSYSTEM, |vm, argc| {
682 let args = pop_args(vm, argc);
683 let status = with_executor(|exec| exec.builtin_zsystem(&args));
684 Value::Status(status)
685 });
686
687 vm.register_builtin(BUILTIN_PCRE_COMPILE, |vm, argc| {
689 let args = pop_args(vm, argc);
690 let status = with_executor(|exec| exec.builtin_pcre_compile(&args));
691 Value::Status(status)
692 });
693
694 vm.register_builtin(BUILTIN_PCRE_MATCH, |vm, argc| {
695 let args = pop_args(vm, argc);
696 let status = with_executor(|exec| exec.builtin_pcre_match(&args));
697 Value::Status(status)
698 });
699
700 vm.register_builtin(BUILTIN_PCRE_STUDY, |vm, argc| {
701 let args = pop_args(vm, argc);
702 let status = with_executor(|exec| exec.builtin_pcre_study(&args));
703 Value::Status(status)
704 });
705
706 vm.register_builtin(BUILTIN_ZTIE, |vm, argc| {
708 let args = pop_args(vm, argc);
709 let status = with_executor(|exec| exec.builtin_ztie(&args));
710 Value::Status(status)
711 });
712
713 vm.register_builtin(BUILTIN_ZUNTIE, |vm, argc| {
714 let args = pop_args(vm, argc);
715 let status = with_executor(|exec| exec.builtin_zuntie(&args));
716 Value::Status(status)
717 });
718
719 vm.register_builtin(BUILTIN_ZGDBMPATH, |vm, argc| {
720 let args = pop_args(vm, argc);
721 let status = with_executor(|exec| exec.builtin_zgdbmpath(&args));
722 Value::Status(status)
723 });
724
725 vm.register_builtin(BUILTIN_PROMPTINIT, |vm, argc| {
727 let args = pop_args(vm, argc);
728 let status = with_executor(|exec| exec.builtin_promptinit(&args));
729 Value::Status(status)
730 });
731
732 vm.register_builtin(BUILTIN_PROMPT, |vm, argc| {
733 let args = pop_args(vm, argc);
734 let status = with_executor(|exec| exec.builtin_prompt(&args));
735 Value::Status(status)
736 });
737
738 vm.register_builtin(BUILTIN_ASYNC, |vm, argc| {
740 let args = pop_args(vm, argc);
741 let status = with_executor(|exec| exec.builtin_async(&args));
742 Value::Status(status)
743 });
744
745 vm.register_builtin(BUILTIN_AWAIT, |vm, argc| {
746 let args = pop_args(vm, argc);
747 let status = with_executor(|exec| exec.builtin_await(&args));
748 Value::Status(status)
749 });
750
751 vm.register_builtin(BUILTIN_PMAP, |vm, argc| {
752 let args = pop_args(vm, argc);
753 let status = with_executor(|exec| exec.builtin_pmap(&args));
754 Value::Status(status)
755 });
756
757 vm.register_builtin(BUILTIN_PGREP, |vm, argc| {
758 let args = pop_args(vm, argc);
759 let status = with_executor(|exec| exec.builtin_pgrep(&args));
760 Value::Status(status)
761 });
762
763 vm.register_builtin(BUILTIN_PEACH, |vm, argc| {
764 let args = pop_args(vm, argc);
765 let status = with_executor(|exec| exec.builtin_peach(&args));
766 Value::Status(status)
767 });
768
769 vm.register_builtin(BUILTIN_BARRIER, |vm, argc| {
770 let args = pop_args(vm, argc);
771 let status = with_executor(|exec| exec.builtin_barrier(&args));
772 Value::Status(status)
773 });
774
775 vm.register_builtin(BUILTIN_INTERCEPT, |vm, argc| {
777 let args = pop_args(vm, argc);
778 let status = with_executor(|exec| exec.builtin_intercept(&args));
779 Value::Status(status)
780 });
781
782 vm.register_builtin(BUILTIN_INTERCEPT_PROCEED, |vm, argc| {
783 let args = pop_args(vm, argc);
784 let status = with_executor(|exec| exec.builtin_intercept_proceed(&args));
785 Value::Status(status)
786 });
787
788 vm.register_builtin(BUILTIN_DOCTOR, |vm, argc| {
790 let args = pop_args(vm, argc);
791 let status = with_executor(|exec| exec.builtin_doctor(&args));
792 Value::Status(status)
793 });
794
795 vm.register_builtin(BUILTIN_DBVIEW, |vm, argc| {
796 let args = pop_args(vm, argc);
797 let status = with_executor(|exec| exec.builtin_dbview(&args));
798 Value::Status(status)
799 });
800
801 vm.register_builtin(BUILTIN_PROFILE, |vm, argc| {
802 let args = pop_args(vm, argc);
803 let status = with_executor(|exec| exec.builtin_profile(&args));
804 Value::Status(status)
805 });
806
807 vm.register_builtin(BUILTIN_ZPROF, |vm, argc| {
808 let args = pop_args(vm, argc);
809 let status = with_executor(|exec| exec.builtin_zprof(&args));
810 Value::Status(status)
811 });
812}
813
814#[inline]
816fn pop_args(vm: &mut fusevm::VM, argc: u8) -> Vec<String> {
817 let mut args = Vec::with_capacity(argc as usize);
818 for _ in 0..argc {
819 args.push(vm.pop().to_str());
820 }
821 args.reverse(); args
823}
824
825fn intercept_matches(pattern: &str, cmd_name: &str, full_cmd: &str) -> bool {
828 if pattern == "*" || pattern == "all" {
829 return true;
830 }
831 if pattern == cmd_name {
832 return true;
833 }
834 if pattern.contains('*') || pattern.contains('?') {
836 if let Ok(pat) = glob::Pattern::new(pattern) {
837 return pat.matches(cmd_name) || pat.matches(full_cmd);
838 }
839 }
840 false
841}
842
843fn cached_regex(pattern: &str) -> Option<regex::Regex> {
845 let mut cache = REGEX_CACHE.lock();
846 if let Some(re) = cache.get(pattern) {
847 return Some(re.clone());
848 }
849 match regex::Regex::new(pattern) {
850 Ok(re) => {
851 cache.insert(pattern.to_string(), re.clone());
852 Some(re)
853 }
854 Err(_) => None,
855 }
856}
857
858static ZSH_OPTIONS_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
860 [
861 "aliases",
862 "allexport",
863 "alwayslastprompt",
864 "alwaystoend",
865 "appendcreate",
866 "appendhistory",
867 "autocd",
868 "autocontinue",
869 "autolist",
870 "automenu",
871 "autonamedirs",
872 "autoparamkeys",
873 "autoparamslash",
874 "autopushd",
875 "autoremoveslash",
876 "autoresume",
877 "badpattern",
878 "banghist",
879 "bareglobqual",
880 "bashautolist",
881 "bashrematch",
882 "beep",
883 "bgnice",
884 "braceccl",
885 "bsdecho",
886 "caseglob",
887 "casematch",
888 "cbases",
889 "cdablevars",
890 "cdsilent",
891 "chasedots",
892 "chaselinks",
893 "checkjobs",
894 "checkrunningjobs",
895 "clobber",
896 "combiningchars",
897 "completealiases",
898 "completeinword",
899 "continueonerror",
900 "correct",
901 "correctall",
902 "cprecedences",
903 "cshjunkiehistory",
904 "cshjunkieloops",
905 "cshjunkiequotes",
906 "cshnullcmd",
907 "cshnullglob",
908 "debugbeforecmd",
909 "dotglob",
910 "dvorak",
911 "emacs",
912 "equals",
913 "errexit",
914 "errreturn",
915 "evallineno",
916 "exec",
917 "extendedglob",
918 "extendedhistory",
919 "flowcontrol",
920 "forcefloat",
921 "functionargzero",
922 "glob",
923 "globassign",
924 "globcomplete",
925 "globdots",
926 "globstarshort",
927 "globsubst",
928 "globalexport",
929 "globalrcs",
930 "hashall",
931 "hashcmds",
932 "hashdirs",
933 "hashexecutablesonly",
934 "hashlistall",
935 "histallowclobber",
936 "histappend",
937 "histbeep",
938 "histexpand",
939 "histexpiredupsfirst",
940 "histfcntllock",
941 "histfindnodups",
942 "histignorealldups",
943 "histignoredups",
944 "histignorespace",
945 "histlexwords",
946 "histnofunctions",
947 "histnostore",
948 "histreduceblanks",
949 "histsavebycopy",
950 "histsavenodups",
951 "histsubstpattern",
952 "histverify",
953 "hup",
954 "ignorebraces",
955 "ignoreclosebraces",
956 "ignoreeof",
957 "incappendhistory",
958 "incappendhistorytime",
959 "interactive",
960 "interactivecomments",
961 "ksharrays",
962 "kshautoload",
963 "kshglob",
964 "kshoptionprint",
965 "kshtypeset",
966 "kshzerosubscript",
967 "listambiguous",
968 "listbeep",
969 "listpacked",
970 "listrowsfirst",
971 "listtypes",
972 "localloops",
973 "localoptions",
974 "localpatterns",
975 "localtraps",
976 "log",
977 "login",
978 "longlistjobs",
979 "magicequalsubst",
980 "mailwarn",
981 "mailwarning",
982 "markdirs",
983 "menucomplete",
984 "monitor",
985 "multibyte",
986 "multifuncdef",
987 "multios",
988 "nomatch",
989 "notify",
990 "nullglob",
991 "numericglobsort",
992 "octalzeroes",
993 "onecmd",
994 "overstrike",
995 "pathdirs",
996 "pathscript",
997 "physical",
998 "pipefail",
999 "posixaliases",
1000 "posixargzero",
1001 "posixbuiltins",
1002 "posixcd",
1003 "posixidentifiers",
1004 "posixjobs",
1005 "posixstrings",
1006 "posixtraps",
1007 "printeightbit",
1008 "printexitvalue",
1009 "privileged",
1010 "promptbang",
1011 "promptcr",
1012 "promptpercent",
1013 "promptsp",
1014 "promptsubst",
1015 "promptvars",
1016 "pushdignoredups",
1017 "pushdminus",
1018 "pushdsilent",
1019 "pushdtohome",
1020 "rcexpandparam",
1021 "rcquotes",
1022 "rcs",
1023 "recexact",
1024 "rematchpcre",
1025 "restricted",
1026 "rmstarsilent",
1027 "rmstarwait",
1028 "sharehistory",
1029 "shfileexpansion",
1030 "shglob",
1031 "shinstdin",
1032 "shnullcmd",
1033 "shoptionletters",
1034 "shortloops",
1035 "shortrepeat",
1036 "shwordsplit",
1037 "singlecommand",
1038 "singlelinezle",
1039 "sourcetrace",
1040 "stdin",
1041 "sunkeyboardhack",
1042 "trackall",
1043 "transientrprompt",
1044 "trapsasync",
1045 "typesetsilent",
1046 "typesettounset",
1047 "unset",
1048 "verbose",
1049 "vi",
1050 "warncreateglobal",
1051 "warnnestedvar",
1052 "xtrace",
1053 "zle",
1054 ]
1055 .into_iter()
1056 .collect()
1057});
1058
1059static BUILTIN_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1061 [
1062 "cd",
1063 "chdir",
1064 "pwd",
1065 "echo",
1066 "export",
1067 "unset",
1068 "source",
1069 "exit",
1070 "return",
1071 "bye",
1072 "logout",
1073 "log",
1074 "true",
1075 "false",
1076 "test",
1077 "local",
1078 "declare",
1079 "typeset",
1080 "read",
1081 "shift",
1082 "eval",
1083 "jobs",
1084 "fg",
1085 "bg",
1086 "kill",
1087 "disown",
1088 "wait",
1089 "autoload",
1090 "history",
1091 "fc",
1092 "trap",
1093 "suspend",
1094 "alias",
1095 "unalias",
1096 "set",
1097 "shopt",
1098 "setopt",
1099 "unsetopt",
1100 "getopts",
1101 "type",
1102 "hash",
1103 "command",
1104 "builtin",
1105 "let",
1106 "pushd",
1107 "popd",
1108 "dirs",
1109 "printf",
1110 "break",
1111 "continue",
1112 "disable",
1113 "enable",
1114 "emulate",
1115 "exec",
1116 "float",
1117 "integer",
1118 "functions",
1119 "print",
1120 "whence",
1121 "where",
1122 "which",
1123 "ulimit",
1124 "limit",
1125 "unlimit",
1126 "umask",
1127 "rehash",
1128 "unhash",
1129 "times",
1130 "zmodload",
1131 "r",
1132 "ttyctl",
1133 "noglob",
1134 "zstat",
1135 "stat",
1136 "strftime",
1137 "zsleep",
1138 "zln",
1139 "zmv",
1140 "zcp",
1141 "coproc",
1142 "zparseopts",
1143 "readonly",
1144 "unfunction",
1145 "getln",
1146 "pushln",
1147 "bindkey",
1148 "zle",
1149 "sched",
1150 "zformat",
1151 "zcompile",
1152 "vared",
1153 "echotc",
1154 "echoti",
1155 "zpty",
1156 "zprof",
1157 "zsocket",
1158 "ztcp",
1159 "zregexparse",
1160 "clone",
1161 "comparguments",
1162 "compcall",
1163 "compctl",
1164 "compdef",
1165 "compdescribe",
1166 "compfiles",
1167 "compgroups",
1168 "compinit",
1169 "compquote",
1170 "comptags",
1171 "comptry",
1172 "compvalues",
1173 "cdreplay",
1174 "cap",
1175 "getcap",
1176 "setcap",
1177 "zftp",
1178 "zcurses",
1179 "sysread",
1180 "syswrite",
1181 "syserror",
1182 "sysopen",
1183 "sysseek",
1184 "private",
1185 "zgetattr",
1186 "zsetattr",
1187 "zdelattr",
1188 "zlistattr",
1189 "[",
1190 ".",
1191 ":",
1192 "compgen",
1193 "complete",
1194 ]
1195 .into_iter()
1196 .collect()
1197});
1198
1199fn float_to_hex(val: f64, uppercase: bool) -> String {
1201 if val.is_nan() {
1202 return if uppercase { "NAN" } else { "nan" }.to_string();
1203 }
1204 if val.is_infinite() {
1205 return if val > 0.0 {
1206 if uppercase {
1207 "INF"
1208 } else {
1209 "inf"
1210 }
1211 } else {
1212 if uppercase {
1213 "-INF"
1214 } else {
1215 "-inf"
1216 }
1217 }
1218 .to_string();
1219 }
1220 if val == 0.0 {
1221 let sign = if val.is_sign_negative() { "-" } else { "" };
1222 return if uppercase {
1223 format!("{}0X0P+0", sign)
1224 } else {
1225 format!("{}0x0p+0", sign)
1226 };
1227 }
1228
1229 let sign = if val < 0.0 { "-" } else { "" };
1230 let abs_val = val.abs();
1231 let bits = abs_val.to_bits();
1232 let exponent = ((bits >> 52) & 0x7ff) as i32 - 1023;
1233 let mantissa = bits & 0xfffffffffffff;
1234
1235 let hex_mantissa = format!("{:013x}", mantissa);
1236 let hex_mantissa = hex_mantissa.trim_end_matches('0');
1237 let hex_mantissa = if hex_mantissa.is_empty() {
1238 "0"
1239 } else {
1240 hex_mantissa
1241 };
1242
1243 if uppercase {
1244 format!("{}0X1.{}P{:+}", sign, hex_mantissa.to_uppercase(), exponent)
1245 } else {
1246 format!("{}0x1.{}p{:+}", sign, hex_mantissa, exponent)
1247 }
1248}
1249
1250fn shell_quote(s: &str) -> String {
1252 if s.is_empty() {
1253 return "''".to_string();
1254 }
1255 let needs_quotes = s.chars().any(|c| {
1257 matches!(
1258 c,
1259 ' ' | '\t'
1260 | '\n'
1261 | '\''
1262 | '"'
1263 | '\\'
1264 | '$'
1265 | '`'
1266 | '!'
1267 | '*'
1268 | '?'
1269 | '['
1270 | ']'
1271 | '{'
1272 | '}'
1273 | '('
1274 | ')'
1275 | '<'
1276 | '>'
1277 | '|'
1278 | '&'
1279 | ';'
1280 | '#'
1281 | '~'
1282 )
1283 });
1284 if !needs_quotes {
1285 return s.to_string();
1286 }
1287 format!("'{}'", s.replace('\'', "'\\''"))
1289}
1290
1291fn shell_quote_value(s: &str) -> String {
1294 if s.is_empty() {
1295 return "''".to_string();
1296 }
1297 let needs_quotes = s.chars().any(|c| {
1298 matches!(
1299 c,
1300 ' ' | '\t'
1301 | '\n'
1302 | '\''
1303 | '"'
1304 | '\\'
1305 | '$'
1306 | '`'
1307 | '!'
1308 | '*'
1309 | '?'
1310 | '['
1311 | ']'
1312 | '{'
1313 | '}'
1314 | '('
1315 | ')'
1316 | '<'
1317 | '>'
1318 | '|'
1319 | '&'
1320 | ';'
1321 | '#'
1322 | '~'
1323 | '^'
1324 )
1325 });
1326 if !needs_quotes {
1327 return s.to_string();
1328 }
1329 format!("'{}'", s.replace('\'', "'\\''"))
1330}
1331
1332use crate::jobs::{continue_job, wait_for_child, wait_for_job, JobState, JobTable};
1333use crate::parser::{
1334 CaseTerminator, CompoundCommand, CondExpr, ListOp, Redirect, RedirectOp, ShellCommand,
1335 ShellParser, ShellWord, SimpleCommand, VarModifier, ZshParamFlag,
1336};
1337use crate::zwc::ZwcFile;
1338use std::collections::HashMap;
1339use std::env;
1340use std::fs::{self, File, OpenOptions};
1341use std::io;
1342use std::path::{Path, PathBuf};
1343use std::process::{Child, Command, Stdio};
1344
1345#[derive(Debug, Clone, Default)]
1347pub struct CompSpec {
1348 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>, }
1356
1357#[derive(Debug, Clone)]
1359pub struct CompMatch {
1360 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, }
1374
1375impl Default for CompMatch {
1376 fn default() -> Self {
1377 Self {
1378 word: String::new(),
1379 display: None,
1380 prefix: None,
1381 suffix: None,
1382 hidden_prefix: None,
1383 hidden_suffix: None,
1384 ignored_prefix: None,
1385 ignored_suffix: None,
1386 group: None,
1387 description: None,
1388 remove_suffix: None,
1389 file_match: false,
1390 quote_match: false,
1391 }
1392 }
1393}
1394
1395#[derive(Debug, Clone, Default)]
1397pub struct CompGroup {
1398 pub name: String,
1399 pub matches: Vec<CompMatch>,
1400 pub explanation: Option<String>,
1401 pub sorted: bool,
1402}
1403
1404#[derive(Debug, Clone, Default)]
1406pub struct CompState {
1407 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, }
1433
1434#[derive(Debug, Clone)]
1436pub struct ZStyle {
1437 pub pattern: String,
1438 pub style: String,
1439 pub values: Vec<String>,
1440}
1441
1442bitflags::bitflags! {
1443 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1445 pub struct AutoloadFlags: u32 {
1446 const NO_ALIAS = 0b00000001; const ZSH_STYLE = 0b00000010; const KSH_STYLE = 0b00000100; const TRACE = 0b00001000; const USE_CALLER_DIR = 0b00010000; const LOADED = 0b00100000; }
1453}
1454
1455pub struct ZptyState {
1457 pub pid: u32,
1458 pub cmd: String,
1459 pub stdin: Option<std::process::ChildStdin>,
1460 pub stdout: Option<std::process::ChildStdout>,
1461 pub child: Option<std::process::Child>,
1462}
1463
1464pub struct ScheduledCommand {
1466 pub id: u32,
1467 pub run_at: std::time::SystemTime,
1468 pub command: String,
1469}
1470
1471#[derive(Clone, Default)]
1473pub struct ProfileEntry {
1474 pub calls: u64,
1475 pub total_time_us: u64,
1476 pub self_time_us: u64,
1477}
1478
1479pub struct UnixSocketState {
1481 pub path: Option<PathBuf>,
1482 pub listening: bool,
1483 pub stream: Option<std::os::unix::net::UnixStream>,
1484 pub listener: Option<std::os::unix::net::UnixListener>,
1485}
1486
1487pub struct ShellExecutor {
1488 pub functions: HashMap<String, ShellCommand>,
1489 pub aliases: HashMap<String, String>,
1490 pub global_aliases: HashMap<String, String>, pub suffix_aliases: HashMap<String, String>, pub last_status: i32,
1493 pub variables: HashMap<String, String>,
1494 pub arrays: HashMap<String, Vec<String>>,
1495 pub assoc_arrays: HashMap<String, HashMap<String, String>>, pub jobs: JobTable,
1497 pub fpath: Vec<PathBuf>,
1498 pub zwc_cache: HashMap<PathBuf, ZwcFile>,
1499 pub positional_params: Vec<String>,
1500 pub history: Option<HistoryEngine>,
1501 process_sub_counter: u32,
1502 pub traps: HashMap<String, String>,
1503 pub options: HashMap<String, bool>,
1504 pub completions: HashMap<String, CompSpec>, pub dir_stack: Vec<PathBuf>,
1506 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>)>,
1520 pub local_scope_depth: usize,
1522 pub autoload_pending: HashMap<String, AutoloadFlags>, pub hook_functions: HashMap<String, Vec<String>>, pub named_dirs: HashMap<String, PathBuf>, pub zptys: HashMap<String, ZptyState>,
1529 pub open_fds: HashMap<i32, std::fs::File>,
1531 pub next_fd: i32,
1532 pub scheduled_commands: Vec<ScheduledCommand>,
1534 pub profile_data: HashMap<String, ProfileEntry>,
1536 pub profiling_enabled: bool,
1537 pub unix_sockets: HashMap<i32, UnixSocketState>,
1539 pub compsys_cache: Option<CompsysCache>,
1541 pub compinit_pending: Option<(
1543 std::sync::mpsc::Receiver<CompInitBgResult>,
1544 std::time::Instant,
1545 )>,
1546 pub plugin_cache: Option<crate::plugin_cache::PluginCache>,
1548 pub deferred_compdefs: Vec<Vec<String>>,
1550 pub command_hash: HashMap<String, String>,
1552 returning: Option<i32>, breaking: i32, continuing: i32, pub pcre_state: PcreState,
1558 pub tcp_sessions: TcpSessions,
1559 pub zftp: Zftp,
1560 pub profiler: Profiler,
1561 pub style_table: StyleTable,
1562 pub zsh_compat: bool,
1564 pub posix_mode: bool,
1566 pub worker_pool: std::sync::Arc<crate::worker::WorkerPool>,
1568 pub intercepts: Vec<Intercept>,
1571 pub async_jobs: HashMap<u32, crossbeam_channel::Receiver<(i32, String)>>,
1573 pub next_async_id: u32,
1575 pub defer_stack: Vec<Vec<String>>,
1577}
1578
1579impl ShellExecutor {
1580 pub fn new() -> Self {
1581 tracing::debug!("ShellExecutor::new() initializing");
1582 let fpath = env::var("FPATH")
1584 .unwrap_or_default()
1585 .split(':')
1586 .filter(|s| !s.is_empty())
1587 .map(PathBuf::from)
1588 .collect();
1589
1590 let history = HistoryEngine::new().ok();
1591
1592 let mut variables = HashMap::new();
1594 variables.insert("ZSH_VERSION".to_string(), "5.9".to_string());
1595 variables.insert(
1596 "ZSH_PATCHLEVEL".to_string(),
1597 "zsh-5.9-0-g73d3173".to_string(),
1598 );
1599 variables.insert("ZSH_NAME".to_string(), "zsh".to_string());
1600 variables.insert(
1601 "SHLVL".to_string(),
1602 env::var("SHLVL")
1603 .map(|v| {
1604 v.parse::<i32>()
1605 .map(|n| (n + 1).to_string())
1606 .unwrap_or_else(|_| "1".to_string())
1607 })
1608 .unwrap_or_else(|_| "1".to_string()),
1609 );
1610
1611 Self {
1612 functions: HashMap::new(),
1613 aliases: HashMap::new(),
1614 global_aliases: HashMap::new(),
1615 suffix_aliases: HashMap::new(),
1616 last_status: 0,
1617 variables,
1618 arrays: {
1619 let mut a = HashMap::new();
1620 let path_dirs: Vec<String> = env::var("PATH")
1622 .unwrap_or_default()
1623 .split(':')
1624 .map(|s| s.to_string())
1625 .collect();
1626 a.insert("path".to_string(), path_dirs);
1627 a
1628 },
1629 assoc_arrays: HashMap::new(),
1630 jobs: JobTable::new(),
1631 fpath,
1632 zwc_cache: HashMap::new(),
1633 positional_params: Vec::new(),
1634 history,
1635 completions: HashMap::new(),
1636 dir_stack: Vec::new(),
1637 process_sub_counter: 0,
1638 traps: HashMap::new(),
1639 options: Self::default_options(),
1640 comp_matches: Vec::new(),
1642 comp_groups: Vec::new(),
1643 comp_state: CompState::default(),
1644 zstyles: Vec::new(),
1645 comp_words: Vec::new(),
1646 comp_current: 0,
1647 comp_prefix: String::new(),
1648 comp_suffix: String::new(),
1649 comp_iprefix: String::new(),
1650 comp_isuffix: String::new(),
1651 readonly_vars: std::collections::HashSet::new(),
1652 local_save_stack: Vec::new(),
1653 local_scope_depth: 0,
1654 autoload_pending: HashMap::new(),
1655 hook_functions: HashMap::new(),
1656 named_dirs: HashMap::new(),
1657 zptys: HashMap::new(),
1658 open_fds: HashMap::new(),
1659 next_fd: 10,
1660 scheduled_commands: Vec::new(),
1661 profile_data: HashMap::new(),
1662 profiling_enabled: false,
1663 unix_sockets: HashMap::new(),
1664 compsys_cache: {
1665 let cache_path = compsys::cache::default_cache_path();
1666 if cache_path.exists() {
1667 let db_size = std::fs::metadata(&cache_path).map(|m| m.len()).unwrap_or(0);
1668 match CompsysCache::open(&cache_path) {
1669 Ok(c) => {
1670 tracing::info!(
1671 db_bytes = db_size,
1672 path = %cache_path.display(),
1673 "compsys: sqlite cache opened"
1674 );
1675 Some(c)
1676 }
1677 Err(e) => {
1678 tracing::warn!(error = %e, "compsys: failed to open cache");
1679 None
1680 }
1681 }
1682 } else {
1683 tracing::debug!("compsys: no cache at {}", cache_path.display());
1684 None
1685 }
1686 },
1687 compinit_pending: None, plugin_cache: {
1689 let pc_path = crate::plugin_cache::default_cache_path();
1690 if let Some(parent) = pc_path.parent() {
1691 let _ = std::fs::create_dir_all(parent);
1692 }
1693 match crate::plugin_cache::PluginCache::open(&pc_path) {
1694 Ok(pc) => {
1695 let (plugins, functions) = pc.stats();
1696 tracing::info!(
1697 plugins,
1698 cached_functions = functions,
1699 path = %pc_path.display(),
1700 "plugin_cache: sqlite opened"
1701 );
1702 Some(pc)
1703 }
1704 Err(e) => {
1705 tracing::warn!(error = %e, "plugin_cache: failed to open");
1706 None
1707 }
1708 }
1709 },
1710 deferred_compdefs: Vec::new(),
1711 command_hash: HashMap::new(),
1712 returning: None,
1713 breaking: 0,
1714 continuing: 0,
1715 pcre_state: PcreState::new(),
1716 tcp_sessions: TcpSessions::new(),
1717 zftp: Zftp::new(),
1718 profiler: Profiler::new(),
1719 style_table: StyleTable::new(),
1720 zsh_compat: false,
1721 posix_mode: false,
1722 worker_pool: {
1723 let config = crate::config::load();
1724 let pool_size = crate::config::resolve_pool_size(&config.worker_pool);
1725 std::sync::Arc::new(crate::worker::WorkerPool::new(pool_size))
1726 },
1727 intercepts: Vec::new(),
1728 async_jobs: HashMap::new(),
1729 next_async_id: 1,
1730 defer_stack: Vec::new(),
1731 }
1732 }
1733
1734 pub fn enter_posix_mode(&mut self) {
1737 self.posix_mode = true;
1738 self.plugin_cache = None;
1739 self.compsys_cache = None;
1740 self.compinit_pending = None;
1741 self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
1745 tracing::info!("POSIX strict mode: SQLite caches dropped, worker pool shrunk to 1");
1746 }
1747
1748 pub fn run_hooks(&mut self, hook_name: &str) {
1750 if let Some(funcs) = self.hook_functions.get(hook_name).cloned() {
1751 for func_name in funcs {
1752 if self.functions.contains_key(&func_name) {
1753 let _ = self.execute_script(&format!("{}", func_name));
1754 }
1755 }
1756 }
1757 let array_name = format!("{}_functions", hook_name);
1759 if let Some(funcs) = self.arrays.get(&array_name).cloned() {
1760 for func_name in funcs {
1761 if self.functions.contains_key(&func_name) {
1762 let _ = self.execute_script(&format!("{}", func_name));
1763 }
1764 }
1765 }
1766 }
1767
1768 pub fn add_hook(&mut self, hook_name: &str, func_name: &str) {
1770 self.hook_functions
1771 .entry(hook_name.to_string())
1772 .or_default()
1773 .push(func_name.to_string());
1774 }
1775
1776 pub fn add_named_dir(&mut self, name: &str, path: &str) {
1778 self.named_dirs
1779 .insert(name.to_string(), PathBuf::from(path));
1780 }
1781
1782 pub fn expand_tilde_named(&self, path: &str) -> String {
1784 if path.starts_with('~') {
1785 let rest = &path[1..];
1786 let (name, suffix) = if let Some(slash_pos) = rest.find('/') {
1788 (&rest[..slash_pos], &rest[slash_pos..])
1789 } else {
1790 (rest, "")
1791 };
1792
1793 if name.is_empty() {
1794 if let Ok(home) = std::env::var("HOME") {
1796 return format!("{}{}", home, suffix);
1797 }
1798 } else if let Some(dir) = self.named_dirs.get(name) {
1799 return format!("{}{}", dir.display(), suffix);
1800 }
1801 }
1802 path.to_string()
1803 }
1804
1805 fn all_zsh_options() -> &'static [&'static str] {
1806 &[
1807 "aliases",
1808 "aliasfuncdef",
1809 "allexport",
1810 "alwayslastprompt",
1811 "alwaystoend",
1812 "appendcreate",
1813 "appendhistory",
1814 "autocd",
1815 "autocontinue",
1816 "autolist",
1817 "automenu",
1818 "autonamedirs",
1819 "autoparamkeys",
1820 "autoparamslash",
1821 "autopushd",
1822 "autoremoveslash",
1823 "autoresume",
1824 "badpattern",
1825 "banghist",
1826 "bareglobqual",
1827 "bashautolist",
1828 "bashrematch",
1829 "beep",
1830 "bgnice",
1831 "braceccl",
1832 "braceexpand",
1833 "bsdecho",
1834 "caseglob",
1835 "casematch",
1836 "casepaths",
1837 "cbases",
1838 "cdablevars",
1839 "cdsilent",
1840 "chasedots",
1841 "chaselinks",
1842 "checkjobs",
1843 "checkrunningjobs",
1844 "clobber",
1845 "clobberempty",
1846 "combiningchars",
1847 "completealiases",
1848 "completeinword",
1849 "continueonerror",
1850 "correct",
1851 "correctall",
1852 "cprecedences",
1853 "cshjunkiehistory",
1854 "cshjunkieloops",
1855 "cshjunkiequotes",
1856 "cshnullcmd",
1857 "cshnullglob",
1858 "debugbeforecmd",
1859 "dotglob",
1860 "dvorak",
1861 "emacs",
1862 "equals",
1863 "errexit",
1864 "errreturn",
1865 "evallineno",
1866 "exec",
1867 "extendedglob",
1868 "extendedhistory",
1869 "flowcontrol",
1870 "forcefloat",
1871 "functionargzero",
1872 "glob",
1873 "globassign",
1874 "globcomplete",
1875 "globdots",
1876 "globstarshort",
1877 "globsubst",
1878 "globalexport",
1879 "globalrcs",
1880 "hashall",
1881 "hashcmds",
1882 "hashdirs",
1883 "hashexecutablesonly",
1884 "hashlistall",
1885 "histallowclobber",
1886 "histappend",
1887 "histbeep",
1888 "histexpand",
1889 "histexpiredupsfirst",
1890 "histfcntllock",
1891 "histfindnodups",
1892 "histignorealldups",
1893 "histignoredups",
1894 "histignorespace",
1895 "histlexwords",
1896 "histnofunctions",
1897 "histnostore",
1898 "histreduceblanks",
1899 "histsavebycopy",
1900 "histsavenodups",
1901 "histsubstpattern",
1902 "histverify",
1903 "hup",
1904 "ignorebraces",
1905 "ignoreclosebraces",
1906 "ignoreeof",
1907 "incappendhistory",
1908 "incappendhistorytime",
1909 "interactive",
1910 "interactivecomments",
1911 "ksharrays",
1912 "kshautoload",
1913 "kshglob",
1914 "kshoptionprint",
1915 "kshtypeset",
1916 "kshzerosubscript",
1917 "listambiguous",
1918 "listbeep",
1919 "listpacked",
1920 "listrowsfirst",
1921 "listtypes",
1922 "localloops",
1923 "localoptions",
1924 "localpatterns",
1925 "localtraps",
1926 "log",
1927 "login",
1928 "longlistjobs",
1929 "magicequalsubst",
1930 "mailwarn",
1931 "mailwarning",
1932 "markdirs",
1933 "menucomplete",
1934 "monitor",
1935 "multibyte",
1936 "multifuncdef",
1937 "multios",
1938 "nomatch",
1939 "notify",
1940 "nullglob",
1941 "numericglobsort",
1942 "octalzeroes",
1943 "onecmd",
1944 "overstrike",
1945 "pathdirs",
1946 "pathscript",
1947 "physical",
1948 "pipefail",
1949 "posixaliases",
1950 "posixargzero",
1951 "posixbuiltins",
1952 "posixcd",
1953 "posixidentifiers",
1954 "posixjobs",
1955 "posixstrings",
1956 "posixtraps",
1957 "printeightbit",
1958 "printexitvalue",
1959 "privileged",
1960 "promptbang",
1961 "promptcr",
1962 "promptpercent",
1963 "promptsp",
1964 "promptsubst",
1965 "promptvars",
1966 "pushdignoredups",
1967 "pushdminus",
1968 "pushdsilent",
1969 "pushdtohome",
1970 "rcexpandparam",
1971 "rcquotes",
1972 "rcs",
1973 "recexact",
1974 "rematchpcre",
1975 "restricted",
1976 "rmstarsilent",
1977 "rmstarwait",
1978 "sharehistory",
1979 "shfileexpansion",
1980 "shglob",
1981 "shinstdin",
1982 "shnullcmd",
1983 "shoptionletters",
1984 "shortloops",
1985 "shortrepeat",
1986 "shwordsplit",
1987 "singlecommand",
1988 "singlelinezle",
1989 "sourcetrace",
1990 "stdin",
1991 "sunkeyboardhack",
1992 "trackall",
1993 "transientrprompt",
1994 "trapsasync",
1995 "typesetsilent",
1996 "typesettounset",
1997 "unset",
1998 "verbose",
1999 "vi",
2000 "warncreateglobal",
2001 "warnnestedvar",
2002 "xtrace",
2003 "zle",
2004 ]
2005 }
2006
2007 fn default_options() -> HashMap<String, bool> {
2008 let mut opts = HashMap::new();
2009 for opt in Self::all_zsh_options() {
2011 opts.insert(opt.to_string(), false);
2012 }
2013 let defaults_on = [
2015 "aliases",
2016 "alwayslastprompt",
2017 "appendhistory",
2018 "autolist",
2019 "automenu",
2020 "autoparamkeys",
2021 "autoparamslash",
2022 "autoremoveslash",
2023 "badpattern",
2024 "banghist",
2025 "bareglobqual",
2026 "beep",
2027 "bgnice",
2028 "caseglob",
2029 "casematch",
2030 "checkjobs",
2031 "checkrunningjobs",
2032 "clobber",
2033 "debugbeforecmd",
2034 "equals",
2035 "evallineno",
2036 "exec",
2037 "flowcontrol",
2038 "functionargzero",
2039 "glob",
2040 "globalexport",
2041 "globalrcs",
2042 "hashcmds",
2043 "hashdirs",
2044 "hashlistall",
2045 "histbeep",
2046 "histsavebycopy",
2047 "hup",
2048 "interactive",
2049 "listambiguous",
2050 "listbeep",
2051 "listtypes",
2052 "monitor",
2053 "multibyte",
2054 "multifuncdef",
2055 "multios",
2056 "nomatch",
2057 "notify",
2058 "promptcr",
2059 "promptpercent",
2060 "promptsp",
2061 "rcs",
2062 "shinstdin",
2063 "shortloops",
2064 "unset",
2065 "zle",
2066 ];
2067 for opt in defaults_on {
2068 opts.insert(opt.to_string(), true);
2069 }
2070 opts
2071 }
2072
2073 fn normalize_option_name(name: &str) -> (String, bool) {
2075 let normalized = name.to_lowercase().replace(['-', '_'], "");
2076 if let Some(stripped) = normalized.strip_prefix("no") {
2077 if ZSH_OPTIONS_SET.contains(stripped) {
2079 return (stripped.to_string(), false);
2080 }
2081 }
2082 (normalized, true)
2083 }
2084
2085 fn option_matches_pattern(opt: &str, pattern: &str) -> bool {
2087 let pat = pattern.to_lowercase().replace(['-', '_'], "");
2088 let opt_lower = opt.to_lowercase();
2089
2090 if pat.contains('*') || pat.contains('?') || pat.contains('[') {
2091 let regex_pat = pat.replace('.', "\\.").replace('*', ".*").replace('?', ".");
2092 let full_pattern = format!("^{}$", regex_pat);
2093 cached_regex(&full_pattern)
2094 .map(|re| re.is_match(&opt_lower))
2095 .unwrap_or(false)
2096 } else {
2097 opt_lower == pat
2098 }
2099 }
2100
2101 pub fn autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
2103 if let Some(func) = self.functions.get(name) {
2105 return Some(func.clone());
2106 }
2107
2108 for i in 0..self.fpath.len() {
2110 let dir = self.fpath[i].clone();
2111 let zwc_path = dir.with_extension("zwc");
2113 if zwc_path.exists() {
2114 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
2115 return Some(func);
2116 }
2117 }
2118
2119 let func_zwc = dir.join(format!("{}.zwc", name));
2121 if func_zwc.exists() {
2122 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
2123 return Some(func);
2124 }
2125 }
2126
2127 if dir.is_dir() {
2129 if let Ok(entries) = fs::read_dir(&dir) {
2130 for entry in entries.flatten() {
2131 let path = entry.path();
2132 if path.extension().map_or(false, |e| e == "zwc") {
2133 if let Some(func) = self.load_function_from_zwc(&path, name) {
2134 return Some(func);
2135 }
2136 }
2137 }
2138 }
2139 }
2140 }
2141
2142 None
2143 }
2144
2145 fn load_function_from_zwc(&mut self, path: &Path, name: &str) -> Option<ShellCommand> {
2147 let zwc = if let Some(cached) = self.zwc_cache.get(path) {
2149 cached
2150 } else {
2151 let zwc = ZwcFile::load(path).ok()?;
2153 self.zwc_cache.insert(path.to_path_buf(), zwc);
2154 self.zwc_cache.get(path)?
2155 };
2156
2157 let func = zwc.get_function(name)?;
2159 let decoded = zwc.decode_function(func)?;
2160
2161 let shell_func = decoded.to_shell_function()?;
2163
2164 if let ShellCommand::FunctionDef(fname, body) = &shell_func {
2166 self.functions.insert(fname.clone(), (**body).clone());
2167 }
2168
2169 Some(shell_func)
2170 }
2171
2172 pub fn add_fpath(&mut self, path: PathBuf) {
2174 if !self.fpath.contains(&path) {
2175 self.fpath.insert(0, path);
2176 }
2177 }
2178
2179 fn glob_match(&self, s: &str, pattern: &str) -> bool {
2181 let mut regex_pattern = String::from("^");
2183 let mut chars = pattern.chars().peekable();
2184
2185 while let Some(c) = chars.next() {
2186 match c {
2187 '*' => regex_pattern.push_str(".*"),
2188 '?' => regex_pattern.push('.'),
2189 '[' => {
2190 regex_pattern.push('[');
2191 while let Some(cc) = chars.next() {
2193 if cc == ']' {
2194 regex_pattern.push(']');
2195 break;
2196 }
2197 regex_pattern.push(cc);
2198 }
2199 }
2200 '(' => {
2201 regex_pattern.push('(');
2203 }
2204 ')' => regex_pattern.push(')'),
2205 '|' => regex_pattern.push('|'),
2206 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
2207 regex_pattern.push('\\');
2208 regex_pattern.push(c);
2209 }
2210 _ => regex_pattern.push(c),
2211 }
2212 }
2213 regex_pattern.push('$');
2214
2215 regex::Regex::new(®ex_pattern)
2216 .map(|re| re.is_match(s))
2217 .unwrap_or(false)
2218 }
2219
2220 pub fn glob_match_static(s: &str, pattern: &str) -> bool {
2223 let mut regex_pattern = String::from("^");
2224 let mut chars = pattern.chars().peekable();
2225 while let Some(c) = chars.next() {
2226 match c {
2227 '*' => regex_pattern.push_str(".*"),
2228 '?' => regex_pattern.push('.'),
2229 '[' => {
2230 regex_pattern.push('[');
2231 while let Some(cc) = chars.next() {
2232 if cc == ']' {
2233 regex_pattern.push(']');
2234 break;
2235 }
2236 regex_pattern.push(cc);
2237 }
2238 }
2239 '(' => regex_pattern.push('('),
2240 ')' => regex_pattern.push(')'),
2241 '|' => regex_pattern.push('|'),
2242 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
2243 regex_pattern.push('\\');
2244 regex_pattern.push(c);
2245 }
2246 _ => regex_pattern.push(c),
2247 }
2248 }
2249 regex_pattern.push('$');
2250 regex::Regex::new(®ex_pattern)
2251 .map(|re| re.is_match(s))
2252 .unwrap_or(false)
2253 }
2254
2255 pub fn execute_script_file(&mut self, file_path: &str) -> Result<i32, String> {
2258 let content =
2260 std::fs::read_to_string(file_path).map_err(|e| format!("{}: {}", file_path, e))?;
2261 self.execute_script(&content)
2262 }
2263
2264 #[tracing::instrument(skip(self, script), fields(len = script.len()))]
2265 pub fn execute_script(&mut self, script: &str) -> Result<i32, String> {
2266 let expanded = self.expand_history(script);
2268
2269 let mut parser = ShellParser::new(&expanded);
2270 let commands = parser.parse_script()?;
2271 tracing::trace!(cmds = commands.len(), "execute_script: parsed");
2272
2273 let compiler = crate::shell_compiler::ShellCompiler::new();
2277 let chunk = compiler.compile(&commands);
2278
2279 if !chunk.ops.is_empty() {
2280 if std::env::var("ZSHRS_DEBUG_OPS").is_ok() {
2281 eprintln!("[DEBUG] Compiled {} ops:", chunk.ops.len());
2282 for (i, op) in chunk.ops.iter().enumerate() {
2283 eprintln!(" {:3}: {:?}", i, op);
2284 }
2285 }
2286 let mut vm = fusevm::VM::new(chunk);
2287 register_builtins(&mut vm);
2288
2289 let _ctx = ExecutorContext::enter(self);
2291
2292 match vm.run() {
2293 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
2294 self.last_status = vm.last_status;
2295 }
2296 fusevm::VMResult::Error(e) => {
2297 return Err(format!("VM error: {}", e));
2298 }
2299 }
2300 }
2301
2302 if let Some(action) = self.traps.remove("EXIT") {
2305 tracing::debug!("firing EXIT trap");
2306 let _ = self.execute_script(&action);
2307 }
2308
2309 Ok(self.last_status)
2310 }
2311
2312 fn expand_history(&self, input: &str) -> String {
2314 let Some(ref engine) = self.history else {
2315 return input.to_string();
2316 };
2317
2318 if !input.contains('!') && !input.starts_with('^') {
2320 return input.to_string();
2321 }
2322
2323 let history_count = engine.count().unwrap_or(0) as usize;
2324 if history_count == 0 {
2325 return input.to_string();
2326 }
2327
2328 let chars: Vec<char> = input.chars().collect();
2329
2330 if chars.first() == Some(&'^') {
2332 if let Some(expanded) = self.history_quick_subst(&chars, engine) {
2333 return expanded;
2334 }
2335 }
2336
2337 let mut result = String::new();
2338 let mut i = 0;
2339 let mut in_single_quote = false;
2340 let mut in_brace = 0; let mut last_subst: Option<(String, String)> = None; while i < chars.len() {
2344 if chars[i] == '\'' && in_brace == 0 {
2346 in_single_quote = !in_single_quote;
2347 result.push(chars[i]);
2348 i += 1;
2349 continue;
2350 }
2351 if in_single_quote {
2352 result.push(chars[i]);
2353 i += 1;
2354 continue;
2355 }
2356
2357 if i + 1 < chars.len() && chars[i] == '$' && chars[i + 1] == '{' {
2359 in_brace += 1;
2360 result.push(chars[i]);
2361 i += 1;
2362 result.push(chars[i]);
2363 i += 1;
2364 continue;
2365 }
2366 if chars[i] == '}' && in_brace > 0 {
2367 in_brace -= 1;
2368 result.push(chars[i]);
2369 i += 1;
2370 continue;
2371 }
2372
2373 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '!' {
2375 result.push('!');
2376 i += 2;
2377 continue;
2378 }
2379
2380 if chars[i] == '!' && in_brace == 0 {
2381 if i + 1 >= chars.len() {
2382 result.push('!');
2384 i += 1;
2385 continue;
2386 }
2387
2388 let next = chars[i + 1];
2389 if next == ' ' || next == '\t' || next == '=' || next == '(' || next == '\n' {
2391 result.push('!');
2392 i += 1;
2393 continue;
2394 }
2395
2396 let (event_str, new_i) = self.history_resolve_event(&chars, i, engine, &result);
2398 if let Some(ev) = event_str {
2399 let (final_str, final_i) = self.history_apply_designators_and_modifiers(
2401 &chars,
2402 new_i,
2403 &ev,
2404 &mut last_subst,
2405 );
2406 result.push_str(&final_str);
2407 i = final_i;
2408 } else {
2409 result.push('!');
2411 i += 1;
2412 }
2413 continue;
2414 }
2415 result.push(chars[i]);
2416 i += 1;
2417 }
2418
2419 result
2420 }
2421
2422 fn history_quick_subst(
2425 &self,
2426 chars: &[char],
2427 engine: &crate::history::HistoryEngine,
2428 ) -> Option<String> {
2429 let mut i = 1; let mut old = String::new();
2431 while i < chars.len() && chars[i] != '^' {
2432 old.push(chars[i]);
2433 i += 1;
2434 }
2435 if i >= chars.len() {
2436 return None;
2437 }
2438 i += 1; let mut new = String::new();
2440 while i < chars.len() && chars[i] != '^' && chars[i] != '\n' {
2441 new.push(chars[i]);
2442 i += 1;
2443 }
2444 let prev = engine.get_by_offset(0).ok()??;
2445 Some(prev.command.replacen(&old, &new, 1))
2446 }
2447
2448 fn history_resolve_event(
2451 &self,
2452 chars: &[char],
2453 bang_pos: usize,
2454 engine: &crate::history::HistoryEngine,
2455 current_line: &str,
2456 ) -> (Option<String>, usize) {
2457 let mut i = bang_pos + 1; let in_brace = i < chars.len() && chars[i] == '{';
2461 if in_brace {
2462 i += 1;
2463 }
2464
2465 let c = if i < chars.len() {
2466 chars[i]
2467 } else {
2468 return (None, bang_pos);
2469 };
2470
2471 let (event, new_i) = match c {
2472 '!' => {
2473 let entry = engine.get_by_offset(0).ok().flatten();
2475 (entry.map(|e| e.command), i + 1)
2476 }
2477 '#' => {
2478 (Some(current_line.to_string()), i + 1)
2480 }
2481 '-' => {
2482 i += 1;
2484 let start = i;
2485 while i < chars.len() && chars[i].is_ascii_digit() {
2486 i += 1;
2487 }
2488 if i > start {
2489 let n: usize = chars[start..i]
2490 .iter()
2491 .collect::<String>()
2492 .parse()
2493 .unwrap_or(0);
2494 if n > 0 {
2495 let entry = engine.get_by_offset(n - 1).ok().flatten();
2496 (entry.map(|e| e.command), i)
2497 } else {
2498 (None, bang_pos)
2499 }
2500 } else {
2501 (None, bang_pos)
2502 }
2503 }
2504 '?' => {
2505 i += 1;
2507 let start = i;
2508 while i < chars.len() && chars[i] != '?' && chars[i] != '\n' {
2509 i += 1;
2510 }
2511 let search: String = chars[start..i].iter().collect();
2512 if i < chars.len() && chars[i] == '?' {
2513 i += 1;
2514 }
2515 let entry = engine
2516 .search(&search, 1)
2517 .ok()
2518 .and_then(|v| v.into_iter().next());
2519 (entry.map(|e| e.command), i)
2520 }
2521 c if c.is_ascii_digit() => {
2522 let start = i;
2524 while i < chars.len() && chars[i].is_ascii_digit() {
2525 i += 1;
2526 }
2527 let n: i64 = chars[start..i]
2528 .iter()
2529 .collect::<String>()
2530 .parse()
2531 .unwrap_or(0);
2532 if n > 0 {
2533 let entry = engine.get_by_number(n).ok().flatten();
2534 (entry.map(|e| e.command), i)
2535 } else {
2536 (None, bang_pos)
2537 }
2538 }
2539 '$' => {
2540 let entry = engine.get_by_offset(0).ok().flatten();
2542 let word =
2543 entry.and_then(|e| Self::history_split_words(&e.command).last().cloned());
2544 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2546 i + 2
2547 } else {
2548 i + 1
2549 };
2550 return (word, final_i);
2551 }
2552 '^' => {
2553 let entry = engine.get_by_offset(0).ok().flatten();
2555 let word = entry.and_then(|e| {
2556 let words = Self::history_split_words(&e.command);
2557 words.get(1).cloned()
2558 });
2559 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2560 i + 2
2561 } else {
2562 i + 1
2563 };
2564 return (word, final_i);
2565 }
2566 '*' => {
2567 let entry = engine.get_by_offset(0).ok().flatten();
2569 let word = entry.map(|e| {
2570 let words = Self::history_split_words(&e.command);
2571 if words.len() > 1 {
2572 words[1..].join(" ")
2573 } else {
2574 String::new()
2575 }
2576 });
2577 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2578 i + 2
2579 } else {
2580 i + 1
2581 };
2582 return (word, final_i);
2583 }
2584 c if c.is_alphabetic() || c == '_' || c == '/' || c == '.' => {
2585 let start = i;
2587 while i < chars.len()
2588 && !chars[i].is_whitespace()
2589 && chars[i] != ':'
2590 && chars[i] != '!'
2591 && chars[i] != '}'
2592 {
2593 i += 1;
2594 }
2595 let prefix: String = chars[start..i].iter().collect();
2596 let entry = engine
2597 .search_prefix(&prefix, 1)
2598 .ok()
2599 .and_then(|v| v.into_iter().next());
2600 (entry.map(|e| e.command), i)
2601 }
2602 _ => (None, bang_pos),
2603 };
2604
2605 let final_i = if in_brace && new_i < chars.len() && chars[new_i] == '}' {
2607 new_i + 1
2608 } else {
2609 new_i
2610 };
2611
2612 (event, final_i)
2613 }
2614
2615 fn history_split_words(cmd: &str) -> Vec<String> {
2617 let mut words = Vec::new();
2618 let mut current = String::new();
2619 let mut in_sq = false;
2620 let mut in_dq = false;
2621 let mut escaped = false;
2622
2623 for c in cmd.chars() {
2624 if escaped {
2625 current.push(c);
2626 escaped = false;
2627 continue;
2628 }
2629 if c == '\\' {
2630 current.push(c);
2631 escaped = true;
2632 continue;
2633 }
2634 if c == '\'' && !in_dq {
2635 in_sq = !in_sq;
2636 current.push(c);
2637 continue;
2638 }
2639 if c == '"' && !in_sq {
2640 in_dq = !in_dq;
2641 current.push(c);
2642 continue;
2643 }
2644 if c.is_whitespace() && !in_sq && !in_dq {
2645 if !current.is_empty() {
2646 words.push(std::mem::take(&mut current));
2647 }
2648 continue;
2649 }
2650 current.push(c);
2651 }
2652 if !current.is_empty() {
2653 words.push(current);
2654 }
2655 words
2656 }
2657
2658 fn history_apply_designators_and_modifiers(
2662 &self,
2663 chars: &[char],
2664 mut i: usize,
2665 event: &str,
2666 last_subst: &mut Option<(String, String)>,
2667 ) -> (String, usize) {
2668 let words = Self::history_split_words(event);
2669 let argc = words.len().saturating_sub(1); let mut sline = event.to_string();
2673
2674 if i < chars.len() && chars[i] == ':' {
2675 i += 1;
2676 if i < chars.len() {
2677 let (farg, larg, new_i) = self.history_parse_word_range(chars, i, argc);
2679 i = new_i;
2680 if farg.is_some() || larg.is_some() {
2681 let f = farg.unwrap_or(0);
2682 let l = larg.unwrap_or(argc);
2683 let selected: Vec<&String> = words
2684 .iter()
2685 .enumerate()
2686 .filter(|(idx, _)| *idx >= f && *idx <= l)
2687 .map(|(_, w)| w)
2688 .collect();
2689 sline = selected
2690 .iter()
2691 .map(|s| s.as_str())
2692 .collect::<Vec<_>>()
2693 .join(" ");
2694 }
2695 }
2696 } else if i < chars.len() && chars[i] == '*' {
2697 i += 1;
2699 if words.len() > 1 {
2700 sline = words[1..].join(" ");
2701 } else {
2702 sline = String::new();
2703 }
2704 }
2705
2706 while i < chars.len() && chars[i] == ':' {
2708 i += 1;
2709 if i >= chars.len() {
2710 break;
2711 }
2712 let mut global = false;
2713 if chars[i] == 'g' && i + 1 < chars.len() {
2714 global = true;
2715 i += 1;
2716 }
2717 match chars[i] {
2718 'h' => {
2719 i += 1;
2721 if let Some(pos) = sline.rfind('/') {
2722 if pos > 0 {
2723 sline = sline[..pos].to_string();
2724 } else {
2725 sline = "/".to_string();
2726 }
2727 }
2728 }
2729 't' => {
2730 i += 1;
2732 if let Some(pos) = sline.rfind('/') {
2733 sline = sline[pos + 1..].to_string();
2734 }
2735 }
2736 'r' => {
2737 i += 1;
2739 if let Some(pos) = sline.rfind('.') {
2740 if pos > 0 && sline[..pos].rfind('/').map_or(true, |sp| sp < pos) {
2741 sline = sline[..pos].to_string();
2742 }
2743 }
2744 }
2745 'e' => {
2746 i += 1;
2748 if let Some(pos) = sline.rfind('.') {
2749 sline = sline[pos + 1..].to_string();
2750 } else {
2751 sline = String::new();
2752 }
2753 }
2754 'l' => {
2755 i += 1;
2757 sline = sline.to_lowercase();
2758 }
2759 'u' => {
2760 i += 1;
2762 sline = sline.to_uppercase();
2763 }
2764 'p' => {
2765 i += 1;
2767 }
2769 'q' => {
2770 i += 1;
2772 sline = format!("'{}'", sline.replace('\'', "'\\''"));
2773 }
2774 'Q' => {
2775 i += 1;
2777 sline = sline.replace('\'', "").replace('"', "");
2778 }
2779 'a' => {
2780 i += 1;
2782 if !sline.starts_with('/') {
2783 if let Ok(cwd) = std::env::current_dir() {
2784 sline = format!("{}/{}", cwd.display(), sline);
2785 }
2786 }
2787 }
2788 'A' => {
2789 i += 1;
2791 if let Ok(real) = std::fs::canonicalize(&sline) {
2792 sline = real.to_string_lossy().to_string();
2793 }
2794 }
2795 's' | 'S' => {
2796 i += 1;
2798 if i < chars.len() {
2799 let delim = chars[i];
2800 i += 1;
2801 let mut old_s = String::new();
2802 while i < chars.len() && chars[i] != delim {
2803 old_s.push(chars[i]);
2804 i += 1;
2805 }
2806 if i < chars.len() {
2807 i += 1;
2808 } let mut new_s = String::new();
2810 while i < chars.len()
2811 && chars[i] != delim
2812 && chars[i] != ':'
2813 && chars[i] != ' '
2814 {
2815 new_s.push(chars[i]);
2816 i += 1;
2817 }
2818 if i < chars.len() && chars[i] == delim {
2819 i += 1;
2820 } *last_subst = Some((old_s.clone(), new_s.clone()));
2822 if global {
2823 sline = sline.replace(&old_s, &new_s);
2824 } else {
2825 sline = sline.replacen(&old_s, &new_s, 1);
2826 }
2827 }
2828 }
2829 '&' => {
2830 i += 1;
2832 if let Some((ref old_s, ref new_s)) = last_subst {
2833 if global {
2834 sline = sline.replace(old_s.as_str(), new_s.as_str());
2835 } else {
2836 sline = sline.replacen(old_s.as_str(), new_s.as_str(), 1);
2837 }
2838 }
2839 }
2840 _ => {
2841 if global {
2842 }
2845 break;
2846 }
2847 }
2848 }
2849
2850 (sline, i)
2851 }
2852
2853 fn history_parse_word_range(
2855 &self,
2856 chars: &[char],
2857 mut i: usize,
2858 argc: usize,
2859 ) -> (Option<usize>, Option<usize>, usize) {
2860 if i >= chars.len() {
2861 return (None, None, i);
2862 }
2863
2864 match chars[i] {
2866 'h' | 't' | 'r' | 'e' | 's' | 'S' | 'g' | 'p' | 'q' | 'Q' | 'l' | 'u' | 'a' | 'A'
2867 | '&' => {
2868 return (None, None, i - 1); }
2871 _ => {}
2872 }
2873
2874 let farg = if chars[i] == '^' {
2875 i += 1;
2876 Some(1usize)
2877 } else if chars[i] == '$' {
2878 i += 1;
2879 return (Some(argc), Some(argc), i);
2880 } else if chars[i] == '*' {
2881 i += 1;
2882 return (Some(1), Some(argc), i);
2883 } else if chars[i].is_ascii_digit() {
2884 let start = i;
2885 while i < chars.len() && chars[i].is_ascii_digit() {
2886 i += 1;
2887 }
2888 let n: usize = chars[start..i]
2889 .iter()
2890 .collect::<String>()
2891 .parse()
2892 .unwrap_or(0);
2893 Some(n)
2894 } else {
2895 None
2896 };
2897
2898 if i < chars.len() && chars[i] == '-' {
2900 i += 1;
2901 if i < chars.len() && chars[i] == '$' {
2902 i += 1;
2903 return (farg, Some(argc), i);
2904 } else if i < chars.len() && chars[i].is_ascii_digit() {
2905 let start = i;
2906 while i < chars.len() && chars[i].is_ascii_digit() {
2907 i += 1;
2908 }
2909 let m: usize = chars[start..i]
2910 .iter()
2911 .collect::<String>()
2912 .parse()
2913 .unwrap_or(0);
2914 return (farg, Some(m), i);
2915 } else {
2916 return (farg, Some(argc.saturating_sub(1)), i);
2918 }
2919 }
2920
2921 if farg.is_some() {
2922 (farg, farg, i)
2923 } else {
2924 (None, None, i)
2925 }
2926 }
2927
2928 #[tracing::instrument(level = "trace", skip_all)]
2929 pub fn execute_command(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
2930 match cmd {
2931 ShellCommand::Simple(simple) => self.execute_simple(simple),
2932 ShellCommand::Pipeline(cmds, negated) => {
2933 let status = self.execute_pipeline(cmds)?;
2934 if *negated {
2935 self.last_status = if status == 0 { 1 } else { 0 };
2936 } else {
2937 self.last_status = status;
2938 }
2939 Ok(self.last_status)
2940 }
2941 ShellCommand::List(items) => self.execute_list(items),
2942 ShellCommand::Compound(compound) => self.execute_compound(compound),
2943 ShellCommand::FunctionDef(name, body) => {
2944 if name.is_empty() {
2945 let result = self.execute_command(body);
2947 if let Some(ret) = self.returning.take() {
2949 self.last_status = ret;
2950 return Ok(ret);
2951 }
2952 result
2953 } else {
2954 self.functions.insert(name.clone(), (**body).clone());
2956 self.last_status = 0;
2957 Ok(0)
2958 }
2959 }
2960 }
2961 }
2962
2963 #[tracing::instrument(level = "trace", skip_all)]
2964 fn execute_simple(&mut self, cmd: &SimpleCommand) -> Result<i32, String> {
2965 for (var, val, is_append) in &cmd.assignments {
2967 match val {
2968 ShellWord::ArrayLiteral(elements) => {
2969 let new_elements: Vec<String> = elements
2974 .iter()
2975 .flat_map(|e| self.expand_word_split(e))
2976 .collect();
2977
2978 if self.assoc_arrays.contains_key(var) {
2980 if *is_append {
2982 let assoc = self.assoc_arrays.get_mut(var).unwrap();
2983 let mut iter = new_elements.iter();
2984 while let Some(key) = iter.next() {
2985 if let Some(val) = iter.next() {
2986 assoc.insert(key.clone(), val.clone());
2987 }
2988 }
2989 } else {
2990 let mut assoc = HashMap::new();
2991 let mut iter = new_elements.iter();
2992 while let Some(key) = iter.next() {
2993 if let Some(val) = iter.next() {
2994 assoc.insert(key.clone(), val.clone());
2995 }
2996 }
2997 self.assoc_arrays.insert(var.clone(), assoc);
2998 }
2999 } else if *is_append {
3000 let arr = self.arrays.entry(var.clone()).or_insert_with(Vec::new);
3002 arr.extend(new_elements);
3003 } else {
3004 self.arrays.insert(var.clone(), new_elements);
3005 }
3006 }
3007 _ => {
3008 let expanded = self.expand_word(val);
3009
3010 if let Some(bracket_pos) = var.find('[') {
3012 if var.ends_with(']') {
3013 let array_name = &var[..bracket_pos];
3014 let key = &var[bracket_pos + 1..var.len() - 1];
3015 let key = self.expand_string(key); if self.assoc_arrays.contains_key(array_name) {
3019 let assoc = self.assoc_arrays.get_mut(array_name).unwrap();
3020 if *is_append {
3021 let existing = assoc.get(&key).cloned().unwrap_or_default();
3022 assoc.insert(key, existing + &expanded);
3023 } else {
3024 assoc.insert(key, expanded);
3025 }
3026 } else if let Ok(idx) = key.parse::<i64>() {
3027 let idx = if idx < 0 { 0 } else { (idx - 1) as usize }; let arr = self
3030 .arrays
3031 .entry(array_name.to_string())
3032 .or_insert_with(Vec::new);
3033 while arr.len() <= idx {
3034 arr.push(String::new());
3035 }
3036 if *is_append {
3037 arr[idx].push_str(&expanded);
3038 } else {
3039 arr[idx] = expanded;
3040 }
3041 } else {
3042 let assoc = self
3044 .assoc_arrays
3045 .entry(array_name.to_string())
3046 .or_insert_with(HashMap::new);
3047 if *is_append {
3048 let existing = assoc.get(&key).cloned().unwrap_or_default();
3049 assoc.insert(key, existing + &expanded);
3050 } else {
3051 assoc.insert(key, expanded);
3052 }
3053 }
3054 continue;
3055 }
3056 }
3057
3058 let final_value = if *is_append {
3060 let existing = self.variables.get(var).cloned().unwrap_or_default();
3061 existing + &expanded
3062 } else {
3063 expanded
3064 };
3065
3066 if self.readonly_vars.contains(var) {
3067 eprintln!("zshrs: read-only variable: {}", var);
3068 self.last_status = 1;
3069 return Ok(1);
3070 }
3071 if cmd.words.is_empty() {
3072 env::set_var(var, &final_value);
3074 }
3075 self.variables.insert(var.clone(), final_value);
3076 }
3077 }
3078 }
3079
3080 if cmd.words.is_empty() {
3081 self.last_status = 0;
3082 return Ok(0);
3083 }
3084
3085 let is_noglob = cmd
3087 .words
3088 .first()
3089 .map(|w| self.expand_word(w) == "noglob")
3090 .unwrap_or(false);
3091 let saved_noglob = if is_noglob {
3092 let saved = self.options.get("noglob").copied();
3093 self.options.insert("noglob".to_string(), true);
3094 saved
3095 } else {
3096 None
3097 };
3098
3099 let preflight = self.preflight_command_subs(&cmd.words);
3103
3104 let mut words: Vec<String> = cmd
3105 .words
3106 .iter()
3107 .enumerate()
3108 .flat_map(|(i, w)| {
3109 if let Some(rx) = &preflight[i] {
3110 vec![rx.recv().unwrap_or_default()]
3112 } else {
3113 self.expand_word_glob(w)
3114 }
3115 })
3116 .collect();
3117
3118 if is_noglob {
3120 match saved_noglob {
3121 Some(v) => {
3122 self.options.insert("noglob".to_string(), v);
3123 }
3124 None => {
3125 self.options.remove("noglob");
3126 }
3127 }
3128 }
3129 if words.is_empty() {
3130 self.last_status = 0;
3131 return Ok(0);
3132 }
3133
3134 if !self.global_aliases.is_empty() {
3136 let global_aliases = self.global_aliases.clone();
3137 words = words
3138 .into_iter()
3139 .map(|w| global_aliases.get(&w).cloned().unwrap_or(w))
3140 .collect();
3141 }
3142
3143 if self.options.get("xtrace").copied().unwrap_or(false) {
3145 let ps4 = self
3146 .variables
3147 .get("PS4")
3148 .cloned()
3149 .unwrap_or_else(|| "+".to_string());
3150 eprintln!("{}{}", ps4, words.join(" "));
3151 }
3152
3153 let cmd_name = &words[0];
3155 if let Some(alias_value) = self.aliases.get(cmd_name).cloned() {
3156 let expanded_cmd = if words.len() > 1 {
3158 format!("{} {}", alias_value, words[1..].join(" "))
3159 } else {
3160 alias_value
3161 };
3162 return self.execute_script(&expanded_cmd);
3164 }
3165
3166 if !self.suffix_aliases.is_empty() {
3168 let cmd_path = std::path::Path::new(cmd_name);
3169 if let Some(ext) = cmd_path.extension().and_then(|e| e.to_str()) {
3170 if let Some(handler) = self.suffix_aliases.get(ext).cloned() {
3171 let expanded_cmd = format!("{} {}", handler, words.join(" "));
3173 return self.execute_script(&expanded_cmd);
3174 }
3175 }
3176 }
3177
3178 let args = &words[1..];
3179
3180 let is_exec_with_redirects_only =
3183 cmd_name == "exec" && args.is_empty() && !cmd.redirects.is_empty();
3184
3185 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
3187 for redirect in &cmd.redirects {
3188 let target = self.expand_word(&redirect.target);
3189
3190 if let Some(ref var_name) = redirect.fd_var {
3192 use std::os::unix::io::IntoRawFd;
3193 let file_result = match redirect.op {
3194 RedirectOp::Write | RedirectOp::Clobber => std::fs::File::create(&target),
3195 RedirectOp::Append => std::fs::OpenOptions::new()
3196 .create(true)
3197 .append(true)
3198 .open(&target),
3199 RedirectOp::Read => std::fs::File::open(&target),
3200 _ => continue,
3201 };
3202 match file_result {
3203 Ok(file) => {
3204 let new_fd = file.into_raw_fd();
3205 self.variables.insert(var_name.clone(), new_fd.to_string());
3206 if !is_exec_with_redirects_only {
3208 }
3210 }
3211 Err(e) => {
3212 eprintln!("{}: {}: {}", cmd_name, target, e);
3213 return Ok(1);
3214 }
3215 }
3216 continue;
3217 }
3218
3219 let fd = redirect.fd.unwrap_or(match redirect.op {
3220 RedirectOp::Read
3221 | RedirectOp::HereDoc
3222 | RedirectOp::HereString
3223 | RedirectOp::ReadWrite => 0,
3224 _ => 1,
3225 });
3226
3227 match redirect.op {
3228 RedirectOp::Write | RedirectOp::Clobber => {
3229 use std::os::unix::io::IntoRawFd;
3230 if !is_exec_with_redirects_only {
3231 let saved = unsafe { libc::dup(fd) };
3232 if saved >= 0 {
3233 saved_fds.push((fd, saved));
3234 }
3235 }
3236 if let Ok(file) = std::fs::File::create(&target) {
3237 let new_fd = file.into_raw_fd();
3238 unsafe {
3239 libc::dup2(new_fd, fd);
3240 }
3241 unsafe {
3242 libc::close(new_fd);
3243 }
3244 }
3245 }
3246 RedirectOp::Append => {
3247 use std::os::unix::io::IntoRawFd;
3248 if !is_exec_with_redirects_only {
3249 let saved = unsafe { libc::dup(fd) };
3250 if saved >= 0 {
3251 saved_fds.push((fd, saved));
3252 }
3253 }
3254 if let Ok(file) = std::fs::OpenOptions::new()
3255 .create(true)
3256 .append(true)
3257 .open(&target)
3258 {
3259 let new_fd = file.into_raw_fd();
3260 unsafe {
3261 libc::dup2(new_fd, fd);
3262 }
3263 unsafe {
3264 libc::close(new_fd);
3265 }
3266 }
3267 }
3268 RedirectOp::Read => {
3269 use std::os::unix::io::IntoRawFd;
3270 if !is_exec_with_redirects_only {
3271 let saved = unsafe { libc::dup(fd) };
3272 if saved >= 0 {
3273 saved_fds.push((fd, saved));
3274 }
3275 }
3276 if let Ok(file) = std::fs::File::open(&target) {
3277 let new_fd = file.into_raw_fd();
3278 unsafe {
3279 libc::dup2(new_fd, fd);
3280 }
3281 unsafe {
3282 libc::close(new_fd);
3283 }
3284 }
3285 }
3286 RedirectOp::DupWrite | RedirectOp::DupRead => {
3287 if let Ok(target_fd) = target.parse::<i32>() {
3288 if !is_exec_with_redirects_only {
3289 let saved = unsafe { libc::dup(fd) };
3290 if saved >= 0 {
3291 saved_fds.push((fd, saved));
3292 }
3293 }
3294 unsafe {
3295 libc::dup2(target_fd, fd);
3296 }
3297 }
3298 }
3299 _ => {}
3300 }
3301 }
3302
3303 if is_exec_with_redirects_only {
3305 self.last_status = 0;
3306 return Ok(0);
3307 }
3308
3309 let status = match cmd_name.as_str() {
3311 "cd" => self.builtin_cd(args),
3312 "pwd" => self.builtin_pwd(&cmd.redirects),
3313 "echo" => self.builtin_echo(args, &cmd.redirects),
3314 "export" => self.builtin_export(args),
3315 "unset" => self.builtin_unset(args),
3316 "source" | "." => self.builtin_source(args),
3317 "exit" | "bye" | "logout" => self.builtin_exit(args),
3318 "return" => self.builtin_return(args),
3319 "true" => 0,
3320 "false" => 1,
3321 ":" => 0,
3322 "chdir" => self.builtin_cd(args),
3323 "test" | "[" => self.builtin_test(args),
3324 "local" => self.builtin_local(args),
3325 "declare" | "typeset" => self.builtin_declare(args),
3326 "read" => self.builtin_read(args),
3327 "shift" => self.builtin_shift(args),
3328 "eval" => self.builtin_eval(args),
3329 "jobs" => self.builtin_jobs(args),
3330 "fg" => self.builtin_fg(args),
3331 "bg" => self.builtin_bg(args),
3332 "kill" => self.builtin_kill(args),
3333 "disown" => self.builtin_disown(args),
3334 "wait" => self.builtin_wait(args),
3335 "autoload" => self.builtin_autoload(args),
3336 "history" => self.builtin_history(args),
3337 "fc" => self.builtin_fc(args),
3338 "trap" => self.builtin_trap(args),
3339 "suspend" => self.builtin_suspend(args),
3340 "alias" => self.builtin_alias(args),
3341 "unalias" => self.builtin_unalias(args),
3342 "set" => self.builtin_set(args),
3343 "shopt" => self.builtin_shopt(args),
3344 "bind" => self.builtin_bindkey(args),
3346 "caller" => self.builtin_caller(args),
3347 "help" => self.builtin_help(args),
3348 "doctor" => self.builtin_doctor(args),
3349 "dbview" => self.builtin_dbview(args),
3350 "profile" => self.builtin_profile(args),
3351 "intercept" => self.builtin_intercept(args),
3352 "intercept_proceed" => self.builtin_intercept_proceed(args),
3353 "async" => self.builtin_async(args),
3355 "await" => self.builtin_await(args),
3356 "pmap" => self.builtin_pmap(args),
3357 "pgrep" => self.builtin_pgrep(args),
3358 "peach" => self.builtin_peach(args),
3359 "barrier" => self.builtin_barrier(args),
3360 "readarray" | "mapfile" => self.builtin_readarray(args),
3361 "setopt" => self.builtin_setopt(args),
3362 "unsetopt" => self.builtin_unsetopt(args),
3363 "getopts" => self.builtin_getopts(args),
3364 "type" => self.builtin_type(args),
3365 "hash" => self.builtin_hash(args),
3366 "add-zsh-hook" => self.builtin_add_zsh_hook(args),
3367 "command" => self.builtin_command(args, &cmd.redirects),
3368 "builtin" => self.builtin_builtin(args, &cmd.redirects),
3369 "let" => self.builtin_let(args),
3370 "compgen" => self.builtin_compgen(args),
3371 "complete" => self.builtin_complete(args),
3372 "compopt" => self.builtin_compopt(args),
3373 "compadd" => self.builtin_compadd(args),
3374 "compset" => self.builtin_compset(args),
3375 "compdef" => self.builtin_compdef(args),
3376 "compinit" => self.builtin_compinit(args),
3377 "cdreplay" => self.builtin_cdreplay(args),
3378 "zstyle" => self.builtin_zstyle(args),
3379 "ztie" => self.builtin_ztie(args),
3381 "zuntie" => self.builtin_zuntie(args),
3382 "zgdbmpath" => self.builtin_zgdbmpath(args),
3383 "pushd" => self.builtin_pushd(args),
3384 "popd" => self.builtin_popd(args),
3385 "dirs" => self.builtin_dirs(args),
3386 "printf" => self.builtin_printf(args),
3387 "break" => self.builtin_break(args),
3389 "continue" => self.builtin_continue(args),
3390 "disable" => self.builtin_disable(args),
3392 "enable" => self.builtin_enable(args),
3393 "emulate" => self.builtin_emulate(args),
3395 "promptinit" => self.builtin_promptinit(args),
3397 "prompt" => self.builtin_prompt(args),
3398 "pcre_compile" => self.builtin_pcre_compile(args),
3400 "pcre_match" => self.builtin_pcre_match(args),
3401 "pcre_study" => self.builtin_pcre_study(args),
3402 "exec" => self.builtin_exec(args),
3404 "float" => self.builtin_float(args),
3406 "integer" => self.builtin_integer(args),
3407 "functions" => self.builtin_functions(args),
3409 "print" => self.builtin_print(args),
3411 "whence" => self.builtin_whence(args),
3413 "where" => self.builtin_where(args),
3414 "which" => self.builtin_which(args),
3415 "ulimit" => self.builtin_ulimit(args),
3417 "limit" => self.builtin_limit(args),
3418 "unlimit" => self.builtin_unlimit(args),
3419 "umask" => self.builtin_umask(args),
3421 "rehash" => self.builtin_rehash(args),
3423 "unhash" => self.builtin_unhash(args),
3424 "times" => self.builtin_times(args),
3426 "zmodload" => self.builtin_zmodload(args),
3428 "r" => self.builtin_r(args),
3430 "ttyctl" => self.builtin_ttyctl(args),
3432 "noglob" => self.builtin_noglob(args, &cmd.redirects),
3434 "zstat" | "stat" => self.builtin_zstat(args),
3436 "strftime" => self.builtin_strftime(args),
3438 "zsleep" => self.builtin_zsleep(args),
3440 "zsystem" => self.builtin_zsystem(args),
3442 "sync" => self.builtin_sync(args),
3444 "mkdir" => self.builtin_mkdir(args),
3445 "rmdir" => self.builtin_rmdir(args),
3446 "ln" => self.builtin_ln(args),
3447 "mv" => self.builtin_mv(args),
3448 "cp" => self.builtin_cp(args),
3449 "rm" => self.builtin_rm(args),
3450 "chown" => self.builtin_chown(args),
3451 "chmod" => self.builtin_chmod(args),
3452 "zln" | "zmv" | "zcp" => self.builtin_zfiles(cmd_name, args),
3453 "coproc" => self.builtin_coproc(args),
3455 "zparseopts" => self.builtin_zparseopts(args),
3457 "readonly" => self.builtin_readonly(args),
3459 "unfunction" => self.builtin_unfunction(args),
3460 "getln" => self.builtin_getln(args),
3462 "pushln" => self.builtin_pushln(args),
3463 "bindkey" => self.builtin_bindkey(args),
3465 "zle" => self.builtin_zle(args),
3467 "sched" => self.builtin_sched(args),
3469 "zformat" => self.builtin_zformat(args),
3471 "zcompile" => self.builtin_zcompile(args),
3473 "vared" => self.builtin_vared(args),
3475 "echotc" => self.builtin_echotc(args),
3477 "echoti" => self.builtin_echoti(args),
3478 "zpty" => self.builtin_zpty(args),
3480 "zprof" => self.builtin_zprof(args),
3481 "zsocket" => self.builtin_zsocket(args),
3482 "ztcp" => self.builtin_ztcp(args),
3483 "zregexparse" => self.builtin_zregexparse(args),
3484 "clone" => self.builtin_clone(args),
3485 "log" => self.builtin_log(args),
3486 "comparguments" => self.builtin_comparguments(args),
3488 "compcall" => self.builtin_compcall(args),
3489 "compctl" => self.builtin_compctl(args),
3490 "compdescribe" => self.builtin_compdescribe(args),
3491 "compfiles" => self.builtin_compfiles(args),
3492 "compgroups" => self.builtin_compgroups(args),
3493 "compquote" => self.builtin_compquote(args),
3494 "comptags" => self.builtin_comptags(args),
3495 "comptry" => self.builtin_comptry(args),
3496 "compvalues" => self.builtin_compvalues(args),
3497 "cap" | "getcap" | "setcap" => self.builtin_cap(args),
3499 "zftp" => self.builtin_zftp(args),
3501 "zcurses" => self.builtin_zcurses(args),
3503 "sysread" => self.builtin_sysread(args),
3505 "syswrite" => self.builtin_syswrite(args),
3506 "syserror" => self.builtin_syserror(args),
3507 "sysopen" => self.builtin_sysopen(args),
3508 "sysseek" => self.builtin_sysseek(args),
3509 "mapfile" => 0, "private" => self.builtin_private(args),
3513 "zgetattr" | "zsetattr" | "zdelattr" | "zlistattr" => {
3515 self.builtin_zattr(cmd_name, args)
3516 }
3517 "_arguments" | "_describe" | "_description" | "_message" | "_tags" | "_requested"
3520 | "_all_labels" | "_next_label" | "_files" | "_path_files" | "_directories" | "_cd"
3521 | "_default" | "_dispatch" | "_complete" | "_main_complete" | "_normal"
3522 | "_approximate" | "_correct" | "_expand" | "_history" | "_match" | "_menu"
3523 | "_oldlist" | "_list" | "_prefix" | "_generic" | "_wanted" | "_alternative"
3524 | "_values" | "_sequence" | "_sep_parts" | "_multi_parts" | "_combination"
3525 | "_parameters" | "_command" | "_command_names" | "_commands" | "_functions"
3526 | "_aliases" | "_builtins" | "_jobs" | "_pids" | "_process_names" | "_signals"
3527 | "_users" | "_groups" | "_hosts" | "_domains" | "_urls" | "_email_addresses"
3528 | "_options" | "_contexts" | "_set_options" | "_unset_options" | "_vars"
3529 | "_env_variables" | "_shell_variables" | "_arrays" | "_globflags" | "_globquals"
3530 | "_globqual_delims" | "_subscript" | "_history_modifiers" | "_brace_parameter"
3531 | "_tilde" | "_style" | "_cache_invalid" | "_store_cache" | "_retrieve_cache"
3532 | "_call_function" | "_call_program" | "_pick_variant" | "_setup"
3533 | "_comp_priv_prefix" | "_regex_arguments" | "_regex_words" | "_guard"
3534 | "_gnu_generic" | "_long_options" | "_x_arguments" | "_sub_commands"
3535 | "_cmdstring" | "_cmdambivalent" | "_first" | "_precommand" | "_user_at_host"
3536 | "_user_expand" | "_path_commands" | "_globbed_files" | "_have_glob_qual" => {
3537 0
3540 }
3541 _ => {
3542 if !self.intercepts.is_empty() {
3546 let full_cmd = if args.is_empty() {
3547 cmd_name.to_string()
3548 } else {
3549 format!("{} {}", cmd_name, args.join(" "))
3550 };
3551 if let Some(result) = self.run_intercepts(cmd_name, &full_cmd, args) {
3552 return result;
3553 }
3554 }
3555
3556 if let Some(func) = self.functions.get(cmd_name).cloned() {
3558 return self.call_function(&func, args);
3559 }
3560
3561 if self.maybe_autoload(cmd_name) {
3563 if let Some(func) = self.functions.get(cmd_name).cloned() {
3564 return self.call_function(&func, args);
3565 }
3566 }
3567
3568 if self.autoload_function(cmd_name).is_some() {
3570 if let Some(func) = self.functions.get(cmd_name).cloned() {
3571 return self.call_function(&func, args);
3572 }
3573 }
3574
3575 self.execute_external(cmd_name, args, &cmd.redirects)?
3577 }
3578 };
3579
3580 for (fd, saved) in saved_fds.into_iter().rev() {
3582 unsafe {
3583 libc::dup2(saved, fd);
3584 libc::close(saved);
3585 }
3586 }
3587
3588 self.last_status = status;
3589 Ok(status)
3590 }
3591
3592 #[tracing::instrument(level = "debug", skip_all)]
3594 fn call_function(&mut self, func: &ShellCommand, args: &[String]) -> Result<i32, String> {
3595 let saved_params = std::mem::take(&mut self.positional_params);
3597
3598 let saved_local_vars = self.local_save_stack.len();
3601 self.local_scope_depth += 1;
3602
3603 self.positional_params = args.to_vec();
3605
3606 let result = self.execute_command(func);
3608
3609 let final_result = if let Some(ret) = self.returning.take() {
3611 self.last_status = ret;
3612 Ok(ret)
3613 } else {
3614 result
3615 };
3616
3617 self.local_scope_depth -= 1;
3619 while self.local_save_stack.len() > saved_local_vars {
3620 if let Some((name, old_val)) = self.local_save_stack.pop() {
3621 match old_val {
3622 Some(v) => {
3623 self.variables.insert(name, v);
3624 }
3625 None => {
3626 self.variables.remove(&name);
3627 }
3628 }
3629 }
3630 }
3631
3632 self.positional_params = saved_params;
3634
3635 final_result
3636 }
3637
3638 fn execute_external(
3639 &mut self,
3640 cmd: &str,
3641 args: &[String],
3642 redirects: &[Redirect],
3643 ) -> Result<i32, String> {
3644 self.execute_external_bg(cmd, args, redirects, false)
3645 }
3646
3647 fn execute_external_bg(
3648 &mut self,
3649 cmd: &str,
3650 args: &[String],
3651 redirects: &[Redirect],
3652 background: bool,
3653 ) -> Result<i32, String> {
3654 tracing::trace!(cmd, bg = background, "exec external");
3655 let mut command = Command::new(cmd);
3656 command.args(args);
3657
3658 for redir in redirects {
3660 let target = self.expand_word(&redir.target);
3661 match redir.op {
3662 RedirectOp::Read => match File::open(&target) {
3663 Ok(f) => {
3664 command.stdin(Stdio::from(f));
3665 }
3666 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3667 },
3668 RedirectOp::Write => match File::create(&target) {
3669 Ok(f) => {
3670 command.stdout(Stdio::from(f));
3671 }
3672 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
3673 },
3674 RedirectOp::Append => {
3675 match OpenOptions::new().create(true).append(true).open(&target) {
3676 Ok(f) => {
3677 command.stdout(Stdio::from(f));
3678 }
3679 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3680 }
3681 }
3682 RedirectOp::WriteBoth => match File::create(&target) {
3683 Ok(f) => {
3684 let f2 = f
3685 .try_clone()
3686 .map_err(|e| format!("Cannot clone fd: {}", e))?;
3687 command.stdout(Stdio::from(f));
3688 command.stderr(Stdio::from(f2));
3689 }
3690 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
3691 },
3692 RedirectOp::AppendBoth => {
3693 match OpenOptions::new().create(true).append(true).open(&target) {
3694 Ok(f) => {
3695 let f2 = f
3696 .try_clone()
3697 .map_err(|e| format!("Cannot clone fd: {}", e))?;
3698 command.stdout(Stdio::from(f));
3699 command.stderr(Stdio::from(f2));
3700 }
3701 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3702 }
3703 }
3704 RedirectOp::HereDoc => {
3705 if let Some(ref content) = redir.heredoc_content {
3707 let expanded = self.expand_string(content);
3709 command.stdin(Stdio::piped());
3710 use std::io::Write;
3713 let mut temp_file = tempfile::NamedTempFile::new()
3714 .map_err(|e| format!("Cannot create temp file: {}", e))?;
3715 temp_file
3716 .write_all(expanded.as_bytes())
3717 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
3718 let temp_path = temp_file.into_temp_path();
3719 let f = File::open(&temp_path)
3720 .map_err(|e| format!("Cannot open temp file: {}", e))?;
3721 command.stdin(Stdio::from(f));
3722 }
3723 }
3724 RedirectOp::HereString => {
3725 use std::io::Write;
3727 let content = format!("{}\n", target);
3728 let mut temp_file = tempfile::NamedTempFile::new()
3729 .map_err(|e| format!("Cannot create temp file: {}", e))?;
3730 temp_file
3731 .write_all(content.as_bytes())
3732 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
3733 let temp_path = temp_file.into_temp_path();
3734 let f = File::open(&temp_path)
3735 .map_err(|e| format!("Cannot open temp file: {}", e))?;
3736 command.stdin(Stdio::from(f));
3737 }
3738 _ => {
3739 }
3741 }
3742
3743 if let Some(ref var_name) = redir.fd_var {
3745 #[cfg(unix)]
3748 {
3749 use std::os::unix::io::AsRawFd;
3750 let fd = match redir.op {
3751 RedirectOp::Write | RedirectOp::Append => {
3752 let f = if redir.op == RedirectOp::Write {
3753 File::create(&target)
3754 } else {
3755 OpenOptions::new().create(true).append(true).open(&target)
3756 };
3757 match f {
3758 Ok(file) => {
3759 let raw_fd = file.as_raw_fd();
3760 std::mem::forget(file); raw_fd
3762 }
3763 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3764 }
3765 }
3766 RedirectOp::Read => match File::open(&target) {
3767 Ok(file) => {
3768 let raw_fd = file.as_raw_fd();
3769 std::mem::forget(file);
3770 raw_fd
3771 }
3772 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3773 },
3774 _ => continue,
3775 };
3776 self.variables.insert(var_name.clone(), fd.to_string());
3777 }
3778 }
3779 }
3780
3781 if background {
3782 match command.spawn() {
3783 Ok(child) => {
3784 let pid = child.id();
3785 let cmd_str = format!("{} {}", cmd, args.join(" "));
3786 let job_id = self.jobs.add_job(child, cmd_str, JobState::Running);
3787 println!("[{}] {}", job_id, pid);
3788 Ok(0)
3789 }
3790 Err(e) => {
3791 if e.kind() == io::ErrorKind::NotFound {
3792 eprintln!("zshrs: command not found: {}", cmd);
3793 Ok(127)
3794 } else {
3795 Err(format!("zshrs: {}: {}", cmd, e))
3796 }
3797 }
3798 }
3799 } else {
3800 match command.status() {
3801 Ok(status) => Ok(status.code().unwrap_or(1)),
3802 Err(e) => {
3803 if e.kind() == io::ErrorKind::NotFound {
3804 eprintln!("zshrs: command not found: {}", cmd);
3805 Ok(127)
3806 } else {
3807 Err(format!("zshrs: {}: {}", cmd, e))
3808 }
3809 }
3810 }
3811 }
3812 }
3813
3814 #[tracing::instrument(level = "trace", skip_all, fields(stages = cmds.len()))]
3815 fn execute_pipeline(&mut self, cmds: &[ShellCommand]) -> Result<i32, String> {
3816 if cmds.len() == 1 {
3817 return self.execute_command(&cmds[0]);
3818 }
3819
3820 let mut children: Vec<Child> = Vec::new();
3821 let mut prev_stdout: Option<std::process::ChildStdout> = None;
3822
3823 for (i, cmd) in cmds.iter().enumerate() {
3824 if let ShellCommand::Simple(simple) = cmd {
3825 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
3826 if words.is_empty() {
3827 continue;
3828 }
3829
3830 let mut command = Command::new(&words[0]);
3831 command.args(&words[1..]);
3832
3833 if let Some(stdout) = prev_stdout.take() {
3834 command.stdin(Stdio::from(stdout));
3835 }
3836
3837 if i < cmds.len() - 1 {
3838 command.stdout(Stdio::piped());
3839 }
3840
3841 match command.spawn() {
3842 Ok(mut child) => {
3843 prev_stdout = child.stdout.take();
3844 children.push(child);
3845 }
3846 Err(e) => {
3847 eprintln!("zshrs: {}: {}", words[0], e);
3848 return Ok(127);
3849 }
3850 }
3851 }
3852 }
3853
3854 let mut last_status = 0;
3856 for mut child in children {
3857 if let Ok(status) = child.wait() {
3858 last_status = status.code().unwrap_or(1);
3859 }
3860 }
3861
3862 Ok(last_status)
3863 }
3864
3865 fn execute_list(&mut self, items: &[(ShellCommand, ListOp)]) -> Result<i32, String> {
3866 for (cmd, op) in items {
3867 let background = matches!(op, ListOp::Amp);
3869
3870 let status = if background {
3871 self.execute_command_bg(cmd)?
3872 } else {
3873 self.execute_command(cmd)?
3874 };
3875
3876 if self.returning.is_some() || self.breaking > 0 || self.continuing > 0 {
3878 return Ok(status);
3879 }
3880
3881 match op {
3882 ListOp::And => {
3883 if status != 0 {
3884 return Ok(status);
3885 }
3886 }
3887 ListOp::Or => {
3888 if status == 0 {
3889 return Ok(0);
3890 }
3891 }
3892 ListOp::Amp => {
3893 }
3895 ListOp::Semi | ListOp::Newline => {
3896 }
3898 }
3899 }
3900
3901 Ok(self.last_status)
3902 }
3903
3904 fn execute_command_bg(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
3905 if let ShellCommand::Simple(simple) = cmd {
3907 if simple.words.is_empty() {
3908 return Ok(0);
3909 }
3910 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
3911 let cmd_name = &words[0];
3912 let args: Vec<String> = words[1..].to_vec();
3913 return self.execute_external_bg(cmd_name, &args, &simple.redirects, true);
3914 }
3915 self.execute_command(cmd)
3917 }
3918
3919 #[tracing::instrument(level = "trace", skip_all)]
3920 fn execute_compound(&mut self, compound: &CompoundCommand) -> Result<i32, String> {
3921 match compound {
3922 CompoundCommand::BraceGroup(cmds) => {
3923 for cmd in cmds {
3924 self.execute_command(cmd)?;
3925 if self.returning.is_some() {
3926 break;
3927 }
3928 }
3929 Ok(self.last_status)
3930 }
3931 CompoundCommand::Subshell(cmds) => {
3932 let saved_vars = self.variables.clone();
3935 let saved_arrays = self.arrays.clone();
3936 let saved_assoc = self.assoc_arrays.clone();
3937 let saved_params = self.positional_params.clone();
3938
3939 for cmd in cmds {
3940 self.execute_command(cmd)?;
3941 if self.returning.is_some() {
3942 break;
3943 }
3944 }
3945 let status = self.last_status;
3946
3947 self.variables = saved_vars;
3949 self.arrays = saved_arrays;
3950 self.assoc_arrays = saved_assoc;
3951 self.positional_params = saved_params;
3952 self.last_status = status;
3953
3954 Ok(status)
3955 }
3956
3957 CompoundCommand::If {
3958 conditions,
3959 else_part,
3960 } => {
3961 for (cond, body) in conditions {
3962 for cmd in cond {
3964 self.execute_command(cmd)?;
3965 }
3966
3967 if self.last_status == 0 {
3968 for cmd in body {
3970 self.execute_command(cmd)?;
3971 }
3972 return Ok(self.last_status);
3973 }
3974 }
3975
3976 if let Some(else_cmds) = else_part {
3978 for cmd in else_cmds {
3979 self.execute_command(cmd)?;
3980 }
3981 }
3982
3983 Ok(self.last_status)
3984 }
3985
3986 CompoundCommand::For { var, words, body } => {
3987 let items: Vec<String> = if let Some(words) = words {
3988 words
3989 .iter()
3990 .flat_map(|w| self.expand_word_split(w))
3991 .collect()
3992 } else {
3993 self.positional_params.clone()
3995 };
3996
3997 for item in items {
3998 env::set_var(var, &item);
3999 self.variables.insert(var.clone(), item);
4000
4001 for cmd in body {
4002 self.execute_command(cmd)?;
4003 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4004 break;
4005 }
4006 }
4007
4008 if self.continuing > 0 {
4009 self.continuing -= 1;
4010 if self.continuing > 0 {
4011 break;
4012 }
4013 continue;
4014 }
4015 if self.breaking > 0 {
4016 self.breaking -= 1;
4017 break;
4018 }
4019 if self.returning.is_some() {
4020 break;
4021 }
4022 }
4023
4024 Ok(self.last_status)
4025 }
4026
4027 CompoundCommand::ForArith {
4028 init,
4029 cond,
4030 step,
4031 body,
4032 } => {
4033 if !init.is_empty() {
4036 self.evaluate_arithmetic_expr(init);
4037 }
4038
4039 loop {
4041 if !cond.is_empty() {
4043 let cond_result = self.eval_arith_expr(cond);
4044 if cond_result == 0 {
4045 break;
4046 }
4047 }
4048
4049 for cmd in body {
4051 self.execute_command(cmd)?;
4052 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4053 break;
4054 }
4055 }
4056
4057 if self.continuing > 0 {
4058 self.continuing -= 1;
4059 if self.continuing > 0 {
4060 break;
4061 }
4062 continue;
4063 }
4064 if self.breaking > 0 {
4065 self.breaking -= 1;
4066 break;
4067 }
4068 if self.returning.is_some() {
4069 break;
4070 }
4071
4072 if !step.is_empty() {
4074 self.evaluate_arithmetic_expr(step);
4075 }
4076 }
4077 Ok(self.last_status)
4078 }
4079
4080 CompoundCommand::While { condition, body } => {
4081 loop {
4082 for cmd in condition {
4083 self.execute_command(cmd)?;
4084 if self.breaking > 0 || self.returning.is_some() {
4085 break;
4086 }
4087 }
4088
4089 if self.last_status != 0 || self.breaking > 0 || self.returning.is_some() {
4090 break;
4091 }
4092
4093 for cmd in body {
4094 self.execute_command(cmd)?;
4095 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4096 break;
4097 }
4098 }
4099
4100 if self.continuing > 0 {
4101 self.continuing -= 1;
4102 if self.continuing > 0 {
4103 break;
4104 }
4105 continue;
4106 }
4107 if self.breaking > 0 {
4108 self.breaking -= 1;
4109 break;
4110 }
4111 }
4112 Ok(self.last_status)
4113 }
4114
4115 CompoundCommand::Until { condition, body } => {
4116 loop {
4117 for cmd in condition {
4118 self.execute_command(cmd)?;
4119 if self.breaking > 0 || self.returning.is_some() {
4120 break;
4121 }
4122 }
4123
4124 if self.last_status == 0 || self.breaking > 0 || self.returning.is_some() {
4125 break;
4126 }
4127
4128 for cmd in body {
4129 self.execute_command(cmd)?;
4130 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4131 break;
4132 }
4133 }
4134
4135 if self.continuing > 0 {
4136 self.continuing -= 1;
4137 if self.continuing > 0 {
4138 break;
4139 }
4140 continue;
4141 }
4142 if self.breaking > 0 {
4143 self.breaking -= 1;
4144 break;
4145 }
4146 }
4147 Ok(self.last_status)
4148 }
4149
4150 CompoundCommand::Case { word, cases } => {
4151 let value = self.expand_word(word);
4152
4153 for (patterns, body, term) in cases {
4154 for pattern in patterns {
4155 let pat = self.expand_word(pattern);
4156 if self.matches_pattern(&value, &pat) {
4157 for cmd in body {
4158 self.execute_command(cmd)?;
4159 }
4160
4161 match term {
4162 CaseTerminator::Break => return Ok(self.last_status),
4163 CaseTerminator::Fallthrough => {
4164 }
4166 CaseTerminator::Continue => {
4167 break;
4169 }
4170 }
4171 }
4172 }
4173 }
4174
4175 Ok(self.last_status)
4176 }
4177
4178 CompoundCommand::Select { var, words, body } => {
4179 if let Some(words) = words {
4181 if let Some(first) = words.first() {
4182 let val = self.expand_word(first);
4183 env::set_var(var, &val);
4184 for cmd in body {
4185 self.execute_command(cmd)?;
4186 }
4187 }
4188 }
4189 Ok(self.last_status)
4190 }
4191
4192 CompoundCommand::Repeat { count, body } => {
4193 let n: i64 = self
4194 .expand_word(&ShellWord::Literal(count.clone()))
4195 .parse()
4196 .unwrap_or(0);
4197
4198 for _ in 0..n {
4199 for cmd in body {
4200 self.execute_command(cmd)?;
4201 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4202 break;
4203 }
4204 }
4205
4206 if self.continuing > 0 {
4207 self.continuing -= 1;
4208 if self.continuing > 0 {
4209 break;
4210 }
4211 continue;
4212 }
4213 if self.breaking > 0 {
4214 self.breaking -= 1;
4215 break;
4216 }
4217 if self.returning.is_some() {
4218 break;
4219 }
4220 }
4221
4222 Ok(self.last_status)
4223 }
4224
4225 CompoundCommand::Try {
4226 try_body,
4227 always_body,
4228 } => {
4229 for cmd in try_body {
4232 if let Err(_e) = self.execute_command(cmd) {
4233 break;
4234 }
4235 if self.returning.is_some() {
4236 break;
4237 }
4238 }
4239
4240 let endval = self.last_status;
4242
4243 let save_returning = self.returning.take();
4245 let save_breaking = self.breaking;
4246 let save_continuing = self.continuing;
4247 self.breaking = 0;
4248 self.continuing = 0;
4249
4250 for cmd in always_body {
4252 let _ = self.execute_command(cmd);
4253 }
4254
4255 if self.returning.is_none() {
4258 self.returning = save_returning;
4259 }
4260 if self.breaking == 0 {
4261 self.breaking = save_breaking;
4262 }
4263 if self.continuing == 0 {
4264 self.continuing = save_continuing;
4265 }
4266
4267 self.last_status = endval;
4268 Ok(endval)
4269 }
4270
4271 CompoundCommand::Cond(expr) => {
4272 let result = self.eval_cond_expr(expr);
4273 self.last_status = if result { 0 } else { 1 };
4274 Ok(self.last_status)
4275 }
4276
4277 CompoundCommand::Arith(expr) => {
4278 let result = self.evaluate_arithmetic_expr(expr);
4280 self.last_status = if result != 0 { 0 } else { 1 };
4282 Ok(self.last_status)
4283 }
4284
4285 CompoundCommand::Coproc { name, body } => {
4286 let (stdin_read, stdin_write) =
4288 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
4289 let (stdout_read, stdout_write) =
4290 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
4291
4292 let cmd_str = match body.as_ref() {
4294 ShellCommand::Simple(simple) => simple
4295 .words
4296 .iter()
4297 .map(|w| self.expand_word(w))
4298 .collect::<Vec<_>>()
4299 .join(" "),
4300 ShellCommand::Compound(CompoundCommand::BraceGroup(_cmds)) => {
4301 "bash -c 'true'".to_string()
4304 }
4305 _ => "true".to_string(),
4306 };
4307
4308 let parts: Vec<&str> = cmd_str.split_whitespace().collect();
4310 if parts.is_empty() {
4311 return Ok(0);
4312 }
4313
4314 let mut command = Command::new(parts[0]);
4315 if parts.len() > 1 {
4316 command.args(&parts[1..]);
4317 }
4318
4319 use std::os::unix::io::{FromRawFd, IntoRawFd};
4320
4321 command.stdin(unsafe { Stdio::from_raw_fd(stdin_read.into_raw_fd()) });
4322 command.stdout(unsafe { Stdio::from_raw_fd(stdout_write.into_raw_fd()) });
4323
4324 match command.spawn() {
4325 Ok(child) => {
4326 let pid = child.id();
4327 let coproc_name = name.clone().unwrap_or_else(|| "COPROC".to_string());
4328
4329 let read_fd = stdout_read.into_raw_fd();
4333 let write_fd = stdin_write.into_raw_fd();
4334
4335 self.arrays.insert(
4336 coproc_name.clone(),
4337 vec![read_fd.to_string(), write_fd.to_string()],
4338 );
4339
4340 self.variables
4342 .insert(format!("{}_PID", coproc_name), pid.to_string());
4343
4344 let cmd_str_clone = cmd_str.clone();
4345 self.jobs.add_job(child, cmd_str_clone, JobState::Running);
4346
4347 Ok(0)
4348 }
4349 Err(e) => {
4350 if e.kind() == io::ErrorKind::NotFound {
4351 eprintln!("zshrs: command not found: {}", parts[0]);
4352 Ok(127)
4353 } else {
4354 Err(format!("zshrs: coproc: {}: {}", parts[0], e))
4355 }
4356 }
4357 }
4358 }
4359
4360 CompoundCommand::WithRedirects(cmd, redirects) => {
4361 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
4363
4364 for redirect in redirects {
4366 let fd = redirect.fd.unwrap_or(match redirect.op {
4367 RedirectOp::Read
4368 | RedirectOp::HereDoc
4369 | RedirectOp::HereString
4370 | RedirectOp::ReadWrite => 0,
4371 _ => 1,
4372 });
4373
4374 let target = self.expand_word(&redirect.target);
4375
4376 match redirect.op {
4377 RedirectOp::Write | RedirectOp::Clobber => {
4378 use std::os::unix::io::IntoRawFd;
4379 let saved = unsafe { libc::dup(fd) };
4380 if saved >= 0 {
4381 saved_fds.push((fd, saved));
4382 }
4383 if let Ok(file) = std::fs::File::create(&target) {
4384 let new_fd = file.into_raw_fd();
4385 unsafe {
4386 libc::dup2(new_fd, fd);
4387 }
4388 unsafe {
4389 libc::close(new_fd);
4390 }
4391 }
4392 }
4393 RedirectOp::Append => {
4394 use std::os::unix::io::IntoRawFd;
4395 let saved = unsafe { libc::dup(fd) };
4396 if saved >= 0 {
4397 saved_fds.push((fd, saved));
4398 }
4399 if let Ok(file) = std::fs::OpenOptions::new()
4400 .create(true)
4401 .append(true)
4402 .open(&target)
4403 {
4404 let new_fd = file.into_raw_fd();
4405 unsafe {
4406 libc::dup2(new_fd, fd);
4407 }
4408 unsafe {
4409 libc::close(new_fd);
4410 }
4411 }
4412 }
4413 RedirectOp::Read => {
4414 use std::os::unix::io::IntoRawFd;
4415 let saved = unsafe { libc::dup(fd) };
4416 if saved >= 0 {
4417 saved_fds.push((fd, saved));
4418 }
4419 if let Ok(file) = std::fs::File::open(&target) {
4420 let new_fd = file.into_raw_fd();
4421 unsafe {
4422 libc::dup2(new_fd, fd);
4423 }
4424 unsafe {
4425 libc::close(new_fd);
4426 }
4427 }
4428 }
4429 RedirectOp::DupWrite | RedirectOp::DupRead => {
4430 if let Ok(target_fd) = target.parse::<i32>() {
4431 let saved = unsafe { libc::dup(fd) };
4432 if saved >= 0 {
4433 saved_fds.push((fd, saved));
4434 }
4435 unsafe {
4436 libc::dup2(target_fd, fd);
4437 }
4438 }
4439 }
4440 _ => {}
4441 }
4442 }
4443
4444 let result = self.execute_command(cmd);
4446
4447 for (fd, saved) in saved_fds.into_iter().rev() {
4449 unsafe {
4450 libc::dup2(saved, fd);
4451 libc::close(saved);
4452 }
4453 }
4454
4455 result
4456 }
4457 }
4458 }
4459
4460 #[tracing::instrument(level = "trace", skip_all)]
4462 fn expand_word_glob(&mut self, word: &ShellWord) -> Vec<String> {
4463 match word {
4464 ShellWord::SingleQuoted(s) => vec![s.clone()],
4465 ShellWord::DoubleQuoted(parts) => {
4466 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
4468 }
4469 _ => {
4470 let expanded = self.expand_word(word);
4471
4472 let brace_expanded = self.expand_braces(&expanded);
4474
4475 let noglob = self.options.get("noglob").copied().unwrap_or(false)
4477 || self.options.get("GLOB").map(|v| !v).unwrap_or(false);
4478 brace_expanded
4479 .into_iter()
4480 .flat_map(|s| {
4481 if !noglob
4482 && (s.contains('*')
4483 || s.contains('?')
4484 || s.contains('[')
4485 || self.has_extglob_pattern(&s))
4486 {
4487 self.expand_glob(&s)
4488 } else {
4489 vec![s]
4490 }
4491 })
4492 .collect()
4493 }
4494 }
4495 }
4496
4497 fn expand_braces(&self, s: &str) -> Vec<String> {
4499 let mut depth = 0;
4501 let mut brace_start = None;
4502
4503 for (i, c) in s.char_indices() {
4504 match c {
4505 '{' => {
4506 if depth == 0 {
4507 brace_start = Some(i);
4508 }
4509 depth += 1;
4510 }
4511 '}' => {
4512 depth -= 1;
4513 if depth == 0 {
4514 if let Some(start) = brace_start {
4515 let prefix = &s[..start];
4516 let content = &s[start + 1..i];
4517 let suffix = &s[i + 1..];
4518
4519 let expansions = if content.contains("..") {
4521 self.expand_brace_sequence(content)
4522 } else if content.contains(',') {
4523 self.expand_brace_list(content)
4524 } else {
4525 return vec![s.to_string()];
4527 };
4528
4529 let mut results = Vec::new();
4531 for exp in expansions {
4532 let combined = format!("{}{}{}", prefix, exp, suffix);
4533 results.extend(self.expand_braces(&combined));
4535 }
4536 return results;
4537 }
4538 }
4539 }
4540 _ => {}
4541 }
4542 }
4543
4544 vec![s.to_string()]
4546 }
4547
4548 fn expand_brace_list(&self, content: &str) -> Vec<String> {
4550 let mut parts = Vec::new();
4552 let mut current = String::new();
4553 let mut depth = 0;
4554
4555 for c in content.chars() {
4556 match c {
4557 '{' => {
4558 depth += 1;
4559 current.push(c);
4560 }
4561 '}' => {
4562 depth -= 1;
4563 current.push(c);
4564 }
4565 ',' if depth == 0 => {
4566 parts.push(current.clone());
4567 current.clear();
4568 }
4569 _ => current.push(c),
4570 }
4571 }
4572 parts.push(current);
4573
4574 parts
4575 }
4576
4577 fn expand_brace_sequence(&self, content: &str) -> Vec<String> {
4579 let parts: Vec<&str> = content.splitn(3, "..").collect();
4580 if parts.len() < 2 {
4581 return vec![content.to_string()];
4582 }
4583
4584 let start = parts[0];
4585 let end = parts[1];
4586 let step: i64 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
4587
4588 if let (Ok(start_num), Ok(end_num)) = (start.parse::<i64>(), end.parse::<i64>()) {
4590 let mut results = Vec::new();
4591 if start_num <= end_num {
4592 let mut i = start_num;
4593 while i <= end_num {
4594 results.push(i.to_string());
4595 i += step;
4596 }
4597 } else {
4598 let mut i = start_num;
4599 while i >= end_num {
4600 results.push(i.to_string());
4601 i -= step;
4602 }
4603 }
4604 return results;
4605 }
4606
4607 if start.len() == 1 && end.len() == 1 {
4609 let start_char = start.chars().next().unwrap();
4610 let end_char = end.chars().next().unwrap();
4611 let mut results = Vec::new();
4612
4613 if start_char <= end_char {
4614 let mut c = start_char;
4615 while c <= end_char {
4616 results.push(c.to_string());
4617 c = (c as u8 + step as u8) as char;
4618 if c as u8 > end_char as u8 {
4619 break;
4620 }
4621 }
4622 } else {
4623 let mut c = start_char;
4624 while c >= end_char {
4625 results.push(c.to_string());
4626 if (c as u8) < step as u8 {
4627 break;
4628 }
4629 c = (c as u8 - step as u8) as char;
4630 }
4631 }
4632 return results;
4633 }
4634
4635 vec![content.to_string()]
4636 }
4637
4638 fn expand_glob(&self, pattern: &str) -> Vec<String> {
4640 let (glob_pattern, qualifiers) = self.parse_glob_qualifiers(pattern);
4642
4643 if self.has_extglob_pattern(&glob_pattern) {
4645 let expanded = self.expand_extglob(&glob_pattern);
4646 return self.filter_by_qualifiers(expanded, &qualifiers);
4647 }
4648
4649 let nullglob = self.options.get("nullglob").copied().unwrap_or(false);
4650 let dotglob = self.options.get("dotglob").copied().unwrap_or(false);
4651 let nocaseglob = self.options.get("nocaseglob").copied().unwrap_or(false);
4652
4653 let expanded = if glob_pattern.contains("**/") {
4658 self.expand_glob_parallel(&glob_pattern, dotglob, nocaseglob)
4659 } else {
4660 let options = glob::MatchOptions {
4661 case_sensitive: !nocaseglob,
4662 require_literal_separator: false,
4663 require_literal_leading_dot: !dotglob,
4664 };
4665 match glob::glob_with(&glob_pattern, options) {
4666 Ok(paths) => paths
4667 .filter_map(|p| p.ok())
4668 .map(|p| p.to_string_lossy().to_string())
4669 .collect(),
4670 Err(_) => vec![],
4671 }
4672 };
4673
4674 let mut expanded = self.filter_by_qualifiers(expanded, &qualifiers);
4675 expanded.sort();
4676
4677 if expanded.is_empty() {
4678 if nullglob {
4679 vec![]
4680 } else {
4681 vec![pattern.to_string()]
4682 }
4683 } else {
4684 expanded
4685 }
4686 }
4687
4688 fn expand_glob_parallel(&self, pattern: &str, dotglob: bool, nocaseglob: bool) -> Vec<String> {
4694 use walkdir::WalkDir;
4695
4696 let (base, file_glob) = if let Some(pos) = pattern.find("**/") {
4700 let base = if pos == 0 {
4701 "."
4702 } else {
4703 &pattern[..pos.saturating_sub(1)]
4704 };
4705 let rest = &pattern[pos + 3..]; (base.to_string(), rest.to_string())
4707 } else {
4708 return vec![];
4709 };
4710
4711 if file_glob.contains("**/") {
4714 let options = glob::MatchOptions {
4715 case_sensitive: !nocaseglob,
4716 require_literal_separator: false,
4717 require_literal_leading_dot: !dotglob,
4718 };
4719 return match glob::glob_with(pattern, options) {
4720 Ok(paths) => paths
4721 .filter_map(|p| p.ok())
4722 .map(|p| p.to_string_lossy().to_string())
4723 .collect(),
4724 Err(_) => vec![],
4725 };
4726 }
4727
4728 let match_opts = glob::MatchOptions {
4730 case_sensitive: !nocaseglob,
4731 require_literal_separator: false,
4732 require_literal_leading_dot: !dotglob,
4733 };
4734 let file_pat = match glob::Pattern::new(&file_glob) {
4735 Ok(p) => p,
4736 Err(_) => return vec![],
4737 };
4738
4739 let top_entries: Vec<std::path::PathBuf> = match std::fs::read_dir(&base) {
4741 Ok(rd) => rd.filter_map(|e| e.ok()).map(|e| e.path()).collect(),
4742 Err(_) => return vec![],
4743 };
4744
4745 let mut results: Vec<String> = Vec::new();
4747 for entry in &top_entries {
4748 if entry.is_file() || entry.is_symlink() {
4749 if let Some(name) = entry.file_name().and_then(|n| n.to_str()) {
4750 if file_pat.matches_with(name, match_opts) {
4751 results.push(entry.to_string_lossy().to_string());
4752 }
4753 }
4754 }
4755 }
4756
4757 let subdirs: Vec<std::path::PathBuf> = top_entries
4759 .into_iter()
4760 .filter(|p| p.is_dir())
4761 .filter(|p| {
4762 dotglob
4763 || !p
4764 .file_name()
4765 .and_then(|n| n.to_str())
4766 .map(|n| n.starts_with('.'))
4767 .unwrap_or(false)
4768 })
4769 .collect();
4770
4771 if subdirs.is_empty() {
4772 return results;
4773 }
4774
4775 let (tx, rx) = std::sync::mpsc::channel::<Vec<String>>();
4776
4777 for subdir in &subdirs {
4778 let tx = tx.clone();
4779 let subdir = subdir.clone();
4780 let file_pat = file_pat.clone();
4781 let skip_dot = !dotglob;
4782 self.worker_pool.submit(move || {
4783 let mut matches = Vec::new();
4784 let walker = WalkDir::new(&subdir)
4785 .follow_links(false)
4786 .into_iter()
4787 .filter_entry(move |e| {
4788 if skip_dot {
4790 if let Some(name) = e.file_name().to_str() {
4791 if name.starts_with('.') && e.depth() > 0 {
4792 return false;
4793 }
4794 }
4795 }
4796 true
4797 });
4798 for entry in walker.filter_map(|e| e.ok()) {
4799 if entry.file_type().is_file() || entry.file_type().is_symlink() {
4800 if let Some(name) = entry.file_name().to_str() {
4801 if file_pat.matches_with(name, match_opts) {
4802 matches.push(entry.path().to_string_lossy().to_string());
4803 }
4804 }
4805 }
4806 }
4807 let _ = tx.send(matches);
4808 });
4809 }
4810
4811 drop(tx);
4813
4814 for batch in rx {
4816 results.extend(batch);
4817 }
4818
4819 results
4820 }
4821
4822 fn parse_glob_qualifiers(&self, pattern: &str) -> (String, String) {
4825 if !pattern.ends_with(')') {
4828 return (pattern.to_string(), String::new());
4829 }
4830
4831 let chars: Vec<char> = pattern.chars().collect();
4833 let mut depth = 0;
4834 let mut qual_start = None;
4835
4836 for i in (0..chars.len()).rev() {
4837 match chars[i] {
4838 ')' => depth += 1,
4839 '(' => {
4840 depth -= 1;
4841 if depth == 0 {
4842 qual_start = Some(i);
4843 break;
4844 }
4845 }
4846 _ => {}
4847 }
4848 }
4849
4850 if let Some(start) = qual_start {
4851 let qual_content: String = chars[start + 1..chars.len() - 1].iter().collect();
4852
4853 if !qual_content.contains('|') && self.looks_like_glob_qualifiers(&qual_content) {
4857 let base_pattern: String = chars[..start].iter().collect();
4858 return (base_pattern, qual_content);
4859 }
4860 }
4861
4862 (pattern.to_string(), String::new())
4863 }
4864
4865 fn looks_like_glob_qualifiers(&self, s: &str) -> bool {
4867 if s.is_empty() {
4868 return false;
4869 }
4870 let valid_chars = "./@=p*%brwxAIERWXsStfedDLNnMmcaou^-+:0123456789,[]FT";
4873 s.chars()
4874 .all(|c| valid_chars.contains(c) || c.is_whitespace())
4875 }
4876
4877 fn prefetch_metadata(
4882 &self,
4883 files: &[String],
4884 ) -> HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)> {
4885 if files.len() < 32 {
4886 return files
4888 .iter()
4889 .map(|f| {
4890 let meta = std::fs::metadata(f).ok();
4891 let symlink_meta = std::fs::symlink_metadata(f).ok();
4892 (f.clone(), (meta, symlink_meta))
4893 })
4894 .collect();
4895 }
4896
4897 let pool_size = self.worker_pool.size();
4898 let chunk_size = (files.len() + pool_size - 1) / pool_size;
4899 let (tx, rx) = std::sync::mpsc::channel();
4900
4901 for chunk in files.chunks(chunk_size) {
4902 let tx = tx.clone();
4903 let chunk: Vec<String> = chunk.to_vec();
4904 self.worker_pool.submit(move || {
4905 let batch: Vec<(
4906 String,
4907 (Option<std::fs::Metadata>, Option<std::fs::Metadata>),
4908 )> = chunk
4909 .into_iter()
4910 .map(|f| {
4911 let meta = std::fs::metadata(&f).ok();
4912 let symlink_meta = std::fs::symlink_metadata(&f).ok();
4913 (f, (meta, symlink_meta))
4914 })
4915 .collect();
4916 let _ = tx.send(batch);
4917 });
4918 }
4919 drop(tx);
4920
4921 let mut map = HashMap::with_capacity(files.len());
4922 for batch in rx {
4923 for (path, metas) in batch {
4924 map.insert(path, metas);
4925 }
4926 }
4927 map
4928 }
4929
4930 fn filter_by_qualifiers(&self, files: Vec<String>, qualifiers: &str) -> Vec<String> {
4931 if qualifiers.is_empty() {
4932 return files;
4933 }
4934
4935 let meta_cache = self.prefetch_metadata(&files);
4938
4939 let mut result = files;
4940 let mut negate = false;
4941 let mut chars = qualifiers.chars().peekable();
4942
4943 while let Some(c) = chars.next() {
4944 match c {
4945 '^' => negate = !negate,
4947
4948 '.' => {
4950 result = result
4951 .into_iter()
4952 .filter(|f| {
4953 let is_file = meta_cache
4954 .get(f)
4955 .and_then(|(m, _)| m.as_ref())
4956 .map(|m| m.is_file())
4957 .unwrap_or(false);
4958 if negate {
4959 !is_file
4960 } else {
4961 is_file
4962 }
4963 })
4964 .collect();
4965 negate = false;
4966 }
4967 '/' => {
4968 result = result
4969 .into_iter()
4970 .filter(|f| {
4971 let is_dir = meta_cache
4972 .get(f)
4973 .and_then(|(m, _)| m.as_ref())
4974 .map(|m| m.is_dir())
4975 .unwrap_or(false);
4976 if negate {
4977 !is_dir
4978 } else {
4979 is_dir
4980 }
4981 })
4982 .collect();
4983 negate = false;
4984 }
4985 '@' => {
4986 result = result
4987 .into_iter()
4988 .filter(|f| {
4989 let is_link = meta_cache
4990 .get(f)
4991 .and_then(|(_, sm)| sm.as_ref())
4992 .map(|m| m.file_type().is_symlink())
4993 .unwrap_or(false);
4994 if negate {
4995 !is_link
4996 } else {
4997 is_link
4998 }
4999 })
5000 .collect();
5001 negate = false;
5002 }
5003 '=' => {
5004 use std::os::unix::fs::FileTypeExt;
5006 result = result
5007 .into_iter()
5008 .filter(|f| {
5009 let is_socket = meta_cache
5010 .get(f)
5011 .and_then(|(_, sm)| sm.as_ref())
5012 .map(|m| m.file_type().is_socket())
5013 .unwrap_or(false);
5014 if negate {
5015 !is_socket
5016 } else {
5017 is_socket
5018 }
5019 })
5020 .collect();
5021 negate = false;
5022 }
5023 'p' => {
5024 use std::os::unix::fs::FileTypeExt;
5026 result = result
5027 .into_iter()
5028 .filter(|f| {
5029 let is_fifo = meta_cache
5030 .get(f)
5031 .and_then(|(_, sm)| sm.as_ref())
5032 .map(|m| m.file_type().is_fifo())
5033 .unwrap_or(false);
5034 if negate {
5035 !is_fifo
5036 } else {
5037 is_fifo
5038 }
5039 })
5040 .collect();
5041 negate = false;
5042 }
5043 '*' => {
5044 use std::os::unix::fs::PermissionsExt;
5046 result = result
5047 .into_iter()
5048 .filter(|f| {
5049 let is_exec = meta_cache
5050 .get(f)
5051 .and_then(|(m, _)| m.as_ref())
5052 .map(|m| m.is_file() && (m.permissions().mode() & 0o111) != 0)
5053 .unwrap_or(false);
5054 if negate {
5055 !is_exec
5056 } else {
5057 is_exec
5058 }
5059 })
5060 .collect();
5061 negate = false;
5062 }
5063 '%' => {
5064 use std::os::unix::fs::FileTypeExt;
5066 let next = chars.peek().copied();
5067 result = result
5068 .into_iter()
5069 .filter(|f| {
5070 let is_device = meta_cache
5071 .get(f)
5072 .and_then(|(_, sm)| sm.as_ref())
5073 .map(|m| match next {
5074 Some('b') => m.file_type().is_block_device(),
5075 Some('c') => m.file_type().is_char_device(),
5076 _ => {
5077 m.file_type().is_block_device()
5078 || m.file_type().is_char_device()
5079 }
5080 })
5081 .unwrap_or(false);
5082 if negate {
5083 !is_device
5084 } else {
5085 is_device
5086 }
5087 })
5088 .collect();
5089 if next == Some('b') || next == Some('c') {
5090 chars.next();
5091 }
5092 negate = false;
5093 }
5094
5095 'r' => {
5097 result = self.filter_by_permission(result, 0o400, negate, &meta_cache);
5098 negate = false;
5099 }
5100 'w' => {
5101 result = self.filter_by_permission(result, 0o200, negate, &meta_cache);
5102 negate = false;
5103 }
5104 'x' => {
5105 result = self.filter_by_permission(result, 0o100, negate, &meta_cache);
5106 negate = false;
5107 }
5108 'A' => {
5109 result = self.filter_by_permission(result, 0o040, negate, &meta_cache);
5110 negate = false;
5111 }
5112 'I' => {
5113 result = self.filter_by_permission(result, 0o020, negate, &meta_cache);
5114 negate = false;
5115 }
5116 'E' => {
5117 result = self.filter_by_permission(result, 0o010, negate, &meta_cache);
5118 negate = false;
5119 }
5120 'R' => {
5121 result = self.filter_by_permission(result, 0o004, negate, &meta_cache);
5122 negate = false;
5123 }
5124 'W' => {
5125 result = self.filter_by_permission(result, 0o002, negate, &meta_cache);
5126 negate = false;
5127 }
5128 'X' => {
5129 result = self.filter_by_permission(result, 0o001, negate, &meta_cache);
5130 negate = false;
5131 }
5132 's' => {
5133 result = self.filter_by_permission(result, 0o4000, negate, &meta_cache);
5134 negate = false;
5135 }
5136 'S' => {
5137 result = self.filter_by_permission(result, 0o2000, negate, &meta_cache);
5138 negate = false;
5139 }
5140 't' => {
5141 result = self.filter_by_permission(result, 0o1000, negate, &meta_cache);
5142 negate = false;
5143 }
5144
5145 'F' => {
5147 result = result
5149 .into_iter()
5150 .filter(|f| {
5151 let path = std::path::Path::new(f);
5152 let is_nonempty = path.is_dir()
5153 && std::fs::read_dir(path)
5154 .map(|mut d| d.next().is_some())
5155 .unwrap_or(false);
5156 if negate {
5157 !is_nonempty
5158 } else {
5159 is_nonempty
5160 }
5161 })
5162 .collect();
5163 negate = false;
5164 }
5165
5166 'U' => {
5168 let euid = unsafe { libc::geteuid() };
5170 result = result
5171 .into_iter()
5172 .filter(|f| {
5173 use std::os::unix::fs::MetadataExt;
5174 let is_owned = meta_cache
5175 .get(f)
5176 .and_then(|(m, _)| m.as_ref())
5177 .map(|m| m.uid() == euid)
5178 .unwrap_or(false);
5179 if negate {
5180 !is_owned
5181 } else {
5182 is_owned
5183 }
5184 })
5185 .collect();
5186 negate = false;
5187 }
5188 'G' => {
5189 let egid = unsafe { libc::getegid() };
5191 result = result
5192 .into_iter()
5193 .filter(|f| {
5194 use std::os::unix::fs::MetadataExt;
5195 let is_owned = meta_cache
5196 .get(f)
5197 .and_then(|(m, _)| m.as_ref())
5198 .map(|m| m.gid() == egid)
5199 .unwrap_or(false);
5200 if negate {
5201 !is_owned
5202 } else {
5203 is_owned
5204 }
5205 })
5206 .collect();
5207 negate = false;
5208 }
5209
5210 'o' => {
5212 if chars.peek() == Some(&'n') {
5214 chars.next();
5215 result.sort();
5217 } else if chars.peek() == Some(&'L') {
5218 chars.next();
5219 result.sort_by_key(|f| {
5221 meta_cache
5222 .get(f)
5223 .and_then(|(m, _)| m.as_ref())
5224 .map(|m| m.len())
5225 .unwrap_or(0)
5226 });
5227 } else if chars.peek() == Some(&'m') {
5228 chars.next();
5229 result.sort_by_key(|f| {
5231 meta_cache
5232 .get(f)
5233 .and_then(|(m, _)| m.as_ref())
5234 .and_then(|m| m.modified().ok())
5235 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5236 });
5237 } else if chars.peek() == Some(&'a') {
5238 chars.next();
5239 result.sort_by_key(|f| {
5241 meta_cache
5242 .get(f)
5243 .and_then(|(m, _)| m.as_ref())
5244 .and_then(|m| m.accessed().ok())
5245 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5246 });
5247 }
5248 }
5249 'O' => {
5250 if chars.peek() == Some(&'n') {
5252 chars.next();
5253 result.sort();
5254 result.reverse();
5255 } else if chars.peek() == Some(&'L') {
5256 chars.next();
5257 result.sort_by_key(|f| {
5258 meta_cache
5259 .get(f)
5260 .and_then(|(m, _)| m.as_ref())
5261 .map(|m| m.len())
5262 .unwrap_or(0)
5263 });
5264 result.reverse();
5265 } else if chars.peek() == Some(&'m') {
5266 chars.next();
5267 result.sort_by_key(|f| {
5268 meta_cache
5269 .get(f)
5270 .and_then(|(m, _)| m.as_ref())
5271 .and_then(|m| m.modified().ok())
5272 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5273 });
5274 result.reverse();
5275 } else {
5276 result.reverse();
5278 }
5279 }
5280
5281 '[' => {
5283 let mut range_str = String::new();
5284 while let Some(&ch) = chars.peek() {
5285 if ch == ']' {
5286 chars.next();
5287 break;
5288 }
5289 range_str.push(chars.next().unwrap());
5290 }
5291
5292 if let Some((start, end)) = self.parse_subscript_range(&range_str, result.len())
5293 {
5294 result = result.into_iter().skip(start).take(end - start).collect();
5295 }
5296 }
5297
5298 'D' => {
5300 }
5302 'N' => {
5303 }
5305
5306 _ => {}
5308 }
5309 }
5310
5311 result
5312 }
5313
5314 fn filter_by_permission(
5316 &self,
5317 files: Vec<String>,
5318 mode: u32,
5319 negate: bool,
5320 meta_cache: &HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)>,
5321 ) -> Vec<String> {
5322 use std::os::unix::fs::PermissionsExt;
5323 files
5324 .into_iter()
5325 .filter(|f| {
5326 let has_perm = meta_cache
5327 .get(f)
5328 .and_then(|(m, _)| m.as_ref())
5329 .map(|m| (m.permissions().mode() & mode) != 0)
5330 .unwrap_or(false);
5331 if negate {
5332 !has_perm
5333 } else {
5334 has_perm
5335 }
5336 })
5337 .collect()
5338 }
5339
5340 fn parse_subscript_range(&self, s: &str, len: usize) -> Option<(usize, usize)> {
5342 if s.is_empty() || len == 0 {
5343 return None;
5344 }
5345
5346 let parts: Vec<&str> = s.split(',').collect();
5347
5348 let parse_idx = |idx_str: &str| -> Option<usize> {
5349 let idx: i64 = idx_str.trim().parse().ok()?;
5350 if idx < 0 {
5351 let abs = (-idx) as usize;
5353 if abs > len {
5354 None
5355 } else {
5356 Some(len - abs)
5357 }
5358 } else if idx == 0 {
5359 Some(0)
5360 } else {
5361 Some((idx as usize).saturating_sub(1).min(len))
5363 }
5364 };
5365
5366 match parts.len() {
5367 1 => {
5368 let idx = parse_idx(parts[0])?;
5370 Some((idx, idx + 1))
5371 }
5372 2 => {
5373 let start = parse_idx(parts[0])?;
5375 let end = parse_idx(parts[1])?.saturating_add(1);
5376 Some((start.min(end), start.max(end)))
5377 }
5378 _ => None,
5379 }
5380 }
5381
5382 fn has_extglob_pattern(&self, pattern: &str) -> bool {
5384 let chars: Vec<char> = pattern.chars().collect();
5385 for i in 0..chars.len().saturating_sub(1) {
5386 if (chars[i] == '?'
5387 || chars[i] == '*'
5388 || chars[i] == '+'
5389 || chars[i] == '@'
5390 || chars[i] == '!')
5391 && chars[i + 1] == '('
5392 {
5393 return true;
5394 }
5395 }
5396 false
5397 }
5398
5399 fn extglob_to_regex(&self, pattern: &str) -> String {
5401 let mut regex = String::from("^");
5402 let chars: Vec<char> = pattern.chars().collect();
5403 let mut i = 0;
5404
5405 while i < chars.len() {
5406 let c = chars[i];
5407
5408 if i + 1 < chars.len() && chars[i + 1] == '(' {
5410 match c {
5411 '?' => {
5412 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5414 let inner_regex = self.extglob_inner_to_regex(&inner);
5415 regex.push_str(&format!("({})?", inner_regex));
5416 i = end + 1;
5417 continue;
5418 }
5419 '*' => {
5420 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5422 let inner_regex = self.extglob_inner_to_regex(&inner);
5423 regex.push_str(&format!("({})*", inner_regex));
5424 i = end + 1;
5425 continue;
5426 }
5427 '+' => {
5428 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5430 let inner_regex = self.extglob_inner_to_regex(&inner);
5431 regex.push_str(&format!("({})+", inner_regex));
5432 i = end + 1;
5433 continue;
5434 }
5435 '@' => {
5436 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5438 let inner_regex = self.extglob_inner_to_regex(&inner);
5439 regex.push_str(&format!("({})", inner_regex));
5440 i = end + 1;
5441 continue;
5442 }
5443 '!' => {
5444 let (_, end) = self.extract_extglob_inner(&chars, i + 2);
5447 regex.push_str(".*"); i = end + 1;
5449 continue;
5450 }
5451 _ => {}
5452 }
5453 }
5454
5455 match c {
5457 '*' => regex.push_str(".*"),
5458 '?' => regex.push('.'),
5459 '.' => regex.push_str("\\."),
5460 '[' => {
5461 regex.push('[');
5462 i += 1;
5463 while i < chars.len() && chars[i] != ']' {
5464 if chars[i] == '!' && regex.ends_with('[') {
5465 regex.push('^');
5466 } else {
5467 regex.push(chars[i]);
5468 }
5469 i += 1;
5470 }
5471 regex.push(']');
5472 }
5473 '^' | '$' | '(' | ')' | '{' | '}' | '|' | '\\' => {
5474 regex.push('\\');
5475 regex.push(c);
5476 }
5477 _ => regex.push(c),
5478 }
5479 i += 1;
5480 }
5481
5482 regex.push('$');
5483 regex
5484 }
5485
5486 fn extract_extglob_inner(&self, chars: &[char], start: usize) -> (String, usize) {
5488 let mut inner = String::new();
5489 let mut depth = 1;
5490 let mut i = start;
5491
5492 while i < chars.len() && depth > 0 {
5493 if chars[i] == '(' {
5494 depth += 1;
5495 } else if chars[i] == ')' {
5496 depth -= 1;
5497 if depth == 0 {
5498 return (inner, i);
5499 }
5500 }
5501 inner.push(chars[i]);
5502 i += 1;
5503 }
5504
5505 (inner, i)
5506 }
5507
5508 fn extglob_inner_to_regex(&self, inner: &str) -> String {
5510 let alternatives: Vec<String> = inner
5512 .split('|')
5513 .map(|alt| {
5514 let mut result = String::new();
5515 for c in alt.chars() {
5516 match c {
5517 '*' => result.push_str(".*"),
5518 '?' => result.push('.'),
5519 '.' => result.push_str("\\."),
5520 '^' | '$' | '(' | ')' | '{' | '}' | '\\' => {
5521 result.push('\\');
5522 result.push(c);
5523 }
5524 _ => result.push(c),
5525 }
5526 }
5527 result
5528 })
5529 .collect();
5530
5531 alternatives.join("|")
5532 }
5533
5534 fn expand_extglob(&self, pattern: &str) -> Vec<String> {
5536 let (search_dir, file_pattern) = if let Some(last_slash) = pattern.rfind('/') {
5538 (&pattern[..last_slash], &pattern[last_slash + 1..])
5539 } else {
5540 (".", pattern)
5541 };
5542
5543 if let Some((neg_pat, suffix)) = self.extract_neg_extglob(file_pattern) {
5545 return self.expand_neg_extglob(search_dir, &neg_pat, &suffix, pattern);
5546 }
5547
5548 let regex_str = self.extglob_to_regex(file_pattern);
5550
5551 let re = match cached_regex(®ex_str) {
5552 Some(r) => r,
5553 None => return vec![pattern.to_string()],
5554 };
5555
5556 let mut results = Vec::new();
5557
5558 if let Ok(entries) = std::fs::read_dir(search_dir) {
5559 for entry in entries.flatten() {
5560 let name = entry.file_name().to_string_lossy().to_string();
5561 if name.starts_with('.') && !file_pattern.starts_with('.') {
5563 continue;
5564 }
5565
5566 if re.is_match(&name) {
5567 let full_path = if search_dir == "." {
5568 name
5569 } else {
5570 format!("{}/{}", search_dir, name)
5571 };
5572 results.push(full_path);
5573 }
5574 }
5575 }
5576
5577 if results.is_empty() {
5578 vec![pattern.to_string()]
5579 } else {
5580 results.sort();
5581 results
5582 }
5583 }
5584
5585 fn expand_neg_extglob(
5587 &self,
5588 search_dir: &str,
5589 neg_pat: &str,
5590 suffix: &str,
5591 original_pattern: &str,
5592 ) -> Vec<String> {
5593 let mut results = Vec::new();
5594
5595 if let Ok(entries) = std::fs::read_dir(search_dir) {
5596 for entry in entries.flatten() {
5597 let name = entry.file_name().to_string_lossy().to_string();
5598 if name.starts_with('.') {
5600 continue;
5601 }
5602
5603 if !name.ends_with(suffix) {
5605 continue;
5606 }
5607
5608 let basename = &name[..name.len() - suffix.len()];
5609 let alts: Vec<&str> = neg_pat.split('|').collect();
5611 let matches_neg = alts.iter().any(|alt| {
5612 if alt.contains('*') || alt.contains('?') {
5613 let alt_re = self.extglob_inner_to_regex(alt);
5614 let full_pattern = format!("^{}$", alt_re);
5615 if let Some(r) = cached_regex(&full_pattern) {
5616 r.is_match(basename)
5617 } else {
5618 *alt == basename
5619 }
5620 } else {
5621 *alt == basename
5622 }
5623 });
5624
5625 if !matches_neg {
5626 let full_path = if search_dir == "." {
5627 name
5628 } else {
5629 format!("{}/{}", search_dir, name)
5630 };
5631 results.push(full_path);
5632 }
5633 }
5634 }
5635
5636 if results.is_empty() {
5637 vec![original_pattern.to_string()]
5638 } else {
5639 results.sort();
5640 results
5641 }
5642 }
5643
5644 fn extract_neg_extglob(&self, pattern: &str) -> Option<(String, String)> {
5646 let chars: Vec<char> = pattern.chars().collect();
5647 if chars.len() >= 3 && chars[0] == '!' && chars[1] == '(' {
5648 let mut depth = 1;
5649 let mut i = 2;
5650 while i < chars.len() && depth > 0 {
5651 if chars[i] == '(' {
5652 depth += 1;
5653 } else if chars[i] == ')' {
5654 depth -= 1;
5655 }
5656 i += 1;
5657 }
5658 if depth == 0 {
5659 let inner: String = chars[2..i - 1].iter().collect();
5660 let suffix: String = chars[i..].iter().collect();
5661 return Some((inner, suffix));
5662 }
5663 }
5664 None
5665 }
5666
5667 fn expand_word_split(&mut self, word: &ShellWord) -> Vec<String> {
5669 match word {
5670 ShellWord::Literal(s) => {
5671 let brace_expanded = self.expand_braces(s);
5673 brace_expanded
5674 .into_iter()
5675 .flat_map(|item| self.expand_string_split(&item))
5676 .collect()
5677 }
5678 ShellWord::SingleQuoted(s) => vec![s.clone()],
5679 ShellWord::DoubleQuoted(parts) => {
5680 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
5682 }
5683 ShellWord::Variable(name) => {
5684 let val = env::var(name).unwrap_or_default();
5685 self.split_words(&val)
5686 }
5687 ShellWord::VariableBraced(name, modifier) => {
5688 let val = env::var(name).ok();
5689 let expanded = self.apply_var_modifier(name, val, modifier.as_deref());
5690 self.split_words(&expanded)
5691 }
5692 ShellWord::ArrayVar(name, index) => {
5693 let idx_str = self.expand_word(index);
5694 if idx_str == "@" || idx_str == "*" {
5695 self.arrays.get(name).cloned().unwrap_or_default()
5697 } else {
5698 vec![self.expand_array_access(name, index)]
5699 }
5700 }
5701 ShellWord::Glob(pattern) => match glob::glob(pattern) {
5702 Ok(paths) => {
5703 let expanded: Vec<String> = paths
5704 .filter_map(|p| p.ok())
5705 .map(|p| p.to_string_lossy().to_string())
5706 .collect();
5707 if expanded.is_empty() {
5708 vec![pattern.clone()]
5709 } else {
5710 expanded
5711 }
5712 }
5713 Err(_) => vec![pattern.clone()],
5714 },
5715 ShellWord::CommandSub(_) => {
5716 let val = self.expand_word(word);
5718 self.split_words(&val)
5719 }
5720 ShellWord::Concat(parts) => {
5721 let val = self.expand_concat_parallel(parts);
5723 self.split_words(&val)
5724 }
5725 _ => vec![self.expand_word(word)],
5726 }
5727 }
5728
5729 fn expand_string_split(&mut self, s: &str) -> Vec<String> {
5731 let mut results: Vec<String> = Vec::new();
5732 let mut current = String::new();
5733 let mut chars = s.chars().peekable();
5734
5735 while let Some(c) = chars.next() {
5736 if c == '$' {
5737 if chars.peek() == Some(&'{') {
5738 chars.next(); let mut brace_content = String::new();
5740 let mut depth = 1;
5741 while let Some(ch) = chars.next() {
5742 if ch == '{' {
5743 depth += 1;
5744 brace_content.push(ch);
5745 } else if ch == '}' {
5746 depth -= 1;
5747 if depth == 0 {
5748 break;
5749 }
5750 brace_content.push(ch);
5751 } else {
5752 brace_content.push(ch);
5753 }
5754 }
5755
5756 if let Some(bracket_start) = brace_content.find('[') {
5758 let var_name = &brace_content[..bracket_start];
5759 let bracket_content = &brace_content[bracket_start + 1..];
5760 if let Some(bracket_end) = bracket_content.find(']') {
5761 let index = &bracket_content[..bracket_end];
5762 if (index == "@" || index == "*")
5763 && bracket_end + 1 == bracket_content.len()
5764 {
5765 if !current.is_empty() {
5767 results.push(current.clone());
5768 current.clear();
5769 }
5770 if let Some(arr) = self.arrays.get(var_name) {
5771 results.extend(arr.clone());
5772 }
5773 continue;
5774 }
5775 }
5776 }
5777
5778 current.push_str(&self.expand_braced_variable(&brace_content));
5780 } else {
5781 let mut var_name = String::new();
5783 while let Some(&ch) = chars.peek() {
5784 if ch.is_alphanumeric() || ch == '_' {
5785 var_name.push(chars.next().unwrap());
5786 } else {
5787 break;
5788 }
5789 }
5790 let val = self.get_variable(&var_name);
5791 if !current.is_empty() {
5793 results.push(current.clone());
5794 current.clear();
5795 }
5796 results.extend(self.split_words(&val));
5797 }
5798 } else {
5799 current.push(c);
5800 }
5801 }
5802
5803 if !current.is_empty() {
5804 results.push(current);
5805 }
5806
5807 if results.is_empty() {
5808 results.push(String::new());
5809 }
5810
5811 results
5812 }
5813
5814 fn split_words(&self, s: &str) -> Vec<String> {
5816 let ifs = self
5817 .variables
5818 .get("IFS")
5819 .cloned()
5820 .or_else(|| env::var("IFS").ok())
5821 .unwrap_or_else(|| " \t\n".to_string());
5822
5823 if ifs.is_empty() {
5824 return vec![s.to_string()];
5825 }
5826
5827 s.split(|c: char| ifs.contains(c))
5828 .filter(|s| !s.is_empty())
5829 .map(|s| s.to_string())
5830 .collect()
5831 }
5832
5833 #[tracing::instrument(level = "trace", skip_all)]
5834 fn expand_word(&mut self, word: &ShellWord) -> String {
5835 match word {
5836 ShellWord::Literal(s) => {
5837 let expanded = self.expand_string(s);
5838 expanded
5840 }
5841 ShellWord::SingleQuoted(s) => s.clone(),
5842 ShellWord::DoubleQuoted(parts) => parts.iter().map(|p| self.expand_word(p)).collect(),
5843 ShellWord::Variable(name) => self.get_variable(name),
5844 ShellWord::VariableBraced(name, modifier) => {
5845 let val = env::var(name).ok();
5846 self.apply_var_modifier(name, val, modifier.as_deref())
5847 }
5848 ShellWord::Tilde(user) => {
5849 if let Some(u) = user {
5850 format!("/home/{}", u)
5852 } else {
5853 dirs::home_dir()
5854 .map(|p| p.to_string_lossy().to_string())
5855 .unwrap_or_else(|| "~".to_string())
5856 }
5857 }
5858 ShellWord::Glob(pattern) => {
5859 match glob::glob(pattern) {
5861 Ok(paths) => {
5862 let expanded: Vec<String> = paths
5863 .filter_map(|p| p.ok())
5864 .map(|p| p.to_string_lossy().to_string())
5865 .collect();
5866 if expanded.is_empty() {
5867 pattern.clone()
5868 } else {
5869 expanded.join(" ")
5870 }
5871 }
5872 Err(_) => pattern.clone(),
5873 }
5874 }
5875 ShellWord::Concat(parts) => self.expand_concat_parallel(parts),
5876 ShellWord::CommandSub(cmd) => self.execute_command_substitution(cmd),
5877 ShellWord::ProcessSubIn(cmd) => self.execute_process_sub_in(cmd),
5878 ShellWord::ProcessSubOut(cmd) => self.execute_process_sub_out(cmd),
5879 ShellWord::ArithSub(expr) => self.evaluate_arithmetic(expr),
5880 ShellWord::ArrayVar(name, index) => self.expand_array_access(name, index),
5881 ShellWord::ArrayLiteral(elements) => elements
5882 .iter()
5883 .map(|e| self.expand_word(e))
5884 .collect::<Vec<_>>()
5885 .join(" "),
5886 }
5887 }
5888
5889 fn preflight_command_subs(
5892 &mut self,
5893 words: &[ShellWord],
5894 ) -> Vec<Option<crossbeam_channel::Receiver<String>>> {
5895 use crate::parser::ShellWord;
5896 use std::process::{Command, Stdio};
5897
5898 let mut receivers = Vec::with_capacity(words.len());
5899
5900 let external_count = words
5902 .iter()
5903 .filter(|w| {
5904 if let ShellWord::CommandSub(cmd) = w {
5905 if let ShellCommand::Simple(simple) = cmd.as_ref() {
5906 if let Some(first) = simple.words.first() {
5907 let name = self.expand_word(first);
5908 return !self.functions.contains_key(&name) && !self.is_builtin(&name);
5909 }
5910 }
5911 }
5912 false
5913 })
5914 .count();
5915
5916 if external_count < 2 {
5917 return vec![None; words.len()];
5919 }
5920
5921 for word in words {
5922 if let ShellWord::CommandSub(cmd) = word {
5923 if let ShellCommand::Simple(simple) = cmd.as_ref() {
5924 let first = simple.words.first().map(|w| self.expand_word(w));
5925 if let Some(ref name) = first {
5926 if !self.functions.contains_key(name) && !self.is_builtin(name) {
5927 let expanded: Vec<String> =
5928 simple.words.iter().map(|w| self.expand_word(w)).collect();
5929 let rx = self.worker_pool.submit_with_result(move || {
5930 let output = Command::new(&expanded[0])
5931 .args(&expanded[1..])
5932 .stdout(Stdio::piped())
5933 .stderr(Stdio::inherit())
5934 .output();
5935 match output {
5936 Ok(out) => String::from_utf8_lossy(&out.stdout)
5937 .trim_end_matches('\n')
5938 .to_string(),
5939 Err(_) => String::new(),
5940 }
5941 });
5942 receivers.push(Some(rx));
5943 continue;
5944 }
5945 }
5946 }
5947 }
5948 receivers.push(None);
5949 }
5950
5951 receivers
5952 }
5953
5954 fn expand_concat_parallel(&mut self, parts: &[ShellWord]) -> String {
5957 use crate::parser::ShellWord;
5958 use std::process::{Command, Stdio};
5959
5960 let mut preflight: Vec<Option<crossbeam_channel::Receiver<String>>> =
5962 Vec::with_capacity(parts.len());
5963
5964 for part in parts {
5965 if let ShellWord::CommandSub(cmd) = part {
5966 if let ShellCommand::Simple(simple) = cmd.as_ref() {
5967 let first = simple.words.first().map(|w| self.expand_word(w));
5968 if let Some(ref name) = first {
5969 if !self.functions.contains_key(name) && !self.is_builtin(name) {
5970 let words: Vec<String> =
5972 simple.words.iter().map(|w| self.expand_word(w)).collect();
5973 let rx = self.worker_pool.submit_with_result(move || {
5974 let output = Command::new(&words[0])
5975 .args(&words[1..])
5976 .stdout(Stdio::piped())
5977 .stderr(Stdio::inherit())
5978 .output();
5979 match output {
5980 Ok(out) => String::from_utf8_lossy(&out.stdout)
5981 .trim_end_matches('\n')
5982 .to_string(),
5983 Err(_) => String::new(),
5984 }
5985 });
5986 preflight.push(Some(rx));
5987 continue;
5988 }
5989 }
5990 }
5991 }
5992 preflight.push(None); }
5994
5995 let mut result = String::new();
5997 for (i, part) in parts.iter().enumerate() {
5998 if let Some(rx) = preflight[i].take() {
5999 result.push_str(&rx.recv().unwrap_or_default());
6001 } else {
6002 result.push_str(&self.expand_word(part));
6004 }
6005 }
6006 result
6007 }
6008
6009 fn expand_braced_variable(&mut self, content: &str) -> String {
6010 if content.starts_with("${") {
6012 let mut depth = 0;
6014 let mut inner_end = 0;
6015 for (i, c) in content.char_indices() {
6016 match c {
6017 '{' => depth += 1,
6018 '}' => {
6019 depth -= 1;
6020 if depth == 0 {
6021 inner_end = i;
6022 break;
6023 }
6024 }
6025 _ => {}
6026 }
6027 }
6028
6029 if inner_end > 0 {
6030 let inner_content = &content[2..inner_end];
6032 let inner_result = self.expand_braced_variable(inner_content);
6033
6034 let rest = &content[inner_end + 1..];
6036 if rest.starts_with('[') {
6037 if let Some(bracket_end) = rest.find(']') {
6039 let index = &rest[1..bracket_end];
6040 if let Ok(idx) = index.parse::<i64>() {
6041 let chars: Vec<char> = inner_result.chars().collect();
6042 let actual_idx = if idx < 0 {
6043 (chars.len() as i64 + idx).max(0) as usize
6044 } else if idx > 0 {
6045 (idx - 1) as usize
6046 } else {
6047 0
6048 };
6049 return chars
6050 .get(actual_idx)
6051 .map(|c| c.to_string())
6052 .unwrap_or_default();
6053 }
6054 }
6055 }
6056
6057 return inner_result;
6058 }
6059 }
6060
6061 if content.starts_with('(') {
6063 if let Some(close_paren) = content.find(')') {
6064 let flags_str = &content[1..close_paren];
6065 let rest = &content[close_paren + 1..];
6066 let flags = self.parse_zsh_flags(flags_str);
6067
6068 let has_match_flag = flags.iter().any(|f| matches!(f, ZshParamFlag::Match));
6070
6071 if let Some(filter_pos) = rest.find(":#") {
6073 let var_name = &rest[..filter_pos];
6074 let pattern = &rest[filter_pos + 2..];
6075
6076 if let Some(arr) = self.arrays.get(var_name).cloned() {
6078 let filtered: Vec<String> = if arr.len() >= 1000 {
6079 tracing::trace!(
6080 count = arr.len(),
6081 pattern,
6082 "using parallel filter (rayon) for large array"
6083 );
6084 use rayon::prelude::*;
6085 let pattern = pattern.to_string();
6086 arr.into_par_iter()
6087 .filter(|elem| {
6088 let m = Self::glob_match_static(elem, &pattern);
6089 if has_match_flag {
6090 m
6091 } else {
6092 !m
6093 }
6094 })
6095 .collect()
6096 } else {
6097 arr.into_iter()
6098 .filter(|elem| {
6099 let m = self.glob_match(elem, pattern);
6100 if has_match_flag {
6101 m
6102 } else {
6103 !m
6104 }
6105 })
6106 .collect()
6107 };
6108 return filtered.join(" ");
6109 }
6110
6111 let val = self.get_variable(var_name);
6113 let matches = self.glob_match(&val, pattern);
6114
6115 return if has_match_flag {
6116 if matches {
6117 val
6118 } else {
6119 String::new()
6120 }
6121 } else {
6122 if matches {
6123 String::new()
6124 } else {
6125 val
6126 }
6127 };
6128 }
6129
6130 let (var_name, default_val) = if rest.starts_with(":-") {
6133 ("", Some(&rest[2..]))
6135 } else if let Some(pos) = rest.find(":-") {
6136 (&rest[..pos], Some(&rest[pos + 2..]))
6138 } else if rest.starts_with(':') {
6139 ("", None)
6141 } else {
6142 let vn = rest
6144 .split(|c: char| !c.is_alphanumeric() && c != '_')
6145 .next()
6146 .unwrap_or("");
6147 (vn, None)
6148 };
6149
6150 let mut val = self.get_variable(var_name);
6151
6152 if val.is_empty() {
6154 if let Some(def) = default_val {
6155 val = self.expand_string(def);
6157 }
6158 }
6159
6160 for flag in &flags {
6162 val = self.apply_zsh_param_flag(&val, var_name, flag);
6163 }
6164 return val;
6165 }
6166 }
6167
6168 if content.starts_with('#') {
6170 let rest = &content[1..];
6171 if let Some(bracket_start) = rest.find('[') {
6172 let var_name = &rest[..bracket_start];
6173 let bracket_content = &rest[bracket_start + 1..];
6174 if let Some(bracket_end) = bracket_content.find(']') {
6175 let index = &bracket_content[..bracket_end];
6176 if index == "@" || index == "*" {
6177 return self
6179 .arrays
6180 .get(var_name)
6181 .map(|arr| arr.len().to_string())
6182 .unwrap_or_else(|| "0".to_string());
6183 }
6184 }
6185 }
6186 if self.arrays.contains_key(rest) {
6188 return self
6189 .arrays
6190 .get(rest)
6191 .map(|arr| arr.len().to_string())
6192 .unwrap_or_else(|| "0".to_string());
6193 }
6194 if self.assoc_arrays.contains_key(rest) {
6196 return self
6197 .assoc_arrays
6198 .get(rest)
6199 .map(|h| h.len().to_string())
6200 .unwrap_or_else(|| "0".to_string());
6201 }
6202 let val = self.get_variable(rest);
6204 return val.len().to_string();
6205 }
6206
6207 if content.starts_with('+') {
6209 let rest = &content[1..];
6210
6211 if let Some(bracket_start) = rest.find('[') {
6213 let var_name = &rest[..bracket_start];
6214 let bracket_content = &rest[bracket_start + 1..];
6215 if let Some(bracket_end) = bracket_content.find(']') {
6216 let key = &bracket_content[..bracket_end];
6217
6218 if let Some(val) = self.get_special_array_value(var_name, key) {
6220 return if val.is_empty() {
6221 "0".to_string()
6222 } else {
6223 "1".to_string()
6224 };
6225 }
6226
6227 if self.assoc_arrays.contains_key(var_name) {
6229 let expanded_key = self.expand_string(key);
6230 let has_key = self
6231 .assoc_arrays
6232 .get(var_name)
6233 .map(|a| a.contains_key(&expanded_key))
6234 .unwrap_or(false);
6235 return if has_key {
6236 "1".to_string()
6237 } else {
6238 "0".to_string()
6239 };
6240 }
6241
6242 if let Some(arr) = self.arrays.get(var_name) {
6244 if let Ok(idx) = key.parse::<usize>() {
6245 let actual_idx = if idx > 0 { idx - 1 } else { 0 };
6246 return if arr.get(actual_idx).is_some() {
6247 "1".to_string()
6248 } else {
6249 "0".to_string()
6250 };
6251 }
6252 }
6253
6254 return "0".to_string();
6255 }
6256 }
6257
6258 let is_set = self.variables.contains_key(rest)
6260 || self.arrays.contains_key(rest)
6261 || self.assoc_arrays.contains_key(rest)
6262 || std::env::var(rest).is_ok()
6263 || self.functions.contains_key(rest);
6264 return if is_set {
6265 "1".to_string()
6266 } else {
6267 "0".to_string()
6268 };
6269 }
6270
6271 if let Some(bracket_start) = content.find('[') {
6273 let var_name = &content[..bracket_start];
6274 let bracket_content = &content[bracket_start + 1..];
6275 if let Some(bracket_end) = bracket_content.find(']') {
6276 let index = &bracket_content[..bracket_end];
6277
6278 if let Some(val) = self.get_special_array_value(var_name, index) {
6280 return val;
6281 }
6282
6283 if self.assoc_arrays.contains_key(var_name) {
6285 if index == "@" || index == "*" {
6286 return self
6288 .assoc_arrays
6289 .get(var_name)
6290 .map(|a| a.values().cloned().collect::<Vec<_>>().join(" "))
6291 .unwrap_or_default();
6292 } else {
6293 let key = self.expand_string(index);
6295 return self
6296 .assoc_arrays
6297 .get(var_name)
6298 .and_then(|a| a.get(&key).cloned())
6299 .unwrap_or_default();
6300 }
6301 }
6302
6303 if index == "@" || index == "*" {
6305 return self
6307 .arrays
6308 .get(var_name)
6309 .map(|arr| arr.join(" "))
6310 .unwrap_or_default();
6311 }
6312
6313 use crate::subscript::{
6315 get_array_by_subscript, get_array_element_by_subscript, getindex,
6316 };
6317 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
6318
6319 if let Ok(v) = getindex(index, false, ksh_arrays) {
6320 if let Some(arr) = self.arrays.get(var_name) {
6322 if v.is_all() {
6323 return arr.join(" ");
6324 }
6325 let is_range = index.contains(',');
6329 if is_range {
6330 return get_array_by_subscript(arr, &v, ksh_arrays).join(" ");
6332 } else {
6333 return get_array_element_by_subscript(arr, &v, ksh_arrays)
6335 .unwrap_or_default();
6336 }
6337 }
6338
6339 let val = self.get_variable(var_name);
6341 if !val.is_empty() {
6342 let chars: Vec<char> = val.chars().collect();
6343 let idx = v.start;
6344 let actual_idx = if idx < 0 {
6345 (chars.len() as i64 + idx).max(0) as usize
6346 } else if idx > 0 {
6347 (idx - 1) as usize } else {
6349 0
6350 };
6351
6352 if v.end > v.start + 1 {
6353 let end_idx = if v.end < 0 {
6355 (chars.len() as i64 + v.end + 1).max(0) as usize
6356 } else {
6357 v.end as usize
6358 };
6359 let end_idx = end_idx.min(chars.len());
6360 return chars[actual_idx..end_idx].iter().collect();
6361 } else {
6362 return chars
6363 .get(actual_idx)
6364 .map(|c| c.to_string())
6365 .unwrap_or_default();
6366 }
6367 }
6368 return String::new();
6369 }
6370
6371 return String::new();
6373 }
6374 }
6375
6376 if let Some(colon_pos) = content.find(':') {
6378 let var_name = &content[..colon_pos];
6379 let rest = &content[colon_pos + 1..];
6380 let val = self.get_variable(var_name);
6381 let val_opt = if val.is_empty() {
6382 None
6383 } else {
6384 Some(val.clone())
6385 };
6386
6387 if rest.starts_with('-') {
6388 return match val_opt {
6390 Some(v) if !v.is_empty() => v,
6391 _ => self.expand_string(&rest[1..]),
6392 };
6393 } else if rest.starts_with('=') {
6394 return match val_opt {
6396 Some(v) if !v.is_empty() => v,
6397 _ => {
6398 let default = self.expand_string(&rest[1..]);
6399 self.variables.insert(var_name.to_string(), default.clone());
6400 default
6401 }
6402 };
6403 } else if rest.starts_with('?') {
6404 return match val_opt {
6406 Some(v) if !v.is_empty() => v,
6407 _ => {
6408 let msg = self.expand_string(&rest[1..]);
6409 eprintln!("zshrs: {}: {}", var_name, msg);
6410 String::new()
6411 }
6412 };
6413 } else if rest.starts_with('+') {
6414 return match val_opt {
6416 Some(v) if !v.is_empty() => self.expand_string(&rest[1..]),
6417 _ => String::new(),
6418 };
6419 } else if rest.starts_with('#') {
6420 let pattern = &rest[1..];
6423 if self.glob_match(&val, pattern) {
6425 return String::new();
6426 } else {
6427 return val;
6428 }
6429 } else if self.is_history_modifier(rest) {
6430 return self.apply_history_modifiers(&val, rest);
6433 } else if rest
6434 .chars()
6435 .next()
6436 .map(|c| c.is_ascii_digit() || c == '-')
6437 .unwrap_or(false)
6438 {
6439 let parts: Vec<&str> = rest.splitn(2, ':').collect();
6441 let offset: i64 = parts[0].parse().unwrap_or(0);
6442 let length: Option<usize> = parts.get(1).and_then(|s| s.parse().ok());
6443
6444 let start = if offset < 0 {
6445 (val.len() as i64 + offset).max(0) as usize
6446 } else {
6447 (offset as usize).min(val.len())
6448 };
6449
6450 return if let Some(len) = length {
6451 val.chars().skip(start).take(len).collect()
6452 } else {
6453 val.chars().skip(start).collect()
6454 };
6455 }
6456 }
6457
6458 if let Some(slash_pos) = content.find('/') {
6461 let var_name = &content[..slash_pos];
6462 if !var_name.is_empty()
6464 && var_name
6465 .chars()
6466 .next()
6467 .map(|c| c.is_alphabetic() || c == '_')
6468 .unwrap_or(false)
6469 && var_name.chars().all(|c| c.is_alphanumeric() || c == '_')
6470 {
6471 let rest = &content[slash_pos + 1..];
6472 let val = self.get_variable(var_name);
6473
6474 let replace_all = rest.starts_with('/');
6475 let rest = if replace_all { &rest[1..] } else { rest };
6476
6477 let parts: Vec<&str> = rest.splitn(2, '/').collect();
6478 let pattern = parts.get(0).unwrap_or(&"");
6479 let replacement = parts.get(1).unwrap_or(&"");
6480
6481 return if replace_all {
6482 val.replace(pattern, replacement)
6483 } else {
6484 val.replacen(pattern, replacement, 1)
6485 };
6486 }
6487 }
6488
6489 if let Some(hash_pos) = content.find('#') {
6492 if hash_pos > 0 {
6493 let var_name = &content[..hash_pos];
6494 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
6496 let rest = &content[hash_pos + 1..];
6497 let val = self.get_variable(var_name);
6498
6499 let long = rest.starts_with('#');
6500 let pattern = if long { &rest[1..] } else { rest };
6501
6502 let pattern_regex = regex::escape(pattern)
6504 .replace(r"\*", ".*")
6505 .replace(r"\?", ".");
6506 let full_pattern = format!("^{}", pattern_regex);
6507
6508 if let Some(re) = cached_regex(&full_pattern) {
6509 if long {
6510 let mut longest_end = 0;
6512 for m in re.find_iter(&val) {
6513 if m.end() > longest_end {
6514 longest_end = m.end();
6515 }
6516 }
6517 if longest_end > 0 {
6518 return val[longest_end..].to_string();
6519 }
6520 } else {
6521 if let Some(m) = re.find(&val) {
6523 return val[m.end()..].to_string();
6524 }
6525 }
6526 }
6527 return val;
6528 }
6529 }
6530 }
6531
6532 if let Some(pct_pos) = content.find('%') {
6534 if pct_pos > 0 {
6535 let var_name = &content[..pct_pos];
6536 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
6537 let rest = &content[pct_pos + 1..];
6538 let val = self.get_variable(var_name);
6539
6540 let long = rest.starts_with('%');
6541 let pattern = if long { &rest[1..] } else { rest };
6542
6543 if let Ok(glob) = glob::Pattern::new(pattern) {
6545 if long {
6546 for i in 0..=val.len() {
6548 if glob.matches(&val[i..]) {
6549 return val[..i].to_string();
6550 }
6551 }
6552 } else {
6553 for i in (0..=val.len()).rev() {
6555 if glob.matches(&val[i..]) {
6556 return val[..i].to_string();
6557 }
6558 }
6559 }
6560 }
6561 return val;
6562 }
6563 }
6564 }
6565
6566 if let Some(caret_pos) = content.find('^') {
6568 let var_name = &content[..caret_pos];
6569 let val = self.get_variable(var_name);
6570 let all = content[caret_pos + 1..].starts_with('^');
6571
6572 return if all {
6573 val.to_uppercase()
6574 } else {
6575 let mut chars = val.chars();
6576 match chars.next() {
6577 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
6578 None => String::new(),
6579 }
6580 };
6581 }
6582
6583 if let Some(comma_pos) = content.find(',') {
6585 let var_name = &content[..comma_pos];
6586 let val = self.get_variable(var_name);
6587 let all = content[comma_pos + 1..].starts_with(',');
6588
6589 return if all {
6590 val.to_lowercase()
6591 } else {
6592 let mut chars = val.chars();
6593 match chars.next() {
6594 Some(first) => first.to_lowercase().to_string() + chars.as_str(),
6595 None => String::new(),
6596 }
6597 };
6598 }
6599
6600 if content.starts_with('!') {
6602 let rest = &content[1..];
6603 if rest.ends_with('*') || rest.ends_with('@') {
6604 let prefix = &rest[..rest.len() - 1];
6605 let mut matches: Vec<String> = self
6606 .variables
6607 .keys()
6608 .filter(|k| k.starts_with(prefix))
6609 .cloned()
6610 .collect();
6611 for k in self.arrays.keys() {
6613 if k.starts_with(prefix) && !matches.contains(k) {
6614 matches.push(k.clone());
6615 }
6616 }
6617 matches.sort();
6618 return matches.join(" ");
6619 }
6620
6621 let var_name = self.get_variable(rest);
6623 return self.get_variable(&var_name);
6624 }
6625
6626 self.get_variable(content)
6628 }
6629
6630 fn expand_array_access(&mut self, name: &str, index: &ShellWord) -> String {
6631 use crate::subscript::{get_array_by_subscript, get_array_element_by_subscript, getindex};
6632
6633 let idx_str = self.expand_word(index);
6634 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
6635
6636 match getindex(&idx_str, false, ksh_arrays) {
6638 Ok(v) => {
6639 if let Some(arr) = self.arrays.get(name) {
6640 if v.is_all() {
6641 arr.join(" ")
6642 } else if v.start == v.end - 1 {
6643 get_array_element_by_subscript(arr, &v, ksh_arrays).unwrap_or_default()
6645 } else {
6646 get_array_by_subscript(arr, &v, ksh_arrays).join(" ")
6648 }
6649 } else {
6650 String::new()
6651 }
6652 }
6653 Err(_) => String::new(),
6654 }
6655 }
6656
6657 #[tracing::instrument(level = "trace", skip_all)]
6658 fn expand_string(&mut self, s: &str) -> String {
6659 let mut result = String::new();
6660 let mut chars = s.chars().peekable();
6661
6662 while let Some(c) = chars.next() {
6663 if c == '\x00' {
6665 if let Some(literal_char) = chars.next() {
6666 result.push(literal_char);
6667 }
6668 continue;
6669 }
6670 if c == '$' {
6671 if chars.peek() == Some(&'(') {
6672 chars.next(); if chars.peek() == Some(&'(') {
6676 chars.next(); let expr = Self::collect_until_double_paren(&mut chars);
6678 result.push_str(&self.evaluate_arithmetic(&expr));
6679 } else {
6680 let cmd_str = Self::collect_until_paren(&mut chars);
6682 result.push_str(&self.run_command_substitution(&cmd_str));
6683 }
6684 } else if chars.peek() == Some(&'{') {
6685 chars.next();
6686 let mut brace_content = String::new();
6688 let mut depth = 1;
6689 while let Some(c) = chars.next() {
6690 if c == '{' {
6691 depth += 1;
6692 brace_content.push(c);
6693 } else if c == '}' {
6694 depth -= 1;
6695 if depth == 0 {
6696 break;
6697 }
6698 brace_content.push(c);
6699 } else {
6700 brace_content.push(c);
6701 }
6702 }
6703 result.push_str(&self.expand_braced_variable(&brace_content));
6704 } else {
6705 if matches!(chars.peek(), Some(&'$') | Some(&'!') | Some(&'-')) {
6707 let sc = chars.next().unwrap();
6708 result.push_str(&self.get_variable(&sc.to_string()));
6709 continue;
6710 }
6711 if chars.peek() == Some(&'#') {
6713 let mut peek_iter = chars.clone();
6714 peek_iter.next(); if peek_iter
6716 .peek()
6717 .map(|c| c.is_alphabetic() || *c == '_')
6718 .unwrap_or(false)
6719 {
6720 chars.next(); let mut name = String::new();
6722 while let Some(&c) = chars.peek() {
6723 if c.is_alphanumeric() || c == '_' {
6724 name.push(chars.next().unwrap());
6725 } else {
6726 break;
6727 }
6728 }
6729 let len = if let Some(arr) = self.arrays.get(&name) {
6731 arr.len()
6732 } else {
6733 self.get_variable(&name).len()
6734 };
6735 result.push_str(&len.to_string());
6736 continue;
6737 }
6738 }
6739 let mut var_name = String::new();
6740 while let Some(&c) = chars.peek() {
6741 if c.is_alphanumeric()
6742 || c == '_'
6743 || c == '@'
6744 || c == '*'
6745 || c == '#'
6746 || c == '?'
6747 {
6748 var_name.push(chars.next().unwrap());
6749 if matches!(
6751 var_name.as_str(),
6752 "@" | "*"
6753 | "#"
6754 | "?"
6755 | "$"
6756 | "!"
6757 | "-"
6758 | "0"
6759 | "1"
6760 | "2"
6761 | "3"
6762 | "4"
6763 | "5"
6764 | "6"
6765 | "7"
6766 | "8"
6767 | "9"
6768 ) {
6769 break;
6770 }
6771 } else {
6772 break;
6773 }
6774 }
6775 result.push_str(&self.get_variable(&var_name));
6776 }
6777 } else if c == '`' {
6778 let cmd_str: String = chars.by_ref().take_while(|&c| c != '`').collect();
6780 result.push_str(&self.run_command_substitution(&cmd_str));
6781 } else if c == '<' && chars.peek() == Some(&'(') {
6782 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
6785 result.push_str(&self.run_process_sub_in(&cmd_str));
6786 } else if c == '>' && chars.peek() == Some(&'(') {
6787 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
6790 result.push_str(&self.run_process_sub_out(&cmd_str));
6791 } else if c == '~' && result.is_empty() {
6792 if let Some(home) = dirs::home_dir() {
6793 result.push_str(&home.to_string_lossy());
6794 } else {
6795 result.push(c);
6796 }
6797 } else {
6798 result.push(c);
6799 }
6800 }
6801
6802 result
6803 }
6804
6805 fn collect_until_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
6806 let mut result = String::new();
6807 let mut depth = 1;
6808
6809 while let Some(c) = chars.next() {
6810 if c == '(' {
6811 depth += 1;
6812 result.push(c);
6813 } else if c == ')' {
6814 depth -= 1;
6815 if depth == 0 {
6816 break;
6817 }
6818 result.push(c);
6819 } else {
6820 result.push(c);
6821 }
6822 }
6823
6824 result
6825 }
6826
6827 fn collect_until_double_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
6828 let mut result = String::new();
6829 let mut arith_depth = 1; let mut paren_depth = 0; while let Some(c) = chars.next() {
6833 if c == '(' {
6834 if paren_depth == 0 && chars.peek() == Some(&'(') {
6835 paren_depth += 1;
6838 result.push(c);
6839 } else {
6840 paren_depth += 1;
6841 result.push(c);
6842 }
6843 } else if c == ')' {
6844 if paren_depth > 0 {
6845 paren_depth -= 1;
6847 result.push(c);
6848 } else if chars.peek() == Some(&')') {
6849 chars.next();
6851 arith_depth -= 1;
6852 if arith_depth == 0 {
6853 break;
6854 }
6855 result.push_str("))");
6856 } else {
6857 result.push(c);
6859 }
6860 } else {
6861 result.push(c);
6862 }
6863 }
6864
6865 result
6866 }
6867
6868 fn run_process_sub_in(&mut self, cmd_str: &str) -> String {
6869 use std::fs;
6870 use std::process::Stdio;
6871
6872 let mut parser = ShellParser::new(cmd_str);
6874 let commands = match parser.parse_script() {
6875 Ok(cmds) => cmds,
6876 Err(_) => return String::new(),
6877 };
6878
6879 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
6881 let fifo_counter = self.process_sub_counter;
6882 self.process_sub_counter += 1;
6883 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
6884
6885 let _ = fs::remove_file(&fifo_path);
6887 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
6888 return String::new();
6889 }
6890
6891 let fifo_clone = fifo_path.clone();
6893 if let Some(cmd) = commands.first() {
6894 if let ShellCommand::Simple(simple) = cmd {
6895 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
6896 if !words.is_empty() {
6897 let cmd_name = words[0].clone();
6898 let args: Vec<String> = words[1..].to_vec();
6899
6900 self.worker_pool.submit(move || {
6901 if let Ok(fifo) = fs::OpenOptions::new().write(true).open(&fifo_clone) {
6903 let _ = Command::new(&cmd_name)
6904 .args(&args)
6905 .stdout(fifo)
6906 .stderr(Stdio::inherit())
6907 .status();
6908 }
6909 let _ = fs::remove_file(&fifo_clone);
6911 });
6912 }
6913 }
6914 }
6915
6916 fifo_path
6917 }
6918
6919 fn run_process_sub_out(&mut self, cmd_str: &str) -> String {
6920 use std::fs;
6921 use std::process::Stdio;
6922
6923 let mut parser = ShellParser::new(cmd_str);
6925 let commands = match parser.parse_script() {
6926 Ok(cmds) => cmds,
6927 Err(_) => return String::new(),
6928 };
6929
6930 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
6932 let fifo_counter = self.process_sub_counter;
6933 self.process_sub_counter += 1;
6934 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
6935
6936 let _ = fs::remove_file(&fifo_path);
6938 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
6939 return String::new();
6940 }
6941
6942 let fifo_clone = fifo_path.clone();
6944 if let Some(cmd) = commands.first() {
6945 if let ShellCommand::Simple(simple) = cmd {
6946 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
6947 if !words.is_empty() {
6948 let cmd_name = words[0].clone();
6949 let args: Vec<String> = words[1..].to_vec();
6950
6951 self.worker_pool.submit(move || {
6952 if let Ok(fifo) = fs::File::open(&fifo_clone) {
6954 let _ = Command::new(&cmd_name)
6955 .args(&args)
6956 .stdin(fifo)
6957 .stdout(Stdio::inherit())
6958 .stderr(Stdio::inherit())
6959 .status();
6960 }
6961 let _ = fs::remove_file(&fifo_clone);
6963 });
6964 }
6965 }
6966 }
6967
6968 fifo_path
6969 }
6970
6971 fn run_command_substitution(&mut self, cmd_str: &str) -> String {
6972 use std::process::Stdio;
6973
6974 let mut parser = ShellParser::new(cmd_str);
6980 let commands = match parser.parse_script() {
6981 Ok(cmds) => cmds,
6982 Err(_) => return String::new(),
6983 };
6984
6985 if commands.is_empty() {
6986 return String::new();
6987 }
6988
6989 let is_internal = if let ShellCommand::Simple(simple) = &commands[0] {
6992 let first = simple.words.first().map(|w| self.expand_word(w));
6993 if let Some(ref name) = first {
6994 self.functions.contains_key(name) || self.is_builtin(name)
6995 } else {
6996 true
6997 }
6998 } else {
6999 true };
7001
7002 if is_internal {
7003 let (read_fd, write_fd) = {
7005 let mut fds = [0i32; 2];
7006 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
7007 return String::new();
7008 }
7009 (fds[0], fds[1])
7010 };
7011
7012 let saved_stdout = unsafe { libc::dup(1) };
7014 unsafe {
7015 libc::dup2(write_fd, 1);
7016 }
7017 unsafe {
7018 libc::close(write_fd);
7019 }
7020
7021 for cmd in &commands {
7023 let _ = self.execute_command(cmd);
7024 }
7025
7026 use std::io::Write;
7028 let _ = io::stdout().flush();
7029
7030 unsafe {
7032 libc::dup2(saved_stdout, 1);
7033 }
7034 unsafe {
7035 libc::close(saved_stdout);
7036 }
7037
7038 use std::os::unix::io::FromRawFd;
7040 let mut output = String::new();
7041 let read_file = unsafe { std::fs::File::from_raw_fd(read_fd) };
7042 use std::io::Read;
7043 let _ = std::io::BufReader::new(read_file).read_to_string(&mut output);
7044
7045 output.trim_end_matches('\n').to_string()
7046 } else {
7047 if let ShellCommand::Simple(simple) = &commands[0] {
7049 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7050 if words.is_empty() {
7051 return String::new();
7052 }
7053
7054 let output = Command::new(&words[0])
7055 .args(&words[1..])
7056 .stdout(Stdio::piped())
7057 .stderr(Stdio::inherit())
7058 .output();
7059
7060 match output {
7061 Ok(out) => String::from_utf8_lossy(&out.stdout)
7062 .trim_end_matches('\n')
7063 .to_string(),
7064 Err(_) => String::new(),
7065 }
7066 } else {
7067 String::new()
7068 }
7069 }
7070 }
7071
7072 fn execute_process_sub_in(&mut self, cmd: &ShellCommand) -> String {
7074 if let ShellCommand::Simple(simple) = cmd {
7075 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7076 let cmd_str = words.join(" ");
7077 self.run_process_sub_in(&cmd_str)
7078 } else {
7079 String::new()
7080 }
7081 }
7082
7083 fn execute_process_sub_out(&mut self, cmd: &ShellCommand) -> String {
7085 if let ShellCommand::Simple(simple) = cmd {
7086 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7087 let cmd_str = words.join(" ");
7088 self.run_process_sub_out(&cmd_str)
7089 } else {
7090 String::new()
7091 }
7092 }
7093
7094 fn get_special_array_value(&self, array_name: &str, key: &str) -> Option<String> {
7097 match array_name {
7098 "options" => {
7100 if key == "@" || key == "*" {
7101 let opts: Vec<String> = self
7103 .options
7104 .iter()
7105 .map(|(k, v)| format!("{}={}", k, if *v { "on" } else { "off" }))
7106 .collect();
7107 return Some(opts.join(" "));
7108 }
7109 let opt_name = key.to_lowercase().replace('_', "");
7110 let is_on = self.options.get(&opt_name).copied().unwrap_or(false);
7111 Some(if is_on {
7112 "on".to_string()
7113 } else {
7114 "off".to_string()
7115 })
7116 }
7117
7118 "aliases" => {
7120 if key == "@" || key == "*" {
7121 let vals: Vec<String> = self.aliases.values().cloned().collect();
7122 return Some(vals.join(" "));
7123 }
7124 Some(self.aliases.get(key).cloned().unwrap_or_default())
7125 }
7126 "galiases" => {
7127 if key == "@" || key == "*" {
7128 let vals: Vec<String> = self.global_aliases.values().cloned().collect();
7129 return Some(vals.join(" "));
7130 }
7131 Some(self.global_aliases.get(key).cloned().unwrap_or_default())
7132 }
7133 "saliases" => {
7134 if key == "@" || key == "*" {
7135 let vals: Vec<String> = self.suffix_aliases.values().cloned().collect();
7136 return Some(vals.join(" "));
7137 }
7138 Some(self.suffix_aliases.get(key).cloned().unwrap_or_default())
7139 }
7140
7141 "functions" => {
7143 if key == "@" || key == "*" {
7144 let names: Vec<String> = self.functions.keys().cloned().collect();
7145 return Some(names.join(" "));
7146 }
7147 if let Some(body) = self.functions.get(key) {
7148 Some(format!("{:?}", body))
7149 } else {
7150 Some(String::new())
7151 }
7152 }
7153 "functions_source" => {
7154 Some(String::new())
7156 }
7157
7158 "commands" => {
7160 if key == "@" || key == "*" {
7161 return Some(String::new()); }
7163 if let Some(path) = self.find_in_path(key) {
7165 Some(path)
7166 } else {
7167 Some(String::new())
7168 }
7169 }
7170
7171 "builtins" => {
7173 let builtins = Self::get_builtin_names();
7174 if key == "@" || key == "*" {
7175 return Some(builtins.join(" "));
7176 }
7177 if builtins.contains(&key) {
7178 Some("defined".to_string())
7179 } else {
7180 Some(String::new())
7181 }
7182 }
7183
7184 "parameters" => {
7186 if key == "@" || key == "*" {
7187 let mut names: Vec<String> = self.variables.keys().cloned().collect();
7188 names.extend(self.arrays.keys().cloned());
7189 names.extend(self.assoc_arrays.keys().cloned());
7190 return Some(names.join(" "));
7191 }
7192 if self.assoc_arrays.contains_key(key) {
7194 Some("association".to_string())
7195 } else if self.arrays.contains_key(key) {
7196 Some("array".to_string())
7197 } else if self.variables.contains_key(key) || std::env::var(key).is_ok() {
7198 Some("scalar".to_string())
7199 } else {
7200 Some(String::new())
7201 }
7202 }
7203
7204 "nameddirs" => {
7206 if key == "@" || key == "*" {
7207 let vals: Vec<String> = self
7208 .named_dirs
7209 .values()
7210 .map(|p| p.display().to_string())
7211 .collect();
7212 return Some(vals.join(" "));
7213 }
7214 Some(
7215 self.named_dirs
7216 .get(key)
7217 .map(|p| p.display().to_string())
7218 .unwrap_or_default(),
7219 )
7220 }
7221
7222 "userdirs" => {
7224 if key == "@" || key == "*" {
7225 return Some(String::new());
7226 }
7227 #[cfg(unix)]
7229 {
7230 use std::ffi::CString;
7231 if let Ok(name) = CString::new(key) {
7232 unsafe {
7233 let pwd = libc::getpwnam(name.as_ptr());
7234 if !pwd.is_null() {
7235 let dir = std::ffi::CStr::from_ptr((*pwd).pw_dir);
7236 return Some(dir.to_string_lossy().to_string());
7237 }
7238 }
7239 }
7240 }
7241 Some(String::new())
7242 }
7243
7244 "usergroups" => {
7246 if key == "@" || key == "*" {
7247 return Some(String::new());
7248 }
7249 #[cfg(unix)]
7251 {
7252 use std::ffi::CString;
7253 if let Ok(name) = CString::new(key) {
7254 unsafe {
7255 let grp = libc::getgrnam(name.as_ptr());
7256 if !grp.is_null() {
7257 return Some((*grp).gr_gid.to_string());
7258 }
7259 }
7260 }
7261 }
7262 Some(String::new())
7263 }
7264
7265 "dirstack" => {
7267 if key == "@" || key == "*" {
7268 let dirs: Vec<String> = self
7269 .dir_stack
7270 .iter()
7271 .map(|p| p.display().to_string())
7272 .collect();
7273 return Some(dirs.join(" "));
7274 }
7275 if let Ok(idx) = key.parse::<usize>() {
7276 Some(
7277 self.dir_stack
7278 .get(idx)
7279 .map(|p| p.display().to_string())
7280 .unwrap_or_default(),
7281 )
7282 } else {
7283 Some(String::new())
7284 }
7285 }
7286
7287 "jobstates" => {
7289 if key == "@" || key == "*" {
7290 let states: Vec<String> = self
7291 .jobs
7292 .iter()
7293 .map(|(id, job)| format!("{}:{:?}", id, job.state))
7294 .collect();
7295 return Some(states.join(" "));
7296 }
7297 if let Ok(id) = key.parse::<usize>() {
7298 if let Some(job) = self.jobs.get(id) {
7299 return Some(format!("{:?}", job.state));
7300 }
7301 }
7302 Some(String::new())
7303 }
7304 "jobtexts" => {
7305 if key == "@" || key == "*" {
7306 let texts: Vec<String> = self
7307 .jobs
7308 .iter()
7309 .map(|(_, job)| job.command.clone())
7310 .collect();
7311 return Some(texts.join(" "));
7312 }
7313 if let Ok(id) = key.parse::<usize>() {
7314 if let Some(job) = self.jobs.get(id) {
7315 return Some(job.command.clone());
7316 }
7317 }
7318 Some(String::new())
7319 }
7320 "jobdirs" => {
7321 if key == "@" || key == "*" {
7323 return Some(String::new());
7324 }
7325 Some(String::new())
7326 }
7327
7328 "history" => {
7330 if key == "@" || key == "*" {
7331 if let Some(ref engine) = self.history {
7333 if let Ok(entries) = engine.recent(100) {
7334 let cmds: Vec<String> =
7335 entries.iter().map(|e| e.command.clone()).collect();
7336 return Some(cmds.join("\n"));
7337 }
7338 }
7339 return Some(String::new());
7340 }
7341 if let Ok(num) = key.parse::<usize>() {
7342 if let Some(ref engine) = self.history {
7343 if let Ok(Some(entry)) = engine.get_by_offset(num.saturating_sub(1)) {
7344 return Some(entry.command);
7345 }
7346 }
7347 }
7348 Some(String::new())
7349 }
7350 "historywords" => {
7351 Some(String::new())
7353 }
7354
7355 "modules" => {
7357 if key == "@" || key == "*" {
7360 return Some("zsh/parameter zsh/zutil".to_string());
7361 }
7362 match key {
7363 "zsh/parameter" | "zsh/zutil" | "zsh/complete" | "zsh/complist" => {
7364 Some("loaded".to_string())
7365 }
7366 _ => Some(String::new()),
7367 }
7368 }
7369
7370 "reswords" => {
7372 let reswords = [
7373 "do",
7374 "done",
7375 "esac",
7376 "then",
7377 "elif",
7378 "else",
7379 "fi",
7380 "for",
7381 "case",
7382 "if",
7383 "while",
7384 "function",
7385 "repeat",
7386 "time",
7387 "until",
7388 "select",
7389 "coproc",
7390 "nocorrect",
7391 "foreach",
7392 "end",
7393 "in",
7394 ];
7395 if key == "@" || key == "*" {
7396 return Some(reswords.join(" "));
7397 }
7398 if let Ok(idx) = key.parse::<usize>() {
7399 Some(reswords.get(idx).map(|s| s.to_string()).unwrap_or_default())
7400 } else {
7401 Some(String::new())
7402 }
7403 }
7404
7405 "patchars" => {
7407 let patchars = ["?", "*", "[", "]", "^", "#", "~", "(", ")", "|"];
7408 if key == "@" || key == "*" {
7409 return Some(patchars.join(" "));
7410 }
7411 if let Ok(idx) = key.parse::<usize>() {
7412 Some(patchars.get(idx).map(|s| s.to_string()).unwrap_or_default())
7413 } else {
7414 Some(String::new())
7415 }
7416 }
7417
7418 "funcstack" | "functrace" | "funcfiletrace" | "funcsourcetrace" => {
7420 Some(String::new())
7422 }
7423
7424 "dis_aliases"
7426 | "dis_galiases"
7427 | "dis_saliases"
7428 | "dis_functions"
7429 | "dis_functions_source"
7430 | "dis_builtins"
7431 | "dis_reswords"
7432 | "dis_patchars" => {
7433 Some(String::new())
7435 }
7436
7437 _ => None,
7439 }
7440 }
7441
7442 fn get_builtin_names() -> Vec<&'static str> {
7444 vec![
7445 ".",
7446 ":",
7447 "[",
7448 "alias",
7449 "autoload",
7450 "bg",
7451 "bind",
7452 "bindkey",
7453 "break",
7454 "builtin",
7455 "bye",
7456 "caller",
7457 "cd",
7458 "cdreplay",
7459 "chdir",
7460 "clone",
7461 "command",
7462 "compadd",
7463 "comparguments",
7464 "compcall",
7465 "compctl",
7466 "compdef",
7467 "compdescribe",
7468 "compfiles",
7469 "compgen",
7470 "compgroups",
7471 "compinit",
7472 "complete",
7473 "compopt",
7474 "compquote",
7475 "compset",
7476 "comptags",
7477 "comptry",
7478 "compvalues",
7479 "continue",
7480 "coproc",
7481 "declare",
7482 "dirs",
7483 "disable",
7484 "disown",
7485 "echo",
7486 "echotc",
7487 "echoti",
7488 "emulate",
7489 "enable",
7490 "eval",
7491 "exec",
7492 "exit",
7493 "export",
7494 "false",
7495 "fc",
7496 "fg",
7497 "float",
7498 "functions",
7499 "getln",
7500 "getopts",
7501 "hash",
7502 "help",
7503 "history",
7504 "integer",
7505 "jobs",
7506 "kill",
7507 "let",
7508 "limit",
7509 "local",
7510 "log",
7511 "logout",
7512 "mapfile",
7513 "noglob",
7514 "popd",
7515 "print",
7516 "printf",
7517 "private",
7518 "prompt",
7519 "promptinit",
7520 "pushd",
7521 "pushln",
7522 "pwd",
7523 "r",
7524 "read",
7525 "readarray",
7526 "readonly",
7527 "rehash",
7528 "return",
7529 "sched",
7530 "set",
7531 "setopt",
7532 "shift",
7533 "shopt",
7534 "source",
7535 "stat",
7536 "strftime",
7537 "suspend",
7538 "test",
7539 "times",
7540 "trap",
7541 "true",
7542 "ttyctl",
7543 "type",
7544 "typeset",
7545 "ulimit",
7546 "umask",
7547 "unalias",
7548 "unfunction",
7549 "unhash",
7550 "unlimit",
7551 "unset",
7552 "unsetopt",
7553 "vared",
7554 "wait",
7555 "whence",
7556 "where",
7557 "which",
7558 "zcompile",
7559 "zcurses",
7560 "zformat",
7561 "zle",
7562 "zmodload",
7563 "zparseopts",
7564 "zprof",
7565 "zpty",
7566 "zregexparse",
7567 "zsocket",
7568 "zstyle",
7569 "ztcp",
7570 "add-zsh-hook",
7571 ]
7572 }
7573
7574 fn get_variable(&self, name: &str) -> String {
7575 match name {
7577 "" => String::new(), "$" => std::process::id().to_string(),
7579 "@" | "*" => self.positional_params.join(" "),
7580 "#" => self.positional_params.len().to_string(),
7581 "?" => self.last_status.to_string(),
7582 "0" => self
7583 .variables
7584 .get("0")
7585 .cloned()
7586 .unwrap_or_else(|| env::args().next().unwrap_or_default()),
7587 n if !n.is_empty() && n.chars().all(|c| c.is_ascii_digit()) => {
7588 let idx: usize = n.parse().unwrap_or(0);
7589 if idx == 0 {
7590 env::args().next().unwrap_or_default()
7591 } else {
7592 self.positional_params
7593 .get(idx - 1)
7594 .cloned()
7595 .unwrap_or_default()
7596 }
7597 }
7598 _ => {
7599 self.variables
7601 .get(name)
7602 .cloned()
7603 .or_else(|| {
7604 self.arrays.get(name).map(|a| a.join(" "))
7606 })
7607 .or_else(|| env::var(name).ok())
7608 .unwrap_or_default()
7609 }
7610 }
7611 }
7612
7613 fn apply_var_modifier(
7614 &mut self,
7615 name: &str,
7616 val: Option<String>,
7617 modifier: Option<&VarModifier>,
7618 ) -> String {
7619 match modifier {
7620 None => val.unwrap_or_default(),
7621
7622 Some(VarModifier::Default(word)) => match &val {
7624 Some(v) if !v.is_empty() => v.clone(),
7625 _ => self.expand_word(word),
7626 },
7627
7628 Some(VarModifier::DefaultAssign(word)) => match &val {
7630 Some(v) if !v.is_empty() => v.clone(),
7631 _ => self.expand_word(word),
7632 },
7633
7634 Some(VarModifier::Error(word)) => match &val {
7636 Some(v) if !v.is_empty() => v.clone(),
7637 _ => {
7638 let msg = self.expand_word(word);
7639 eprintln!("zshrs: {}", msg);
7640 String::new()
7641 }
7642 },
7643
7644 Some(VarModifier::Alternate(word)) => match &val {
7646 Some(v) if !v.is_empty() => self.expand_word(word),
7647 _ => String::new(),
7648 },
7649
7650 Some(VarModifier::Length) => val
7652 .map(|v| v.len().to_string())
7653 .unwrap_or_else(|| "0".to_string()),
7654
7655 Some(VarModifier::Substring(offset, length)) => {
7657 let v = val.unwrap_or_default();
7658 let start = if *offset < 0 {
7659 (v.len() as i64 + offset).max(0) as usize
7660 } else {
7661 (*offset as usize).min(v.len())
7662 };
7663
7664 if let Some(len) = length {
7665 let len = (*len as usize).min(v.len().saturating_sub(start));
7666 v.chars().skip(start).take(len).collect()
7667 } else {
7668 v.chars().skip(start).collect()
7669 }
7670 }
7671
7672 Some(VarModifier::RemovePrefix(pattern)) => {
7674 let v = val.unwrap_or_default();
7675 let pat = self.expand_word(pattern);
7676 if v.starts_with(&pat) {
7677 v[pat.len()..].to_string()
7678 } else {
7679 v
7680 }
7681 }
7682
7683 Some(VarModifier::RemovePrefixLong(pattern)) => {
7685 let v = val.unwrap_or_default();
7686 let pat = self.expand_word(pattern);
7687 if let Ok(glob) = glob::Pattern::new(&pat) {
7689 for i in (0..=v.len()).rev() {
7690 if glob.matches(&v[..i]) {
7691 return v[i..].to_string();
7692 }
7693 }
7694 }
7695 v
7696 }
7697
7698 Some(VarModifier::RemoveSuffix(pattern)) => {
7700 let v = val.unwrap_or_default();
7701 let pat = self.expand_word(pattern);
7702 if let Ok(glob) = glob::Pattern::new(&pat) {
7704 for i in (0..=v.len()).rev() {
7705 if glob.matches(&v[i..]) {
7706 return v[..i].to_string();
7707 }
7708 }
7709 } else if v.ends_with(&pat) {
7710 return v[..v.len() - pat.len()].to_string();
7711 }
7712 v
7713 }
7714
7715 Some(VarModifier::RemoveSuffixLong(pattern)) => {
7717 let v = val.unwrap_or_default();
7718 let pat = self.expand_word(pattern);
7719 if let Ok(glob) = glob::Pattern::new(&pat) {
7721 for i in 0..=v.len() {
7722 if glob.matches(&v[i..]) {
7723 return v[..i].to_string();
7724 }
7725 }
7726 }
7727 v
7728 }
7729
7730 Some(VarModifier::Replace(pattern, replacement)) => {
7732 let v = val.unwrap_or_default();
7733 let pat = self.expand_word(pattern);
7734 let repl = self.expand_word(replacement);
7735 v.replacen(&pat, &repl, 1)
7736 }
7737
7738 Some(VarModifier::ReplaceAll(pattern, replacement)) => {
7740 let v = val.unwrap_or_default();
7741 let pat = self.expand_word(pattern);
7742 let repl = self.expand_word(replacement);
7743 v.replace(&pat, &repl)
7744 }
7745
7746 Some(VarModifier::Upper) => val.map(|v| v.to_uppercase()).unwrap_or_default(),
7748
7749 Some(VarModifier::Lower) => val.map(|v| v.to_lowercase()).unwrap_or_default(),
7751
7752 Some(VarModifier::ZshFlags(flags)) => {
7754 let mut result = val.unwrap_or_default();
7755 for flag in flags {
7756 result = self.apply_zsh_param_flag(&result, name, flag);
7757 }
7758 result
7759 }
7760
7761 Some(VarModifier::ArrayLength)
7763 | Some(VarModifier::ArrayIndex(_))
7764 | Some(VarModifier::ArrayAll) => val.unwrap_or_default(),
7765 }
7766 }
7767
7768 fn is_history_modifier(&self, s: &str) -> bool {
7770 if s.is_empty() {
7771 return false;
7772 }
7773 let first = s.chars().next().unwrap();
7774 matches!(
7775 first,
7776 'A' | 'a' | 'h' | 't' | 'r' | 'e' | 'l' | 'u' | 'q' | 'Q' | 'P'
7777 )
7778 }
7779
7780 fn apply_history_modifiers(&self, val: &str, modifiers: &str) -> String {
7783 let mut result = val.to_string();
7784 let mut chars = modifiers.chars().peekable();
7785
7786 while let Some(c) = chars.next() {
7787 match c {
7788 ':' => continue,
7789 'A' => {
7790 if let Ok(abs) = std::fs::canonicalize(&result) {
7791 result = abs.to_string_lossy().to_string();
7792 } else if !result.starts_with('/') {
7793 if let Ok(cwd) = std::env::current_dir() {
7794 result = cwd.join(&result).to_string_lossy().to_string();
7795 }
7796 }
7797 }
7798 'a' => {
7799 if !result.starts_with('/') {
7800 if let Ok(cwd) = std::env::current_dir() {
7801 result = cwd.join(&result).to_string_lossy().to_string();
7802 }
7803 }
7804 }
7805 'h' => {
7806 if let Some(pos) = result.rfind('/') {
7807 if pos == 0 {
7808 result = "/".to_string();
7809 } else {
7810 result = result[..pos].to_string();
7811 }
7812 } else {
7813 result = ".".to_string();
7814 }
7815 }
7816 't' => {
7817 if let Some(pos) = result.rfind('/') {
7818 result = result[pos + 1..].to_string();
7819 }
7820 }
7821 'r' => {
7822 if let Some(dot_pos) = result.rfind('.') {
7823 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
7824 if dot_pos > slash_pos {
7825 result = result[..dot_pos].to_string();
7826 }
7827 }
7828 }
7829 'e' => {
7830 if let Some(dot_pos) = result.rfind('.') {
7831 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
7832 if dot_pos > slash_pos {
7833 result = result[dot_pos + 1..].to_string();
7834 } else {
7835 result = String::new();
7836 }
7837 } else {
7838 result = String::new();
7839 }
7840 }
7841 'l' => result = result.to_lowercase(),
7842 'u' => result = result.to_uppercase(),
7843 'q' => result = format!("'{}'", result.replace('\'', "'\\''")),
7844 'Q' => {
7845 if result.starts_with('\'') && result.ends_with('\'') && result.len() >= 2 {
7846 result = result[1..result.len() - 1].to_string();
7847 } else if result.starts_with('"') && result.ends_with('"') && result.len() >= 2
7848 {
7849 result = result[1..result.len() - 1].to_string();
7850 }
7851 }
7852 'P' => {
7853 if let Ok(real) = std::fs::canonicalize(&result) {
7854 result = real.to_string_lossy().to_string();
7855 }
7856 }
7857 _ => break,
7858 }
7859 }
7860 result
7861 }
7862
7863 fn parse_zsh_flags(&self, s: &str) -> Vec<ZshParamFlag> {
7865 let mut flags = Vec::new();
7866 let mut chars = s.chars().peekable();
7867
7868 while let Some(c) = chars.next() {
7869 match c {
7870 'L' => flags.push(ZshParamFlag::Lower),
7871 'U' => flags.push(ZshParamFlag::Upper),
7872 'C' => flags.push(ZshParamFlag::Capitalize),
7873 'j' => {
7874 if let Some(&delim) = chars.peek() {
7876 chars.next(); let mut sep = String::new();
7878 while let Some(&ch) = chars.peek() {
7879 if ch == delim {
7880 chars.next();
7881 break;
7882 }
7883 sep.push(chars.next().unwrap());
7884 }
7885 flags.push(ZshParamFlag::Join(sep));
7886 }
7887 }
7888 'F' => flags.push(ZshParamFlag::JoinNewline),
7889 's' => {
7890 if chars.peek() == Some(&':') {
7892 chars.next();
7893 let mut sep = String::new();
7894 while let Some(&ch) = chars.peek() {
7895 if ch == ':' {
7896 chars.next();
7897 break;
7898 }
7899 sep.push(chars.next().unwrap());
7900 }
7901 flags.push(ZshParamFlag::Split(sep));
7902 }
7903 }
7904 'f' => flags.push(ZshParamFlag::SplitLines),
7905 'z' => flags.push(ZshParamFlag::SplitWords),
7906 't' => flags.push(ZshParamFlag::Type),
7907 'w' => flags.push(ZshParamFlag::Words),
7908 'b' => flags.push(ZshParamFlag::QuoteBackslash),
7909 'q' => {
7910 if chars.peek() == Some(&'q') {
7911 chars.next();
7912 flags.push(ZshParamFlag::DoubleQuote);
7913 } else {
7914 flags.push(ZshParamFlag::Quote);
7915 }
7916 }
7917 'u' => flags.push(ZshParamFlag::Unique),
7918 'O' => flags.push(ZshParamFlag::Reverse),
7919 'o' => flags.push(ZshParamFlag::Sort),
7920 'n' => flags.push(ZshParamFlag::NumericSort),
7921 'a' => flags.push(ZshParamFlag::IndexSort),
7922 'k' => flags.push(ZshParamFlag::Keys),
7923 'v' => flags.push(ZshParamFlag::Values),
7924 '#' => flags.push(ZshParamFlag::Length),
7925 'c' => flags.push(ZshParamFlag::CountChars),
7926 'e' => flags.push(ZshParamFlag::Expand),
7927 '%' => {
7928 if chars.peek() == Some(&'%') {
7929 chars.next();
7930 flags.push(ZshParamFlag::PromptExpandFull);
7931 } else {
7932 flags.push(ZshParamFlag::PromptExpand);
7933 }
7934 }
7935 'V' => flags.push(ZshParamFlag::Visible),
7936 'D' => flags.push(ZshParamFlag::Directory),
7937 'M' => flags.push(ZshParamFlag::Match),
7938 'R' => flags.push(ZshParamFlag::Remove),
7939 'S' => flags.push(ZshParamFlag::Subscript),
7940 'P' => flags.push(ZshParamFlag::Parameter),
7941 '~' => flags.push(ZshParamFlag::Glob),
7942 'l' => {
7943 if chars.peek() == Some(&':') {
7945 chars.next();
7946 let mut len_str = String::new();
7947 while let Some(&ch) = chars.peek() {
7948 if ch == ':' {
7949 chars.next();
7950 break;
7951 }
7952 len_str.push(chars.next().unwrap());
7953 }
7954 let mut fill = ' ';
7955 if let Some(&ch) = chars.peek() {
7956 if ch != ':' {
7957 fill = chars.next().unwrap();
7958 if chars.peek() == Some(&':') {
7959 chars.next();
7960 }
7961 }
7962 }
7963 if let Ok(len) = len_str.parse() {
7964 flags.push(ZshParamFlag::PadLeft(len, fill));
7965 }
7966 }
7967 }
7968 'r' => {
7969 if chars.peek() == Some(&':') {
7971 chars.next();
7972 let mut len_str = String::new();
7973 while let Some(&ch) = chars.peek() {
7974 if ch == ':' {
7975 chars.next();
7976 break;
7977 }
7978 len_str.push(chars.next().unwrap());
7979 }
7980 let mut fill = ' ';
7981 if let Some(&ch) = chars.peek() {
7982 if ch != ':' {
7983 fill = chars.next().unwrap();
7984 if chars.peek() == Some(&':') {
7985 chars.next();
7986 }
7987 }
7988 }
7989 if let Ok(len) = len_str.parse() {
7990 flags.push(ZshParamFlag::PadRight(len, fill));
7991 }
7992 }
7993 }
7994 'm' => {
7995 let mut width_str = String::new();
7997 while let Some(&ch) = chars.peek() {
7998 if ch.is_ascii_digit() {
7999 width_str.push(chars.next().unwrap());
8000 } else {
8001 break;
8002 }
8003 }
8004 if let Ok(w) = width_str.parse() {
8005 flags.push(ZshParamFlag::Width(w));
8006 }
8007 }
8008 _ => {}
8009 }
8010 }
8011 flags
8012 }
8013
8014 fn apply_zsh_param_flag(&self, val: &str, name: &str, flag: &ZshParamFlag) -> String {
8016 match flag {
8017 ZshParamFlag::Lower => val.to_lowercase(),
8018 ZshParamFlag::Upper => val.to_uppercase(),
8019 ZshParamFlag::Capitalize => val
8020 .split_whitespace()
8021 .map(|word| {
8022 let mut c = word.chars();
8023 match c.next() {
8024 None => String::new(),
8025 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
8026 }
8027 })
8028 .collect::<Vec<_>>()
8029 .join(" "),
8030 ZshParamFlag::Join(sep) => {
8031 if let Some(arr) = self.arrays.get(name) {
8032 arr.join(sep)
8033 } else {
8034 val.to_string()
8035 }
8036 }
8037 ZshParamFlag::Split(sep) => val.split(sep).collect::<Vec<_>>().join(" "),
8038 ZshParamFlag::SplitLines => val.lines().collect::<Vec<_>>().join(" "),
8039 ZshParamFlag::Type => {
8040 if self.arrays.contains_key(name) {
8041 "array".to_string()
8042 } else if self.assoc_arrays.contains_key(name) {
8043 "association".to_string()
8044 } else if self.functions.contains_key(name) {
8045 "function".to_string()
8046 } else if std::env::var(name).is_ok() || self.variables.contains_key(name) {
8047 "scalar".to_string()
8048 } else {
8049 "".to_string()
8050 }
8051 }
8052 ZshParamFlag::Words => val.split_whitespace().collect::<Vec<_>>().join(" "),
8053 ZshParamFlag::Quote => format!("'{}'", val.replace('\'', "'\\''")),
8054 ZshParamFlag::DoubleQuote => format!("\"{}\"", val.replace('"', "\\\"")),
8055 ZshParamFlag::Unique => {
8056 let words: Vec<&str> = val.split_whitespace().collect();
8059 let mut seen = std::collections::HashSet::with_capacity(if words.len() >= 1000 {
8060 words.len()
8061 } else {
8062 0
8063 });
8064 if words.len() >= 1000 {
8065 tracing::trace!(
8066 count = words.len(),
8067 "unique on large array ({} elements)",
8068 words.len()
8069 );
8070 }
8071 words
8072 .into_iter()
8073 .filter(|s| seen.insert(*s))
8074 .collect::<Vec<_>>()
8075 .join(" ")
8076 }
8077 ZshParamFlag::Reverse => {
8078 let mut words: Vec<&str> = val.split_whitespace().collect();
8080 if words.len() >= 1000 {
8081 tracing::trace!(
8082 count = words.len(),
8083 "using parallel reverse sort (rayon) for large array"
8084 );
8085 use rayon::prelude::*;
8086 words.par_sort_unstable_by(|a, b| b.cmp(a));
8087 } else {
8088 words.sort_unstable_by(|a, b| b.cmp(a));
8089 }
8090 words.join(" ")
8091 }
8092 ZshParamFlag::Sort => {
8093 let mut words: Vec<&str> = val.split_whitespace().collect();
8094 if words.len() >= 1000 {
8095 tracing::trace!(
8096 count = words.len(),
8097 "using parallel sort (rayon) for large array"
8098 );
8099 use rayon::prelude::*;
8100 words.par_sort_unstable();
8101 } else {
8102 words.sort_unstable();
8103 }
8104 words.join(" ")
8105 }
8106 ZshParamFlag::NumericSort => {
8107 let mut words: Vec<&str> = val.split_whitespace().collect();
8108 let cmp = |a: &&str, b: &&str| {
8109 let na: i64 = a.parse().unwrap_or(0);
8110 let nb: i64 = b.parse().unwrap_or(0);
8111 na.cmp(&nb)
8112 };
8113 if words.len() >= 1000 {
8114 tracing::trace!(
8115 count = words.len(),
8116 "using parallel numeric sort (rayon) for large array"
8117 );
8118 use rayon::prelude::*;
8119 words.par_sort_unstable_by(cmp);
8120 } else {
8121 words.sort_unstable_by(cmp);
8122 }
8123 words.join(" ")
8124 }
8125 ZshParamFlag::Keys => {
8126 if let Some(assoc) = self.assoc_arrays.get(name) {
8127 assoc.keys().cloned().collect::<Vec<_>>().join(" ")
8128 } else {
8129 String::new()
8130 }
8131 }
8132 ZshParamFlag::Values => {
8133 if let Some(assoc) = self.assoc_arrays.get(name) {
8134 assoc.values().cloned().collect::<Vec<_>>().join(" ")
8135 } else {
8136 val.to_string()
8137 }
8138 }
8139 ZshParamFlag::Length => val.len().to_string(),
8140 ZshParamFlag::Head(n) => val
8141 .split_whitespace()
8142 .take(*n)
8143 .collect::<Vec<_>>()
8144 .join(" "),
8145 ZshParamFlag::Tail(n) => {
8146 let words: Vec<&str> = val.split_whitespace().collect();
8147 if words.len() > *n {
8148 words[words.len() - n..].join(" ")
8149 } else {
8150 val.to_string()
8151 }
8152 }
8153 ZshParamFlag::JoinNewline => {
8154 if let Some(arr) = self.arrays.get(name) {
8155 arr.join("\n")
8156 } else {
8157 val.to_string()
8158 }
8159 }
8160 ZshParamFlag::SplitWords => {
8161 val.split_whitespace().collect::<Vec<_>>().join(" ")
8163 }
8164 ZshParamFlag::QuoteBackslash => {
8165 let mut result = String::new();
8167 for c in val.chars() {
8168 if "\\*?[]{}()".contains(c) {
8169 result.push('\\');
8170 }
8171 result.push(c);
8172 }
8173 result
8174 }
8175 ZshParamFlag::IndexSort => {
8176 val.to_string()
8178 }
8179 ZshParamFlag::CountChars => {
8180 val.chars().count().to_string()
8182 }
8183 ZshParamFlag::Expand => {
8184 val.to_string()
8186 }
8187 ZshParamFlag::PromptExpand => {
8188 self.expand_prompt_string(val)
8190 }
8191 ZshParamFlag::PromptExpandFull => {
8192 self.expand_prompt_string(val)
8194 }
8195 ZshParamFlag::Visible => {
8196 val.chars()
8198 .map(|c| {
8199 if c.is_control() {
8200 format!("^{}", (c as u8 + 64) as char)
8201 } else {
8202 c.to_string()
8203 }
8204 })
8205 .collect()
8206 }
8207 ZshParamFlag::Directory => {
8208 if let Some(home) = dirs::home_dir() {
8210 let home_str = home.to_string_lossy();
8211 if val.starts_with(home_str.as_ref()) {
8212 format!("~{}", &val[home_str.len()..])
8213 } else {
8214 val.to_string()
8215 }
8216 } else {
8217 val.to_string()
8218 }
8219 }
8220 ZshParamFlag::PadLeft(len, fill) => {
8221 if val.len() >= *len {
8222 val.to_string()
8223 } else {
8224 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
8225 format!("{}{}", padding, val)
8226 }
8227 }
8228 ZshParamFlag::PadRight(len, fill) => {
8229 if val.len() >= *len {
8230 val.to_string()
8231 } else {
8232 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
8233 format!("{}{}", val, padding)
8234 }
8235 }
8236 ZshParamFlag::Width(_) => {
8237 val.to_string()
8239 }
8240 ZshParamFlag::Match => {
8241 val.to_string()
8244 }
8245 ZshParamFlag::Remove => {
8246 val.to_string()
8248 }
8249 ZshParamFlag::Subscript => {
8250 val.to_string()
8252 }
8253 ZshParamFlag::Parameter => {
8254 self.get_variable(val)
8256 }
8257 ZshParamFlag::Glob => {
8258 val.to_string()
8260 }
8261 }
8262 }
8263
8264 fn expand_prompt_string(&self, s: &str) -> String {
8266 let ctx = self.build_prompt_context();
8267 expand_prompt(s, &ctx)
8268 }
8269
8270 fn build_prompt_context(&self) -> PromptContext {
8272 let pwd = env::current_dir()
8273 .map(|p| p.to_string_lossy().to_string())
8274 .unwrap_or_else(|_| "/".to_string());
8275
8276 let home = env::var("HOME").unwrap_or_default();
8277
8278 let user = env::var("USER")
8279 .or_else(|_| env::var("LOGNAME"))
8280 .unwrap_or_else(|_| "user".to_string());
8281
8282 let host = hostname::get()
8283 .map(|h| h.to_string_lossy().to_string())
8284 .unwrap_or_else(|_| "localhost".to_string());
8285
8286 let host_short = host.split('.').next().unwrap_or(&host).to_string();
8287
8288 let shlvl = env::var("SHLVL")
8289 .ok()
8290 .and_then(|s| s.parse().ok())
8291 .unwrap_or(1);
8292
8293 PromptContext {
8294 pwd,
8295 home,
8296 user,
8297 host,
8298 host_short,
8299 tty: String::new(),
8300 lastval: self.last_status,
8301 histnum: self
8302 .history
8303 .as_ref()
8304 .and_then(|h| h.count().ok())
8305 .unwrap_or(1),
8306 shlvl,
8307 num_jobs: self.jobs.list().len() as i32,
8308 is_root: unsafe { libc::geteuid() } == 0,
8309 cmd_stack: Vec::new(),
8310 psvar: self.get_psvar(),
8311 term_width: self.get_term_width(),
8312 lineno: 1,
8313 }
8314 }
8315
8316 fn get_psvar(&self) -> Vec<String> {
8317 if let Some(arr) = self.arrays.get("psvar") {
8318 arr.clone()
8319 } else {
8320 Vec::new()
8321 }
8322 }
8323
8324 fn get_term_width(&self) -> usize {
8325 env::var("COLUMNS")
8326 .ok()
8327 .and_then(|s| s.parse().ok())
8328 .unwrap_or(80)
8329 }
8330
8331 fn execute_command_substitution(&mut self, cmd: &ShellCommand) -> String {
8333 match self.execute_command_capture(cmd) {
8334 Ok(output) => output.trim_end_matches('\n').to_string(),
8335 Err(_) => String::new(),
8336 }
8337 }
8338
8339 fn execute_command_capture(&mut self, cmd: &ShellCommand) -> Result<String, String> {
8341 if let ShellCommand::Simple(simple) = cmd {
8343 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
8344 if words.is_empty() {
8345 return Ok(String::new());
8346 }
8347
8348 let cmd_name = &words[0];
8349 let args = &words[1..];
8350
8351 match cmd_name.as_str() {
8353 "echo" => {
8354 let output = args.join(" ");
8355 return Ok(format!("{}\n", output));
8356 }
8357 "printf" => {
8358 if !args.is_empty() {
8359 let format = &args[0];
8361 let result = if args.len() > 1 {
8362 let mut out = format.clone();
8364 for (i, arg) in args[1..].iter().enumerate() {
8365 out = out.replacen("%s", arg, 1);
8366 out = out.replacen(&format!("${}", i + 1), arg, 1);
8367 }
8368 out
8369 } else {
8370 format.clone()
8371 };
8372 return Ok(result);
8373 }
8374 return Ok(String::new());
8375 }
8376 "pwd" => {
8377 return Ok(env::current_dir()
8378 .map(|p| format!("{}\n", p.display()))
8379 .unwrap_or_default());
8380 }
8381 _ => {}
8382 }
8383
8384 let output = Command::new(cmd_name)
8386 .args(args)
8387 .stdout(Stdio::piped())
8388 .stderr(Stdio::inherit())
8389 .output();
8390
8391 match output {
8392 Ok(output) => {
8393 self.last_status = output.status.code().unwrap_or(1);
8394 Ok(String::from_utf8_lossy(&output.stdout).to_string())
8395 }
8396 Err(e) => {
8397 self.last_status = 127;
8398 Err(format!("{}: {}", cmd_name, e))
8399 }
8400 }
8401 } else if let ShellCommand::Pipeline(cmds, _negated) = cmd {
8402 if let Some(last) = cmds.last() {
8405 return self.execute_command_capture(last);
8406 }
8407 Ok(String::new())
8408 } else {
8409 let _ = self.execute_command(cmd);
8412 Ok(String::new())
8413 }
8414 }
8415
8416 fn evaluate_arithmetic(&mut self, expr: &str) -> String {
8418 let expr = self.expand_string(expr);
8419 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
8420 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8421 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8422
8423 let mut evaluator = MathEval::new(&expr)
8424 .with_string_variables(&self.variables)
8425 .with_force_float(force_float)
8426 .with_c_precedences(c_prec)
8427 .with_octal_zeroes(octal);
8428
8429 match evaluator.evaluate() {
8430 Ok(result) => {
8431 for (k, v) in evaluator.extract_string_variables() {
8432 self.variables.insert(k.clone(), v.clone());
8433 env::set_var(&k, &v);
8434 }
8435 match result {
8436 crate::math::MathNum::Integer(i) => i.to_string(),
8437 crate::math::MathNum::Float(f) => {
8438 if f.fract() == 0.0 && f.abs() < i64::MAX as f64 {
8439 (f as i64).to_string()
8440 } else {
8441 f.to_string()
8442 }
8443 }
8444 crate::math::MathNum::Unset => "0".to_string(),
8445 }
8446 }
8447 Err(_) => "0".to_string(),
8448 }
8449 }
8450
8451 fn eval_arith_expr(&mut self, expr: &str) -> i64 {
8452 let expr_expanded = self.expand_string(expr);
8453 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8454 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8455
8456 let mut evaluator = MathEval::new(&expr_expanded)
8457 .with_string_variables(&self.variables)
8458 .with_c_precedences(c_prec)
8459 .with_octal_zeroes(octal);
8460
8461 match evaluator.evaluate() {
8462 Ok(result) => {
8463 for (k, v) in evaluator.extract_string_variables() {
8464 self.variables.insert(k.clone(), v.clone());
8465 env::set_var(&k, &v);
8466 }
8467 result.to_int()
8468 }
8469 Err(_) => 0,
8470 }
8471 }
8472
8473 fn eval_arith_expr_float(&mut self, expr: &str) -> f64 {
8474 let expr_expanded = self.expand_string(expr);
8475 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
8476 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8477 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8478
8479 let mut evaluator = MathEval::new(&expr_expanded)
8480 .with_string_variables(&self.variables)
8481 .with_force_float(force_float)
8482 .with_c_precedences(c_prec)
8483 .with_octal_zeroes(octal);
8484
8485 match evaluator.evaluate() {
8486 Ok(result) => {
8487 for (k, v) in evaluator.extract_string_variables() {
8488 self.variables.insert(k.clone(), v.clone());
8489 env::set_var(&k, &v);
8490 }
8491 result.to_float()
8492 }
8493 Err(_) => 0.0,
8494 }
8495 }
8496
8497 fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
8498 if pattern == "*" {
8500 return true;
8501 }
8502 if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
8503 glob::Pattern::new(pattern)
8505 .map(|p| p.matches(value))
8506 .unwrap_or(false)
8507 } else {
8508 value == pattern
8509 }
8510 }
8511
8512 fn eval_cond_expr(&mut self, expr: &CondExpr) -> bool {
8513 match expr {
8514 CondExpr::FileExists(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8515 CondExpr::FileRegular(w) => std::path::Path::new(&self.expand_word(w)).is_file(),
8516 CondExpr::FileDirectory(w) => std::path::Path::new(&self.expand_word(w)).is_dir(),
8517 CondExpr::FileSymlink(w) => std::path::Path::new(&self.expand_word(w)).is_symlink(),
8518 CondExpr::FileReadable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8519 CondExpr::FileWritable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8520 CondExpr::FileExecutable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8521 CondExpr::FileNonEmpty(w) => std::fs::metadata(&self.expand_word(w))
8522 .map(|m| m.len() > 0)
8523 .unwrap_or(false),
8524 CondExpr::StringEmpty(w) => self.expand_word(w).is_empty(),
8525 CondExpr::StringNonEmpty(w) => !self.expand_word(w).is_empty(),
8526 CondExpr::StringEqual(a, b) => {
8527 let left = self.expand_word(a);
8528 let right = self.expand_word(b);
8529 if right.contains('*') || right.contains('?') || right.contains('[') {
8531 crate::glob::pattern_match(&right, &left, true, true)
8532 } else {
8533 left == right
8534 }
8535 }
8536 CondExpr::StringNotEqual(a, b) => {
8537 let left = self.expand_word(a);
8538 let right = self.expand_word(b);
8539 if right.contains('*') || right.contains('?') || right.contains('[') {
8540 !crate::glob::pattern_match(&right, &left, true, true)
8541 } else {
8542 left != right
8543 }
8544 }
8545 CondExpr::StringMatch(a, b) => {
8546 let val = self.expand_word(a);
8547 let pattern = self.expand_word(b);
8548 if let Some(re) = cached_regex(&pattern) {
8549 if let Some(caps) = re.captures(&val) {
8550 if let Some(m) = caps.get(0) {
8552 self.variables
8553 .insert("MATCH".to_string(), m.as_str().to_string());
8554 }
8555 let mut match_arr = Vec::new();
8557 for i in 1..caps.len() {
8558 if let Some(g) = caps.get(i) {
8559 match_arr.push(g.as_str().to_string());
8560 }
8561 }
8562 if !match_arr.is_empty() {
8563 self.arrays.insert("match".to_string(), match_arr);
8564 }
8565 true
8566 } else {
8567 self.variables.remove("MATCH");
8568 self.arrays.remove("match");
8569 false
8570 }
8571 } else {
8572 false
8573 }
8574 }
8575 CondExpr::StringLess(a, b) => self.expand_word(a) < self.expand_word(b),
8576 CondExpr::StringGreater(a, b) => self.expand_word(a) > self.expand_word(b),
8577 CondExpr::NumEqual(a, b) => {
8578 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8579 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8580 a_val == b_val
8581 }
8582 CondExpr::NumNotEqual(a, b) => {
8583 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8584 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8585 a_val != b_val
8586 }
8587 CondExpr::NumLess(a, b) => {
8588 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8589 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8590 a_val < b_val
8591 }
8592 CondExpr::NumLessEqual(a, b) => {
8593 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8594 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8595 a_val <= b_val
8596 }
8597 CondExpr::NumGreater(a, b) => {
8598 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8599 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8600 a_val > b_val
8601 }
8602 CondExpr::NumGreaterEqual(a, b) => {
8603 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8604 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8605 a_val >= b_val
8606 }
8607 CondExpr::Not(inner) => !self.eval_cond_expr(inner),
8608 CondExpr::And(a, b) => self.eval_cond_expr(a) && self.eval_cond_expr(b),
8609 CondExpr::Or(a, b) => self.eval_cond_expr(a) || self.eval_cond_expr(b),
8610 }
8611 }
8612
8613 fn builtin_cd(&mut self, args: &[String]) -> i32 {
8620 let mut quiet = false;
8624 let mut use_cdpath = false;
8625 let mut logical = true; let mut positional_args: Vec<&str> = Vec::new();
8627
8628 for arg in args {
8629 if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
8630 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
8632 positional_args.push(arg);
8633 continue;
8634 }
8635 for ch in arg[1..].chars() {
8636 match ch {
8637 'q' => quiet = true,
8638 's' => use_cdpath = true,
8639 'L' => logical = true,
8640 'P' => logical = false,
8641 _ => {
8642 eprintln!("cd: bad option: -{}", ch);
8643 return 1;
8644 }
8645 }
8646 }
8647 } else if arg.starts_with('+')
8648 && arg.len() > 1
8649 && arg[1..].chars().all(|c| c.is_ascii_digit())
8650 {
8651 positional_args.push(arg);
8653 } else {
8654 positional_args.push(arg);
8655 }
8656 }
8657
8658 if positional_args.len() == 2 {
8660 if let Ok(cwd) = env::current_dir() {
8661 let cwd_str = cwd.to_string_lossy();
8662 let old = positional_args[0];
8663 let new = positional_args[1];
8664 if cwd_str.contains(old) {
8665 let new_path = cwd_str.replace(old, new);
8666 if !quiet {
8667 println!("{}", new_path);
8668 }
8669 positional_args = vec![];
8670 return self.do_cd(&new_path, quiet, use_cdpath, logical);
8671 }
8672 }
8673 }
8674
8675 let path_arg = positional_args.first().map(|s| *s).unwrap_or("~");
8676
8677 if path_arg.starts_with('+') || path_arg.starts_with('-') {
8679 if let Ok(n) = path_arg[1..].parse::<usize>() {
8680 let idx = if path_arg.starts_with('+') {
8681 n
8682 } else {
8683 self.dir_stack.len().saturating_sub(n)
8684 };
8685 if let Some(dir) = self.dir_stack.get(idx) {
8686 let dir_path = dir.to_string_lossy().to_string();
8687 return self.do_cd(&dir_path, quiet, use_cdpath, logical);
8688 } else {
8689 eprintln!("cd: no such entry in dir stack");
8690 return 1;
8691 }
8692 }
8693 }
8694
8695 self.do_cd(path_arg, quiet, use_cdpath, logical)
8696 }
8697
8698 fn do_cd(&mut self, path_arg: &str, quiet: bool, use_cdpath: bool, physical: bool) -> i32 {
8699 let path = if path_arg == "~" || path_arg.is_empty() {
8700 dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
8701 } else if path_arg.starts_with("~/") {
8702 dirs::home_dir()
8703 .unwrap_or_else(|| PathBuf::from("."))
8704 .join(&path_arg[2..])
8705 } else if path_arg == "-" {
8706 if let Ok(oldpwd) = env::var("OLDPWD") {
8707 if !quiet {
8708 println!("{}", oldpwd);
8709 }
8710 PathBuf::from(oldpwd)
8711 } else {
8712 eprintln!("cd: OLDPWD not set");
8713 return 1;
8714 }
8715 } else if use_cdpath && !path_arg.starts_with('/') && !path_arg.starts_with('.') {
8716 let cdpath = env::var("CDPATH").unwrap_or_default();
8718 let mut found = None;
8719 for dir in cdpath.split(':') {
8720 let candidate = if dir.is_empty() {
8721 PathBuf::from(path_arg)
8722 } else {
8723 PathBuf::from(dir).join(path_arg)
8724 };
8725 if candidate.is_dir() {
8726 found = Some(candidate);
8727 break;
8728 }
8729 }
8730 found.unwrap_or_else(|| PathBuf::from(path_arg))
8731 } else {
8732 PathBuf::from(path_arg)
8733 };
8734
8735 if let Ok(cwd) = env::current_dir() {
8736 env::set_var("OLDPWD", &cwd);
8737 }
8738
8739 let target = if !physical {
8741 if let Ok(resolved) = path.canonicalize() {
8742 resolved
8743 } else {
8744 path.clone()
8745 }
8746 } else {
8747 path.clone()
8748 };
8749
8750 match env::set_current_dir(&target) {
8751 Ok(_) => {
8752 if let Ok(cwd) = env::current_dir() {
8753 env::set_var("PWD", &cwd);
8754 self.variables
8755 .insert("PWD".to_string(), cwd.to_string_lossy().to_string());
8756 }
8757 0
8758 }
8759 Err(e) => {
8760 eprintln!("cd: {}: {}", path.display(), e);
8761 1
8762 }
8763 }
8764 }
8765
8766 fn builtin_pwd(&mut self, _redirects: &[Redirect]) -> i32 {
8767 match env::current_dir() {
8768 Ok(path) => {
8769 println!("{}", path.display());
8770 0
8771 }
8772 Err(e) => {
8773 eprintln!("pwd: {}", e);
8774 1
8775 }
8776 }
8777 }
8778
8779 fn builtin_echo(&mut self, args: &[String], _redirects: &[Redirect]) -> i32 {
8780 let mut newline = true;
8781 let mut interpret_escapes = false;
8782 let mut start = 0;
8783
8784 for (i, arg) in args.iter().enumerate() {
8785 match arg.as_str() {
8786 "-n" => {
8787 newline = false;
8788 start = i + 1;
8789 }
8790 "-e" => {
8791 interpret_escapes = true;
8792 start = i + 1;
8793 }
8794 "-E" => {
8795 interpret_escapes = false;
8796 start = i + 1;
8797 }
8798 _ => break,
8799 }
8800 }
8801
8802 let output = args[start..].join(" ");
8803 if interpret_escapes {
8804 print!("{}", output.replace("\\n", "\n").replace("\\t", "\t"));
8805 } else {
8806 print!("{}", output);
8807 }
8808
8809 if newline {
8810 println!();
8811 }
8812 0
8813 }
8814
8815 fn builtin_export(&mut self, args: &[String]) -> i32 {
8816 for arg in args {
8817 if let Some((key, value)) = arg.split_once('=') {
8818 self.variables.insert(key.to_string(), value.to_string());
8819 env::set_var(key, value);
8820 } else {
8821 let val = self.get_variable(arg);
8823 env::set_var(arg, &val);
8824 }
8825 }
8826 0
8827 }
8828
8829 fn builtin_unset(&mut self, args: &[String]) -> i32 {
8830 for arg in args {
8831 env::remove_var(arg);
8832 self.variables.remove(arg);
8833 }
8834 0
8835 }
8836
8837 fn builtin_source(&mut self, args: &[String]) -> i32 {
8838 if args.is_empty() {
8839 eprintln!("source: filename argument required");
8840 return 1;
8841 }
8842
8843 let path = &args[0];
8844
8845 let abs_path = if path.starts_with('/') {
8847 path.clone()
8848 } else if path.starts_with("~/") {
8849 if let Some(home) = dirs::home_dir() {
8850 home.join(&path[2..]).to_string_lossy().to_string()
8851 } else {
8852 path.clone()
8853 }
8854 } else {
8855 std::env::current_dir()
8856 .map(|cwd| cwd.join(path).to_string_lossy().to_string())
8857 .unwrap_or_else(|_| path.clone())
8858 };
8859
8860 let saved_zero = self.variables.get("0").cloned();
8862 self.variables.insert("0".to_string(), abs_path.clone());
8863
8864 let result;
8865
8866 if self.posix_mode {
8867 result = match std::fs::read_to_string(&abs_path) {
8869 Ok(content) => match self.execute_script(&content) {
8870 Ok(status) => status,
8871 Err(e) => {
8872 eprintln!("source: {}: {}", path, e);
8873 1
8874 }
8875 },
8876 Err(e) => {
8877 eprintln!("source: {}: {}", path, e);
8878 1
8879 }
8880 };
8881 } else {
8882 let file_path = std::path::Path::new(&abs_path);
8884
8885 if let Some(ref cache) = self.plugin_cache {
8887 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
8888 if let Some(plugin_id) = cache.check(&abs_path, mt_s, mt_ns) {
8889 if let Ok(delta) = cache.load(plugin_id) {
8890 let t0 = std::time::Instant::now();
8891 self.replay_plugin_delta(&delta);
8892 tracing::info!(
8893 path = %abs_path,
8894 replay_us = t0.elapsed().as_micros() as u64,
8895 funcs = delta.functions.len(),
8896 aliases = delta.aliases.len(),
8897 vars = delta.variables.len() + delta.exports.len(),
8898 "source: cache hit, replayed"
8899 );
8900 if let Some(z) = saved_zero {
8902 self.variables.insert("0".to_string(), z);
8903 } else {
8904 self.variables.remove("0");
8905 }
8906 return 0;
8907 }
8908 }
8909 }
8910 }
8911
8912 let snapshot = self.snapshot_state();
8914 let t0 = std::time::Instant::now();
8915 tracing::debug!(path = %abs_path, "source: cache miss, executing via AST-cached path");
8916 result = match self.execute_script_file(&abs_path) {
8917 Ok(status) => status,
8918 Err(e) => {
8919 tracing::warn!(path = %abs_path, error = %e, "source: execution failed");
8920 eprintln!("source: {}: {}", path, e);
8921 1
8922 }
8923 };
8924 let source_ms = t0.elapsed().as_millis() as u64;
8925
8926 if result == 0 {
8928 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
8929 let delta = self.diff_state(&snapshot);
8930 let store_path = abs_path.clone();
8931 tracing::info!(
8932 path = %abs_path, source_ms,
8933 funcs = delta.functions.len(),
8934 aliases = delta.aliases.len(),
8935 vars = delta.variables.len() + delta.exports.len(),
8936 "source: caching delta on worker"
8937 );
8938 let cache_db_path = crate::plugin_cache::default_cache_path();
8939 self.worker_pool.submit(move || {
8940 match crate::plugin_cache::PluginCache::open(&cache_db_path) {
8941 Ok(cache) => {
8942 if let Err(e) = cache.store(&store_path, mt_s, mt_ns, source_ms, &delta) {
8943 tracing::error!(path = %store_path, error = %e, "plugin_cache: store failed");
8944 } else {
8945 tracing::debug!(path = %store_path, "plugin_cache: stored");
8946 }
8947 }
8948 Err(e) => tracing::error!(error = %e, "plugin_cache: open for write failed"),
8949 }
8950 });
8951 }
8952 }
8953 }
8954
8955 let final_result = if let Some(ret) = self.returning.take() {
8957 ret
8958 } else {
8959 result
8960 };
8961
8962 if let Some(z) = saved_zero {
8964 self.variables.insert("0".to_string(), z);
8965 } else {
8966 self.variables.remove("0");
8967 }
8968
8969 final_result
8970 }
8971
8972 fn snapshot_state(&self) -> PluginSnapshot {
8974 PluginSnapshot {
8975 functions: self.functions.keys().cloned().collect(),
8976 aliases: self.aliases.keys().cloned().collect(),
8977 global_aliases: self.global_aliases.keys().cloned().collect(),
8978 suffix_aliases: self.suffix_aliases.keys().cloned().collect(),
8979 variables: self.variables.clone(),
8980 arrays: self.arrays.keys().cloned().collect(),
8981 assoc_arrays: self.assoc_arrays.keys().cloned().collect(),
8982 fpath: self.fpath.clone(),
8983 options: self.options.clone(),
8984 hooks: self.hook_functions.clone(),
8985 autoloads: self.autoload_pending.keys().cloned().collect(),
8986 }
8987 }
8988
8989 fn diff_state(&self, snap: &PluginSnapshot) -> crate::plugin_cache::PluginDelta {
8991 use crate::plugin_cache::{AliasKind, PluginDelta};
8992 let mut delta = PluginDelta::default();
8993
8994 for (name, body) in &self.functions {
8996 if !snap.functions.contains(name) {
8997 if let Ok(bytes) = bincode::serialize(body) {
8998 delta.functions.push((name.clone(), bytes));
8999 }
9000 }
9001 }
9002
9003 for (name, value) in &self.aliases {
9005 if !snap.aliases.contains(name) {
9006 delta
9007 .aliases
9008 .push((name.clone(), value.clone(), AliasKind::Regular));
9009 }
9010 }
9011 for (name, value) in &self.global_aliases {
9012 if !snap.global_aliases.contains(name) {
9013 delta
9014 .aliases
9015 .push((name.clone(), value.clone(), AliasKind::Global));
9016 }
9017 }
9018 for (name, value) in &self.suffix_aliases {
9019 if !snap.suffix_aliases.contains(name) {
9020 delta
9021 .aliases
9022 .push((name.clone(), value.clone(), AliasKind::Suffix));
9023 }
9024 }
9025
9026 for (name, value) in &self.variables {
9028 if name == "0" {
9029 continue;
9030 } match snap.variables.get(name) {
9032 Some(old) if old == value => {} _ => {
9034 if env::var(name).ok().as_ref() == Some(value) {
9036 delta.exports.push((name.clone(), value.clone()));
9037 } else {
9038 delta.variables.push((name.clone(), value.clone()));
9039 }
9040 }
9041 }
9042 }
9043
9044 for (name, values) in &self.arrays {
9046 if !snap.arrays.contains(name) {
9047 delta.arrays.push((name.clone(), values.clone()));
9048 }
9049 }
9050
9051 for p in &self.fpath {
9053 if !snap.fpath.contains(p) {
9054 delta.fpath_additions.push(p.to_string_lossy().to_string());
9055 }
9056 }
9057
9058 for (name, value) in &self.options {
9060 match snap.options.get(name) {
9061 Some(old) if old == value => {}
9062 _ => delta.options_changed.push((name.clone(), *value)),
9063 }
9064 }
9065
9066 for (hook, funcs) in &self.hook_functions {
9068 let old_funcs = snap.hooks.get(hook);
9069 for f in funcs {
9070 let is_new = old_funcs.map_or(true, |old| !old.contains(f));
9071 if is_new {
9072 delta.hooks.push((hook.clone(), f.clone()));
9073 }
9074 }
9075 }
9076
9077 for (name, flags) in &self.autoload_pending {
9079 if !snap.autoloads.contains(name) {
9080 delta.autoloads.push((name.clone(), format!("{:?}", flags)));
9081 }
9082 }
9083
9084 delta
9085 }
9086
9087 fn replay_plugin_delta(&mut self, delta: &crate::plugin_cache::PluginDelta) {
9089 use crate::plugin_cache::AliasKind;
9090
9091 for (name, value, kind) in &delta.aliases {
9093 match kind {
9094 AliasKind::Regular => {
9095 self.aliases.insert(name.clone(), value.clone());
9096 }
9097 AliasKind::Global => {
9098 self.global_aliases.insert(name.clone(), value.clone());
9099 }
9100 AliasKind::Suffix => {
9101 self.suffix_aliases.insert(name.clone(), value.clone());
9102 }
9103 }
9104 }
9105
9106 for (name, value) in &delta.variables {
9108 self.variables.insert(name.clone(), value.clone());
9109 }
9110
9111 for (name, value) in &delta.exports {
9113 self.variables.insert(name.clone(), value.clone());
9114 env::set_var(name, value);
9115 }
9116
9117 for (name, values) in &delta.arrays {
9119 self.arrays.insert(name.clone(), values.clone());
9120 }
9121
9122 for p in &delta.fpath_additions {
9124 let pb = PathBuf::from(p);
9125 if !self.fpath.contains(&pb) {
9126 self.fpath.push(pb);
9127 }
9128 }
9129
9130 for (cmd, func) in &delta.completions {
9132 if let Some(ref mut comps) = self.assoc_arrays.get_mut("_comps") {
9133 comps.insert(cmd.clone(), func.clone());
9134 }
9135 }
9136
9137 for (name, enabled) in &delta.options_changed {
9139 self.options.insert(name.clone(), *enabled);
9140 }
9141
9142 for (hook, func) in &delta.hooks {
9144 self.hook_functions
9145 .entry(hook.clone())
9146 .or_insert_with(Vec::new)
9147 .push(func.clone());
9148 }
9149
9150 for (name, bytes) in &delta.functions {
9152 if let Ok(ast) = bincode::deserialize::<crate::parser::ShellCommand>(bytes) {
9153 self.functions.insert(name.clone(), ast);
9154 }
9155 }
9156 }
9157
9158 fn builtin_exit(&mut self, args: &[String]) -> i32 {
9159 let code = args
9160 .first()
9161 .and_then(|s| s.parse::<i32>().ok())
9162 .unwrap_or(self.last_status);
9163 std::process::exit(code);
9164 }
9165
9166 fn builtin_return(&mut self, args: &[String]) -> i32 {
9167 let status = args
9168 .first()
9169 .and_then(|s| s.parse::<i32>().ok())
9170 .unwrap_or(self.last_status);
9171 self.returning = Some(status);
9172 status
9173 }
9174
9175 fn builtin_test(&mut self, args: &[String]) -> i32 {
9176 if args.is_empty() {
9177 return 1;
9178 }
9179
9180 let args: Vec<&str> = args
9182 .iter()
9183 .map(|s| s.as_str())
9184 .filter(|&s| s != "]")
9185 .collect();
9186
9187 let mut meta_cache: HashMap<String, Option<std::fs::Metadata>> = HashMap::new();
9190 for arg in &args {
9191 if !arg.starts_with('-') && !arg.starts_with('!') && *arg != "(" && *arg != ")" {
9192 let path_str = arg.to_string();
9193 if !meta_cache.contains_key(&path_str) {
9194 meta_cache.insert(path_str, std::fs::metadata(arg).ok());
9195 }
9196 }
9197 }
9198
9199 let get_meta = |path: &str| -> Option<std::fs::Metadata> {
9201 meta_cache
9202 .get(path)
9203 .cloned()
9204 .unwrap_or_else(|| std::fs::metadata(path).ok())
9205 };
9206
9207 match args.as_slice() {
9208 ["-z", s] => {
9210 if s.is_empty() {
9211 0
9212 } else {
9213 1
9214 }
9215 }
9216 ["-n", s] => {
9217 if !s.is_empty() {
9218 0
9219 } else {
9220 1
9221 }
9222 }
9223
9224 ["-a", path] | ["-e", path] => {
9226 if std::path::Path::new(path).exists() {
9227 0
9228 } else {
9229 1
9230 }
9231 }
9232 ["-f", path] => {
9233 if std::path::Path::new(path).is_file() {
9234 0
9235 } else {
9236 1
9237 }
9238 }
9239 ["-d", path] => {
9240 if std::path::Path::new(path).is_dir() {
9241 0
9242 } else {
9243 1
9244 }
9245 }
9246 ["-b", path] => {
9247 use std::os::unix::fs::FileTypeExt;
9248 if std::fs::symlink_metadata(path)
9249 .map(|m| m.file_type().is_block_device())
9250 .unwrap_or(false)
9251 {
9252 0
9253 } else {
9254 1
9255 }
9256 }
9257 ["-c", path] => {
9258 use std::os::unix::fs::FileTypeExt;
9259 if std::fs::symlink_metadata(path)
9260 .map(|m| m.file_type().is_char_device())
9261 .unwrap_or(false)
9262 {
9263 0
9264 } else {
9265 1
9266 }
9267 }
9268 ["-p", path] => {
9269 use std::os::unix::fs::FileTypeExt;
9270 if std::fs::symlink_metadata(path)
9271 .map(|m| m.file_type().is_fifo())
9272 .unwrap_or(false)
9273 {
9274 0
9275 } else {
9276 1
9277 }
9278 }
9279 ["-S", path] => {
9280 use std::os::unix::fs::FileTypeExt;
9281 if std::fs::symlink_metadata(path)
9282 .map(|m| m.file_type().is_socket())
9283 .unwrap_or(false)
9284 {
9285 0
9286 } else {
9287 1
9288 }
9289 }
9290 ["-h", path] | ["-L", path] => {
9291 if std::path::Path::new(path).is_symlink() {
9292 0
9293 } else {
9294 1
9295 }
9296 }
9297
9298 ["-r", path] => {
9300 use std::os::unix::fs::MetadataExt;
9301 if let Some(meta) = get_meta(path) {
9302 let mode = meta.mode();
9303 let uid = unsafe { libc::geteuid() };
9304 let gid = unsafe { libc::getegid() };
9305 let readable = if meta.uid() == uid {
9306 mode & 0o400 != 0
9307 } else if meta.gid() == gid {
9308 mode & 0o040 != 0
9309 } else {
9310 mode & 0o004 != 0
9311 };
9312 if readable {
9313 0
9314 } else {
9315 1
9316 }
9317 } else {
9318 1
9319 }
9320 }
9321 ["-w", path] => {
9322 use std::os::unix::fs::MetadataExt;
9323 if let Some(meta) = get_meta(path) {
9324 let mode = meta.mode();
9325 let uid = unsafe { libc::geteuid() };
9326 let gid = unsafe { libc::getegid() };
9327 let writable = if meta.uid() == uid {
9328 mode & 0o200 != 0
9329 } else if meta.gid() == gid {
9330 mode & 0o020 != 0
9331 } else {
9332 mode & 0o002 != 0
9333 };
9334 if writable {
9335 0
9336 } else {
9337 1
9338 }
9339 } else {
9340 1
9341 }
9342 }
9343 ["-x", path] => {
9344 use std::os::unix::fs::MetadataExt;
9345 if let Some(meta) = get_meta(path) {
9346 let mode = meta.mode();
9347 let uid = unsafe { libc::geteuid() };
9348 let gid = unsafe { libc::getegid() };
9349 let executable = if meta.uid() == uid {
9350 mode & 0o100 != 0
9351 } else if meta.gid() == gid {
9352 mode & 0o010 != 0
9353 } else {
9354 mode & 0o001 != 0
9355 };
9356 if executable {
9357 0
9358 } else {
9359 1
9360 }
9361 } else {
9362 1
9363 }
9364 }
9365
9366 ["-g", path] => {
9368 use std::os::unix::fs::MetadataExt;
9369 if get_meta(path)
9370 .map(|m| m.mode() & 0o2000 != 0)
9371 .unwrap_or(false)
9372 {
9373 0
9374 } else {
9375 1
9376 }
9377 }
9378 ["-k", path] => {
9379 use std::os::unix::fs::MetadataExt;
9380 if get_meta(path)
9381 .map(|m| m.mode() & 0o1000 != 0)
9382 .unwrap_or(false)
9383 {
9384 0
9385 } else {
9386 1
9387 }
9388 }
9389 ["-u", path] => {
9390 use std::os::unix::fs::MetadataExt;
9391 if get_meta(path)
9392 .map(|m| m.mode() & 0o4000 != 0)
9393 .unwrap_or(false)
9394 {
9395 0
9396 } else {
9397 1
9398 }
9399 }
9400
9401 ["-s", path] => {
9403 if get_meta(path).map(|m| m.len() > 0).unwrap_or(false) {
9404 0
9405 } else {
9406 1
9407 }
9408 }
9409
9410 ["-O", path] => {
9412 use std::os::unix::fs::MetadataExt;
9413 if get_meta(path)
9414 .map(|m| m.uid() == unsafe { libc::geteuid() })
9415 .unwrap_or(false)
9416 {
9417 0
9418 } else {
9419 1
9420 }
9421 }
9422 ["-G", path] => {
9423 use std::os::unix::fs::MetadataExt;
9424 if get_meta(path)
9425 .map(|m| m.gid() == unsafe { libc::getegid() })
9426 .unwrap_or(false)
9427 {
9428 0
9429 } else {
9430 1
9431 }
9432 }
9433
9434 ["-N", path] => {
9436 use std::os::unix::fs::MetadataExt;
9437 if let Some(meta) = get_meta(path) {
9438 if meta.mtime() > meta.atime() {
9439 0
9440 } else {
9441 1
9442 }
9443 } else {
9444 1
9445 }
9446 }
9447
9448 ["-t", fd] => {
9450 if let Ok(fd_num) = fd.parse::<i32>() {
9451 if unsafe { libc::isatty(fd_num) } == 1 {
9452 0
9453 } else {
9454 1
9455 }
9456 } else {
9457 1
9458 }
9459 }
9460
9461 ["-v", varname] => {
9463 if self.variables.contains_key(*varname) || std::env::var(varname).is_ok() {
9464 0
9465 } else {
9466 1
9467 }
9468 }
9469
9470 ["-o", opt] => {
9472 let (name, _) = Self::normalize_option_name(opt);
9473 if self.options.get(&name).copied().unwrap_or(false) {
9474 0
9475 } else {
9476 1
9477 }
9478 }
9479
9480 [a, "=", b] | [a, "==", b] => {
9482 if a == b {
9483 0
9484 } else {
9485 1
9486 }
9487 }
9488 [a, "!=", b] => {
9489 if a != b {
9490 0
9491 } else {
9492 1
9493 }
9494 }
9495 [a, "<", b] => {
9496 if *a < *b {
9497 0
9498 } else {
9499 1
9500 }
9501 }
9502 [a, ">", b] => {
9503 if *a > *b {
9504 0
9505 } else {
9506 1
9507 }
9508 }
9509
9510 [a, "-eq", b] => {
9512 let a: i64 = a.parse().unwrap_or(0);
9513 let b: i64 = b.parse().unwrap_or(0);
9514 if a == b {
9515 0
9516 } else {
9517 1
9518 }
9519 }
9520 [a, "-ne", b] => {
9521 let a: i64 = a.parse().unwrap_or(0);
9522 let b: i64 = b.parse().unwrap_or(0);
9523 if a != b {
9524 0
9525 } else {
9526 1
9527 }
9528 }
9529 [a, "-lt", b] => {
9530 let a: i64 = a.parse().unwrap_or(0);
9531 let b: i64 = b.parse().unwrap_or(0);
9532 if a < b {
9533 0
9534 } else {
9535 1
9536 }
9537 }
9538 [a, "-le", b] => {
9539 let a: i64 = a.parse().unwrap_or(0);
9540 let b: i64 = b.parse().unwrap_or(0);
9541 if a <= b {
9542 0
9543 } else {
9544 1
9545 }
9546 }
9547 [a, "-gt", b] => {
9548 let a: i64 = a.parse().unwrap_or(0);
9549 let b: i64 = b.parse().unwrap_or(0);
9550 if a > b {
9551 0
9552 } else {
9553 1
9554 }
9555 }
9556 [a, "-ge", b] => {
9557 let a: i64 = a.parse().unwrap_or(0);
9558 let b: i64 = b.parse().unwrap_or(0);
9559 if a >= b {
9560 0
9561 } else {
9562 1
9563 }
9564 }
9565
9566 [f1, "-nt", f2] => {
9568 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
9569 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
9570 match (m1, m2) {
9571 (Some(t1), Some(t2)) => {
9572 if t1 > t2 {
9573 0
9574 } else {
9575 1
9576 }
9577 }
9578 (Some(_), None) => 0,
9579 _ => 1,
9580 }
9581 }
9582 [f1, "-ot", f2] => {
9583 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
9584 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
9585 match (m1, m2) {
9586 (Some(t1), Some(t2)) => {
9587 if t1 < t2 {
9588 0
9589 } else {
9590 1
9591 }
9592 }
9593 (None, Some(_)) => 0,
9594 _ => 1,
9595 }
9596 }
9597 [f1, "-ef", f2] => {
9598 use std::os::unix::fs::MetadataExt;
9599 let m1 = std::fs::metadata(f1).ok();
9600 let m2 = std::fs::metadata(f2).ok();
9601 match (m1, m2) {
9602 (Some(a), Some(b)) => {
9603 if a.dev() == b.dev() && a.ino() == b.ino() {
9604 0
9605 } else {
9606 1
9607 }
9608 }
9609 _ => 1,
9610 }
9611 }
9612
9613 [s] => {
9615 if !s.is_empty() {
9616 0
9617 } else {
9618 1
9619 }
9620 }
9621
9622 _ => 1,
9623 }
9624 }
9625
9626 fn builtin_local(&mut self, args: &[String]) -> i32 {
9627 self.builtin_typeset(args)
9628 }
9629
9630 fn builtin_declare(&mut self, args: &[String]) -> i32 {
9631 self.builtin_typeset(args)
9632 }
9633
9634 fn builtin_typeset(&mut self, args: &[String]) -> i32 {
9635 if self.local_scope_depth > 0 {
9638 for arg in args {
9639 if arg.starts_with('-') || arg.starts_with('+') {
9640 continue;
9641 }
9642 let name = arg.split('=').next().unwrap_or(arg);
9643 if !name.is_empty() {
9644 let old_val = self.variables.get(name).cloned();
9645 self.local_save_stack.push((name.to_string(), old_val));
9646 }
9647 }
9648 }
9649
9650 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;
9678 let mut precision: Option<usize> = None;
9679 let mut var_args: Vec<String> = Vec::new();
9680
9681 let mut i = 0;
9682 while i < args.len() {
9683 let arg = &args[i];
9684
9685 if arg == "--" {
9686 i += 1;
9687 while i < args.len() {
9688 var_args.push(args[i].clone());
9689 i += 1;
9690 }
9691 break;
9692 }
9693
9694 if arg == "+" {
9695 plus_mode = true;
9696 i += 1;
9697 continue;
9698 }
9699
9700 if arg.starts_with('+') && arg.len() > 1 {
9701 plus_mode = true;
9702 for c in arg[1..].chars() {
9703 match c {
9704 'a' => is_array = false,
9705 'A' => is_assoc = false,
9706 'x' => is_export = false,
9707 'i' => is_integer = false,
9708 'r' => is_readonly = false,
9709 'l' => is_lower = false,
9710 'u' => is_upper = false,
9711 'L' => is_left_pad = false,
9712 'R' => is_right_pad = false,
9713 'Z' => is_zero_pad = false,
9714 'F' => is_float = false,
9715 'E' => is_float_exp = false,
9716 'f' => is_function = false,
9717 'g' => is_global = false,
9718 'T' => is_tied = false,
9719 'H' => is_hidden = false,
9720 'h' => is_hide_val = false,
9721 't' => is_trace = false,
9722 'p' => print_mode = false,
9723 'm' => pattern_match = false,
9724 _ => {}
9725 }
9726 }
9727 } else if arg.starts_with('-') && arg.len() > 1 {
9728 let mut chars = arg[1..].chars().peekable();
9729 while let Some(c) = chars.next() {
9730 match c {
9731 'a' => is_array = true,
9732 'A' => is_assoc = true,
9733 'x' => is_export = true,
9734 'i' => is_integer = true,
9735 'r' => is_readonly = true,
9736 'l' => is_lower = true,
9737 'u' => is_upper = true,
9738 'L' => {
9739 is_left_pad = true;
9740 let rest: String = chars.clone().collect();
9742 if !rest.is_empty()
9743 && rest
9744 .chars()
9745 .next()
9746 .map(|c| c.is_ascii_digit())
9747 .unwrap_or(false)
9748 {
9749 let num: String =
9750 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9751 width = num.parse().ok();
9752 }
9753 }
9754 'R' => {
9755 is_right_pad = true;
9756 let rest: String = chars.clone().collect();
9757 if !rest.is_empty()
9758 && rest
9759 .chars()
9760 .next()
9761 .map(|c| c.is_ascii_digit())
9762 .unwrap_or(false)
9763 {
9764 let num: String =
9765 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9766 width = num.parse().ok();
9767 }
9768 }
9769 'Z' => {
9770 is_zero_pad = true;
9771 let rest: String = chars.clone().collect();
9772 if !rest.is_empty()
9773 && rest
9774 .chars()
9775 .next()
9776 .map(|c| c.is_ascii_digit())
9777 .unwrap_or(false)
9778 {
9779 let num: String =
9780 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9781 width = num.parse().ok();
9782 }
9783 }
9784 'F' => {
9785 is_float = true;
9786 let rest: String = chars.clone().collect();
9787 if !rest.is_empty()
9788 && rest
9789 .chars()
9790 .next()
9791 .map(|c| c.is_ascii_digit())
9792 .unwrap_or(false)
9793 {
9794 let num: String =
9795 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9796 precision = num.parse().ok();
9797 }
9798 }
9799 'E' => {
9800 is_float_exp = true;
9801 let rest: String = chars.clone().collect();
9802 if !rest.is_empty()
9803 && rest
9804 .chars()
9805 .next()
9806 .map(|c| c.is_ascii_digit())
9807 .unwrap_or(false)
9808 {
9809 let num: String =
9810 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9811 precision = num.parse().ok();
9812 }
9813 }
9814 'f' => is_function = true,
9815 'g' => is_global = true,
9816 'T' => is_tied = true,
9817 'H' => is_hidden = true,
9818 'h' => is_hide_val = true,
9819 't' => is_trace = true,
9820 'p' => print_mode = true,
9821 'm' => pattern_match = true,
9822 _ => {}
9823 }
9824 }
9825 } else {
9826 var_args.push(arg.clone());
9827 }
9828 i += 1;
9829 }
9830
9831 let _ = is_global;
9832 let _ = is_tied;
9833 let _ = is_hidden;
9834 let _ = is_hide_val;
9835 let _ = is_trace;
9836 let _ = pattern_match;
9837 let _ = precision;
9838
9839 if is_function && var_args.is_empty() {
9841 let mut func_names: Vec<_> = self.functions.keys().cloned().collect();
9842 func_names.sort();
9843 for name in &func_names {
9844 if let Some(func) = self.functions.get(name) {
9845 if print_mode {
9846 let body = crate::text::getpermtext(func);
9847 println!("{} () {{\n\t{}\n}}", name, body.trim());
9848 } else {
9849 let body = crate::text::getpermtext(func);
9850 println!("{} () {{\n\t{}\n}}", name, body.trim());
9851 }
9852 }
9853 }
9854 return 0;
9855 }
9856
9857 if is_function {
9859 for name in &var_args {
9860 if let Some(func) = self.functions.get(name) {
9861 if print_mode {
9862 let body = crate::text::getpermtext(func);
9863 println!("{} () {{\n\t{}\n}}", name, body.trim());
9864 } else {
9865 let body = crate::text::getpermtext(func);
9866 println!("{} () {{\n\t{}\n}}", name, body.trim());
9867 }
9868 }
9869 }
9870 return 0;
9871 }
9872
9873 if var_args.is_empty() {
9875 list_mode = true;
9876 }
9877
9878 if list_mode {
9879 let mut sorted_names: Vec<_> = self.variables.keys().cloned().collect();
9880 sorted_names.sort();
9881 for name in &sorted_names {
9882 let val = self.variables.get(name).cloned().unwrap_or_default();
9883 let mut attrs = String::new();
9884 if is_export || env::var(name).is_ok() {
9885 attrs.push('x');
9886 }
9887 let is_arr = self.arrays.contains_key(name);
9888 let is_hash = self.assoc_arrays.contains_key(name);
9889 if is_arr {
9890 attrs.push('a');
9891 }
9892 if is_hash {
9893 attrs.push('A');
9894 }
9895 if print_mode {
9896 let prefix = if attrs.is_empty() {
9898 "typeset".to_string()
9899 } else {
9900 format!("typeset -{}", attrs)
9901 };
9902 if is_hash {
9903 if let Some(assoc) = self.assoc_arrays.get(name) {
9904 let mut pairs: Vec<_> = assoc.iter().collect();
9905 pairs.sort_by_key(|(k, _)| (*k).clone());
9906 let formatted: Vec<String> = pairs
9907 .iter()
9908 .map(|(k, v)| {
9909 format!("[{}]={}", shell_quote_value(k), shell_quote_value(v))
9910 })
9911 .collect();
9912 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
9913 }
9914 } else if is_arr {
9915 if let Some(arr) = self.arrays.get(name) {
9916 let formatted: Vec<String> =
9917 arr.iter().map(|v| shell_quote_value(v)).collect();
9918 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
9919 }
9920 } else {
9921 println!("{} {}={}", prefix, name, shell_quote_value(&val));
9922 }
9923 } else if is_hide_val {
9924 println!("{}={}", name, "*".repeat(val.len().min(8)));
9925 } else {
9926 println!("{}={}", name, val);
9927 }
9928 }
9929 return 0;
9930 }
9931
9932 for arg in var_args {
9934 if let Some(eq_pos) = arg.find('=') {
9936 let name = &arg[..eq_pos];
9937 let rest = &arg[eq_pos + 1..];
9938
9939 if rest.starts_with('(') {
9940 let mut elements = Vec::new();
9942 let current = rest[1..].to_string(); if let Some(close_pos) = current.find(')') {
9946 let content = ¤t[..close_pos];
9947 if !content.is_empty() {
9948 elements.extend(content.split_whitespace().map(|s| s.to_string()));
9949 }
9950 } else {
9951 if !current.is_empty() {
9953 let trimmed = current.trim_end_matches(')');
9954 elements.extend(trimmed.split_whitespace().map(|s| s.to_string()));
9955 }
9956 }
9957
9958 if is_assoc {
9960 let mut assoc = std::collections::HashMap::new();
9961 let mut iter = elements.iter();
9962 while let Some(key) = iter.next() {
9963 if let Some(val) = iter.next() {
9964 assoc.insert(key.clone(), val.clone());
9965 }
9966 }
9967 self.assoc_arrays.insert(name.to_string(), assoc);
9968 } else {
9969 self.arrays.insert(name.to_string(), elements);
9970 }
9971 self.variables.insert(name.to_string(), String::new());
9972 } else {
9973 let mut value = rest.to_string();
9975
9976 if is_integer {
9977 value = self.evaluate_arithmetic(&value).to_string();
9979 }
9980 if is_lower {
9981 value = value.to_lowercase();
9982 }
9983 if is_upper {
9984 value = value.to_uppercase();
9985 }
9986 if let Some(w) = width {
9987 if is_left_pad {
9988 value = format!("{:<width$}", value, width = w);
9989 value.truncate(w);
9990 } else if is_right_pad || is_zero_pad {
9991 let pad_char = if is_zero_pad { '0' } else { ' ' };
9992 if value.len() < w {
9993 value = format!(
9994 "{}{}",
9995 pad_char.to_string().repeat(w - value.len()),
9996 value
9997 );
9998 }
9999 if value.len() > w {
10000 value = value[value.len() - w..].to_string();
10001 }
10002 }
10003 }
10004 if is_float || is_float_exp {
10005 if let Ok(f) = value.parse::<f64>() {
10006 let prec = precision.unwrap_or(10);
10007 value = if is_float_exp {
10008 format!("{:.prec$e}", f, prec = prec)
10009 } else {
10010 format!("{:.prec$}", f, prec = prec)
10011 };
10012 }
10013 }
10014
10015 self.variables.insert(name.to_string(), value.clone());
10016
10017 if is_export {
10018 env::set_var(name, &value);
10019 }
10020 }
10021 } else if is_array || is_assoc {
10022 if is_assoc {
10024 self.assoc_arrays
10025 .insert(arg.clone(), std::collections::HashMap::new());
10026 } else {
10027 self.arrays.insert(arg.clone(), Vec::new());
10028 }
10029 self.variables.insert(arg.clone(), String::new());
10030 } else {
10031 self.variables.insert(arg.clone(), String::new());
10032 if is_export {
10033 env::set_var(&arg, "");
10034 }
10035 }
10036
10037 if is_readonly {
10039 let name = if let Some(eq_pos) = arg.find('=') {
10040 arg[..eq_pos].to_string()
10041 } else {
10042 arg.clone()
10043 };
10044 self.readonly_vars.insert(name);
10045 }
10046 }
10047 0
10048 }
10049
10050 fn builtin_read(&mut self, args: &[String]) -> i32 {
10051 use std::io::{BufRead, Read as IoRead};
10054
10055 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();
10066
10067 let mut i = 0;
10068 while i < args.len() {
10069 let arg = &args[i];
10070
10071 if arg == "--" {
10072 i += 1;
10073 while i < args.len() {
10074 var_names.push(args[i].clone());
10075 i += 1;
10076 }
10077 break;
10078 }
10079
10080 if arg.starts_with('-') && arg.len() > 1 {
10081 let mut chars = arg[1..].chars().peekable();
10082 while let Some(ch) = chars.next() {
10083 match ch {
10084 'r' => raw_mode = true,
10085 's' => silent = true,
10086 'z' => to_history = true,
10087 'A' => use_array = true,
10088 'c' | 'l' | 'n' | 'e' | 'E' => {} 'q' => quiet = true,
10090 't' => {
10091 let rest: String = chars.collect();
10092 if !rest.is_empty() {
10093 timeout = rest.parse().ok();
10094 } else {
10095 i += 1;
10096 if i < args.len() {
10097 timeout = args[i].parse().ok();
10098 }
10099 }
10100 break;
10101 }
10102 'd' => {
10103 let rest: String = chars.collect();
10104 if !rest.is_empty() {
10105 delimiter = rest.chars().next().unwrap_or('\n');
10106 } else {
10107 i += 1;
10108 if i < args.len() {
10109 delimiter = args[i].chars().next().unwrap_or('\n');
10110 }
10111 }
10112 break;
10113 }
10114 'k' => {
10115 let rest: String = chars.collect();
10116 if !rest.is_empty() {
10117 nchars = Some(rest.parse().unwrap_or(1));
10118 } else if i + 1 < args.len()
10119 && args[i + 1].chars().all(|c| c.is_ascii_digit())
10120 {
10121 i += 1;
10122 nchars = Some(args[i].parse().unwrap_or(1));
10123 } else {
10124 nchars = Some(1);
10125 }
10126 break;
10127 }
10128 'u' => {
10129 let rest: String = chars.collect();
10130 if !rest.is_empty() {
10131 fd = rest.parse().unwrap_or(0);
10132 } else {
10133 i += 1;
10134 if i < args.len() {
10135 fd = args[i].parse().unwrap_or(0);
10136 }
10137 }
10138 break;
10139 }
10140 'p' => {
10141 let rest: String = chars.collect();
10142 if !rest.is_empty() {
10143 prompt_str = Some(rest);
10144 } else {
10145 i += 1;
10146 if i < args.len() {
10147 prompt_str = Some(args[i].clone());
10148 }
10149 }
10150 break;
10151 }
10152 _ => {}
10153 }
10154 }
10155 } else {
10156 if let Some(pos) = arg.find('?') {
10157 var_names.push(arg[..pos].to_string());
10158 prompt_str = Some(arg[pos + 1..].to_string());
10159 } else {
10160 var_names.push(arg.clone());
10161 }
10162 }
10163 i += 1;
10164 }
10165
10166 if var_names.is_empty() {
10167 var_names.push("REPLY".to_string());
10168 }
10169
10170 if let Some(ref p) = prompt_str {
10171 eprint!("{}", p);
10172 let _ = std::io::stderr().flush();
10173 }
10174
10175 let _ = to_history;
10176 let _ = fd;
10177 let _ = silent;
10178
10179 let input = if let Some(n) = nchars {
10180 let mut buf = vec![0u8; n];
10181 let stdin = io::stdin();
10182 if let Some(_t) = timeout {
10183 }
10185 match stdin.lock().read_exact(&mut buf) {
10186 Ok(_) => String::from_utf8_lossy(&buf).to_string(),
10187 Err(_) => return 1,
10188 }
10189 } else {
10190 let stdin = io::stdin();
10191 let mut input = String::new();
10192 if delimiter == '\n' {
10193 match stdin.lock().read_line(&mut input) {
10194 Ok(0) => return 1,
10195 Ok(_) => {}
10196 Err(_) => return 1,
10197 }
10198 } else {
10199 let mut byte = [0u8; 1];
10200 loop {
10201 match stdin.lock().read_exact(&mut byte) {
10202 Ok(_) => {
10203 let c = byte[0] as char;
10204 if c == delimiter {
10205 break;
10206 }
10207 input.push(c);
10208 }
10209 Err(_) => break,
10210 }
10211 }
10212 }
10213 input
10214 .trim_end_matches('\n')
10215 .trim_end_matches('\r')
10216 .to_string()
10217 };
10218
10219 let processed = if raw_mode {
10220 input
10221 } else {
10222 input.replace("\\\n", "")
10223 };
10224
10225 if quiet {
10226 return if processed.is_empty() { 1 } else { 0 };
10227 }
10228
10229 if use_array {
10230 let var = &var_names[0];
10231 let words: Vec<String> = processed.split_whitespace().map(String::from).collect();
10232 self.arrays.insert(var.clone(), words);
10233 } else if var_names.len() == 1 {
10234 let var = &var_names[0];
10235 env::set_var(var, &processed);
10236 self.variables.insert(var.clone(), processed);
10237 } else {
10238 let ifs = self
10239 .variables
10240 .get("IFS")
10241 .map(|s| s.as_str())
10242 .unwrap_or(" \t\n");
10243 let words: Vec<&str> = processed
10244 .split(|c| ifs.contains(c))
10245 .filter(|s| !s.is_empty())
10246 .collect();
10247
10248 for (j, var) in var_names.iter().enumerate() {
10249 if j < words.len() {
10250 if j == var_names.len() - 1 && words.len() > var_names.len() {
10251 let remaining = words[j..].join(" ");
10252 env::set_var(var, &remaining);
10253 self.variables.insert(var.clone(), remaining);
10254 } else {
10255 env::set_var(var, words[j]);
10256 self.variables.insert(var.clone(), words[j].to_string());
10257 }
10258 } else {
10259 env::set_var(var, "");
10260 self.variables.insert(var.clone(), String::new());
10261 }
10262 }
10263 }
10264
10265 0
10266 }
10267
10268 fn builtin_shift(&mut self, args: &[String]) -> i32 {
10269 let mut from_end = false;
10275 let mut count = 1usize;
10276 let mut array_names: Vec<String> = Vec::new();
10277
10278 let mut i = 0;
10279 while i < args.len() {
10280 let arg = &args[i];
10281 if arg == "-p" {
10282 from_end = true;
10283 } else if arg.chars().all(|c| c.is_ascii_digit()) {
10284 count = arg.parse().unwrap_or(1);
10285 } else {
10286 array_names.push(arg.clone());
10287 }
10288 i += 1;
10289 }
10290
10291 if array_names.is_empty() {
10292 if from_end {
10294 for _ in 0..count {
10295 if !self.positional_params.is_empty() {
10296 self.positional_params.pop();
10297 }
10298 }
10299 } else {
10300 for _ in 0..count.min(self.positional_params.len()) {
10301 self.positional_params.remove(0);
10302 }
10303 }
10304 } else {
10305 for name in array_names {
10307 if let Some(arr) = self.arrays.get_mut(&name) {
10308 if from_end {
10309 for _ in 0..count {
10310 if !arr.is_empty() {
10311 arr.pop();
10312 }
10313 }
10314 } else {
10315 for _ in 0..count {
10316 if !arr.is_empty() {
10317 arr.remove(0);
10318 }
10319 }
10320 }
10321 }
10322 }
10323 }
10324
10325 0
10326 }
10327
10328 #[tracing::instrument(level = "debug", skip(self))]
10329 fn builtin_eval(&mut self, args: &[String]) -> i32 {
10330 let code = args.join(" ");
10331 match self.execute_script(&code) {
10332 Ok(status) => status,
10333 Err(e) => {
10334 eprintln!("eval: {}", e);
10335 1
10336 }
10337 }
10338 }
10339
10340 fn builtin_autoload(&mut self, args: &[String]) -> i32 {
10341 let mut functions = Vec::new();
10345 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;
10353
10354 let mut i = 0;
10355 while i < args.len() {
10356 let arg = &args[i];
10357
10358 if arg == "--" {
10359 i += 1;
10360 break;
10361 }
10362
10363 if arg.starts_with('+') {
10364 let flags = &arg[1..];
10365 for c in flags.chars() {
10366 match c {
10367 'U' => no_alias = false,
10368 'z' => zsh_style = false,
10369 'k' => ksh_style = false,
10370 't' => trace = false,
10371 'd' => use_caller_dir = false,
10372 _ => {}
10373 }
10374 }
10375 } else if arg.starts_with('-') {
10376 let flags = &arg[1..];
10377 if flags.is_empty() {
10378 i += 1;
10380 break;
10381 }
10382 for c in flags.chars() {
10383 match c {
10384 'U' => no_alias = true,
10385 'z' => zsh_style = true,
10386 'k' => ksh_style = true,
10387 'X' => execute_now = true,
10388 'r' | 'R' => resolve = true,
10389 't' => trace = true,
10390 'T' => {} 'W' => {} 'd' => use_caller_dir = true,
10393 'w' => {} 'm' => {} _ => {}
10396 }
10397 }
10398 } else {
10399 functions.push(arg.clone());
10400 }
10401 i += 1;
10402 }
10403
10404 while i < args.len() {
10406 functions.push(args[i].clone());
10407 i += 1;
10408 }
10409
10410 if functions.is_empty() && !execute_now {
10412 for (name, _) in &self.autoload_pending {
10413 if no_alias && zsh_style {
10414 println!("autoload -Uz {}", name);
10415 } else if no_alias {
10416 println!("autoload -U {}", name);
10417 } else {
10418 println!("autoload {}", name);
10419 }
10420 }
10421 return 0;
10422 }
10423
10424 if execute_now {
10428 for func_name in &functions {
10429 if let Some(loaded) = self.load_autoload_function(func_name) {
10431 let body = match loaded {
10433 ShellCommand::FunctionDef(_, body) => (*body).clone(),
10434 other => other,
10435 };
10436 self.functions.insert(func_name.clone(), body);
10438 self.autoload_pending.remove(func_name);
10440 } else {
10441 eprintln!(
10442 "autoload: {}: function definition file not found",
10443 func_name
10444 );
10445 return 1;
10446 }
10447 }
10448 return 0;
10449 }
10450
10451 for func_name in &functions {
10453 let mut flags = AutoloadFlags::empty();
10455 if no_alias {
10456 flags |= AutoloadFlags::NO_ALIAS;
10457 }
10458 if zsh_style {
10459 flags |= AutoloadFlags::ZSH_STYLE;
10460 }
10461 if ksh_style {
10462 flags |= AutoloadFlags::KSH_STYLE;
10463 }
10464 if trace {
10465 flags |= AutoloadFlags::TRACE;
10466 }
10467 if use_caller_dir {
10468 flags |= AutoloadFlags::USE_CALLER_DIR;
10469 }
10470
10471 self.autoload_pending.insert(func_name.clone(), flags);
10472
10473 let autoload_opts = if zsh_style && no_alias {
10476 "-XUz"
10477 } else if zsh_style {
10478 "-Xz"
10479 } else if no_alias {
10480 "-XU"
10481 } else {
10482 "-X"
10483 };
10484
10485 let stub = ShellCommand::List(vec![
10487 (
10488 ShellCommand::Simple(SimpleCommand {
10489 assignments: vec![],
10490 words: vec![
10491 ShellWord::Literal("builtin".to_string()),
10492 ShellWord::Literal("autoload".to_string()),
10493 ShellWord::Literal(autoload_opts.to_string()),
10494 ShellWord::Literal(func_name.clone()),
10495 ],
10496 redirects: vec![],
10497 }),
10498 ListOp::And,
10499 ),
10500 (
10501 ShellCommand::Simple(SimpleCommand {
10502 assignments: vec![],
10503 words: vec![
10504 ShellWord::Literal(func_name.clone()),
10505 ShellWord::DoubleQuoted(vec![ShellWord::Variable("@".to_string())]),
10506 ],
10507 redirects: vec![],
10508 }),
10509 ListOp::Semi,
10510 ),
10511 ]);
10512
10513 self.functions.insert(func_name.clone(), stub);
10514
10515 if resolve {
10517 if self.find_function_file(func_name).is_none() {
10518 eprintln!(
10519 "autoload: {}: function definition file not found",
10520 func_name
10521 );
10522 }
10523 }
10524 }
10525
10526 if functions.len() >= 4 && !resolve && !execute_now {
10530 let fpath_dirs: Vec<PathBuf> = self.fpath.clone();
10531 let names: Vec<String> = functions.clone();
10532 let pool = std::sync::Arc::clone(&self.worker_pool);
10533
10534 tracing::debug!(
10535 count = names.len(),
10536 fpath_dirs = fpath_dirs.len(),
10537 "batch autoload: pre-resolving fpath lookups on worker pool"
10538 );
10539
10540 let resolved = std::sync::Arc::new(parking_lot::Mutex::new(
10543 HashMap::<String, PathBuf>::with_capacity(names.len()),
10544 ));
10545
10546 for name in names {
10547 let dirs = fpath_dirs.clone();
10548 let resolved = std::sync::Arc::clone(&resolved);
10549 pool.submit(move || {
10550 for dir in &dirs {
10551 let path = dir.join(&name);
10552 if path.exists() && path.is_file() {
10553 let _ = std::fs::read(&path);
10556 resolved.lock().insert(name.clone(), path);
10557 tracing::trace!(func = %name, "autoload batch: pre-resolved");
10558 break;
10559 }
10560 }
10561 });
10562 }
10563 }
10564
10565 0
10566 }
10567
10568 fn find_function_file(&self, name: &str) -> Option<PathBuf> {
10570 for dir in &self.fpath {
10571 let path = dir.join(name);
10572 if path.exists() && path.is_file() {
10573 return Some(path);
10574 }
10575 }
10576 None
10577 }
10578
10579 fn load_autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
10581 if !self.zsh_compat {
10584 if let Some(ref cache) = self.compsys_cache {
10585 if let Ok(Some(bc_blob)) = cache.get_autoload_bytecode(name) {
10587 if let Ok(chunk) = bincode::deserialize::<fusevm::Chunk>(&bc_blob) {
10589 if !chunk.ops.is_empty() {
10590 tracing::trace!(
10591 name,
10592 bytes = bc_blob.len(),
10593 ops = chunk.ops.len(),
10594 "autoload: bytecode cache hit → VM"
10595 );
10596 let mut vm = fusevm::VM::new(chunk);
10598 let _ = vm.run();
10599 self.last_status = vm.last_status;
10600 return Some(ShellCommand::Simple(crate::parser::SimpleCommand {
10602 assignments: Vec::new(),
10603 words: Vec::new(),
10604 redirects: Vec::new(),
10605 }));
10606 }
10607 }
10608 if let Ok(commands) = bincode::deserialize::<Vec<ShellCommand>>(&bc_blob) {
10610 if !commands.is_empty() {
10611 tracing::trace!(
10612 name,
10613 bytes = bc_blob.len(),
10614 "autoload: legacy AST cache hit"
10615 );
10616 return Some(self.wrap_autoload_commands(name, commands));
10617 }
10618 }
10619 }
10620
10621 if let Ok(Some(body)) = cache.get_autoload_body(name) {
10623 let mut parser = ShellParser::new(&body);
10624 if let Ok(commands) = parser.parse_script() {
10625 if !commands.is_empty() {
10626 let compiler = crate::shell_compiler::ShellCompiler::new();
10628 let chunk = compiler.compile(&commands);
10629 if let Ok(blob) = bincode::serialize(&chunk) {
10630 let _ = cache.set_autoload_bytecode(name, &blob);
10631 tracing::trace!(
10632 name,
10633 bytes = blob.len(),
10634 "autoload: bytecodes compiled and cached"
10635 );
10636 }
10637 return Some(self.wrap_autoload_commands(name, commands));
10638 }
10639 }
10640 }
10641 }
10642 }
10643
10644 if !self.functions.contains_key(name) {
10646 for dir in &self.fpath.clone() {
10648 let zwc_path = dir.with_extension("zwc");
10650 if zwc_path.exists() {
10651 let prefixed_name = format!(
10653 "{}/{}",
10654 dir.file_name().and_then(|n| n.to_str()).unwrap_or(""),
10655 name
10656 );
10657 if let Some(func) = self.load_function_from_zwc(&zwc_path, &prefixed_name) {
10658 return Some(func);
10659 }
10660 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
10662 return Some(func);
10663 }
10664 }
10665 let func_zwc = dir.join(format!("{}.zwc", name));
10667 if func_zwc.exists() {
10668 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
10669 return Some(func);
10670 }
10671 }
10672 }
10673 }
10674
10675 let path = self.find_function_file(name)?;
10677
10678 let content = std::fs::read_to_string(&path).ok()?;
10680
10681 let mut parser = ShellParser::new(&content);
10683
10684 if let Ok(commands) = parser.parse_script() {
10685 if commands.is_empty() {
10686 return None;
10687 }
10688
10689 if commands.len() == 1 {
10691 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
10692 if fn_name == name {
10693 return Some(commands[0].clone());
10694 }
10695 }
10696 }
10697
10698 let body = if commands.len() == 1 {
10701 commands.into_iter().next().unwrap()
10702 } else {
10703 let list_cmds: Vec<(ShellCommand, ListOp)> =
10705 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
10706 ShellCommand::List(list_cmds)
10707 };
10708
10709 return Some(ShellCommand::FunctionDef(name.to_string(), Box::new(body)));
10710 }
10711
10712 None
10713 }
10714
10715 fn wrap_autoload_commands(&self, name: &str, commands: Vec<ShellCommand>) -> ShellCommand {
10717 if commands.len() == 1 {
10719 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
10720 if fn_name == name {
10721 return commands.into_iter().next().unwrap();
10722 }
10723 }
10724 }
10725 let body = if commands.len() == 1 {
10727 commands.into_iter().next().unwrap()
10728 } else {
10729 let list_cmds: Vec<(ShellCommand, ListOp)> =
10730 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
10731 ShellCommand::List(list_cmds)
10732 };
10733 ShellCommand::FunctionDef(name.to_string(), Box::new(body))
10734 }
10735
10736 pub fn maybe_autoload(&mut self, name: &str) -> bool {
10738 if self.autoload_pending.contains_key(name) {
10739 if let Some(func) = self.load_autoload_function(name) {
10740 let to_store = match func {
10742 ShellCommand::FunctionDef(_, body) => (*body).clone(),
10743 other => other,
10744 };
10745 self.functions.insert(name.to_string(), to_store);
10746 self.autoload_pending.remove(name);
10747 return true;
10748 }
10749 }
10750 false
10751 }
10752
10753 fn builtin_jobs(&mut self, args: &[String]) -> i32 {
10754 let mut long_format = false;
10763 let mut pids_only = false;
10764 let mut show_dir = false;
10765 let mut running_only = false;
10766 let mut stopped_only = false;
10767 let mut job_ids: Vec<usize> = Vec::new();
10768
10769 for arg in args {
10770 if arg.starts_with('-') {
10771 for c in arg[1..].chars() {
10772 match c {
10773 'l' => long_format = true,
10774 'p' => pids_only = true,
10775 'd' => show_dir = true,
10776 'r' => running_only = true,
10777 's' => stopped_only = true,
10778 'Z' => {} _ => {}
10780 }
10781 }
10782 } else if arg.starts_with('%') {
10783 if let Ok(id) = arg[1..].parse::<usize>() {
10784 job_ids.push(id);
10785 }
10786 } else if let Ok(id) = arg.parse::<usize>() {
10787 job_ids.push(id);
10788 }
10789 }
10790
10791 for job in self.jobs.reap_finished() {
10793 if !running_only && !stopped_only {
10794 if pids_only {
10795 println!("{}", job.pid);
10796 } else {
10797 println!("[{}] Done {}", job.id, job.command);
10798 }
10799 }
10800 }
10801
10802 for job in self.jobs.list() {
10804 if !job_ids.is_empty() && !job_ids.contains(&job.id) {
10806 continue;
10807 }
10808
10809 if running_only && job.state != JobState::Running {
10811 continue;
10812 }
10813 if stopped_only && job.state != JobState::Stopped {
10814 continue;
10815 }
10816
10817 if pids_only {
10818 println!("{}", job.pid);
10819 continue;
10820 }
10821
10822 let marker = if job.is_current { "+" } else { "-" };
10823 let state = match job.state {
10824 JobState::Running => "running",
10825 JobState::Stopped => "suspended",
10826 JobState::Done => "done",
10827 };
10828
10829 if long_format {
10830 println!(
10831 "[{}]{} {:6} {} {}",
10832 job.id, marker, job.pid, state, job.command
10833 );
10834 } else {
10835 println!("[{}]{} {} {}", job.id, marker, state, job.command);
10836 }
10837
10838 if show_dir {
10839 if let Ok(cwd) = env::current_dir() {
10840 println!(" (pwd: {})", cwd.display());
10841 }
10842 }
10843 }
10844 0
10845 }
10846
10847 fn builtin_fg(&mut self, args: &[String]) -> i32 {
10848 let job_id = if let Some(arg) = args.first() {
10849 let s = arg.trim_start_matches('%');
10851 match s.parse::<usize>() {
10852 Ok(id) => Some(id),
10853 Err(_) => {
10854 eprintln!("fg: {}: no such job", arg);
10855 return 1;
10856 }
10857 }
10858 } else {
10859 self.jobs.current().map(|j| j.id)
10860 };
10861
10862 let Some(id) = job_id else {
10863 eprintln!("fg: no current job");
10864 return 1;
10865 };
10866
10867 let Some(job) = self.jobs.get(id) else {
10868 eprintln!("fg: %{}: no such job", id);
10869 return 1;
10870 };
10871
10872 let pid = job.pid;
10873 let cmd = job.command.clone();
10874 println!("{}", cmd);
10875
10876 if let Err(e) = continue_job(pid) {
10878 eprintln!("fg: {}", e);
10879 return 1;
10880 }
10881
10882 match wait_for_job(pid) {
10884 Ok(status) => {
10885 self.jobs.remove(id);
10886 status
10887 }
10888 Err(e) => {
10889 eprintln!("fg: {}", e);
10890 1
10891 }
10892 }
10893 }
10894
10895 fn builtin_bg(&mut self, args: &[String]) -> i32 {
10896 let job_id = if let Some(arg) = args.first() {
10897 let s = arg.trim_start_matches('%');
10898 match s.parse::<usize>() {
10899 Ok(id) => Some(id),
10900 Err(_) => {
10901 eprintln!("bg: {}: no such job", arg);
10902 return 1;
10903 }
10904 }
10905 } else {
10906 self.jobs.current().map(|j| j.id)
10907 };
10908
10909 let Some(id) = job_id else {
10910 eprintln!("bg: no current job");
10911 return 1;
10912 };
10913
10914 let Some(job) = self.jobs.get_mut(id) else {
10915 eprintln!("bg: %{}: no such job", id);
10916 return 1;
10917 };
10918
10919 let pid = job.pid;
10920 let cmd = job.command.clone();
10921
10922 if let Err(e) = continue_job(pid) {
10923 eprintln!("bg: {}", e);
10924 return 1;
10925 }
10926
10927 job.state = JobState::Running;
10928 println!("[{}] {} &", id, cmd);
10929 0
10930 }
10931
10932 fn builtin_kill(&mut self, args: &[String]) -> i32 {
10933 use crate::jobs::send_signal;
10936 use nix::sys::signal::Signal;
10937
10938 if args.is_empty() {
10939 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
10940 eprintln!(" kill -l [sig ...]");
10941 return 1;
10942 }
10943
10944 let signal_map: &[(&str, i32, Signal)] = &[
10946 ("HUP", 1, Signal::SIGHUP),
10947 ("INT", 2, Signal::SIGINT),
10948 ("QUIT", 3, Signal::SIGQUIT),
10949 ("ILL", 4, Signal::SIGILL),
10950 ("TRAP", 5, Signal::SIGTRAP),
10951 ("ABRT", 6, Signal::SIGABRT),
10952 ("BUS", 7, Signal::SIGBUS),
10953 ("FPE", 8, Signal::SIGFPE),
10954 ("KILL", 9, Signal::SIGKILL),
10955 ("USR1", 10, Signal::SIGUSR1),
10956 ("SEGV", 11, Signal::SIGSEGV),
10957 ("USR2", 12, Signal::SIGUSR2),
10958 ("PIPE", 13, Signal::SIGPIPE),
10959 ("ALRM", 14, Signal::SIGALRM),
10960 ("TERM", 15, Signal::SIGTERM),
10961 ("CHLD", 17, Signal::SIGCHLD),
10962 ("CONT", 18, Signal::SIGCONT),
10963 ("STOP", 19, Signal::SIGSTOP),
10964 ("TSTP", 20, Signal::SIGTSTP),
10965 ("TTIN", 21, Signal::SIGTTIN),
10966 ("TTOU", 22, Signal::SIGTTOU),
10967 ("URG", 23, Signal::SIGURG),
10968 ("XCPU", 24, Signal::SIGXCPU),
10969 ("XFSZ", 25, Signal::SIGXFSZ),
10970 ("VTALRM", 26, Signal::SIGVTALRM),
10971 ("PROF", 27, Signal::SIGPROF),
10972 ("WINCH", 28, Signal::SIGWINCH),
10973 ("IO", 29, Signal::SIGIO),
10974 ("SYS", 31, Signal::SIGSYS),
10975 ];
10976
10977 let mut sig = Signal::SIGTERM;
10978 let mut pids: Vec<String> = Vec::new();
10979 let mut list_mode = false;
10980 let mut list_args: Vec<String> = Vec::new();
10981
10982 let mut i = 0;
10983 while i < args.len() {
10984 let arg = &args[i];
10985
10986 if arg == "-l" || arg == "-L" {
10987 list_mode = true;
10988 list_args = args[i + 1..].to_vec();
10990 break;
10991 } else if arg == "-s" {
10992 i += 1;
10994 if i >= args.len() {
10995 eprintln!("kill: -s requires an argument");
10996 return 1;
10997 }
10998 let sig_name = args[i].to_uppercase();
10999 let sig_name = sig_name.strip_prefix("SIG").unwrap_or(&sig_name);
11000 if let Some((_, _, s)) = signal_map.iter().find(|(name, _, _)| *name == sig_name) {
11001 sig = *s;
11002 } else {
11003 eprintln!("kill: invalid signal: {}", args[i]);
11004 return 1;
11005 }
11006 } else if arg == "-n" {
11007 i += 1;
11009 if i >= args.len() {
11010 eprintln!("kill: -n requires an argument");
11011 return 1;
11012 }
11013 let num: i32 = match args[i].parse() {
11014 Ok(n) => n,
11015 Err(_) => {
11016 eprintln!("kill: invalid signal number: {}", args[i]);
11017 return 1;
11018 }
11019 };
11020 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11021 sig = *s;
11022 } else {
11023 eprintln!("kill: invalid signal number: {}", num);
11024 return 1;
11025 }
11026 } else if arg.starts_with('-') && arg.len() > 1 {
11027 let sig_str = &arg[1..];
11029 let sig_upper = sig_str.to_uppercase();
11030 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
11031
11032 if let Ok(num) = sig_str.parse::<i32>() {
11034 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11035 sig = *s;
11036 } else {
11037 eprintln!("kill: invalid signal: {}", arg);
11038 return 1;
11039 }
11040 } else if let Some((_, _, s)) =
11041 signal_map.iter().find(|(name, _, _)| *name == sig_name)
11042 {
11043 sig = *s;
11044 } else {
11045 eprintln!("kill: invalid signal: {}", arg);
11046 return 1;
11047 }
11048 } else {
11049 pids.push(arg.clone());
11050 }
11051 i += 1;
11052 }
11053
11054 if list_mode {
11056 if list_args.is_empty() {
11057 for (name, num, _) in signal_map {
11059 println!("{:2}) SIG{}", num, name);
11060 }
11061 } else {
11062 for arg in &list_args {
11064 if let Ok(num) = arg.parse::<i32>() {
11065 if let Some((name, _, _)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11067 println!("{}", name);
11068 } else {
11069 eprintln!("kill: unknown signal: {}", num);
11070 }
11071 } else {
11072 let sig_upper = arg.to_uppercase();
11074 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
11075 if let Some((_, num, _)) =
11076 signal_map.iter().find(|(name, _, _)| *name == sig_name)
11077 {
11078 println!("{}", num);
11079 } else {
11080 eprintln!("kill: unknown signal: {}", arg);
11081 }
11082 }
11083 }
11084 }
11085 return 0;
11086 }
11087
11088 if pids.is_empty() {
11089 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
11090 return 1;
11091 }
11092
11093 let mut status = 0;
11094 for arg in &pids {
11095 if arg.starts_with('%') {
11097 let id: usize = match arg[1..].parse() {
11098 Ok(id) => id,
11099 Err(_) => {
11100 eprintln!("kill: {}: no such job", arg);
11101 status = 1;
11102 continue;
11103 }
11104 };
11105 if let Some(job) = self.jobs.get(id) {
11106 if let Err(e) = send_signal(job.pid, sig) {
11107 eprintln!("kill: {}", e);
11108 status = 1;
11109 }
11110 } else {
11111 eprintln!("kill: {}: no such job", arg);
11112 status = 1;
11113 }
11114 } else {
11115 let pid: u32 = match arg.parse() {
11117 Ok(p) => p,
11118 Err(_) => {
11119 eprintln!("kill: {}: invalid pid", arg);
11120 status = 1;
11121 continue;
11122 }
11123 };
11124 if let Err(e) = send_signal(pid as i32, sig) {
11125 eprintln!("kill: {}", e);
11126 status = 1;
11127 }
11128 }
11129 }
11130 status
11131 }
11132
11133 fn builtin_disown(&mut self, args: &[String]) -> i32 {
11134 if args.is_empty() {
11135 if let Some(job) = self.jobs.current() {
11137 let id = job.id;
11138 self.jobs.remove(id);
11139 }
11140 return 0;
11141 }
11142
11143 for arg in args {
11144 let s = arg.trim_start_matches('%');
11145 if let Ok(id) = s.parse::<usize>() {
11146 self.jobs.remove(id);
11147 } else {
11148 eprintln!("disown: {}: no such job", arg);
11149 }
11150 }
11151 0
11152 }
11153
11154 fn builtin_wait(&mut self, args: &[String]) -> i32 {
11155 if args.is_empty() {
11156 let ids: Vec<usize> = self.jobs.list().iter().map(|j| j.id).collect();
11158 for id in ids {
11159 if let Some(mut job) = self.jobs.remove(id) {
11160 if let Some(ref mut child) = job.child {
11161 let _ = wait_for_child(child);
11162 }
11163 }
11164 }
11165 return 0;
11166 }
11167
11168 let mut status = 0;
11169 for arg in args {
11170 if arg.starts_with('%') {
11171 let id: usize = match arg[1..].parse() {
11172 Ok(id) => id,
11173 Err(_) => {
11174 eprintln!("wait: {}: no such job", arg);
11175 status = 127;
11176 continue;
11177 }
11178 };
11179 if let Some(mut job) = self.jobs.remove(id) {
11180 if let Some(ref mut child) = job.child {
11181 match wait_for_child(child) {
11182 Ok(s) => status = s,
11183 Err(e) => {
11184 eprintln!("wait: {}", e);
11185 status = 127;
11186 }
11187 }
11188 }
11189 } else {
11190 eprintln!("wait: {}: no such job", arg);
11191 status = 127;
11192 }
11193 } else {
11194 let pid: u32 = match arg.parse() {
11195 Ok(p) => p,
11196 Err(_) => {
11197 eprintln!("wait: {}: invalid pid", arg);
11198 status = 127;
11199 continue;
11200 }
11201 };
11202 match wait_for_job(pid as i32) {
11203 Ok(s) => status = s,
11204 Err(e) => {
11205 eprintln!("wait: {}", e);
11206 status = 127;
11207 }
11208 }
11209 }
11210 }
11211 status
11212 }
11213
11214 fn builtin_suspend(&self, args: &[String]) -> i32 {
11215 let mut force = false;
11216 for arg in args {
11217 if arg == "-f" {
11218 force = true;
11219 }
11220 }
11221
11222 #[cfg(unix)]
11223 {
11224 use nix::sys::signal::{kill, Signal};
11225 use nix::unistd::getppid;
11226
11227 let ppid = getppid();
11229 if !force && ppid == nix::unistd::Pid::from_raw(1) {
11230 eprintln!("suspend: cannot suspend a login shell");
11231 return 1;
11232 }
11233
11234 let pid = nix::unistd::getpid();
11236 if let Err(e) = kill(pid, Signal::SIGTSTP) {
11237 eprintln!("suspend: {}", e);
11238 return 1;
11239 }
11240 0
11241 }
11242
11243 #[cfg(not(unix))]
11244 {
11245 eprintln!("suspend: not supported on this platform");
11246 1
11247 }
11248 }
11249}
11250
11251impl Default for ShellExecutor {
11252 fn default() -> Self {
11253 Self::new()
11254 }
11255}
11256
11257#[cfg(test)]
11258mod tests {
11259 use super::*;
11260
11261 #[test]
11262 fn test_simple_echo() {
11263 let mut exec = ShellExecutor::new();
11264 let status = exec.execute_script("true").unwrap();
11265 assert_eq!(status, 0);
11266 }
11267
11268 #[test]
11269 fn test_if_true() {
11270 let mut exec = ShellExecutor::new();
11271 let status = exec.execute_script("if true; then true; fi").unwrap();
11272 assert_eq!(status, 0);
11273 }
11274
11275 #[test]
11276 fn test_if_false() {
11277 let mut exec = ShellExecutor::new();
11278 let status = exec
11279 .execute_script("if false; then true; else false; fi")
11280 .unwrap();
11281 assert_eq!(status, 1);
11282 }
11283
11284 #[test]
11285 fn test_for_loop() {
11286 let mut exec = ShellExecutor::new();
11287 exec.execute_script("for i in a b c; do true; done")
11288 .unwrap();
11289 assert_eq!(exec.last_status, 0);
11290 }
11291
11292 #[test]
11293 fn test_and_list() {
11294 let mut exec = ShellExecutor::new();
11295 let status = exec.execute_script("true && true").unwrap();
11296 assert_eq!(status, 0);
11297
11298 let status = exec.execute_script("true && false").unwrap();
11299 assert_eq!(status, 1);
11300 }
11301
11302 #[test]
11303 fn test_or_list() {
11304 let mut exec = ShellExecutor::new();
11305 let status = exec.execute_script("false || true").unwrap();
11306 assert_eq!(status, 0);
11307 }
11308}
11309
11310impl ShellExecutor {
11311 fn builtin_history(&self, args: &[String]) -> i32 {
11312 let Some(ref engine) = self.history else {
11313 eprintln!("history: history engine not available");
11314 return 1;
11315 };
11316
11317 let mut count = 20usize;
11319 let mut show_all = false;
11320 let mut search_query = None;
11321
11322 let mut i = 0;
11323 while i < args.len() {
11324 match args[i].as_str() {
11325 "-c" | "--clear" => {
11326 eprintln!("history: clear not supported in this mode");
11328 return 1;
11329 }
11330 "-a" | "--all" => show_all = true,
11331 "-n" => {
11332 if i + 1 < args.len() {
11333 i += 1;
11334 count = args[i].parse().unwrap_or(20);
11335 }
11336 }
11337 s if s.starts_with('-') && s[1..].chars().all(|c| c.is_ascii_digit()) => {
11338 count = s[1..].parse().unwrap_or(20);
11339 }
11340 s if s.chars().all(|c| c.is_ascii_digit()) => {
11341 count = s.parse().unwrap_or(20);
11342 }
11343 s => {
11344 search_query = Some(s.to_string());
11345 }
11346 }
11347 i += 1;
11348 }
11349
11350 if show_all {
11351 count = 10000;
11352 }
11353
11354 let entries = if let Some(ref q) = search_query {
11355 engine.search(q, count)
11356 } else {
11357 engine.recent(count)
11358 };
11359
11360 match entries {
11361 Ok(entries) => {
11362 for entry in entries.into_iter().rev() {
11364 println!("{:>6} {}", entry.id, entry.command);
11365 }
11366 0
11367 }
11368 Err(e) => {
11369 eprintln!("history: {}", e);
11370 1
11371 }
11372 }
11373 }
11374
11375 fn builtin_fc(&mut self, args: &[String]) -> i32 {
11381 let Some(ref engine) = self.history else {
11382 eprintln!("fc: history engine not available");
11383 return 1;
11384 };
11385
11386 let mut list_mode = false;
11388 let mut no_numbers = false;
11389 let mut reverse = false;
11390 let mut show_time = false;
11391 let mut show_duration = false;
11392 let mut editor: Option<String> = None;
11393 let mut read_file = false;
11394 let mut write_file = false;
11395 let mut append_file = false;
11396 let mut substitute_mode = false;
11397 let mut positional: Vec<&str> = Vec::new();
11398 let mut substitutions: Vec<(String, String)> = Vec::new();
11399
11400 let mut i = 0;
11401 while i < args.len() {
11402 let arg = &args[i];
11403 if arg == "--" {
11404 i += 1;
11405 while i < args.len() {
11406 positional.push(&args[i]);
11407 i += 1;
11408 }
11409 break;
11410 }
11411 if arg.starts_with('-') && arg.len() > 1 {
11412 let chars: Vec<char> = arg[1..].chars().collect();
11413 let mut j = 0;
11414 while j < chars.len() {
11415 match chars[j] {
11416 'l' => list_mode = true,
11417 'n' => no_numbers = true,
11418 'r' => reverse = true,
11419 'd' | 'f' | 'E' | 'i' => show_time = true,
11420 'D' => show_duration = true,
11421 'R' => read_file = true,
11422 'W' => write_file = true,
11423 'A' => append_file = true,
11424 's' => substitute_mode = true,
11425 'e' => {
11426 if j + 1 < chars.len() {
11427 editor = Some(chars[j + 1..].iter().collect());
11428 break;
11429 } else {
11430 i += 1;
11431 if i < args.len() {
11432 editor = Some(args[i].clone());
11433 }
11434 }
11435 }
11436 't' => {
11437 show_time = true;
11438 if j + 1 < chars.len() {
11439 break;
11440 } else {
11441 i += 1;
11442 }
11443 }
11444 'p' | 'P' | 'a' | 'I' | 'L' | 'm' => {} _ => {
11446 if chars[j].is_ascii_digit() {
11447 positional.push(arg);
11448 break;
11449 }
11450 }
11451 }
11452 j += 1;
11453 }
11454 } else if arg.contains('=') && !list_mode {
11455 if let Some((old, new)) = arg.split_once('=') {
11456 substitutions.push((old.to_string(), new.to_string()));
11457 }
11458 } else {
11459 positional.push(arg);
11460 }
11461 i += 1;
11462 }
11463
11464 if read_file || write_file || append_file {
11467 let filename = positional.first().map(|s| *s).unwrap_or("~/.zsh_history");
11468 let path = if filename.starts_with("~/") {
11469 dirs::home_dir()
11470 .map(|h| h.join(&filename[2..]))
11471 .unwrap_or_else(|| std::path::PathBuf::from(filename))
11472 } else {
11473 std::path::PathBuf::from(filename)
11474 };
11475
11476 if read_file {
11477 if let Ok(contents) = std::fs::read_to_string(&path) {
11479 for line in contents.lines() {
11480 if !line.is_empty() && !line.starts_with('#') && !line.starts_with(':') {
11481 let _ = engine.add(line, None);
11482 }
11483 }
11484 } else {
11485 eprintln!("fc: cannot read {}", path.display());
11486 return 1;
11487 }
11488 } else if write_file || append_file {
11489 let mode = if append_file {
11491 std::fs::OpenOptions::new()
11492 .create(true)
11493 .append(true)
11494 .open(&path)
11495 } else {
11496 std::fs::File::create(&path)
11497 };
11498 match mode {
11499 Ok(mut file) => {
11500 use std::io::Write;
11501 if let Ok(entries) = engine.recent(10000) {
11502 for entry in entries.iter().rev() {
11503 let _ = writeln!(file, ": {}:0;{}", entry.timestamp, entry.command);
11504 }
11505 }
11506 }
11507 Err(e) => {
11508 eprintln!("fc: cannot write {}: {}", path.display(), e);
11509 return 1;
11510 }
11511 }
11512 }
11513 return 0;
11514 }
11515
11516 if list_mode || args.is_empty() {
11518 let (first, last) = match positional.len() {
11519 0 => (-16i64, -1i64),
11520 1 => {
11521 let n = positional[0].parse::<i64>().unwrap_or(-16);
11522 (n, -1)
11523 }
11524 _ => {
11525 let f = positional[0].parse::<i64>().unwrap_or(-16);
11526 let l = positional[1].parse::<i64>().unwrap_or(-1);
11527 (f, l)
11528 }
11529 };
11530
11531 let count = if first < 0 { (-first) as usize } else { 16 };
11532 match engine.recent(count.max(100)) {
11533 Ok(mut entries) => {
11534 if reverse {
11535 entries.reverse();
11536 }
11537 for entry in entries.iter().rev().take(count) {
11538 if no_numbers {
11539 println!("{}", entry.command);
11540 } else if show_time {
11541 println!(
11542 "{:>6} {:>10} {}",
11543 entry.id, entry.timestamp, entry.command
11544 );
11545 } else if show_duration {
11546 println!(
11547 "{:>6} {:>5} {}",
11548 entry.id,
11549 entry.duration_ms.unwrap_or(0),
11550 entry.command
11551 );
11552 } else {
11553 println!("{:>6} {}", entry.id, entry.command);
11554 }
11555 }
11556 0
11557 }
11558 Err(e) => {
11559 eprintln!("fc: {}", e);
11560 1
11561 }
11562 }
11563 } else if substitute_mode || !substitutions.is_empty() {
11564 match engine.get_by_offset(0) {
11566 Ok(Some(entry)) => {
11567 let mut cmd = entry.command.clone();
11568 for (old, new) in &substitutions {
11569 cmd = cmd.replace(old, new);
11570 }
11571 println!("{}", cmd);
11572 self.execute_script(&cmd).unwrap_or(1)
11573 }
11574 Ok(None) => {
11575 eprintln!("fc: no command to re-execute");
11576 1
11577 }
11578 Err(e) => {
11579 eprintln!("fc: {}", e);
11580 1
11581 }
11582 }
11583 } else if editor.as_deref() == Some("-") {
11584 match engine.get_by_offset(0) {
11586 Ok(Some(entry)) => {
11587 println!("{}", entry.command);
11588 self.execute_script(&entry.command).unwrap_or(1)
11589 }
11590 Ok(None) => {
11591 eprintln!("fc: no command to re-execute");
11592 1
11593 }
11594 Err(e) => {
11595 eprintln!("fc: {}", e);
11596 1
11597 }
11598 }
11599 } else if let Some(arg) = positional.first() {
11600 if arg.starts_with('-') || arg.starts_with('+') {
11601 let n: usize = arg[1..].parse().unwrap_or(1);
11603 let offset = if arg.starts_with('-') { n - 1 } else { n };
11604 match engine.get_by_offset(offset) {
11605 Ok(Some(entry)) => {
11606 println!("{}", entry.command);
11607 self.execute_script(&entry.command).unwrap_or(1)
11608 }
11609 Ok(None) => {
11610 eprintln!("fc: event not found");
11611 1
11612 }
11613 Err(e) => {
11614 eprintln!("fc: {}", e);
11615 1
11616 }
11617 }
11618 } else {
11619 match engine.search_prefix(arg, 1) {
11621 Ok(entries) if !entries.is_empty() => {
11622 println!("{}", entries[0].command);
11623 self.execute_script(&entries[0].command).unwrap_or(1)
11624 }
11625 Ok(_) => {
11626 eprintln!("fc: event not found: {}", arg);
11627 1
11628 }
11629 Err(e) => {
11630 eprintln!("fc: {}", e);
11631 1
11632 }
11633 }
11634 }
11635 } else {
11636 match engine.get_by_offset(0) {
11638 Ok(Some(entry)) => {
11639 println!("{}", entry.command);
11640 self.execute_script(&entry.command).unwrap_or(1)
11641 }
11642 Ok(None) => {
11643 eprintln!("fc: no command to re-execute");
11644 1
11645 }
11646 Err(e) => {
11647 eprintln!("fc: {}", e);
11648 1
11649 }
11650 }
11651 }
11652 }
11653
11654 fn builtin_trap(&mut self, args: &[String]) -> i32 {
11655 if args.is_empty() {
11656 for (sig, action) in &self.traps {
11658 println!("trap -- '{}' {}", action, sig);
11659 }
11660 return 0;
11661 }
11662
11663 if args.len() == 1 && args[0] == "-l" {
11665 let signals = [
11666 "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
11667 "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN",
11668 "TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS",
11669 ];
11670 for (i, sig) in signals.iter().enumerate() {
11671 print!("{:2}) SIG{:<8}", i + 1, sig);
11672 if (i + 1) % 5 == 0 {
11673 println!();
11674 }
11675 }
11676 println!();
11677 return 0;
11678 }
11679
11680 if args.len() >= 1 && args[0] == "-p" {
11682 let signals = if args.len() > 1 {
11683 &args[1..]
11684 } else {
11685 &[] as &[String]
11686 };
11687 if signals.is_empty() {
11688 for (sig, action) in &self.traps {
11689 println!("trap -- '{}' {}", action, sig);
11690 }
11691 } else {
11692 for sig in signals {
11693 if let Some(action) = self.traps.get(sig) {
11694 println!("trap -- '{}' {}", action, sig);
11695 }
11696 }
11697 }
11698 return 0;
11699 }
11700
11701 if args.len() == 1 {
11705 let sig = &args[0];
11707 if let Some(action) = self.traps.get(sig) {
11708 println!("trap -- '{}' {}", action, sig);
11709 }
11710 return 0;
11711 }
11712
11713 let action = &args[0];
11714 let signals = &args[1..];
11715
11716 for sig in signals {
11717 let sig_upper = sig.to_uppercase();
11718 let sig_name = if sig_upper.starts_with("SIG") {
11719 sig_upper[3..].to_string()
11720 } else {
11721 sig_upper.clone()
11722 };
11723
11724 if action.is_empty() || action == "-" {
11725 self.traps.remove(&sig_name);
11727 } else {
11728 self.traps.insert(sig_name, action.clone());
11729 }
11730 }
11731
11732 0
11733 }
11734
11735 pub fn run_trap(&mut self, signal: &str) {
11737 if let Some(action) = self.traps.get(signal).cloned() {
11738 let _ = self.execute_script(&action);
11739 }
11740 }
11741
11742 fn builtin_alias(&mut self, args: &[String]) -> i32 {
11743 let mut is_global = false;
11752 let mut is_suffix = false;
11753 let mut list_form = false;
11754 let mut pattern_match = false;
11755 let mut print_global = false;
11756 let mut print_suffix = false;
11757 let mut print_regular = false;
11758 let mut positional_args = Vec::new();
11759
11760 let mut i = 0;
11761 while i < args.len() {
11762 let arg = &args[i];
11763 if arg.starts_with('+') && arg.len() > 1 {
11764 for ch in arg[1..].chars() {
11766 match ch {
11767 'g' => print_global = true,
11768 's' => print_suffix = true,
11769 'r' => print_regular = true,
11770 'L' => list_form = true,
11771 'm' => pattern_match = true,
11772 _ => {}
11773 }
11774 }
11775 } else if arg.starts_with('-') && arg != "-" {
11776 for ch in arg[1..].chars() {
11777 match ch {
11778 'g' => is_global = true,
11779 's' => is_suffix = true,
11780 'L' => list_form = true,
11781 'm' => pattern_match = true,
11782 'r' => {} _ => {
11784 eprintln!("zshrs: alias: bad option: -{}", ch);
11785 return 1;
11786 }
11787 }
11788 }
11789 } else {
11790 positional_args.push(arg.clone());
11791 }
11792 i += 1;
11793 }
11794
11795 if print_global || print_suffix || print_regular {
11797 if print_regular {
11798 for (name, value) in &self.aliases {
11799 if list_form {
11800 println!("alias {}='{}'", name, value);
11801 } else {
11802 println!("{}='{}'", name, value);
11803 }
11804 }
11805 }
11806 if print_global {
11807 for (name, value) in &self.global_aliases {
11808 if list_form {
11809 println!("alias -g {}='{}'", name, value);
11810 } else {
11811 println!("{}='{}'", name, value);
11812 }
11813 }
11814 }
11815 if print_suffix {
11816 for (name, value) in &self.suffix_aliases {
11817 if list_form {
11818 println!("alias -s {}='{}'", name, value);
11819 } else {
11820 println!("{}='{}'", name, value);
11821 }
11822 }
11823 }
11824 return 0;
11825 }
11826
11827 if positional_args.is_empty() {
11828 let prefix = if is_suffix {
11830 "alias -s "
11831 } else if is_global {
11832 "alias -g "
11833 } else {
11834 "alias "
11835 };
11836 let alias_map: Vec<(String, String)> = if is_suffix {
11837 self.suffix_aliases
11838 .iter()
11839 .map(|(k, v)| (k.clone(), v.clone()))
11840 .collect()
11841 } else if is_global {
11842 self.global_aliases
11843 .iter()
11844 .map(|(k, v)| (k.clone(), v.clone()))
11845 .collect()
11846 } else {
11847 self.aliases
11848 .iter()
11849 .map(|(k, v)| (k.clone(), v.clone()))
11850 .collect()
11851 };
11852 for (name, value) in alias_map {
11853 if list_form {
11854 println!("{}{}='{}'", prefix, name, value);
11855 } else {
11856 println!("{}='{}'", name, value);
11857 }
11858 }
11859 return 0;
11860 }
11861
11862 for arg in &positional_args {
11863 if let Some(eq_pos) = arg.find('=') {
11864 let name = &arg[..eq_pos];
11866 let value = &arg[eq_pos + 1..];
11867 if is_suffix {
11868 self.suffix_aliases
11869 .insert(name.to_string(), value.to_string());
11870 } else if is_global {
11871 self.global_aliases
11872 .insert(name.to_string(), value.to_string());
11873 } else {
11874 self.aliases.insert(name.to_string(), value.to_string());
11875 }
11876 } else if pattern_match {
11877 let pattern = arg.replace("*", ".*").replace("?", ".");
11879 let re = regex::Regex::new(&format!("^{}$", pattern));
11880
11881 let alias_map: &HashMap<String, String> = if is_suffix {
11882 &self.suffix_aliases
11883 } else if is_global {
11884 &self.global_aliases
11885 } else {
11886 &self.aliases
11887 };
11888
11889 let prefix = if is_suffix {
11890 "alias -s "
11891 } else if is_global {
11892 "alias -g "
11893 } else {
11894 "alias "
11895 };
11896
11897 for (name, value) in alias_map {
11898 let matches = if let Ok(ref r) = re {
11899 r.is_match(name)
11900 } else {
11901 name.contains(arg.as_str())
11902 };
11903 if matches {
11904 if list_form {
11905 println!("{}{}='{}'", prefix, name, value);
11906 } else {
11907 println!("{}='{}'", name, value);
11908 }
11909 }
11910 }
11911 } else {
11912 let value = if is_suffix {
11914 self.suffix_aliases.get(arg.as_str()).cloned()
11915 } else if is_global {
11916 self.global_aliases.get(arg.as_str()).cloned()
11917 } else {
11918 self.aliases.get(arg.as_str()).cloned()
11919 };
11920 if let Some(v) = value {
11921 println!("{}='{}'", arg, v);
11922 } else {
11923 eprintln!("zshrs: alias: {}: not found", arg);
11924 return 1;
11925 }
11926 }
11927 }
11928 0
11929 }
11930
11931 fn builtin_unalias(&mut self, args: &[String]) -> i32 {
11932 if args.is_empty() {
11933 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
11934 return 1;
11935 }
11936
11937 let mut is_global = false;
11938 let mut is_suffix = false;
11939 let mut remove_all = false;
11940 let mut positional_args = Vec::new();
11941
11942 for arg in args {
11943 if arg.starts_with('-') && arg != "-" {
11944 for ch in arg[1..].chars() {
11945 match ch {
11946 'a' => remove_all = true,
11947 'g' => is_global = true,
11948 's' => is_suffix = true,
11949 'm' => {} _ => {
11951 eprintln!("zshrs: unalias: bad option: -{}", ch);
11952 return 1;
11953 }
11954 }
11955 }
11956 } else {
11957 positional_args.push(arg.clone());
11958 }
11959 }
11960
11961 if remove_all {
11962 if is_suffix {
11963 self.suffix_aliases.clear();
11964 } else if is_global {
11965 self.global_aliases.clear();
11966 } else {
11967 self.aliases.clear();
11969 self.global_aliases.clear();
11970 self.suffix_aliases.clear();
11971 }
11972 return 0;
11973 }
11974
11975 if positional_args.is_empty() {
11976 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
11977 return 1;
11978 }
11979
11980 for name in positional_args {
11981 let removed = if is_suffix {
11982 self.suffix_aliases.remove(&name).is_some()
11983 } else if is_global {
11984 self.global_aliases.remove(&name).is_some()
11985 } else {
11986 self.aliases.remove(&name).is_some()
11987 };
11988 if !removed {
11989 eprintln!("zshrs: unalias: {}: not found", name);
11990 return 1;
11991 }
11992 }
11993 0
11994 }
11995
11996 fn builtin_set(&mut self, args: &[String]) -> i32 {
11997 if args.is_empty() {
11998 let mut vars: Vec<_> = self.variables.iter().collect();
12000 vars.sort_by_key(|(k, _)| *k);
12001 for (k, v) in vars {
12002 println!("{}={}", k, shell_quote(v));
12003 }
12004 let mut arrs: Vec<_> = self.arrays.iter().collect();
12006 arrs.sort_by_key(|(k, _)| *k);
12007 for (k, v) in arrs {
12008 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
12009 println!("{}=( {} )", k, quoted.join(" "));
12010 }
12011 return 0;
12012 }
12013
12014 if args.len() == 1 && args[0] == "+" {
12016 let mut names: Vec<_> = self.variables.keys().collect();
12017 names.extend(self.arrays.keys());
12018 names.sort();
12019 names.dedup();
12020 for name in names {
12021 println!("{}", name);
12022 }
12023 return 0;
12024 }
12025
12026 let mut iter = args.iter().peekable();
12027 let mut set_array: Option<bool> = None; let mut array_name: Option<String> = None;
12029 let mut sort_asc = false;
12030 let mut sort_desc = false;
12031
12032 while let Some(arg) = iter.next() {
12033 match arg.as_str() {
12034 "-o" => {
12035 if iter.peek().is_none()
12037 || iter
12038 .peek()
12039 .map(|s| s.starts_with('-') || s.starts_with('+'))
12040 .unwrap_or(false)
12041 {
12042 self.print_options_table();
12043 continue;
12044 }
12045 if let Some(opt) = iter.next() {
12046 let (name, enable) = Self::normalize_option_name(opt);
12047 self.options.insert(name, enable);
12048 }
12049 }
12050 "+o" => {
12051 if iter.peek().is_none()
12053 || iter
12054 .peek()
12055 .map(|s| s.starts_with('-') || s.starts_with('+'))
12056 .unwrap_or(false)
12057 {
12058 self.print_options_reentrant();
12059 continue;
12060 }
12061 if let Some(opt) = iter.next() {
12062 let (name, enable) = Self::normalize_option_name(opt);
12063 self.options.insert(name, !enable);
12064 }
12065 }
12066 "-A" => {
12067 set_array = Some(true);
12068 if let Some(name) = iter.next() {
12069 if !name.starts_with('-') && !name.starts_with('+') {
12070 array_name = Some(name.clone());
12071 }
12072 }
12073 if array_name.is_none() {
12074 let mut arrs: Vec<_> = self.arrays.iter().collect();
12076 arrs.sort_by_key(|(k, _)| *k);
12077 for (k, v) in arrs {
12078 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
12079 println!("{}=( {} )", k, quoted.join(" "));
12080 }
12081 return 0;
12082 }
12083 }
12084 "+A" => {
12085 set_array = Some(false);
12086 if let Some(name) = iter.next() {
12087 if !name.starts_with('-') && !name.starts_with('+') {
12088 array_name = Some(name.clone());
12089 }
12090 }
12091 if array_name.is_none() {
12092 let mut names: Vec<_> = self.arrays.keys().collect();
12094 names.sort();
12095 for name in names {
12096 println!("{}", name);
12097 }
12098 return 0;
12099 }
12100 }
12101 "-s" => sort_asc = true,
12102 "+s" => sort_desc = true,
12103 "-e" => {
12104 self.options.insert("errexit".to_string(), true);
12105 }
12106 "+e" => {
12107 self.options.insert("errexit".to_string(), false);
12108 }
12109 "-x" => {
12110 self.options.insert("xtrace".to_string(), true);
12111 }
12112 "+x" => {
12113 self.options.insert("xtrace".to_string(), false);
12114 }
12115 "-u" => {
12116 self.options.insert("nounset".to_string(), true);
12117 }
12118 "+u" => {
12119 self.options.insert("nounset".to_string(), false);
12120 }
12121 "-v" => {
12122 self.options.insert("verbose".to_string(), true);
12123 }
12124 "+v" => {
12125 self.options.insert("verbose".to_string(), false);
12126 }
12127 "-n" => {
12128 self.options.insert("exec".to_string(), false);
12129 }
12130 "+n" => {
12131 self.options.insert("exec".to_string(), true);
12132 }
12133 "-f" => {
12134 self.options.insert("glob".to_string(), false);
12135 }
12136 "+f" => {
12137 self.options.insert("glob".to_string(), true);
12138 }
12139 "-m" => {
12140 self.options.insert("monitor".to_string(), true);
12141 }
12142 "+m" => {
12143 self.options.insert("monitor".to_string(), false);
12144 }
12145 "-C" => {
12146 self.options.insert("clobber".to_string(), false);
12147 }
12148 "+C" => {
12149 self.options.insert("clobber".to_string(), true);
12150 }
12151 "-b" => {
12152 self.options.insert("notify".to_string(), true);
12153 }
12154 "+b" => {
12155 self.options.insert("notify".to_string(), false);
12156 }
12157 "--" => {
12158 let remaining: Vec<String> = iter.cloned().collect();
12159 if let Some(ref name) = array_name {
12160 let mut values = remaining;
12161 if sort_asc {
12162 values.sort();
12163 } else if sort_desc {
12164 values.sort();
12165 values.reverse();
12166 }
12167 if set_array == Some(true) {
12168 self.arrays.insert(name.clone(), values);
12169 } else {
12170 let arr = self.arrays.entry(name.clone()).or_default();
12172 for (i, v) in values.into_iter().enumerate() {
12173 if i < arr.len() {
12174 arr[i] = v;
12175 } else {
12176 arr.push(v);
12177 }
12178 }
12179 }
12180 } else if remaining.is_empty() {
12181 self.positional_params.clear();
12183 } else {
12184 let mut values = remaining;
12185 if sort_asc {
12186 values.sort();
12187 } else if sort_desc {
12188 values.sort();
12189 values.reverse();
12190 }
12191 self.positional_params = values;
12192 }
12193 return 0;
12194 }
12195 _ => {
12196 if arg.starts_with('-') && arg.len() > 1 {
12198 for c in arg[1..].chars() {
12199 match c {
12200 'e' => {
12201 self.options.insert("errexit".to_string(), true);
12202 }
12203 'x' => {
12204 self.options.insert("xtrace".to_string(), true);
12205 }
12206 'u' => {
12207 self.options.insert("nounset".to_string(), true);
12208 }
12209 'v' => {
12210 self.options.insert("verbose".to_string(), true);
12211 }
12212 'n' => {
12213 self.options.insert("exec".to_string(), false);
12214 }
12215 'f' => {
12216 self.options.insert("glob".to_string(), false);
12217 }
12218 'm' => {
12219 self.options.insert("monitor".to_string(), true);
12220 }
12221 'C' => {
12222 self.options.insert("clobber".to_string(), false);
12223 }
12224 'b' => {
12225 self.options.insert("notify".to_string(), true);
12226 }
12227 _ => {
12228 eprintln!("zshrs: set: -{}: invalid option", c);
12229 return 1;
12230 }
12231 }
12232 }
12233 continue;
12234 }
12235 if arg.starts_with('+') && arg.len() > 1 {
12236 for c in arg[1..].chars() {
12237 match c {
12238 'e' => {
12239 self.options.insert("errexit".to_string(), false);
12240 }
12241 'x' => {
12242 self.options.insert("xtrace".to_string(), false);
12243 }
12244 'u' => {
12245 self.options.insert("nounset".to_string(), false);
12246 }
12247 'v' => {
12248 self.options.insert("verbose".to_string(), false);
12249 }
12250 'n' => {
12251 self.options.insert("exec".to_string(), true);
12252 }
12253 'f' => {
12254 self.options.insert("glob".to_string(), true);
12255 }
12256 'm' => {
12257 self.options.insert("monitor".to_string(), false);
12258 }
12259 'C' => {
12260 self.options.insert("clobber".to_string(), true);
12261 }
12262 'b' => {
12263 self.options.insert("notify".to_string(), false);
12264 }
12265 _ => {
12266 eprintln!("zshrs: set: +{}: invalid option", c);
12267 return 1;
12268 }
12269 }
12270 }
12271 continue;
12272 }
12273 let mut values: Vec<String> =
12275 std::iter::once(arg.clone()).chain(iter.cloned()).collect();
12276 if sort_asc {
12277 values.sort();
12278 } else if sort_desc {
12279 values.sort();
12280 values.reverse();
12281 }
12282 if let Some(ref name) = array_name {
12283 if set_array == Some(true) {
12284 self.arrays.insert(name.clone(), values);
12285 } else {
12286 let arr = self.arrays.entry(name.clone()).or_default();
12287 for (i, v) in values.into_iter().enumerate() {
12288 if i < arr.len() {
12289 arr[i] = v;
12290 } else {
12291 arr.push(v);
12292 }
12293 }
12294 }
12295 } else {
12296 self.positional_params = values;
12297 }
12298 return 0;
12299 }
12300 }
12301 }
12302 0
12303 }
12304
12305 fn default_on_options() -> &'static [&'static str] {
12306 &[
12307 "aliases",
12308 "alwayslastprompt",
12309 "appendhistory",
12310 "autolist",
12311 "automenu",
12312 "autoparamkeys",
12313 "autoparamslash",
12314 "autoremoveslash",
12315 "badpattern",
12316 "banghist",
12317 "bareglobqual",
12318 "beep",
12319 "bgnice",
12320 "caseglob",
12321 "casematch",
12322 "checkjobs",
12323 "checkrunningjobs",
12324 "clobber",
12325 "debugbeforecmd",
12326 "equals",
12327 "evallineno",
12328 "exec",
12329 "flowcontrol",
12330 "functionargzero",
12331 "glob",
12332 "globalexport",
12333 "globalrcs",
12334 "hashcmds",
12335 "hashdirs",
12336 "hashlistall",
12337 "histbeep",
12338 "histsavebycopy",
12339 "hup",
12340 "interactive",
12341 "listambiguous",
12342 "listbeep",
12343 "listtypes",
12344 "monitor",
12345 "multibyte",
12346 "multifuncdef",
12347 "multios",
12348 "nomatch",
12349 "notify",
12350 "promptcr",
12351 "promptpercent",
12352 "promptsp",
12353 "rcs",
12354 "shinstdin",
12355 "shortloops",
12356 "unset",
12357 "zle",
12358 ]
12359 }
12360
12361 fn print_options_table(&self) {
12362 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
12363 opts.sort();
12364 let defaults_on = Self::default_on_options();
12365 for &opt in &opts {
12366 let enabled = self.options.get(opt).copied().unwrap_or(false);
12367 let is_default_on = defaults_on.contains(&opt);
12368 let (display_name, display_state) = if is_default_on {
12371 (format!("no{}", opt), if enabled { "off" } else { "on" })
12372 } else {
12373 (opt.to_string(), if enabled { "on" } else { "off" })
12374 };
12375 println!("{:<22}{}", display_name, display_state);
12376 }
12377 }
12378
12379 fn print_options_reentrant(&self) {
12380 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
12381 opts.sort();
12382 let defaults_on = Self::default_on_options();
12383 for &opt in &opts {
12384 let enabled = self.options.get(opt).copied().unwrap_or(false);
12385 let is_default_on = defaults_on.contains(&opt);
12386 let (display_name, use_minus) = if is_default_on {
12388 (format!("no{}", opt), !enabled)
12389 } else {
12390 (opt.to_string(), enabled)
12391 };
12392 if use_minus {
12393 println!("set -o {}", display_name);
12394 } else {
12395 println!("set +o {}", display_name);
12396 }
12397 }
12398 }
12399
12400 fn builtin_caller(&self, args: &[String]) -> i32 {
12402 let depth: usize = args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
12403 if depth == 0 {
12406 println!("1 main");
12407 } else {
12408 println!("{} main", depth);
12409 }
12410 0
12411 }
12412
12413 fn builtin_doctor(&self, _args: &[String]) -> i32 {
12415 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
12416 let red = |s: &str| format!("\x1b[31m{}\x1b[0m", s);
12417 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
12418 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12419 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
12420
12421 println!("{}", bold("zshrs doctor"));
12422 println!("{}", dim(&"=".repeat(60)));
12423 println!();
12424
12425 println!("{}", bold("Environment"));
12427 println!(" version: zshrs {}", env!("CARGO_PKG_VERSION"));
12428 println!(" pid: {}", std::process::id());
12429 let cwd = env::current_dir()
12430 .map(|p| p.to_string_lossy().to_string())
12431 .unwrap_or_else(|_| "?".to_string());
12432 println!(" cwd: {}", cwd);
12433 println!(
12434 " shell: {}",
12435 env::var("SHELL").unwrap_or_else(|_| "?".to_string())
12436 );
12437 println!(" pool size: {}", self.worker_pool.size());
12438 println!(
12439 " pool done: {} tasks completed",
12440 self.worker_pool.completed()
12441 );
12442 println!(" pool queue: {} pending", self.worker_pool.queue_depth());
12443 println!();
12444
12445 println!("{}", bold("Config"));
12447 let config_path = crate::config::config_path();
12448 if config_path.exists() {
12449 println!(" {} {}", green("*"), config_path.display());
12450 } else {
12451 println!(
12452 " {} {} {}",
12453 dim("-"),
12454 config_path.display(),
12455 dim("(using defaults)")
12456 );
12457 }
12458 println!();
12459
12460 println!("{}", bold("PATH"));
12462 let path_var = env::var("PATH").unwrap_or_default();
12463 let path_dirs: Vec<&str> = path_var.split(':').filter(|s| !s.is_empty()).collect();
12464 let path_ok = path_dirs
12465 .iter()
12466 .filter(|d| std::path::Path::new(d).is_dir())
12467 .count();
12468 let path_missing = path_dirs.len() - path_ok;
12469 println!(
12470 " directories: {} total, {} {}, {} {}",
12471 path_dirs.len(),
12472 path_ok,
12473 green("valid"),
12474 path_missing,
12475 if path_missing > 0 {
12476 red("missing")
12477 } else {
12478 green("missing")
12479 },
12480 );
12481 println!(" hash table: {} entries", self.command_hash.len());
12482 println!();
12483
12484 println!("{}", bold("FPATH"));
12486 println!(" directories: {}", self.fpath.len());
12487 let fpath_ok = self.fpath.iter().filter(|d| d.is_dir()).count();
12488 let fpath_missing = self.fpath.len() - fpath_ok;
12489 if fpath_missing > 0 {
12490 println!(" {} {} missing fpath directories", red("!"), fpath_missing);
12491 }
12492 println!(" functions: {} loaded", self.functions.len());
12493 println!(" autoload: {} pending", self.autoload_pending.len());
12494 println!();
12495
12496 println!("{}", bold("SQLite Caches"));
12498 if let Some(ref engine) = self.history {
12499 let count = engine.count().unwrap_or(0);
12500 println!(" history: {} entries {}", count, green("OK"));
12501 } else {
12502 println!(" history: {}", yellow("not initialized"));
12503 }
12504
12505 if let Some(ref cache) = self.compsys_cache {
12506 let count = compsys::cache_entry_count(cache);
12507 println!(" compsys: {} completions {}", count, green("OK"));
12508
12509 if let Ok(missing) = cache.get_autoloads_missing_bytecode() {
12511 if missing.is_empty() {
12512 println!(
12513 " bytecode cache: {}",
12514 green("all functions compiled to bytecode")
12515 );
12516 } else {
12517 println!(
12518 " bytecode cache: {} functions {}",
12519 missing.len(),
12520 yellow("missing bytecode blobs")
12521 );
12522 }
12523 }
12524 } else {
12525 println!(" compsys: {}", yellow("no cache"));
12526 }
12527
12528 if let Some(ref cache) = self.plugin_cache {
12529 let (plugins, functions) = cache.stats();
12530 println!(
12531 " plugins: {} plugins, {} cached functions {}",
12532 plugins,
12533 functions,
12534 green("OK")
12535 );
12536 } else {
12537 println!(" plugins: {}", yellow("no cache"));
12538 }
12539 println!();
12540
12541 println!("{}", bold("Shell State"));
12543 println!(" aliases: {}", self.aliases.len());
12544 println!(" global: {} aliases", self.global_aliases.len());
12545 println!(" suffix: {} aliases", self.suffix_aliases.len());
12546 println!(" variables: {}", self.variables.len());
12547 println!(" arrays: {}", self.arrays.len());
12548 println!(" assoc: {}", self.assoc_arrays.len());
12549 println!(
12550 " options: {} set",
12551 self.options.iter().filter(|(_, v)| **v).count()
12552 );
12553 println!(" traps: {} active", self.traps.len());
12554 println!(
12555 " hooks: {} registered",
12556 self.hook_functions.values().map(|v| v.len()).sum::<usize>()
12557 );
12558 println!();
12559
12560 println!("{}", bold("Log"));
12562 let log_path = crate::log::log_path();
12563 if log_path.exists() {
12564 let size = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);
12565 println!(" {} {} bytes", log_path.display(), size);
12566 } else {
12567 println!(" {}", dim("no log file yet"));
12568 }
12569 println!();
12570
12571 println!("{}", bold("Profiling"));
12573 println!(
12574 " chrome tracing: {}",
12575 if crate::log::profiling_enabled() {
12576 green("enabled")
12577 } else {
12578 dim("disabled")
12579 }
12580 );
12581 println!(
12582 " flamegraph: {}",
12583 if crate::log::flamegraph_enabled() {
12584 green("enabled")
12585 } else {
12586 dim("disabled")
12587 }
12588 );
12589 println!(
12590 " prometheus: {}",
12591 if crate::log::prometheus_enabled() {
12592 green("enabled")
12593 } else {
12594 dim("disabled")
12595 }
12596 );
12597 println!();
12598
12599 0
12600 }
12601
12602 fn builtin_dbview(&self, args: &[String]) -> i32 {
12615 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12616 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
12617 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
12618 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
12619 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
12620
12621 if args.is_empty() {
12622 println!("{}", bold("zshrs SQLite caches"));
12624 println!();
12625
12626 if let Some(ref cache) = self.compsys_cache {
12627 println!(" {} {}", bold("compsys.db"), dim("(completion cache)"));
12628 if let Ok(n) = cache.count_table("autoloads") {
12629 let bc_count = cache
12630 .count_table_where("autoloads", "bytecode IS NOT NULL")
12631 .unwrap_or(0);
12632 println!(" autoloads: {:>6} rows ({} compiled)", n, bc_count);
12633 }
12634 if let Ok(n) = cache.count_table("comps") {
12635 println!(" comps: {:>6} rows", n);
12636 }
12637 if let Ok(n) = cache.count_table("services") {
12638 println!(" services: {:>6} rows", n);
12639 }
12640 if let Ok(n) = cache.count_table("patcomps") {
12641 println!(" patcomps: {:>6} rows", n);
12642 }
12643 if let Ok(n) = cache.count_table("executables") {
12644 println!(" executables: {:>6} rows", n);
12645 }
12646 if let Ok(n) = cache.count_table("zstyles") {
12647 println!(" zstyles: {:>6} rows", n);
12648 }
12649 println!();
12650 }
12651
12652 if let Some(ref engine) = self.history {
12653 println!(" {} {}", bold("history.db"), dim("(command history)"));
12654 if let Ok(n) = engine.count() {
12655 println!(" entries: {:>6} rows", n);
12656 }
12657 println!();
12658 }
12659
12660 if let Some(ref cache) = self.plugin_cache {
12661 let (plugins, functions) = cache.stats();
12662 println!(" {} {}", bold("plugins.db"), dim("(plugin source cache)"));
12663 println!(" plugins: {:>6} rows", plugins);
12664 println!(" functions: {:>6} rows", functions);
12665 println!();
12666 }
12667
12668 println!(" Usage: {} <table> [name] [--count]", cyan("dbview"));
12669 return 0;
12670 }
12671
12672 let table = args[0].as_str();
12673 let filter = args.get(1).map(|s| s.as_str());
12674 let count_only = args.iter().any(|a| a == "--count" || a == "-c");
12675
12676 match table {
12677 "autoloads" => {
12678 let Some(ref cache) = self.compsys_cache else {
12679 eprintln!("dbview: no compsys cache");
12680 return 1;
12681 };
12682
12683 if count_only {
12684 let n = cache.count_table("autoloads").unwrap_or(0);
12685 println!("{}", n);
12686 return 0;
12687 }
12688
12689 if let Some(name) = filter {
12690 match cache.get_autoload(name) {
12692 Ok(Some(stub)) => {
12693 println!("{}", bold(&format!("autoload: {}", name)));
12694 println!(" source: {}", stub.source);
12695 println!(
12696 " body: {} bytes",
12697 stub.body.as_ref().map(|b| b.len()).unwrap_or(0)
12698 );
12699 match cache.get_autoload_bytecode(name) {
12700 Ok(Some(blob)) => {
12701 println!(" bytecode: {} {} bytes", green("YES"), blob.len())
12702 }
12703 _ => println!(" bytecode: {}", yellow("NULL")),
12704 }
12705 if let Some(ref body) = stub.body {
12707 println!(" preview:");
12708 for (i, line) in body.lines().take(10).enumerate() {
12709 println!(" {:>3}: {}", i + 1, dim(line));
12710 }
12711 let total = body.lines().count();
12712 if total > 10 {
12713 println!(" {} ({} more lines)", dim("..."), total - 10);
12714 }
12715 }
12716 }
12717 _ => {
12718 eprintln!("dbview: autoload '{}' not found", name);
12719 return 1;
12720 }
12721 }
12722 return 0;
12723 }
12724
12725 let conn = &cache.conn();
12727 match conn.prepare("SELECT name, source, length(body), length(bytecode) FROM autoloads ORDER BY name LIMIT 200") {
12728 Ok(mut stmt) => {
12729 let rows = stmt.query_map([], |row| {
12730 Ok((
12731 row.get::<_, String>(0)?,
12732 row.get::<_, String>(1)?,
12733 row.get::<_, Option<i64>>(2)?,
12734 row.get::<_, Option<i64>>(3)?,
12735 ))
12736 });
12737 if let Ok(rows) = rows {
12738 println!("{:<40} {:>8} {:>8} {}", bold("NAME"), bold("BODY"), bold("BYTECODE"), bold("SOURCE"));
12739 let mut count = 0;
12740 for row in rows.flatten() {
12741 let (name, source, body_len, ast_len) = row;
12742 let ast_str = match ast_len {
12743 Some(n) => green(&format!("{:>8}", n)),
12744 None => yellow(&format!("{:>8}", "NULL")),
12745 };
12746 let body_str = match body_len {
12747 Some(n) => format!("{:>8}", n),
12748 None => dim("NULL").to_string(),
12749 };
12750 let src_short = if source.len() > 50 {
12752 format!("...{}", &source[source.len() - 47..])
12753 } else {
12754 source
12755 };
12756 println!("{:<40} {} {} {}", name, body_str, ast_str, dim(&src_short));
12757 count += 1;
12758 }
12759 println!("\n{} rows shown (LIMIT 200)", count);
12760 }
12761 }
12762 Err(e) => {
12763 eprintln!("dbview: query failed: {}", e);
12764 return 1;
12765 }
12766 }
12767 }
12768
12769 "comps" => {
12770 let Some(ref cache) = self.compsys_cache else {
12771 eprintln!("dbview: no compsys cache");
12772 return 1;
12773 };
12774 if count_only {
12775 println!("{}", cache.count_table("comps").unwrap_or(0));
12776 return 0;
12777 }
12778 let conn = cache.conn();
12779 let query = if let Some(pat) = filter {
12780 format!("SELECT command, function FROM comps WHERE command LIKE '%{}%' ORDER BY command LIMIT 100", pat)
12781 } else {
12782 "SELECT command, function FROM comps ORDER BY command LIMIT 100".to_string()
12783 };
12784 match conn.prepare(&query) {
12785 Ok(mut stmt) => {
12786 println!("{:<40} {}", bold("COMMAND"), bold("FUNCTION"));
12787 let rows = stmt.query_map([], |row| {
12788 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
12789 });
12790 if let Ok(rows) = rows {
12791 for row in rows.flatten() {
12792 println!("{:<40} {}", row.0, cyan(&row.1));
12793 }
12794 }
12795 }
12796 Err(e) => {
12797 eprintln!("dbview: {}", e);
12798 return 1;
12799 }
12800 }
12801 }
12802
12803 "executables" => {
12804 let Some(ref cache) = self.compsys_cache else {
12805 eprintln!("dbview: no compsys cache");
12806 return 1;
12807 };
12808 if count_only {
12809 println!("{}", cache.count_table("executables").unwrap_or(0));
12810 return 0;
12811 }
12812 let conn = cache.conn();
12813 let query = if let Some(pat) = filter {
12814 format!("SELECT name, path FROM executables WHERE name LIKE '%{}%' ORDER BY name LIMIT 100", pat)
12815 } else {
12816 "SELECT name, path FROM executables ORDER BY name LIMIT 100".to_string()
12817 };
12818 match conn.prepare(&query) {
12819 Ok(mut stmt) => {
12820 println!("{:<30} {}", bold("NAME"), bold("PATH"));
12821 let rows = stmt.query_map([], |row| {
12822 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
12823 });
12824 if let Ok(rows) = rows {
12825 for row in rows.flatten() {
12826 println!("{:<30} {}", row.0, dim(&row.1));
12827 }
12828 }
12829 }
12830 Err(e) => {
12831 eprintln!("dbview: {}", e);
12832 return 1;
12833 }
12834 }
12835 }
12836
12837 "history" => {
12838 let Some(ref engine) = self.history else {
12839 eprintln!("dbview: no history engine");
12840 return 1;
12841 };
12842 if count_only {
12843 println!("{}", engine.count().unwrap_or(0));
12844 return 0;
12845 }
12846 if let Some(pat) = filter {
12847 if let Ok(entries) = engine.search(pat, 20) {
12848 for e in entries {
12849 println!(
12850 " {} {} {}",
12851 dim(&e.timestamp.to_string()),
12852 cyan(&e.command),
12853 dim(&format!("[{}]", e.exit_code.unwrap_or(0)))
12854 );
12855 }
12856 }
12857 } else if let Ok(entries) = engine.recent(20) {
12858 for e in entries {
12859 println!(
12860 " {} {} {}",
12861 dim(&e.timestamp.to_string()),
12862 cyan(&e.command),
12863 dim(&format!("[{}]", e.exit_code.unwrap_or(0)))
12864 );
12865 }
12866 }
12867 }
12868
12869 "plugins" => {
12870 let Some(ref cache) = self.plugin_cache else {
12871 eprintln!("dbview: no plugin cache");
12872 return 1;
12873 };
12874 let (plugins, functions) = cache.stats();
12875 println!("{} plugins, {} cached functions", plugins, functions);
12876 }
12877
12878 _ => {
12879 eprintln!("dbview: unknown table '{}'. Available: autoloads, comps, executables, history, plugins", table);
12880 return 1;
12881 }
12882 }
12883
12884 0
12885 }
12886
12887 fn builtin_profile(&mut self, args: &[String]) -> i32 {
12900 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12901 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
12902 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
12903 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
12904
12905 if args.is_empty() {
12906 println!("Usage: profile {{ commands }}");
12907 println!(" profile -s 'script string'");
12908 println!(" profile -f function_name [args...]");
12909 println!(" profile --clear");
12910 println!(" profile --dump");
12911 return 0;
12912 }
12913
12914 if args[0] == "--clear" {
12915 self.profiler = crate::zprof::Profiler::new();
12916 println!("profile data cleared");
12917 return 0;
12918 }
12919
12920 if args[0] == "--dump" {
12921 let (_, output) = crate::zprof::builtin_zprof(
12922 &mut self.profiler,
12923 &crate::zprof::ZprofOptions { clear: false },
12924 );
12925 if !output.is_empty() {
12926 print!("{}", output);
12927 } else {
12928 println!("{}", dim("no profile data"));
12929 }
12930 return 0;
12931 }
12932
12933 let code = if args[0] == "-s" {
12935 if args.len() < 2 {
12937 eprintln!("profile: -s requires a script string");
12938 return 1;
12939 }
12940 args[1..].join(" ")
12941 } else if args[0] == "-f" {
12942 if args.len() < 2 {
12944 eprintln!("profile: -f requires a function name");
12945 return 1;
12946 }
12947 args[1..].join(" ")
12948 } else {
12949 args.join(" ")
12951 };
12952
12953 let was_enabled = self.profiling_enabled;
12955 self.profiling_enabled = true;
12956 self.profiler = crate::zprof::Profiler::new(); let t0 = std::time::Instant::now();
12959 let result = self.execute_script(&code);
12960 let elapsed = t0.elapsed();
12961 let status = match result {
12962 Ok(s) => s,
12963 Err(e) => {
12964 eprintln!("profile: {}", e);
12965 1
12966 }
12967 };
12968
12969 println!();
12971 println!("{}", bold("profile results"));
12972 println!("{}", dim(&"─".repeat(60)));
12973 let dur_str = if elapsed.as_secs() > 0 {
12974 format!("{:.3}s", elapsed.as_secs_f64())
12975 } else if elapsed.as_millis() > 0 {
12976 format!("{:.3}ms", elapsed.as_secs_f64() * 1000.0)
12977 } else {
12978 format!("{:.1}µs", elapsed.as_secs_f64() * 1_000_000.0)
12979 };
12980 println!(" total: {}", cyan(&dur_str));
12981 println!(" status: {}", status);
12982 println!();
12983
12984 let (_, output) = crate::zprof::builtin_zprof(
12986 &mut self.profiler,
12987 &crate::zprof::ZprofOptions { clear: false },
12988 );
12989 if !output.is_empty() {
12990 println!("{}", bold("function breakdown"));
12991 print!("{}", output);
12992 }
12993
12994 println!();
12996 println!(
12997 " {} set ZSHRS_LOG=trace for per-command tracing",
12998 yellow("tip:")
12999 );
13000 println!(
13001 " {} output: {}",
13002 dim("log"),
13003 dim(&crate::log::log_path().display().to_string())
13004 );
13005
13006 self.profiling_enabled = was_enabled;
13007 status
13008 }
13009
13010 fn run_intercepts(
13017 &mut self,
13018 cmd_name: &str,
13019 full_cmd: &str,
13020 args: &[String],
13021 ) -> Option<Result<i32, String>> {
13022 let matching: Vec<Intercept> = self
13024 .intercepts
13025 .iter()
13026 .filter(|i| intercept_matches(&i.pattern, cmd_name, full_cmd))
13027 .cloned()
13028 .collect();
13029
13030 if matching.is_empty() {
13031 return None;
13032 }
13033
13034 self.variables
13036 .insert("INTERCEPT_NAME".to_string(), cmd_name.to_string());
13037 self.variables
13038 .insert("INTERCEPT_ARGS".to_string(), args.join(" "));
13039 self.variables
13040 .insert("INTERCEPT_CMD".to_string(), full_cmd.to_string());
13041
13042 for advice in matching
13044 .iter()
13045 .filter(|i| matches!(i.kind, AdviceKind::Before))
13046 {
13047 let _ = self.execute_advice(&advice.code);
13048 }
13049
13050 let around = matching
13052 .iter()
13053 .find(|i| matches!(i.kind, AdviceKind::Around));
13054
13055 let t0 = std::time::Instant::now();
13056
13057 let result = if let Some(advice) = around {
13058 self.variables
13061 .insert("__intercept_proceed".to_string(), "0".to_string());
13062 let advice_result = self.execute_advice(&advice.code);
13063
13064 let proceeded = self
13066 .variables
13067 .get("__intercept_proceed")
13068 .map(|v| v == "1")
13069 .unwrap_or(false);
13070
13071 if proceeded {
13072 advice_result
13074 } else {
13075 advice_result
13077 }
13078 } else {
13079 let has_after = matching.iter().any(|i| matches!(i.kind, AdviceKind::After));
13084 if !has_after {
13085 return None;
13087 }
13088
13089 self.run_original_command(cmd_name, args)
13091 };
13092
13093 let elapsed = t0.elapsed();
13094
13095 let ms = elapsed.as_secs_f64() * 1000.0;
13097 self.variables
13098 .insert("INTERCEPT_MS".to_string(), format!("{:.3}", ms));
13099 self.variables
13100 .insert("INTERCEPT_US".to_string(), format!("{:.0}", ms * 1000.0));
13101
13102 for advice in matching
13104 .iter()
13105 .filter(|i| matches!(i.kind, AdviceKind::After))
13106 {
13107 let _ = self.execute_advice(&advice.code);
13108 }
13109
13110 self.variables.remove("INTERCEPT_NAME");
13112 self.variables.remove("INTERCEPT_ARGS");
13113 self.variables.remove("INTERCEPT_CMD");
13114 self.variables.remove("INTERCEPT_MS");
13115 self.variables.remove("INTERCEPT_US");
13116 self.variables.remove("__intercept_proceed");
13117
13118 Some(result)
13119 }
13120
13121 fn execute_advice(&mut self, code: &str) -> Result<i32, String> {
13125 let code = code.trim();
13126 if code.starts_with('@') {
13127 let stryke_code = code.trim_start_matches('@').trim();
13128 if let Some(status) = crate::try_stryke_dispatch(stryke_code) {
13129 self.last_status = status;
13130 return Ok(status);
13131 }
13132 }
13134 self.execute_script(code)
13135 }
13136
13137 fn run_original_command(&mut self, cmd_name: &str, args: &[String]) -> Result<i32, String> {
13138 if let Some(func) = self.functions.get(cmd_name).cloned() {
13140 return self.call_function(&func, args);
13141 }
13142 if self.maybe_autoload(cmd_name) {
13143 if let Some(func) = self.functions.get(cmd_name).cloned() {
13144 return self.call_function(&func, args);
13145 }
13146 }
13147 self.execute_external(cmd_name, &args.to_vec(), &[])
13149 }
13150
13151 fn builtin_intercept(&mut self, args: &[String]) -> i32 {
13161 if args.is_empty() {
13162 println!("Usage: intercept <before|after|around> <pattern> {{ code }}");
13163 println!(" intercept list | remove <id> | clear");
13164 return 0;
13165 }
13166
13167 match args[0].as_str() {
13168 "list" => {
13169 if self.intercepts.is_empty() {
13170 println!("no intercepts registered");
13171 } else {
13172 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
13173 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
13174 println!(
13175 "{:>4} {:<8} {:<20} {}",
13176 bold("ID"),
13177 bold("KIND"),
13178 bold("PATTERN"),
13179 bold("CODE")
13180 );
13181 for i in &self.intercepts {
13182 let kind = match i.kind {
13183 AdviceKind::Before => "before",
13184 AdviceKind::After => "after",
13185 AdviceKind::Around => "around",
13186 };
13187 let code_preview = if i.code.len() > 40 {
13188 format!("{}...", &i.code[..37])
13189 } else {
13190 i.code.clone()
13191 };
13192 println!(
13193 "{:>4} {:<8} {:<20} {}",
13194 cyan(&i.id.to_string()),
13195 kind,
13196 i.pattern,
13197 code_preview
13198 );
13199 }
13200 }
13201 0
13202 }
13203 "clear" => {
13204 let count = self.intercepts.len();
13205 self.intercepts.clear();
13206 println!("cleared {} intercepts", count);
13207 0
13208 }
13209 "remove" => {
13210 if args.len() < 2 {
13211 eprintln!("intercept remove: requires ID");
13212 return 1;
13213 }
13214 if let Ok(id) = args[1].parse::<u32>() {
13215 let before = self.intercepts.len();
13216 self.intercepts.retain(|i| i.id != id);
13217 if self.intercepts.len() < before {
13218 println!("removed intercept {}", id);
13219 0
13220 } else {
13221 eprintln!("intercept: no intercept with ID {}", id);
13222 1
13223 }
13224 } else {
13225 eprintln!("intercept remove: invalid ID");
13226 1
13227 }
13228 }
13229 "before" | "after" | "around" => {
13230 let kind = match args[0].as_str() {
13231 "before" => AdviceKind::Before,
13232 "after" => AdviceKind::After,
13233 "around" => AdviceKind::Around,
13234 _ => unreachable!(),
13235 };
13236
13237 if args.len() < 3 {
13238 eprintln!("intercept {}: requires <pattern> {{ code }}", args[0]);
13239 return 1;
13240 }
13241
13242 let pattern = args[1].clone();
13243 let code = args[2..].join(" ");
13245 let code = code.trim().to_string();
13247 let code = if code.starts_with('{') && code.ends_with('}') {
13248 code[1..code.len() - 1].trim().to_string()
13249 } else {
13250 code
13251 };
13252
13253 let id = self.intercepts.iter().map(|i| i.id).max().unwrap_or(0) + 1;
13254 self.intercepts.push(Intercept {
13255 pattern,
13256 kind: kind.clone(),
13257 code: code.clone(),
13258 id,
13259 });
13260
13261 let kind_str = match kind {
13262 AdviceKind::Before => "before",
13263 AdviceKind::After => "after",
13264 AdviceKind::Around => "around",
13265 };
13266 println!(
13267 "intercept #{}: {} {} → {}",
13268 id,
13269 kind_str,
13270 self.intercepts.last().unwrap().pattern,
13271 if code.len() > 50 {
13272 format!("{}...", &code[..47])
13273 } else {
13274 code
13275 }
13276 );
13277 0
13278 }
13279 _ => {
13280 eprintln!(
13281 "intercept: unknown subcommand '{}'. Use before|after|around|list|remove|clear",
13282 args[0]
13283 );
13284 1
13285 }
13286 }
13287 }
13288
13289 fn builtin_intercept_proceed(&mut self, _args: &[String]) -> i32 {
13291 self.variables
13292 .insert("__intercept_proceed".to_string(), "1".to_string());
13293 let cmd_name = self
13295 .variables
13296 .get("INTERCEPT_NAME")
13297 .cloned()
13298 .unwrap_or_default();
13299 let args_str = self
13300 .variables
13301 .get("INTERCEPT_ARGS")
13302 .cloned()
13303 .unwrap_or_default();
13304 let args: Vec<String> = if args_str.is_empty() {
13305 Vec::new()
13306 } else {
13307 args_str.split_whitespace().map(|s| s.to_string()).collect()
13308 };
13309 match self.run_original_command(&cmd_name, &args) {
13310 Ok(status) => status,
13311 Err(e) => {
13312 eprintln!("intercept_proceed: {}", e);
13313 1
13314 }
13315 }
13316 }
13317
13318 fn builtin_async(&mut self, args: &[String]) -> i32 {
13331 if args.is_empty() {
13332 eprintln!("async: requires a command string");
13333 return 1;
13334 }
13335
13336 let code = args.join(" ");
13337 let id = self.next_async_id;
13338 self.next_async_id += 1;
13339
13340 let (tx, rx) = crossbeam_channel::bounded::<(i32, String)>(1);
13341 let pool = std::sync::Arc::clone(&self.worker_pool);
13342
13343 pool.submit(move || {
13344 use std::process::{Command, Stdio};
13346 let output = Command::new("sh")
13347 .args(["-c", &code])
13348 .stdout(Stdio::piped())
13349 .stderr(Stdio::inherit())
13350 .output();
13351 match output {
13352 Ok(out) => {
13353 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
13354 let status = out.status.code().unwrap_or(1);
13355 let _ = tx.send((status, stdout));
13356 }
13357 Err(_) => {
13358 let _ = tx.send((127, String::new()));
13359 }
13360 }
13361 });
13362
13363 self.async_jobs.insert(id, rx);
13364 println!("{}", id);
13366 0
13367 }
13368
13369 fn builtin_await(&mut self, args: &[String]) -> i32 {
13376 if args.is_empty() {
13377 eprintln!("await: requires a job ID");
13378 return 1;
13379 }
13380
13381 let id: u32 = match args[0].parse() {
13382 Ok(n) => n,
13383 Err(_) => {
13384 eprintln!("await: invalid job ID '{}'", args[0]);
13385 return 1;
13386 }
13387 };
13388
13389 let rx = match self.async_jobs.remove(&id) {
13390 Some(rx) => rx,
13391 None => {
13392 eprintln!("await: no async job with ID {}", id);
13393 return 1;
13394 }
13395 };
13396
13397 match rx.recv() {
13399 Ok((status, stdout)) => {
13400 if !stdout.is_empty() {
13401 print!("{}", stdout);
13402 }
13403 self.last_status = status;
13404 status
13405 }
13406 Err(_) => {
13407 eprintln!("await: job {} died without result", id);
13408 1
13409 }
13410 }
13411 }
13412
13413 fn builtin_pmap(&mut self, args: &[String]) -> i32 {
13422 if args.len() < 2 {
13423 eprintln!("pmap: requires 'command {{}}' followed by arguments");
13424 return 1;
13425 }
13426
13427 let template = &args[0];
13428 let items = &args[1..];
13429
13430 let mut receivers = Vec::with_capacity(items.len());
13432 for item in items {
13433 let cmd = template.replace("{}", item);
13434 let rx = self.worker_pool.submit_with_result(move || {
13435 use std::process::{Command, Stdio};
13436 let output = Command::new("sh")
13437 .args(["-c", &cmd])
13438 .stdout(Stdio::piped())
13439 .stderr(Stdio::inherit())
13440 .output();
13441 match output {
13442 Ok(out) => (
13443 out.status.code().unwrap_or(1),
13444 String::from_utf8_lossy(&out.stdout).to_string(),
13445 ),
13446 Err(_) => (127, String::new()),
13447 }
13448 });
13449 receivers.push(rx);
13450 }
13451
13452 let mut any_fail = false;
13454 for rx in receivers {
13455 if let Ok((status, stdout)) = rx.recv() {
13456 if !stdout.is_empty() {
13457 print!("{}", stdout);
13458 }
13459 if status != 0 {
13460 any_fail = true;
13461 }
13462 }
13463 }
13464
13465 if any_fail {
13466 1
13467 } else {
13468 0
13469 }
13470 }
13471
13472 fn builtin_pgrep(&mut self, args: &[String]) -> i32 {
13479 if args.len() < 2 {
13480 eprintln!("pgrep: requires 'test_command {{}}' followed by arguments");
13481 return 1;
13482 }
13483
13484 let template = &args[0];
13485 let items = &args[1..];
13486
13487 let mut receivers: Vec<(String, crossbeam_channel::Receiver<bool>)> =
13488 Vec::with_capacity(items.len());
13489 for item in items {
13490 let cmd = template.replace("{}", item);
13491 let rx = self.worker_pool.submit_with_result(move || {
13492 use std::process::{Command, Stdio};
13493 Command::new("sh")
13494 .args(["-c", &cmd])
13495 .stdout(Stdio::null())
13496 .stderr(Stdio::null())
13497 .status()
13498 .map(|s| s.success())
13499 .unwrap_or(false)
13500 });
13501 receivers.push((item.clone(), rx));
13502 }
13503
13504 for (item, rx) in receivers {
13505 if let Ok(true) = rx.recv() {
13506 println!("{}", item);
13507 }
13508 }
13509
13510 0
13511 }
13512
13513 fn builtin_peach(&mut self, args: &[String]) -> i32 {
13520 if args.len() < 2 {
13521 eprintln!("peach: requires 'command {{}}' followed by arguments");
13522 return 1;
13523 }
13524
13525 let template = &args[0];
13526 let items = &args[1..];
13527
13528 let (tx, rx) = crossbeam_channel::unbounded::<(String, i32, String)>();
13529
13530 for item in items {
13531 let cmd = template.replace("{}", item);
13532 let item_clone = item.clone();
13533 let tx = tx.clone();
13534 self.worker_pool.submit(move || {
13535 use std::process::{Command, Stdio};
13536 let output = Command::new("sh")
13537 .args(["-c", &cmd])
13538 .stdout(Stdio::piped())
13539 .stderr(Stdio::inherit())
13540 .output();
13541 match output {
13542 Ok(out) => {
13543 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
13544 let status = out.status.code().unwrap_or(1);
13545 let _ = tx.send((item_clone, status, stdout));
13546 }
13547 Err(_) => {
13548 let _ = tx.send((item_clone, 127, String::new()));
13549 }
13550 }
13551 });
13552 }
13553 drop(tx);
13554
13555 let mut any_fail = false;
13556 for (_, status, stdout) in rx {
13557 if !stdout.is_empty() {
13558 print!("{}", stdout);
13559 }
13560 if status != 0 {
13561 any_fail = true;
13562 }
13563 }
13564
13565 if any_fail {
13566 1
13567 } else {
13568 0
13569 }
13570 }
13571
13572 fn builtin_barrier(&mut self, args: &[String]) -> i32 {
13579 if args.is_empty() {
13580 eprintln!("barrier: requires commands separated by :::");
13581 return 1;
13582 }
13583
13584 let mut commands: Vec<String> = Vec::new();
13586 let mut current = String::new();
13587 for arg in args {
13588 if arg == ":::" {
13589 if !current.is_empty() {
13590 commands.push(current.trim().to_string());
13591 current.clear();
13592 }
13593 } else {
13594 if !current.is_empty() {
13595 current.push(' ');
13596 }
13597 current.push_str(arg);
13598 }
13599 }
13600 if !current.is_empty() {
13601 commands.push(current.trim().to_string());
13602 }
13603
13604 if commands.is_empty() {
13605 return 0;
13606 }
13607
13608 let mut receivers = Vec::with_capacity(commands.len());
13610 for cmd in &commands {
13611 let cmd = cmd.clone();
13612 let rx = self.worker_pool.submit_with_result(move || {
13613 use std::process::{Command, Stdio};
13614 Command::new("sh")
13615 .args(["-c", &cmd])
13616 .stdout(Stdio::inherit())
13617 .stderr(Stdio::inherit())
13618 .status()
13619 .map(|s| s.code().unwrap_or(1))
13620 .unwrap_or(127)
13621 });
13622 receivers.push(rx);
13623 }
13624
13625 let mut worst = 0i32;
13627 for rx in receivers {
13628 if let Ok(status) = rx.recv() {
13629 if status > worst {
13630 worst = status;
13631 }
13632 }
13633 }
13634
13635 self.last_status = worst;
13636 worst
13637 }
13638
13639 fn builtin_help(&self, args: &[String]) -> i32 {
13641 if args.is_empty() {
13642 println!("zshrs shell builtins:");
13643 println!("");
13644 println!(" alias, bg, bind, break, builtin, cd, command, continue,");
13645 println!(" declare, dirs, disown, echo, enable, eval, exec, exit,");
13646 println!(" export, false, fc, fg, getopts, hash, help, history,");
13647 println!(" jobs, kill, let, local, logout, popd, printf, pushd,");
13648 println!(" pwd, read, readonly, return, set, shift, shopt, source,");
13649 println!(" suspend, test, times, trap, true, type, typeset, ulimit,");
13650 println!(" umask, unalias, unset, wait, whence, where, which");
13651 println!("");
13652 println!("Type 'help name' for more information about 'name'.");
13653 return 0;
13654 }
13655
13656 let cmd = &args[0];
13657 match cmd.as_str() {
13658 "cd" => println!("cd: cd [-L|-P] [dir]\n Change the shell working directory."),
13659 "echo" => println!("echo: echo [-neE] [arg ...]\n Write arguments to standard output."),
13660 "export" => println!("export: export [-fn] [name[=value] ...]\n Set export attribute for shell variables."),
13661 "alias" => println!("alias: alias [-p] [name[=value] ...]\n Define or display aliases."),
13662 "history" => println!("history: history [-c] [-d offset] [n]\n Display or manipulate the history list."),
13663 "jobs" => println!("jobs: jobs [-lnprs] [jobspec ...]\n Display status of jobs."),
13664 "kill" => println!("kill: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ...\n Send a signal to a job."),
13665 "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."),
13666 "set" => println!("set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]\n Set or unset values of shell options and positional parameters."),
13667 "test" | "[" => println!("test: test [expr]\n Evaluate conditional expression."),
13668 "type" => println!("type: type [-afptP] name [name ...]\n Display information about command type."),
13669 _ => println!("{}: no help available", cmd),
13670 }
13671 0
13672 }
13673
13674 fn builtin_readarray(&mut self, args: &[String]) -> i32 {
13676 use std::io::{BufRead, BufReader};
13677
13678 let mut array_name = "MAPFILE".to_string();
13679 let mut delimiter = '\n';
13680 let mut count = 0usize; let mut skip = 0usize;
13682 let mut strip_trailing = false;
13683 let mut callback: Option<String> = None;
13684 let mut callback_quantum = 0usize;
13685
13686 let mut i = 0;
13687 while i < args.len() {
13688 match args[i].as_str() {
13689 "-d" => {
13690 i += 1;
13691 if i < args.len() && !args[i].is_empty() {
13692 delimiter = args[i].chars().next().unwrap_or('\n');
13693 }
13694 }
13695 "-n" => {
13696 i += 1;
13697 if i < args.len() {
13698 count = args[i].parse().unwrap_or(0);
13699 }
13700 }
13701 "-O" => {
13702 i += 1;
13703 }
13705 "-s" => {
13706 i += 1;
13707 if i < args.len() {
13708 skip = args[i].parse().unwrap_or(0);
13709 }
13710 }
13711 "-t" => strip_trailing = true,
13712 "-C" => {
13713 i += 1;
13714 if i < args.len() {
13715 callback = Some(args[i].clone());
13716 }
13717 }
13718 "-c" => {
13719 i += 1;
13720 if i < args.len() {
13721 callback_quantum = args[i].parse().unwrap_or(5000);
13722 }
13723 }
13724 "-u" => {
13725 i += 1;
13726 }
13728 s if !s.starts_with('-') => {
13729 array_name = s.to_string();
13730 }
13731 _ => {}
13732 }
13733 i += 1;
13734 }
13735
13736 let stdin = std::io::stdin();
13737 let reader = BufReader::new(stdin.lock());
13738 let mut lines = Vec::new();
13739 let mut line_count = 0usize;
13740
13741 for line_result in reader.lines() {
13742 if let Ok(mut line) = line_result {
13743 line_count += 1;
13744
13745 if line_count <= skip {
13746 continue;
13747 }
13748
13749 if strip_trailing {
13750 while line.ends_with('\n') || line.ends_with('\r') {
13751 line.pop();
13752 }
13753 }
13754
13755 lines.push(line);
13756
13757 if count > 0 && lines.len() >= count {
13758 break;
13759 }
13760 }
13761 }
13762
13763 self.arrays.insert(array_name, lines);
13764 let _ = (callback, callback_quantum);
13765 0
13766 }
13767
13768 fn builtin_shopt(&mut self, args: &[String]) -> i32 {
13769 if args.is_empty() {
13770 for (opt, val) in &self.options {
13772 println!("shopt {} {}", if *val { "-s" } else { "-u" }, opt);
13773 }
13774 return 0;
13775 }
13776
13777 let mut set = None;
13778 let mut opts = Vec::new();
13779
13780 for arg in args {
13781 match arg.as_str() {
13782 "-s" => set = Some(true),
13783 "-u" => set = Some(false),
13784 "-p" => {
13785 for opt in &opts {
13787 let val = self.options.get(opt).copied().unwrap_or(false);
13788 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
13789 }
13790 return 0;
13791 }
13792 _ => opts.push(arg.clone()),
13793 }
13794 }
13795
13796 if let Some(enable) = set {
13797 for opt in &opts {
13798 self.options.insert(opt.clone(), enable);
13799 }
13800 } else {
13801 for opt in &opts {
13803 let val = self.options.get(opt).copied().unwrap_or(false);
13804 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
13805 }
13806 }
13807 0
13808 }
13809
13810 fn builtin_setopt(&mut self, args: &[String]) -> i32 {
13812 if args.is_empty() {
13813 let defaults_on = Self::default_on_options();
13817 let mut diff_opts: Vec<String> = Vec::new();
13818
13819 for &opt in Self::all_zsh_options() {
13820 let enabled = self.options.get(opt).copied().unwrap_or(false);
13821 let is_default_on = defaults_on.contains(&opt);
13822
13823 if is_default_on && !enabled {
13824 diff_opts.push(format!("no{}", opt));
13826 } else if !is_default_on && enabled {
13827 diff_opts.push(opt.to_string());
13829 }
13830 }
13831 diff_opts.sort();
13832 for opt in diff_opts {
13833 println!("{}", opt);
13834 }
13835 return 0;
13836 }
13837
13838 let mut use_pattern = false;
13839 let mut iter = args.iter().peekable();
13840
13841 while let Some(arg) = iter.next() {
13842 match arg.as_str() {
13843 "-m" => use_pattern = true,
13844 "-o" => {
13845 if let Some(opt) = iter.next() {
13847 let (name, enable) = Self::normalize_option_name(opt);
13848 self.options.insert(name, enable);
13849 }
13850 }
13851 "+o" => {
13852 if let Some(opt) = iter.next() {
13854 let (name, enable) = Self::normalize_option_name(opt);
13855 self.options.insert(name, !enable);
13856 }
13857 }
13858 _ => {
13859 if use_pattern {
13860 for opt in Self::all_zsh_options() {
13862 if Self::option_matches_pattern(opt, arg) {
13863 self.options.insert(opt.to_string(), true);
13864 }
13865 }
13866 } else {
13867 let (name, enable) = Self::normalize_option_name(arg);
13868 self.options.insert(name, enable);
13870 }
13871 }
13872 }
13873 }
13874 0
13875 }
13876
13877 fn builtin_unsetopt(&mut self, args: &[String]) -> i32 {
13879 if args.is_empty() {
13880 let defaults_on = Self::default_on_options();
13884 let mut all_opts: Vec<String> = Vec::new();
13885
13886 for &opt in Self::all_zsh_options() {
13887 let is_default_on = defaults_on.contains(&opt);
13888 if is_default_on {
13889 all_opts.push(format!("no{}", opt));
13890 } else {
13891 all_opts.push(opt.to_string());
13892 }
13893 }
13894 all_opts.sort();
13895 for opt in all_opts {
13896 println!("{}", opt);
13897 }
13898 return 0;
13899 }
13900
13901 let mut use_pattern = false;
13902 let mut iter = args.iter().peekable();
13903
13904 while let Some(arg) = iter.next() {
13905 match arg.as_str() {
13906 "-m" => use_pattern = true,
13907 "-o" => {
13908 if let Some(opt) = iter.next() {
13910 let (name, enable) = Self::normalize_option_name(opt);
13911 self.options.insert(name, !enable);
13912 }
13913 }
13914 "+o" => {
13915 if let Some(opt) = iter.next() {
13917 let (name, enable) = Self::normalize_option_name(opt);
13918 self.options.insert(name, enable);
13919 }
13920 }
13921 _ => {
13922 if use_pattern {
13923 for opt in Self::all_zsh_options() {
13924 if Self::option_matches_pattern(opt, arg) {
13925 self.options.insert(opt.to_string(), false);
13926 }
13927 }
13928 } else {
13929 let (name, enable) = Self::normalize_option_name(arg);
13930 self.options.insert(name, !enable);
13932 }
13933 }
13934 }
13935 }
13936 0
13937 }
13938
13939 fn builtin_getopts(&mut self, args: &[String]) -> i32 {
13940 if args.len() < 2 {
13941 eprintln!("zshrs: getopts: usage: getopts optstring name [arg ...]");
13942 return 1;
13943 }
13944
13945 let optstring = &args[0];
13946 let varname = &args[1];
13947 let opt_args: Vec<&str> = if args.len() > 2 {
13948 args[2..].iter().map(|s| s.as_str()).collect()
13949 } else {
13950 self.positional_params.iter().map(|s| s.as_str()).collect()
13951 };
13952
13953 let optind: usize = self
13955 .variables
13956 .get("OPTIND")
13957 .and_then(|s| s.parse().ok())
13958 .unwrap_or(1);
13959
13960 if optind > opt_args.len() {
13961 self.variables.insert(varname.to_string(), "?".to_string());
13962 return 1;
13963 }
13964
13965 let current_arg = opt_args[optind - 1];
13966
13967 if !current_arg.starts_with('-') || current_arg == "-" {
13968 self.variables.insert(varname.to_string(), "?".to_string());
13969 return 1;
13970 }
13971
13972 if current_arg == "--" {
13973 self.variables
13974 .insert("OPTIND".to_string(), (optind + 1).to_string());
13975 self.variables.insert(varname.to_string(), "?".to_string());
13976 return 1;
13977 }
13978
13979 let optpos: usize = self
13981 .variables
13982 .get("_OPTPOS")
13983 .and_then(|s| s.parse().ok())
13984 .unwrap_or(1);
13985
13986 let opt_char = current_arg.chars().nth(optpos);
13987
13988 if let Some(c) = opt_char {
13989 let opt_idx = optstring.find(c);
13991
13992 match opt_idx {
13993 Some(idx) => {
13994 let takes_arg = optstring.chars().nth(idx + 1) == Some(':');
13996
13997 if takes_arg {
13998 let arg = if optpos + 1 < current_arg.len() {
14000 current_arg[optpos + 1..].to_string()
14002 } else if optind < opt_args.len() {
14003 self.variables
14005 .insert("OPTIND".to_string(), (optind + 2).to_string());
14006 self.variables.remove("_OPTPOS");
14007 opt_args[optind].to_string()
14008 } else {
14009 self.variables.insert(varname.to_string(), "?".to_string());
14011 if !optstring.starts_with(':') {
14012 eprintln!("zshrs: getopts: option requires an argument -- {}", c);
14013 }
14014 self.variables.insert("OPTARG".to_string(), c.to_string());
14015 return 1;
14016 };
14017
14018 self.variables.insert("OPTARG".to_string(), arg);
14019 self.variables
14020 .insert("OPTIND".to_string(), (optind + 1).to_string());
14021 self.variables.remove("_OPTPOS");
14022 } else {
14023 if optpos + 1 < current_arg.len() {
14025 self.variables
14027 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
14028 } else {
14029 self.variables
14031 .insert("OPTIND".to_string(), (optind + 1).to_string());
14032 self.variables.remove("_OPTPOS");
14033 }
14034 }
14035
14036 self.variables.insert(varname.to_string(), c.to_string());
14037 0
14038 }
14039 None => {
14040 if !optstring.starts_with(':') {
14042 eprintln!("zshrs: getopts: illegal option -- {}", c);
14043 }
14044 self.variables.insert(varname.to_string(), "?".to_string());
14045 self.variables.insert("OPTARG".to_string(), c.to_string());
14046
14047 if optpos + 1 < current_arg.len() {
14049 self.variables
14050 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
14051 } else {
14052 self.variables
14053 .insert("OPTIND".to_string(), (optind + 1).to_string());
14054 self.variables.remove("_OPTPOS");
14055 }
14056 0
14057 }
14058 }
14059 } else {
14060 self.variables
14062 .insert("OPTIND".to_string(), (optind + 1).to_string());
14063 self.variables.remove("_OPTPOS");
14064 self.variables.insert(varname.to_string(), "?".to_string());
14065 1
14066 }
14067 }
14068
14069 fn builtin_type(&mut self, args: &[String]) -> i32 {
14070 if args.is_empty() {
14071 return 0;
14072 }
14073
14074 let mut show_all = false;
14075 let mut path_only = false;
14076 let mut silent = false;
14077 let mut show_type = false;
14078 let mut names = Vec::new();
14079
14080 let mut iter = args.iter();
14081 while let Some(arg) = iter.next() {
14082 if arg.starts_with('-') && arg.len() > 1 {
14083 for c in arg[1..].chars() {
14084 match c {
14085 'a' => show_all = true,
14086 'p' => path_only = true,
14087 'P' => path_only = true,
14088 's' => silent = true,
14089 't' => show_type = true,
14090 'f' => {} 'w' => {} _ => {}
14093 }
14094 }
14095 } else {
14096 names.push(arg.clone());
14097 }
14098 }
14099
14100 if names.is_empty() {
14101 return 0;
14102 }
14103
14104 let mut status = 0;
14105 for name in &names {
14106 let mut found_any = false;
14107
14108 if !path_only && self.aliases.contains_key(name) {
14110 found_any = true;
14111 if !silent {
14112 if show_type {
14113 println!("alias");
14114 } else {
14115 println!(
14116 "{} is aliased to `{}'",
14117 name,
14118 self.aliases.get(name).unwrap()
14119 );
14120 }
14121 }
14122 if !show_all {
14123 continue;
14124 }
14125 }
14126
14127 if !path_only && self.functions.contains_key(name) {
14129 found_any = true;
14130 if !silent {
14131 if show_type {
14132 println!("function");
14133 } else {
14134 println!("{} is a shell function", name);
14135 }
14136 }
14137 if !show_all {
14138 continue;
14139 }
14140 }
14141
14142 if !path_only && (self.is_builtin(name) || name == ":" || name == "[") {
14144 found_any = true;
14145 if !silent {
14146 if show_type {
14147 println!("builtin");
14148 } else {
14149 println!("{} is a shell builtin", name);
14150 }
14151 }
14152 if !show_all {
14153 continue;
14154 }
14155 }
14156
14157 if let Ok(path_env) = std::env::var("PATH") {
14159 for dir in path_env.split(':') {
14160 let full_path = format!("{}/{}", dir, name);
14161 if std::path::Path::new(&full_path).exists() {
14162 found_any = true;
14163 if !silent {
14164 if show_type {
14165 println!("file");
14166 } else {
14167 println!("{} is {}", name, full_path);
14168 }
14169 }
14170 if !show_all {
14171 break;
14172 }
14173 }
14174 }
14175 }
14176
14177 if !found_any {
14178 if !silent {
14179 eprintln!("zshrs: type: {}: not found", name);
14180 }
14181 status = 1;
14182 }
14183 }
14184 status
14185 }
14186
14187 fn builtin_hash(&mut self, args: &[String]) -> i32 {
14188 let mut dir_mode = false;
14197 let mut rehash = false;
14198 let mut fill_all = false;
14199 let mut pattern_match = false;
14200 let mut verbose = false;
14201 let mut list_form = false;
14202 let mut names = Vec::new();
14203
14204 let mut i = 0;
14205 while i < args.len() {
14206 let arg = &args[i];
14207 if arg.starts_with('-') && arg.len() > 1 {
14208 for ch in arg[1..].chars() {
14209 match ch {
14210 'd' => dir_mode = true,
14211 'r' => rehash = true,
14212 'f' => fill_all = true,
14213 'm' => pattern_match = true,
14214 'v' => verbose = true,
14215 'L' => list_form = true,
14216 _ => {}
14217 }
14218 }
14219 } else {
14220 names.push(arg.clone());
14221 }
14222 i += 1;
14223 }
14224
14225 if rehash && !dir_mode && names.is_empty() {
14227 self.command_hash.clear();
14228 return 0;
14229 }
14230
14231 if fill_all {
14233 if let Ok(path_var) = env::var("PATH") {
14234 for dir in path_var.split(':') {
14235 if let Ok(entries) = std::fs::read_dir(dir) {
14236 for entry in entries.flatten() {
14237 if let Ok(ft) = entry.file_type() {
14238 if ft.is_file() || ft.is_symlink() {
14239 if let Some(name) = entry.file_name().to_str() {
14240 let path = entry.path().to_string_lossy().to_string();
14241 self.command_hash.insert(name.to_string(), path);
14242 }
14243 }
14244 }
14245 }
14246 }
14247 }
14248 }
14249 return 0;
14250 }
14251
14252 if dir_mode {
14253 if names.is_empty() {
14255 for (name, path) in &self.named_dirs {
14257 if list_form {
14258 println!("hash -d {}={}", name, path.display());
14259 } else if verbose {
14260 println!("{}={}", name, path.display());
14261 } else {
14262 println!("{}={}", name, path.display());
14263 }
14264 }
14265 return 0;
14266 }
14267
14268 if rehash {
14269 if pattern_match {
14271 let to_remove: Vec<String> = self
14273 .named_dirs
14274 .keys()
14275 .filter(|k| {
14276 names.iter().any(|pat| {
14277 let pattern = pat.replace("*", ".*").replace("?", ".");
14278 regex::Regex::new(&format!("^{}$", pattern))
14279 .map(|r| r.is_match(k))
14280 .unwrap_or(false)
14281 })
14282 })
14283 .cloned()
14284 .collect();
14285 for name in to_remove {
14286 self.named_dirs.remove(&name);
14287 }
14288 } else {
14289 for name in &names {
14290 self.named_dirs.remove(name);
14291 }
14292 }
14293 return 0;
14294 }
14295
14296 for name in &names {
14298 if let Some((n, p)) = name.split_once('=') {
14299 self.add_named_dir(n, p);
14300 } else {
14301 eprintln!("hash: -d: {} not in name=value format", name);
14302 return 1;
14303 }
14304 }
14305 return 0;
14306 }
14307
14308 if names.is_empty() {
14310 for (name, path) in &self.command_hash {
14312 if list_form {
14313 println!("hash {}={}", name, path);
14314 } else {
14315 println!("{}={}", name, path);
14316 }
14317 }
14318 return 0;
14319 }
14320
14321 for name in &names {
14322 if let Some((cmd, path)) = name.split_once('=') {
14323 self.command_hash.insert(cmd.to_string(), path.to_string());
14325 if verbose {
14326 println!("{}={}", cmd, path);
14327 }
14328 } else if let Some(path) = self.find_in_path(name) {
14329 self.command_hash.insert(name.clone(), path.clone());
14331 if verbose {
14332 println!("{}={}", name, path);
14333 }
14334 } else {
14335 eprintln!("zshrs: hash: {}: not found", name);
14336 return 1;
14337 }
14338 }
14339 0
14340 }
14341
14342 fn builtin_add_zsh_hook(&mut self, args: &[String]) -> i32 {
14344 if args.len() < 2 {
14346 eprintln!("usage: add-zsh-hook [-d] hook function");
14347 return 1;
14348 }
14349
14350 let (delete, hook, func) = if args[0] == "-d" {
14351 if args.len() < 3 {
14352 eprintln!("usage: add-zsh-hook -d hook function");
14353 return 1;
14354 }
14355 (true, &args[1], &args[2])
14356 } else {
14357 (false, &args[0], &args[1])
14358 };
14359
14360 if delete {
14361 if let Some(funcs) = self.hook_functions.get_mut(hook.as_str()) {
14363 funcs.retain(|f| f != func);
14364 }
14365 } else {
14366 self.add_hook(hook, func);
14368 }
14369 0
14370 }
14371
14372 fn builtin_command(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
14373 let mut use_default_path = false;
14378 let mut print_path = false;
14379 let mut verbose = false;
14380 let mut positional_args: Vec<&str> = Vec::new();
14381
14382 let mut i = 0;
14383 while i < args.len() {
14384 let arg = &args[i];
14385 if arg.starts_with('-') && arg.len() > 1 && positional_args.is_empty() {
14386 for ch in arg[1..].chars() {
14387 match ch {
14388 'p' => use_default_path = true,
14389 'v' => print_path = true,
14390 'V' => verbose = true,
14391 '-' => {
14392 i += 1;
14394 break;
14395 }
14396 _ => {
14397 eprintln!("command: bad option: -{}", ch);
14398 return 1;
14399 }
14400 }
14401 }
14402 } else {
14403 positional_args.push(arg);
14404 }
14405 i += 1;
14406 }
14407
14408 while i < args.len() {
14410 positional_args.push(&args[i]);
14411 i += 1;
14412 }
14413
14414 if positional_args.is_empty() {
14415 return 0;
14416 }
14417
14418 let cmd = positional_args[0];
14419
14420 if print_path || verbose {
14422 let path_var = if use_default_path {
14424 "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
14425 } else {
14426 env::var("PATH").unwrap_or_default()
14427 };
14428
14429 for dir in path_var.split(':') {
14430 let full_path = PathBuf::from(dir).join(cmd);
14431 if full_path.exists() && full_path.is_file() {
14432 if verbose {
14433 println!("{} is {}", cmd, full_path.display());
14434 } else {
14435 println!("{}", full_path.display());
14436 }
14437 return 0;
14438 }
14439 }
14440
14441 if verbose {
14442 eprintln!("{} not found", cmd);
14443 }
14444 return 1;
14445 }
14446
14447 let cmd_args: Vec<String> = positional_args[1..].iter().map(|s| s.to_string()).collect();
14449
14450 if use_default_path {
14451 let old_path = env::var("PATH").ok();
14453 env::set_var("PATH", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin");
14454 let result = self
14455 .execute_external(
14456 cmd,
14457 &cmd_args
14458 .iter()
14459 .map(|s| s.as_str())
14460 .collect::<Vec<_>>()
14461 .join(" ")
14462 .split_whitespace()
14463 .map(String::from)
14464 .collect::<Vec<_>>(),
14465 redirects,
14466 )
14467 .unwrap_or(127);
14468 if let Some(p) = old_path {
14469 env::set_var("PATH", p);
14470 }
14471 result
14472 } else {
14473 self.execute_external(cmd, &cmd_args, redirects)
14474 .unwrap_or(127)
14475 }
14476 }
14477
14478 fn builtin_builtin(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
14479 if args.is_empty() {
14481 return 0;
14482 }
14483
14484 let cmd = &args[0];
14485 let cmd_args = &args[1..];
14486
14487 match cmd.as_str() {
14488 "cd" => self.builtin_cd(cmd_args),
14489 "pwd" => self.builtin_pwd(redirects),
14490 "echo" => self.builtin_echo(cmd_args, redirects),
14491 "export" => self.builtin_export(cmd_args),
14492 "unset" => self.builtin_unset(cmd_args),
14493 "exit" => self.builtin_exit(cmd_args),
14494 "return" => self.builtin_return(cmd_args),
14495 "true" => 0,
14496 "false" => 1,
14497 ":" => 0,
14498 "test" | "[" => self.builtin_test(cmd_args),
14499 "local" => self.builtin_local(cmd_args),
14500 "declare" | "typeset" => self.builtin_declare(cmd_args),
14501 "read" => self.builtin_read(cmd_args),
14502 "shift" => self.builtin_shift(cmd_args),
14503 "eval" => self.builtin_eval(cmd_args),
14504 "alias" => self.builtin_alias(cmd_args),
14505 "unalias" => self.builtin_unalias(cmd_args),
14506 "set" => self.builtin_set(cmd_args),
14507 "shopt" => self.builtin_shopt(cmd_args),
14508 "getopts" => self.builtin_getopts(cmd_args),
14509 "type" => self.builtin_type(cmd_args),
14510 "hash" => self.builtin_hash(cmd_args),
14511 "add-zsh-hook" => self.builtin_add_zsh_hook(cmd_args),
14512 "autoload" => self.builtin_autoload(cmd_args),
14513 "source" | "." => self.builtin_source(cmd_args),
14514 "functions" => self.builtin_functions(cmd_args),
14515 "zle" => self.builtin_zle(cmd_args),
14516 "bindkey" => self.builtin_bindkey(cmd_args),
14517 "setopt" => self.builtin_setopt(cmd_args),
14518 "unsetopt" => self.builtin_unsetopt(cmd_args),
14519 "emulate" => self.builtin_emulate(cmd_args),
14520 "zstyle" => self.builtin_zstyle(cmd_args),
14521 "compadd" => self.builtin_compadd(cmd_args),
14522 "compset" => self.builtin_compset(cmd_args),
14523 "compdef" => self.builtin_compdef(cmd_args),
14524 "compinit" => self.builtin_compinit(cmd_args),
14525 "cdreplay" => self.builtin_cdreplay(cmd_args),
14526 "zmodload" => self.builtin_zmodload(cmd_args),
14527 "zcompile" => self.builtin_zcompile(cmd_args),
14528 "zformat" => self.builtin_zformat(cmd_args),
14529 "zprof" => self.builtin_zprof(cmd_args),
14530 "print" => self.builtin_print(cmd_args),
14531 "printf" => self.builtin_printf(cmd_args),
14532 "command" => self.builtin_command(cmd_args, redirects),
14533 "whence" => self.builtin_whence(cmd_args),
14534 "which" => self.builtin_which(cmd_args),
14535 "where" => self.builtin_where(cmd_args),
14536 "fc" => self.builtin_fc(cmd_args),
14537 "history" => self.builtin_history(cmd_args),
14538 "dirs" => self.builtin_dirs(cmd_args),
14539 "pushd" => self.builtin_pushd(cmd_args),
14540 "popd" => self.builtin_popd(cmd_args),
14541 "bg" => self.builtin_bg(cmd_args),
14542 "fg" => self.builtin_fg(cmd_args),
14543 "jobs" => self.builtin_jobs(cmd_args),
14544 "kill" => self.builtin_kill(cmd_args),
14545 "wait" => self.builtin_wait(cmd_args),
14546 "trap" => self.builtin_trap(cmd_args),
14547 "umask" => self.builtin_umask(cmd_args),
14548 "ulimit" => self.builtin_ulimit(cmd_args),
14549 "times" => self.builtin_times(cmd_args),
14550 "let" => self.builtin_let(cmd_args),
14551 "integer" => self.builtin_integer(cmd_args),
14552 "float" => self.builtin_float(cmd_args),
14553 "readonly" => self.builtin_readonly(cmd_args),
14554 _ => {
14555 eprintln!("zshrs: builtin: {}: not a shell builtin", cmd);
14556 1
14557 }
14558 }
14559 }
14560
14561 fn builtin_let(&mut self, args: &[String]) -> i32 {
14562 if args.is_empty() {
14563 return 1;
14564 }
14565
14566 let mut result = 0i64;
14567 for expr in args {
14568 result = self.evaluate_arithmetic_expr(expr);
14569 }
14570
14571 if result == 0 {
14573 1
14574 } else {
14575 0
14576 }
14577 }
14578
14579 fn builtin_compgen(&self, args: &[String]) -> i32 {
14581 let mut i = 0;
14582 let mut prefix = String::new();
14583 let mut actions = Vec::new();
14584 let mut wordlist = None;
14585 let mut globpat = None;
14586
14587 while i < args.len() {
14588 match args[i].as_str() {
14589 "-W" => {
14590 i += 1;
14591 if i < args.len() {
14592 wordlist = Some(args[i].clone());
14593 }
14594 }
14595 "-G" => {
14596 i += 1;
14597 if i < args.len() {
14598 globpat = Some(args[i].clone());
14599 }
14600 }
14601 "-a" => actions.push("alias"),
14602 "-b" => actions.push("builtin"),
14603 "-c" => actions.push("command"),
14604 "-d" => actions.push("directory"),
14605 "-e" => actions.push("export"),
14606 "-f" => actions.push("file"),
14607 "-j" => actions.push("job"),
14608 "-k" => actions.push("keyword"),
14609 "-u" => actions.push("user"),
14610 "-v" => actions.push("variable"),
14611 s if !s.starts_with('-') => prefix = s.to_string(),
14612 _ => {}
14613 }
14614 i += 1;
14615 }
14616
14617 let mut results = Vec::new();
14618
14619 for action in actions {
14621 match action {
14622 "alias" => {
14623 for name in self.aliases.keys() {
14624 if name.starts_with(&prefix) {
14625 results.push(name.clone());
14626 }
14627 }
14628 }
14629 "builtin" => {
14630 for name in [
14631 "cd", "pwd", "echo", "export", "unset", "source", "exit", "return", "true",
14632 "false", ":", "test", "[", "local", "declare", "jobs", "fg", "bg", "kill",
14633 "disown", "wait", "alias", "unalias", "set", "shopt",
14634 ] {
14635 if name.starts_with(&prefix) {
14636 results.push(name.to_string());
14637 }
14638 }
14639 }
14640 "directory" => {
14641 if let Ok(entries) = std::fs::read_dir(".") {
14642 for entry in entries.flatten() {
14643 if let Ok(ft) = entry.file_type() {
14644 if ft.is_dir() {
14645 let name = entry.file_name().to_string_lossy().to_string();
14646 if name.starts_with(&prefix) {
14647 results.push(name);
14648 }
14649 }
14650 }
14651 }
14652 }
14653 }
14654 "file" => {
14655 if let Ok(entries) = std::fs::read_dir(".") {
14656 for entry in entries.flatten() {
14657 let name = entry.file_name().to_string_lossy().to_string();
14658 if name.starts_with(&prefix) {
14659 results.push(name);
14660 }
14661 }
14662 }
14663 }
14664 "variable" => {
14665 for name in self.variables.keys() {
14666 if name.starts_with(&prefix) {
14667 results.push(name.clone());
14668 }
14669 }
14670 for name in std::env::vars().map(|(k, _)| k) {
14671 if name.starts_with(&prefix) && !results.contains(&name) {
14672 results.push(name);
14673 }
14674 }
14675 }
14676 _ => {}
14677 }
14678 }
14679
14680 if let Some(words) = wordlist {
14682 for word in words.split_whitespace() {
14683 if word.starts_with(&prefix) {
14684 results.push(word.to_string());
14685 }
14686 }
14687 }
14688
14689 if let Some(_pattern) = globpat {
14691 let full_pattern = format!("{}*", prefix);
14692 if let Ok(paths) = glob::glob(&full_pattern) {
14693 for path in paths.flatten() {
14694 results.push(path.to_string_lossy().to_string());
14695 }
14696 }
14697 }
14698
14699 results.sort();
14700 results.dedup();
14701 for r in results {
14702 println!("{}", r);
14703 }
14704 0
14705 }
14706
14707 fn builtin_complete(&mut self, args: &[String]) -> i32 {
14709 if args.is_empty() {
14710 for (cmd, spec) in &self.completions {
14712 let mut parts = vec!["complete".to_string()];
14713 for action in &spec.actions {
14714 parts.push(format!("-{}", action));
14715 }
14716 if let Some(ref w) = spec.wordlist {
14717 parts.push("-W".to_string());
14718 parts.push(format!("'{}'", w));
14719 }
14720 if let Some(ref f) = spec.function {
14721 parts.push("-F".to_string());
14722 parts.push(f.clone());
14723 }
14724 if let Some(ref c) = spec.command {
14725 parts.push("-C".to_string());
14726 parts.push(c.clone());
14727 }
14728 parts.push(cmd.clone());
14729 println!("{}", parts.join(" "));
14730 }
14731 return 0;
14732 }
14733
14734 let mut spec = CompSpec::default();
14735 let mut commands = Vec::new();
14736 let mut i = 0;
14737
14738 while i < args.len() {
14739 match args[i].as_str() {
14740 "-W" => {
14741 i += 1;
14742 if i < args.len() {
14743 spec.wordlist = Some(args[i].clone());
14744 }
14745 }
14746 "-F" => {
14747 i += 1;
14748 if i < args.len() {
14749 spec.function = Some(args[i].clone());
14750 }
14751 }
14752 "-C" => {
14753 i += 1;
14754 if i < args.len() {
14755 spec.command = Some(args[i].clone());
14756 }
14757 }
14758 "-G" => {
14759 i += 1;
14760 if i < args.len() {
14761 spec.globpat = Some(args[i].clone());
14762 }
14763 }
14764 "-P" => {
14765 i += 1;
14766 if i < args.len() {
14767 spec.prefix = Some(args[i].clone());
14768 }
14769 }
14770 "-S" => {
14771 i += 1;
14772 if i < args.len() {
14773 spec.suffix = Some(args[i].clone());
14774 }
14775 }
14776 "-a" => spec.actions.push("a".to_string()),
14777 "-b" => spec.actions.push("b".to_string()),
14778 "-c" => spec.actions.push("c".to_string()),
14779 "-d" => spec.actions.push("d".to_string()),
14780 "-e" => spec.actions.push("e".to_string()),
14781 "-f" => spec.actions.push("f".to_string()),
14782 "-j" => spec.actions.push("j".to_string()),
14783 "-r" => {
14784 i += 1;
14786 while i < args.len() {
14787 self.completions.remove(&args[i]);
14788 i += 1;
14789 }
14790 return 0;
14791 }
14792 s if !s.starts_with('-') => commands.push(s.to_string()),
14793 _ => {}
14794 }
14795 i += 1;
14796 }
14797
14798 for cmd in commands {
14799 self.completions.insert(cmd, spec.clone());
14800 }
14801 0
14802 }
14803
14804 fn builtin_compopt(&mut self, args: &[String]) -> i32 {
14806 let _ = args;
14808 0
14809 }
14810
14811 fn builtin_compadd(&mut self, args: &[String]) -> i32 {
14813 let _ = args;
14816 0
14817 }
14818
14819 fn builtin_compset(&mut self, args: &[String]) -> i32 {
14821 let _ = args;
14823 0
14824 }
14825
14826 fn builtin_compdef(&mut self, args: &[String]) -> i32 {
14831 if let Some(cache) = &mut self.compsys_cache {
14832 compsys::compdef::compdef_execute(cache, args)
14833 } else {
14834 self.deferred_compdefs.push(args.to_vec());
14836 0
14837 }
14838 }
14839
14840 #[tracing::instrument(level = "info", skip(self))]
14843 fn builtin_compinit(&mut self, args: &[String]) -> i32 {
14844 let mut quiet = false;
14851 let mut no_dump = false;
14852 let mut dump_file: Option<String> = None;
14853 let mut use_cache = false;
14854 let mut ignore_insecure = false;
14855 let mut use_insecure = false;
14856
14857 let mut i = 0;
14858 while i < args.len() {
14859 match args[i].as_str() {
14860 "-q" => quiet = true,
14861 "-C" => use_cache = true,
14862 "-D" => no_dump = true,
14863 "-d" => {
14864 i += 1;
14865 if i < args.len() {
14866 dump_file = Some(args[i].clone());
14867 }
14868 }
14869 "-u" => use_insecure = true,
14870 "-i" => ignore_insecure = true,
14871 _ => {}
14872 }
14873 i += 1;
14874 }
14875
14876 if !use_insecure && !self.posix_mode {
14878 if let Some(ref cache) = self.plugin_cache {
14879 let insecure = cache.compaudit_cached(&self.fpath);
14880 if !insecure.is_empty() && !ignore_insecure {
14881 if !quiet {
14882 eprintln!("compinit: insecure directories:");
14883 for d in &insecure {
14884 eprintln!(" {}", d);
14885 }
14886 eprintln!("compinit: run with -i to ignore or -u to use anyway");
14887 }
14888 return 1;
14889 }
14890 }
14891 }
14892
14893 if self.zsh_compat {
14895 return self.compinit_compat(quiet, no_dump, dump_file, use_cache);
14896 }
14897
14898 if use_cache {
14902 if let Some(cache) = &self.compsys_cache {
14903 if compsys::cache_is_valid(cache) {
14904 if let Ok(result) = compsys::load_from_cache(cache) {
14906 if !quiet {
14907 tracing::info!(
14908 comps = result.comps.len(),
14909 "compinit: using cached completions"
14910 );
14911 }
14912 self.assoc_arrays.insert("_comps".to_string(), result.comps);
14913 self.assoc_arrays
14914 .insert("_services".to_string(), result.services);
14915 self.assoc_arrays
14916 .insert("_patcomps".to_string(), result.patcomps);
14917
14918 if let Some(ref cache) = self.compsys_cache {
14921 if let Ok(missing) = cache.count_autoloads_missing_bytecode() {
14922 if missing > 0 {
14923 tracing::info!(
14924 count = missing,
14925 "compinit: backfilling bytecode blobs on worker pool"
14926 );
14927 let cache_path = compsys::cache::default_cache_path();
14928 let total_missing = missing;
14929 self.worker_pool.submit(move || {
14930 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
14931 Ok(c) => c,
14932 Err(_) => return,
14933 };
14934 let mut total_cached = 0usize;
14938 loop {
14939 let stubs = match cache.get_autoloads_missing_bytecode_batch(100) {
14940 Ok(s) if !s.is_empty() => s,
14941 _ => break,
14942 };
14943 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(stubs.len());
14944 for (name, body) in &stubs {
14945 let mut parser = crate::parser::ShellParser::new(body);
14946 if let Ok(commands) = parser.parse_script() {
14947 if !commands.is_empty() {
14948 let compiler = crate::shell_compiler::ShellCompiler::new();
14949 let chunk = compiler.compile(&commands);
14950 if let Ok(blob) = bincode::serialize(&chunk) {
14951 batch.push((name.clone(), blob));
14952 }
14953 }
14954 }
14955 }
14956 total_cached += batch.len();
14957 if let Err(e) = cache.set_autoload_bytecodes_bulk(&batch) {
14958 tracing::warn!(error = %e, "compinit: bytecode backfill batch failed");
14959 break;
14960 }
14961 if stubs.len() < 100 {
14963 break;
14964 }
14965 }
14966 tracing::info!(
14967 cached = total_cached,
14968 total = total_missing,
14969 "compinit: bytecode backfill complete"
14970 );
14971 });
14972 }
14973 }
14974 }
14975
14976 return 0;
14977 }
14978 }
14979 }
14980 }
14981
14982 let fpath = self.fpath.clone();
14986 let fpath_count = fpath.len();
14987 let pool_size = self.worker_pool.size();
14988 let (tx, rx) = std::sync::mpsc::channel();
14989 let bg_start = std::time::Instant::now();
14990 tracing::info!(
14991 fpath_dirs = fpath_count,
14992 worker_pool = pool_size,
14993 "compinit: shipping to worker pool"
14994 );
14995 self.worker_pool.submit(move || {
14996 tracing::debug!("compinit-bg: thread started");
14997 let cache_path = compsys::cache::default_cache_path();
14998 if let Some(parent) = cache_path.parent() {
14999 let _ = std::fs::create_dir_all(parent);
15000 }
15001 let _ = std::fs::remove_file(&cache_path);
15003 let _ = std::fs::remove_file(format!("{}-shm", cache_path.display()));
15004 let _ = std::fs::remove_file(format!("{}-wal", cache_path.display()));
15005
15006 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
15007 Ok(c) => c,
15008 Err(e) => {
15009 tracing::error!("compinit: failed to create cache: {}", e);
15010 return;
15011 }
15012 };
15013
15014 let result = match compsys::build_cache_from_fpath(&fpath, &mut cache) {
15015 Ok(r) => r,
15016 Err(e) => {
15017 tracing::error!("compinit: scan failed: {}", e);
15018 return;
15019 }
15020 };
15021
15022 tracing::info!(
15023 functions = result.files_scanned,
15024 comps = result.comps.len(),
15025 dirs = result.dirs_scanned,
15026 ms = result.scan_time_ms,
15027 "compinit: background scan complete"
15028 );
15029
15030 let parse_start = std::time::Instant::now();
15034 let mut parse_ok = 0usize;
15035 let mut parse_fail = 0usize;
15036 let mut no_body = 0usize;
15037 let batch_size = 100;
15038 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(batch_size);
15039
15040 for file in &result.files {
15041 if let Some(ref body) = file.body {
15042 let mut parser = crate::parser::ShellParser::new(body);
15043 match parser.parse_script() {
15044 Ok(commands) if !commands.is_empty() => {
15045 let compiler = crate::shell_compiler::ShellCompiler::new();
15047 let chunk = compiler.compile(&commands);
15048 if let Ok(blob) = bincode::serialize(&chunk) {
15049 batch.push((file.name.clone(), blob));
15050 parse_ok += 1;
15051 if batch.len() >= batch_size {
15052 let _ = cache.set_autoload_bytecodes_bulk(&batch);
15053 batch.clear();
15054 }
15055 }
15056 }
15057 Ok(_) => {
15058 parse_fail += 1;
15059 }
15060 Err(_) => {
15061 parse_fail += 1;
15062 }
15063 }
15064 } else {
15065 no_body += 1;
15066 }
15067 }
15068 if !batch.is_empty() {
15070 let _ = cache.set_autoload_bytecodes_bulk(&batch);
15071 batch.clear();
15072 }
15073
15074 tracing::info!(
15075 cached = parse_ok,
15076 failed = parse_fail,
15077 no_body = no_body,
15078 total = result.files.len(),
15079 ms = parse_start.elapsed().as_millis() as u64,
15080 "compinit: bytecode blobs cached"
15081 );
15082
15083 let _ = tx.send(CompInitBgResult { result, cache });
15084 });
15085
15086 self.compinit_pending = Some((rx, bg_start));
15087 0
15088 }
15089
15090 pub fn drain_compinit_bg(&mut self) {
15094 if let Some((rx, start)) = self.compinit_pending.take() {
15095 match rx.try_recv() {
15096 Ok(bg) => {
15097 let comps = bg.result.comps.len();
15098 self.assoc_arrays
15099 .insert("_comps".to_string(), bg.result.comps);
15100 self.assoc_arrays
15101 .insert("_services".to_string(), bg.result.services);
15102 self.assoc_arrays
15103 .insert("_patcomps".to_string(), bg.result.patcomps);
15104 self.compsys_cache = Some(bg.cache);
15105 tracing::info!(
15106 wall_ms = start.elapsed().as_millis() as u64,
15107 comps,
15108 "compinit: background results merged"
15109 );
15110 }
15111 Err(std::sync::mpsc::TryRecvError::Empty) => {
15112 self.compinit_pending = Some((rx, start));
15114 }
15115 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
15116 tracing::warn!("compinit: background thread died without sending results");
15117 }
15118 }
15119 }
15120 }
15121
15122 fn compinit_compat(
15125 &mut self,
15126 quiet: bool,
15127 no_dump: bool,
15128 dump_file: Option<String>,
15129 use_cache: bool,
15130 ) -> i32 {
15131 let zdotdir = self
15132 .variables
15133 .get("ZDOTDIR")
15134 .cloned()
15135 .or_else(|| std::env::var("ZDOTDIR").ok())
15136 .unwrap_or_else(|| std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
15137
15138 let dump_path = dump_file
15139 .map(PathBuf::from)
15140 .unwrap_or_else(|| PathBuf::from(&zdotdir).join(".zcompdump"));
15141
15142 if use_cache && dump_path.exists() {
15144 if compsys::check_dump(&dump_path, &self.fpath, "zshrs-0.1.0") {
15145 if !quiet {
15148 tracing::info!("compinit: .zcompdump valid, rescanning for compat");
15149 }
15150 }
15151 }
15152
15153 let result = compsys::compinit(&self.fpath);
15155
15156 if !quiet {
15157 tracing::info!(
15158 functions = result.files_scanned,
15159 comps = result.comps.len(),
15160 dirs = result.dirs_scanned,
15161 ms = result.scan_time_ms,
15162 "compinit: fpath scan complete"
15163 );
15164 }
15165
15166 if !no_dump {
15168 let _ = compsys::compdump(&result, &dump_path, "zshrs-0.1.0");
15169 }
15170
15171 self.assoc_arrays
15173 .insert("_comps".to_string(), result.comps.clone());
15174 self.assoc_arrays
15175 .insert("_services".to_string(), result.services.clone());
15176 self.assoc_arrays
15177 .insert("_patcomps".to_string(), result.patcomps.clone());
15178
15179 self.compsys_cache = None;
15181
15182 0
15183 }
15184
15185 fn builtin_cdreplay(&mut self, args: &[String]) -> i32 {
15188 let quiet = args.contains(&"-q".to_string());
15189
15190 if self.deferred_compdefs.is_empty() {
15191 return 0;
15192 }
15193
15194 let deferred = std::mem::take(&mut self.deferred_compdefs);
15195 let count = deferred.len();
15196
15197 if let Some(cache) = &mut self.compsys_cache {
15198 for compdef_args in deferred {
15199 compsys::compdef::compdef_execute(cache, &compdef_args);
15200 }
15201 }
15202
15203 if !quiet {
15204 eprintln!("cdreplay: replayed {} compdef calls", count);
15205 }
15206
15207 0
15208 }
15209
15210 fn builtin_zstyle(&mut self, args: &[String]) -> i32 {
15212 if args.is_empty() {
15213 for (pattern, style, values) in self.style_table.list(None) {
15215 println!("zstyle '{}' {} {}", pattern, style, values.join(" "));
15216 }
15217 return 0;
15218 }
15219
15220 if args[0].starts_with('-') {
15222 match args[0].as_str() {
15223 "-d" => {
15224 let pattern = args.get(1).map(|s| s.as_str());
15226 let style = args.get(2).map(|s| s.as_str());
15227 self.style_table.delete(pattern, style);
15228 return 0;
15229 }
15230 "-g" => {
15231 if args.len() >= 4 {
15233 let array_name = &args[1];
15234 let context = &args[2];
15235 let style = &args[3];
15236 if let Some(values) = self.style_table.get(context, style) {
15237 self.arrays.insert(array_name.clone(), values.to_vec());
15238 return 0;
15239 }
15240 }
15241 return 1;
15242 }
15243 "-s" => {
15244 if args.len() >= 4 {
15246 let var_name = &args[1];
15247 let context = &args[2];
15248 let style = &args[3];
15249 let sep = args.get(4).map(|s| s.as_str()).unwrap_or(" ");
15250 if let Some(values) = self.style_table.get(context, style) {
15251 self.variables.insert(var_name.clone(), values.join(sep));
15252 return 0;
15253 }
15254 }
15255 return 1;
15256 }
15257 "-t" => {
15258 if args.len() >= 3 {
15260 let context = &args[1];
15261 let style = &args[2];
15262 return if self.style_table.test_bool(context, style).unwrap_or(false) {
15263 0
15264 } else {
15265 1
15266 };
15267 }
15268 return 1;
15269 }
15270 "-L" => {
15271 for (pattern, style, values) in self.style_table.list(None) {
15273 let values_str = values
15274 .iter()
15275 .map(|v| format!("'{}'", v.replace('\'', "'\\''")))
15276 .collect::<Vec<_>>()
15277 .join(" ");
15278 println!("zstyle '{}' {} {}", pattern, style, values_str);
15279 }
15280 return 0;
15281 }
15282 _ => {}
15283 }
15284 }
15285
15286 if args.len() >= 2 {
15288 let pattern = &args[0];
15289 let style = &args[1];
15290 let values: Vec<String> = args[2..].to_vec();
15291 self.style_table.set(pattern, style, values.clone(), false);
15292
15293 if let Some(cache) = &self.compsys_cache {
15295 let _ = cache.set_zstyle(pattern, style, &values, false);
15296 }
15297
15298 let existing = self
15300 .zstyles
15301 .iter_mut()
15302 .find(|s| s.pattern == *pattern && s.style == *style);
15303 if let Some(s) = existing {
15304 s.values = args[2..].to_vec();
15305 } else {
15306 self.zstyles.push(ZStyle {
15307 pattern: pattern.clone(),
15308 style: style.clone(),
15309 values: args[2..].to_vec(),
15310 });
15311 }
15312 }
15313 0
15314 }
15315
15316 fn builtin_ztie(&mut self, args: &[String]) -> i32 {
15319 use crate::db_gdbm;
15320
15321 let mut db_type: Option<String> = None;
15322 let mut file_path: Option<String> = None;
15323 let mut readonly = false;
15324 let mut param_args: Vec<String> = Vec::new();
15325
15326 let mut i = 0;
15327 while i < args.len() {
15328 match args[i].as_str() {
15329 "-d" => {
15330 if i + 1 < args.len() {
15331 db_type = Some(args[i + 1].clone());
15332 i += 2;
15333 } else {
15334 eprintln!("ztie: -d requires an argument");
15335 return 1;
15336 }
15337 }
15338 "-f" => {
15339 if i + 1 < args.len() {
15340 file_path = Some(args[i + 1].clone());
15341 i += 2;
15342 } else {
15343 eprintln!("ztie: -f requires an argument");
15344 return 1;
15345 }
15346 }
15347 "-r" => {
15348 readonly = true;
15349 i += 1;
15350 }
15351 arg if arg.starts_with('-') => {
15352 eprintln!("ztie: bad option: {}", arg);
15353 return 1;
15354 }
15355 _ => {
15356 param_args.push(args[i].clone());
15357 i += 1;
15358 }
15359 }
15360 }
15361
15362 match db_gdbm::ztie(
15363 ¶m_args,
15364 readonly,
15365 db_type.as_deref(),
15366 file_path.as_deref(),
15367 ) {
15368 Ok(()) => 0,
15369 Err(e) => {
15370 eprintln!("ztie: {}", e);
15371 1
15372 }
15373 }
15374 }
15375
15376 fn builtin_zuntie(&mut self, args: &[String]) -> i32 {
15379 use crate::db_gdbm;
15380
15381 let mut force_unset = false;
15382 let mut param_args: Vec<String> = Vec::new();
15383
15384 for arg in args {
15385 match arg.as_str() {
15386 "-u" => force_unset = true,
15387 a if a.starts_with('-') => {
15388 eprintln!("zuntie: bad option: {}", a);
15389 return 1;
15390 }
15391 _ => param_args.push(arg.clone()),
15392 }
15393 }
15394
15395 if param_args.is_empty() {
15396 eprintln!("zuntie: not enough arguments");
15397 return 1;
15398 }
15399
15400 match db_gdbm::zuntie(¶m_args, force_unset) {
15401 Ok(()) => 0,
15402 Err(e) => {
15403 eprintln!("zuntie: {}", e);
15404 1
15405 }
15406 }
15407 }
15408
15409 fn builtin_zgdbmpath(&mut self, args: &[String]) -> i32 {
15413 use crate::db_gdbm;
15414
15415 if args.is_empty() {
15416 eprintln!(
15417 "zgdbmpath: parameter name (whose path is to be written to $REPLY) is required"
15418 );
15419 return 1;
15420 }
15421
15422 match db_gdbm::zgdbmpath(&args[0]) {
15423 Ok(path) => {
15424 self.variables.insert("REPLY".to_string(), path.clone());
15425 std::env::set_var("REPLY", &path);
15426 0
15427 }
15428 Err(e) => {
15429 eprintln!("zgdbmpath: {}", e);
15430 1
15431 }
15432 }
15433 }
15434
15435 fn builtin_pushd(&mut self, args: &[String]) -> i32 {
15437 let mut quiet = false;
15446 let mut physical = false;
15447 let mut positional_args: Vec<String> = Vec::new();
15448
15449 for arg in args {
15450 if arg.starts_with('-') && arg.len() > 1 {
15451 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15453 positional_args.push(arg.clone());
15454 continue;
15455 }
15456 for ch in arg[1..].chars() {
15457 match ch {
15458 'q' => quiet = true,
15459 's' => physical = false,
15460 'L' => physical = false,
15461 'P' => physical = true,
15462 _ => {}
15463 }
15464 }
15465 } else if arg.starts_with('+') {
15466 positional_args.push(arg.clone());
15467 } else {
15468 positional_args.push(arg.clone());
15469 }
15470 }
15471
15472 let current = match std::env::current_dir() {
15473 Ok(p) => p,
15474 Err(e) => {
15475 eprintln!("pushd: {}", e);
15476 return 1;
15477 }
15478 };
15479
15480 if positional_args.is_empty() {
15481 if self.dir_stack.is_empty() {
15483 eprintln!("pushd: no other directory");
15484 return 1;
15485 }
15486 let target = self.dir_stack.pop().unwrap();
15487 self.dir_stack.push(current.clone());
15488
15489 let resolved = if physical {
15490 target.canonicalize().unwrap_or(target.clone())
15491 } else {
15492 target.clone()
15493 };
15494
15495 if let Err(e) = std::env::set_current_dir(&resolved) {
15496 eprintln!("pushd: {}: {}", target.display(), e);
15497 self.dir_stack.pop();
15498 self.dir_stack.push(target);
15499 return 1;
15500 }
15501 if !quiet {
15502 self.print_dir_stack();
15503 }
15504 return 0;
15505 }
15506
15507 let arg = &positional_args[0];
15508
15509 if arg.starts_with('+') || arg.starts_with('-') {
15511 if let Ok(n) = arg[1..].parse::<usize>() {
15512 let total = self.dir_stack.len() + 1;
15513 if n >= total {
15514 eprintln!("pushd: {}: directory stack index out of range", arg);
15515 return 1;
15516 }
15517 let rotate_pos = if arg.starts_with('+') { n } else { total - n };
15519 let mut full_stack = vec![current.clone()];
15520 full_stack.extend(self.dir_stack.iter().cloned());
15521 full_stack.rotate_left(rotate_pos);
15522
15523 let target = full_stack.remove(0);
15524 self.dir_stack = full_stack;
15525
15526 let resolved = if physical {
15527 target.canonicalize().unwrap_or(target.clone())
15528 } else {
15529 target.clone()
15530 };
15531
15532 if let Err(e) = std::env::set_current_dir(&resolved) {
15533 eprintln!("pushd: {}: {}", target.display(), e);
15534 return 1;
15535 }
15536 if !quiet {
15537 self.print_dir_stack();
15538 }
15539 return 0;
15540 }
15541 }
15542
15543 let target = PathBuf::from(arg);
15545 let resolved = if physical {
15546 target.canonicalize().unwrap_or(target.clone())
15547 } else {
15548 target.clone()
15549 };
15550
15551 self.dir_stack.push(current);
15552 if let Err(e) = std::env::set_current_dir(&resolved) {
15553 eprintln!("pushd: {}: {}", arg, e);
15554 self.dir_stack.pop();
15555 return 1;
15556 }
15557 if !quiet {
15558 self.print_dir_stack();
15559 }
15560 0
15561 }
15562
15563 fn builtin_popd(&mut self, args: &[String]) -> i32 {
15565 let mut quiet = false;
15572 let mut physical = false;
15573 let mut stack_index: Option<String> = None;
15574
15575 for arg in args {
15576 if arg.starts_with('-') && arg.len() > 1 {
15577 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15579 stack_index = Some(arg.clone());
15580 continue;
15581 }
15582 for ch in arg[1..].chars() {
15583 match ch {
15584 'q' => quiet = true,
15585 's' => physical = false,
15586 'L' => physical = false,
15587 'P' => physical = true,
15588 _ => {}
15589 }
15590 }
15591 } else if arg.starts_with('+') {
15592 stack_index = Some(arg.clone());
15593 }
15594 }
15595
15596 if self.dir_stack.is_empty() {
15597 eprintln!("popd: directory stack empty");
15598 return 1;
15599 }
15600
15601 if let Some(arg) = stack_index {
15603 if arg.starts_with('+') || arg.starts_with('-') {
15604 if let Ok(n) = arg[1..].parse::<usize>() {
15605 let total = self.dir_stack.len() + 1;
15606 if n >= total {
15607 eprintln!("popd: {}: directory stack index out of range", arg);
15608 return 1;
15609 }
15610 let remove_pos = if arg.starts_with('+') {
15611 n
15612 } else {
15613 total - 1 - n
15614 };
15615 if remove_pos == 0 {
15616 let target = self.dir_stack.remove(0);
15618 let resolved = if physical {
15619 target.canonicalize().unwrap_or(target.clone())
15620 } else {
15621 target.clone()
15622 };
15623 if let Err(e) = std::env::set_current_dir(&resolved) {
15624 eprintln!("popd: {}: {}", target.display(), e);
15625 return 1;
15626 }
15627 } else {
15628 self.dir_stack.remove(remove_pos - 1);
15629 }
15630 if !quiet {
15631 self.print_dir_stack();
15632 }
15633 return 0;
15634 }
15635 }
15636 }
15637
15638 let target = self.dir_stack.pop().unwrap();
15639 let resolved = if physical {
15640 target.canonicalize().unwrap_or(target.clone())
15641 } else {
15642 target.clone()
15643 };
15644 if let Err(e) = std::env::set_current_dir(&resolved) {
15645 eprintln!("popd: {}: {}", target.display(), e);
15646 self.dir_stack.push(target);
15647 return 1;
15648 }
15649 if !quiet {
15650 self.print_dir_stack();
15651 }
15652 0
15653 }
15654
15655 fn builtin_dirs(&mut self, args: &[String]) -> i32 {
15657 let mut clear = false;
15664 let mut full_paths = false;
15665 let mut per_line = false;
15666 let mut verbose = false;
15667 let mut indices: Vec<i32> = Vec::new();
15668
15669 for arg in args {
15670 if arg.starts_with('-') && arg.len() > 1 {
15671 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15673 if let Ok(n) = arg.parse::<i32>() {
15674 indices.push(n);
15675 continue;
15676 }
15677 }
15678 for ch in arg[1..].chars() {
15679 match ch {
15680 'c' => clear = true,
15681 'l' => full_paths = true,
15682 'p' => per_line = true,
15683 'v' => verbose = true,
15684 _ => {}
15685 }
15686 }
15687 } else if arg.starts_with('+') && arg.len() > 1 {
15688 if let Ok(n) = arg[1..].parse::<i32>() {
15689 indices.push(n);
15690 }
15691 } else {
15692 if let Ok(n) = arg.parse::<i32>() {
15694 indices.push(n);
15695 }
15696 }
15697 }
15698
15699 if clear {
15700 self.dir_stack.clear();
15701 return 0;
15702 }
15703
15704 let current = std::env::current_dir().unwrap_or_default();
15705 let home = dirs::home_dir().unwrap_or_default();
15706
15707 let format_path = |p: &std::path::Path| -> String {
15708 let path_str = p.to_string_lossy().to_string();
15709 if !full_paths {
15710 let home_str = home.to_string_lossy();
15711 if path_str.starts_with(home_str.as_ref()) {
15712 return format!("~{}", &path_str[home_str.len()..]);
15713 }
15714 }
15715 path_str
15716 };
15717
15718 if !indices.is_empty() {
15720 let stack_len = self.dir_stack.len() + 1; for idx in indices {
15722 let actual_idx = if idx >= 0 {
15723 idx as usize
15724 } else {
15725 stack_len.saturating_sub((-idx) as usize)
15726 };
15727
15728 if actual_idx == 0 {
15729 println!("{}", format_path(¤t));
15730 } else if actual_idx <= self.dir_stack.len() {
15731 let stack_idx = self.dir_stack.len() - actual_idx;
15733 if let Some(dir) = self.dir_stack.get(stack_idx) {
15734 println!("{}", format_path(dir));
15735 }
15736 }
15737 }
15738 return 0;
15739 }
15740
15741 if verbose {
15742 println!(" 0 {}", format_path(¤t));
15743 for (i, dir) in self.dir_stack.iter().rev().enumerate() {
15744 println!("{:2} {}", i + 1, format_path(dir));
15745 }
15746 } else if per_line {
15747 println!("{}", format_path(¤t));
15748 for dir in self.dir_stack.iter().rev() {
15749 println!("{}", format_path(dir));
15750 }
15751 } else {
15752 let mut parts = vec![format_path(¤t)];
15753 for dir in self.dir_stack.iter().rev() {
15754 parts.push(format_path(dir));
15755 }
15756 println!("{}", parts.join(" "));
15757 }
15758 0
15759 }
15760
15761 fn print_dir_stack(&self) {
15762 let current = std::env::current_dir().unwrap_or_default();
15763 let mut parts = vec![current.to_string_lossy().to_string()];
15764 for dir in self.dir_stack.iter().rev() {
15765 parts.push(dir.to_string_lossy().to_string());
15766 }
15767 println!("{}", parts.join(" "));
15768 }
15769
15770 fn builtin_printf(&self, args: &[String]) -> i32 {
15772 if args.is_empty() {
15773 eprintln!("printf: usage: printf format [arguments]");
15774 return 1;
15775 }
15776
15777 let format = &args[0];
15778 let format_args = &args[1..];
15779 let mut arg_idx = 0;
15780 let mut output = String::new();
15781 let mut chars = format.chars().peekable();
15782
15783 while let Some(c) = chars.next() {
15784 if c == '\\' {
15785 match chars.next() {
15786 Some('n') => output.push('\n'),
15787 Some('t') => output.push('\t'),
15788 Some('r') => output.push('\r'),
15789 Some('\\') => output.push('\\'),
15790 Some('a') => output.push('\x07'),
15791 Some('b') => output.push('\x08'),
15792 Some('e') | Some('E') => output.push('\x1b'),
15793 Some('f') => output.push('\x0c'),
15794 Some('v') => output.push('\x0b'),
15795 Some('"') => output.push('"'),
15796 Some('\'') => output.push('\''),
15797 Some('0') => {
15798 let mut octal = String::new();
15799 while octal.len() < 3 {
15800 if let Some(&d) = chars.peek() {
15801 if d >= '0' && d <= '7' {
15802 octal.push(d);
15803 chars.next();
15804 } else {
15805 break;
15806 }
15807 } else {
15808 break;
15809 }
15810 }
15811 if octal.is_empty() {
15812 output.push('\0');
15813 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
15814 output.push(val as char);
15815 }
15816 }
15817 Some('x') => {
15818 let mut hex = String::new();
15819 while hex.len() < 2 {
15820 if let Some(&d) = chars.peek() {
15821 if d.is_ascii_hexdigit() {
15822 hex.push(d);
15823 chars.next();
15824 } else {
15825 break;
15826 }
15827 } else {
15828 break;
15829 }
15830 }
15831 if !hex.is_empty() {
15832 if let Ok(val) = u8::from_str_radix(&hex, 16) {
15833 output.push(val as char);
15834 }
15835 }
15836 }
15837 Some('u') => {
15838 let mut hex = String::new();
15839 while hex.len() < 4 {
15840 if let Some(&d) = chars.peek() {
15841 if d.is_ascii_hexdigit() {
15842 hex.push(d);
15843 chars.next();
15844 } else {
15845 break;
15846 }
15847 } else {
15848 break;
15849 }
15850 }
15851 if !hex.is_empty() {
15852 if let Ok(val) = u32::from_str_radix(&hex, 16) {
15853 if let Some(c) = char::from_u32(val) {
15854 output.push(c);
15855 }
15856 }
15857 }
15858 }
15859 Some('U') => {
15860 let mut hex = String::new();
15861 while hex.len() < 8 {
15862 if let Some(&d) = chars.peek() {
15863 if d.is_ascii_hexdigit() {
15864 hex.push(d);
15865 chars.next();
15866 } else {
15867 break;
15868 }
15869 } else {
15870 break;
15871 }
15872 }
15873 if !hex.is_empty() {
15874 if let Ok(val) = u32::from_str_radix(&hex, 16) {
15875 if let Some(c) = char::from_u32(val) {
15876 output.push(c);
15877 }
15878 }
15879 }
15880 }
15881 Some('c') => {
15882 print!("{}", output);
15883 return 0;
15884 }
15885 Some(other) => {
15886 output.push('\\');
15887 output.push(other);
15888 }
15889 None => output.push('\\'),
15890 }
15891 } else if c == '%' {
15892 if chars.peek() == Some(&'%') {
15893 chars.next();
15894 output.push('%');
15895 continue;
15896 }
15897
15898 let mut flags = String::new();
15899 while let Some(&f) = chars.peek() {
15900 if f == '-' || f == '+' || f == ' ' || f == '#' || f == '0' {
15901 flags.push(f);
15902 chars.next();
15903 } else {
15904 break;
15905 }
15906 }
15907
15908 let mut width = String::new();
15909 if chars.peek() == Some(&'*') {
15910 chars.next();
15911 if arg_idx < format_args.len() {
15912 width = format_args[arg_idx].clone();
15913 arg_idx += 1;
15914 }
15915 } else {
15916 while let Some(&d) = chars.peek() {
15917 if d.is_ascii_digit() {
15918 width.push(d);
15919 chars.next();
15920 } else {
15921 break;
15922 }
15923 }
15924 }
15925
15926 let mut precision = String::new();
15927 if chars.peek() == Some(&'.') {
15928 chars.next();
15929 if chars.peek() == Some(&'*') {
15930 chars.next();
15931 if arg_idx < format_args.len() {
15932 precision = format_args[arg_idx].clone();
15933 arg_idx += 1;
15934 }
15935 } else {
15936 while let Some(&d) = chars.peek() {
15937 if d.is_ascii_digit() {
15938 precision.push(d);
15939 chars.next();
15940 } else {
15941 break;
15942 }
15943 }
15944 }
15945 }
15946
15947 let specifier = chars.next().unwrap_or('s');
15948 let arg = if arg_idx < format_args.len() {
15949 let a = &format_args[arg_idx];
15950 arg_idx += 1;
15951 a.clone()
15952 } else {
15953 String::new()
15954 };
15955
15956 let width_val: usize = width.parse().unwrap_or(0);
15957 let prec_val: Option<usize> = if precision.is_empty() {
15958 None
15959 } else {
15960 precision.parse().ok()
15961 };
15962 let left_align = flags.contains('-');
15963 let zero_pad = flags.contains('0') && !left_align;
15964 let plus_sign = flags.contains('+');
15965 let space_sign = flags.contains(' ') && !plus_sign;
15966 let alt_form = flags.contains('#');
15967
15968 match specifier {
15969 's' => {
15970 let mut s = arg;
15971 if let Some(p) = prec_val {
15972 s = s.chars().take(p).collect();
15973 }
15974 if width_val > s.len() {
15975 if left_align {
15976 output.push_str(&s);
15977 output.push_str(&" ".repeat(width_val - s.len()));
15978 } else {
15979 output.push_str(&" ".repeat(width_val - s.len()));
15980 output.push_str(&s);
15981 }
15982 } else {
15983 output.push_str(&s);
15984 }
15985 }
15986 'b' => {
15987 let expanded = self.expand_printf_escapes(&arg);
15988 if let Some(p) = prec_val {
15989 let s: String = expanded.chars().take(p).collect();
15990 output.push_str(&s);
15991 } else {
15992 output.push_str(&expanded);
15993 }
15994 }
15995 'c' => {
15996 if let Some(ch) = arg.chars().next() {
15997 output.push(ch);
15998 }
15999 }
16000 'q' => {
16001 output.push('\'');
16002 for ch in arg.chars() {
16003 if ch == '\'' {
16004 output.push_str("'\\''");
16005 } else {
16006 output.push(ch);
16007 }
16008 }
16009 output.push('\'');
16010 }
16011 'd' | 'i' => {
16012 let val: i64 = if arg.starts_with("0x") || arg.starts_with("0X") {
16013 i64::from_str_radix(&arg[2..], 16).unwrap_or(0)
16014 } else if arg.starts_with("0") && arg.len() > 1 && !arg.contains('.') {
16015 i64::from_str_radix(&arg[1..], 8).unwrap_or(0)
16016 } else if arg.starts_with('\'') || arg.starts_with('"') {
16017 arg.chars().nth(1).map(|c| c as i64).unwrap_or(0)
16018 } else {
16019 arg.parse().unwrap_or(0)
16020 };
16021
16022 let sign = if val < 0 {
16023 "-"
16024 } else if plus_sign {
16025 "+"
16026 } else if space_sign {
16027 " "
16028 } else {
16029 ""
16030 };
16031 let abs_val = val.abs();
16032 let num_str = abs_val.to_string();
16033 let total_len = sign.len() + num_str.len();
16034
16035 if width_val > total_len {
16036 if left_align {
16037 output.push_str(sign);
16038 output.push_str(&num_str);
16039 output.push_str(&" ".repeat(width_val - total_len));
16040 } else if zero_pad {
16041 output.push_str(sign);
16042 output.push_str(&"0".repeat(width_val - total_len));
16043 output.push_str(&num_str);
16044 } else {
16045 output.push_str(&" ".repeat(width_val - total_len));
16046 output.push_str(sign);
16047 output.push_str(&num_str);
16048 }
16049 } else {
16050 output.push_str(sign);
16051 output.push_str(&num_str);
16052 }
16053 }
16054 'u' => {
16055 let val: u64 = if arg.starts_with("0x") || arg.starts_with("0X") {
16056 u64::from_str_radix(&arg[2..], 16).unwrap_or(0)
16057 } else if arg.starts_with("0") && arg.len() > 1 {
16058 u64::from_str_radix(&arg[1..], 8).unwrap_or(0)
16059 } else {
16060 arg.parse().unwrap_or(0)
16061 };
16062 let num_str = val.to_string();
16063 if width_val > num_str.len() {
16064 if left_align {
16065 output.push_str(&num_str);
16066 output.push_str(&" ".repeat(width_val - num_str.len()));
16067 } else if zero_pad {
16068 output.push_str(&"0".repeat(width_val - num_str.len()));
16069 output.push_str(&num_str);
16070 } else {
16071 output.push_str(&" ".repeat(width_val - num_str.len()));
16072 output.push_str(&num_str);
16073 }
16074 } else {
16075 output.push_str(&num_str);
16076 }
16077 }
16078 'o' => {
16079 let val: u64 = arg.parse().unwrap_or(0);
16080 let num_str = format!("{:o}", val);
16081 let prefix = if alt_form && val != 0 { "0" } else { "" };
16082 let total_len = prefix.len() + num_str.len();
16083 if width_val > total_len {
16084 if left_align {
16085 output.push_str(prefix);
16086 output.push_str(&num_str);
16087 output.push_str(&" ".repeat(width_val - total_len));
16088 } else {
16089 output.push_str(&" ".repeat(width_val - total_len));
16090 output.push_str(prefix);
16091 output.push_str(&num_str);
16092 }
16093 } else {
16094 output.push_str(prefix);
16095 output.push_str(&num_str);
16096 }
16097 }
16098 'x' => {
16099 let val: u64 = arg.parse().unwrap_or(0);
16100 let num_str = format!("{:x}", val);
16101 let prefix = if alt_form && val != 0 { "0x" } else { "" };
16102 let total_len = prefix.len() + num_str.len();
16103 if width_val > total_len {
16104 if left_align {
16105 output.push_str(prefix);
16106 output.push_str(&num_str);
16107 output.push_str(&" ".repeat(width_val - total_len));
16108 } else {
16109 output.push_str(&" ".repeat(width_val - total_len));
16110 output.push_str(prefix);
16111 output.push_str(&num_str);
16112 }
16113 } else {
16114 output.push_str(prefix);
16115 output.push_str(&num_str);
16116 }
16117 }
16118 'X' => {
16119 let val: u64 = arg.parse().unwrap_or(0);
16120 let num_str = format!("{:X}", val);
16121 let prefix = if alt_form && val != 0 { "0X" } else { "" };
16122 let total_len = prefix.len() + num_str.len();
16123 if width_val > total_len {
16124 if left_align {
16125 output.push_str(prefix);
16126 output.push_str(&num_str);
16127 output.push_str(&" ".repeat(width_val - total_len));
16128 } else {
16129 output.push_str(&" ".repeat(width_val - total_len));
16130 output.push_str(prefix);
16131 output.push_str(&num_str);
16132 }
16133 } else {
16134 output.push_str(prefix);
16135 output.push_str(&num_str);
16136 }
16137 }
16138 'e' | 'E' => {
16139 let val: f64 = arg.parse().unwrap_or(0.0);
16140 let prec = prec_val.unwrap_or(6);
16141 let formatted = if specifier == 'e' {
16142 format!("{:.prec$e}", val, prec = prec)
16143 } else {
16144 format!("{:.prec$E}", val, prec = prec)
16145 };
16146 if width_val > formatted.len() {
16147 if left_align {
16148 output.push_str(&formatted);
16149 output.push_str(&" ".repeat(width_val - formatted.len()));
16150 } else {
16151 output.push_str(&" ".repeat(width_val - formatted.len()));
16152 output.push_str(&formatted);
16153 }
16154 } else {
16155 output.push_str(&formatted);
16156 }
16157 }
16158 'f' | 'F' => {
16159 let val: f64 = arg.parse().unwrap_or(0.0);
16160 let prec = prec_val.unwrap_or(6);
16161 let sign = if val < 0.0 {
16162 "-"
16163 } else if plus_sign {
16164 "+"
16165 } else if space_sign {
16166 " "
16167 } else {
16168 ""
16169 };
16170 let formatted = format!("{:.prec$}", val.abs(), prec = prec);
16171 let total = sign.len() + formatted.len();
16172 if width_val > total {
16173 if left_align {
16174 output.push_str(sign);
16175 output.push_str(&formatted);
16176 output.push_str(&" ".repeat(width_val - total));
16177 } else if zero_pad {
16178 output.push_str(sign);
16179 output.push_str(&"0".repeat(width_val - total));
16180 output.push_str(&formatted);
16181 } else {
16182 output.push_str(&" ".repeat(width_val - total));
16183 output.push_str(sign);
16184 output.push_str(&formatted);
16185 }
16186 } else {
16187 output.push_str(sign);
16188 output.push_str(&formatted);
16189 }
16190 }
16191 'g' | 'G' => {
16192 let val: f64 = arg.parse().unwrap_or(0.0);
16193 let prec = prec_val.unwrap_or(6).max(1);
16194 let formatted = if specifier == 'g' {
16195 format!("{:.prec$}", val, prec = prec)
16196 } else {
16197 format!("{:.prec$}", val, prec = prec).to_uppercase()
16198 };
16199 output.push_str(&formatted);
16200 }
16201 'a' | 'A' => {
16202 let val: f64 = arg.parse().unwrap_or(0.0);
16203 let formatted = float_to_hex(val, specifier == 'A');
16204 output.push_str(&formatted);
16205 }
16206 _ => {
16207 output.push('%');
16208 output.push(specifier);
16209 }
16210 }
16211 } else {
16212 output.push(c);
16213 }
16214 }
16215
16216 print!("{}", output);
16217 0
16218 }
16219
16220 fn expand_printf_escapes(&self, s: &str) -> String {
16221 let mut result = String::new();
16222 let mut chars = s.chars().peekable();
16223 while let Some(c) = chars.next() {
16224 if c == '\\' {
16225 match chars.next() {
16226 Some('n') => result.push('\n'),
16227 Some('t') => result.push('\t'),
16228 Some('r') => result.push('\r'),
16229 Some('\\') => result.push('\\'),
16230 Some('a') => result.push('\x07'),
16231 Some('b') => result.push('\x08'),
16232 Some('e') | Some('E') => result.push('\x1b'),
16233 Some('f') => result.push('\x0c'),
16234 Some('v') => result.push('\x0b'),
16235 Some('0') => {
16236 let mut octal = String::new();
16237 while octal.len() < 3 {
16238 if let Some(&d) = chars.peek() {
16239 if d >= '0' && d <= '7' {
16240 octal.push(d);
16241 chars.next();
16242 } else {
16243 break;
16244 }
16245 } else {
16246 break;
16247 }
16248 }
16249 if octal.is_empty() {
16250 result.push('\0');
16251 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
16252 result.push(val as char);
16253 }
16254 }
16255 Some('c') => break,
16256 Some(other) => {
16257 result.push('\\');
16258 result.push(other);
16259 }
16260 None => result.push('\\'),
16261 }
16262 } else {
16263 result.push(c);
16264 }
16265 }
16266 result
16267 }
16268
16269 fn evaluate_arithmetic_expr(&mut self, expr: &str) -> i64 {
16270 self.eval_arith_expr(expr)
16271 }
16272
16273 fn builtin_break(&mut self, args: &[String]) -> i32 {
16279 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
16280 self.breaking = levels.max(1);
16281 0
16282 }
16283
16284 fn builtin_continue(&mut self, args: &[String]) -> i32 {
16286 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
16287 self.continuing = levels.max(1);
16288 0
16289 }
16290
16291 fn builtin_disable(&mut self, args: &[String]) -> i32 {
16293 let mut disable_aliases = false;
16294 let mut disable_builtins = false;
16295 let mut disable_functions = false;
16296 let mut names = Vec::new();
16297
16298 let mut iter = args.iter();
16299 while let Some(arg) = iter.next() {
16300 match arg.as_str() {
16301 "-a" => disable_aliases = true,
16302 "-f" => disable_functions = true,
16303 "-r" => disable_builtins = true,
16304 _ if arg.starts_with('-') => {}
16305 _ => names.push(arg.clone()),
16306 }
16307 }
16308
16309 if !disable_aliases && !disable_functions {
16311 disable_builtins = true;
16312 }
16313
16314 for name in names {
16315 if disable_aliases {
16316 self.aliases.remove(&name);
16317 }
16318 if disable_functions {
16319 self.functions.remove(&name);
16320 }
16321 if disable_builtins {
16322 self.options.insert(format!("_disabled_{}", name), true);
16324 }
16325 }
16326 0
16327 }
16328
16329 fn builtin_enable(&mut self, args: &[String]) -> i32 {
16331 for arg in args {
16332 if !arg.starts_with('-') {
16333 self.options.remove(&format!("_disabled_{}", arg));
16334 }
16335 }
16336 0
16337 }
16338
16339 fn builtin_emulate(&mut self, args: &[String]) -> i32 {
16341 let mut local_mode = false;
16344 let mut reset_mode = false;
16345 let mut list_mode = false;
16346 let mut mode: Option<String> = None;
16347 let mut command_arg: Option<String> = None;
16348 let mut extra_set_opts: Vec<String> = Vec::new();
16349 let mut extra_unset_opts: Vec<String> = Vec::new();
16350
16351 let mut i = 0;
16352 while i < args.len() {
16353 let arg = &args[i];
16354
16355 if arg == "-c" {
16356 i += 1;
16358 if i < args.len() {
16359 command_arg = Some(args[i].clone());
16360 } else {
16361 eprintln!("emulate: -c requires an argument");
16362 return 1;
16363 }
16364 } else if arg == "-o" {
16365 i += 1;
16367 if i < args.len() {
16368 extra_set_opts.push(args[i].clone());
16369 } else {
16370 eprintln!("emulate: -o requires an argument");
16371 return 1;
16372 }
16373 } else if arg == "+o" {
16374 i += 1;
16376 if i < args.len() {
16377 extra_unset_opts.push(args[i].clone());
16378 } else {
16379 eprintln!("emulate: +o requires an argument");
16380 return 1;
16381 }
16382 } else if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
16383 for ch in arg[1..].chars() {
16385 match ch {
16386 'L' => local_mode = true,
16387 'R' => reset_mode = true,
16388 'l' => list_mode = true,
16389 _ => {
16390 eprintln!("emulate: bad option: -{}", ch);
16391 return 1;
16392 }
16393 }
16394 }
16395 } else if arg.starts_with('+') && arg.len() > 1 {
16396 for ch in arg[1..].chars() {
16398 extra_unset_opts.push(ch.to_string());
16400 }
16401 } else if mode.is_none() {
16402 mode = Some(arg.clone());
16403 }
16404 i += 1;
16405 }
16406
16407 if local_mode && command_arg.is_some() {
16409 eprintln!("emulate: -L and -c are mutually exclusive");
16410 return 1;
16411 }
16412
16413 if mode.is_none() && !list_mode {
16415 let current = self
16416 .variables
16417 .get("EMULATE")
16418 .cloned()
16419 .unwrap_or_else(|| "zsh".to_string());
16420 println!("{}", current);
16421 return 0;
16422 }
16423
16424 let mode = mode.unwrap_or_else(|| "zsh".to_string());
16425
16426 let (set_opts, unset_opts) = Self::emulate_mode_options(&mode, reset_mode);
16428
16429 if list_mode {
16431 for opt in &set_opts {
16432 println!("{}", opt);
16433 }
16434 for opt in &unset_opts {
16435 println!("no{}", opt);
16436 }
16437 if local_mode {
16438 println!("localoptions");
16439 println!("localpatterns");
16440 println!("localtraps");
16441 }
16442 return 0;
16443 }
16444
16445 let saved_options = if command_arg.is_some() {
16447 Some(self.options.clone())
16448 } else {
16449 None
16450 };
16451 let saved_emulate = if command_arg.is_some() {
16452 self.variables.get("EMULATE").cloned()
16453 } else {
16454 None
16455 };
16456
16457 self.variables.insert("EMULATE".to_string(), mode.clone());
16459
16460 for opt in &set_opts {
16462 let opt_name = opt.to_lowercase().replace('_', "");
16463 self.options.insert(opt_name, true);
16464 }
16465 for opt in &unset_opts {
16466 let opt_name = opt.to_lowercase().replace('_', "");
16467 self.options.insert(opt_name, false);
16468 }
16469
16470 for opt in &extra_set_opts {
16472 let opt_name = opt.to_lowercase().replace('_', "");
16473 self.options.insert(opt_name, true);
16474 }
16475 for opt in &extra_unset_opts {
16476 let opt_name = opt.to_lowercase().replace('_', "");
16477 self.options.insert(opt_name, false);
16478 }
16479
16480 if local_mode {
16482 self.options.insert("localoptions".to_string(), true);
16483 self.options.insert("localpatterns".to_string(), true);
16484 self.options.insert("localtraps".to_string(), true);
16485 }
16486
16487 let result = if let Some(cmd) = command_arg {
16489 let status = self.execute_script(&cmd).unwrap_or(1);
16490
16491 if let Some(opts) = saved_options {
16493 self.options = opts;
16494 }
16495 if let Some(emu) = saved_emulate {
16496 self.variables.insert("EMULATE".to_string(), emu);
16497 } else {
16498 self.variables.remove("EMULATE");
16499 }
16500
16501 status
16502 } else {
16503 0
16504 };
16505
16506 result
16507 }
16508
16509 fn emulate_mode_options(mode: &str, reset: bool) -> (Vec<&'static str>, Vec<&'static str>) {
16511 match mode {
16512 "zsh" => {
16513 if reset {
16514 (
16516 vec![
16517 "aliases",
16518 "alwayslastprompt",
16519 "autolist",
16520 "automenu",
16521 "autoparamslash",
16522 "autoremoveslash",
16523 "banghist",
16524 "bareglobqual",
16525 "completeinword",
16526 "extendedhistory",
16527 "functionargzero",
16528 "glob",
16529 "hashcmds",
16530 "hashdirs",
16531 "histexpand",
16532 "histignoredups",
16533 "interactivecomments",
16534 "listambiguous",
16535 "listtypes",
16536 "multios",
16537 "nomatch",
16538 "notify",
16539 "promptpercent",
16540 "promptsubst",
16541 ],
16542 vec![
16543 "ksharrays",
16544 "kshglob",
16545 "shwordsplit",
16546 "shglob",
16547 "posixbuiltins",
16548 "posixidentifiers",
16549 "posixstrings",
16550 "bsdecho",
16551 "ignorebraces",
16552 ],
16553 )
16554 } else {
16555 (vec!["functionargzero"], vec!["ksharrays", "shwordsplit"])
16557 }
16558 }
16559 "sh" => {
16560 let set = vec![
16561 "ksharrays",
16562 "shwordsplit",
16563 "posixbuiltins",
16564 "shglob",
16565 "shfileexpansion",
16566 "globsubst",
16567 "interactivecomments",
16568 "rmstarsilent",
16569 "bsdecho",
16570 "ignorebraces",
16571 ];
16572 let unset = vec![
16573 "badpattern",
16574 "banghist",
16575 "bgnice",
16576 "equals",
16577 "functionargzero",
16578 "globalexport",
16579 "multios",
16580 "nomatch",
16581 "notify",
16582 "promptpercent",
16583 ];
16584 (set, unset)
16585 }
16586 "ksh" => {
16587 let set = vec![
16588 "ksharrays",
16589 "kshglob",
16590 "shwordsplit",
16591 "posixbuiltins",
16592 "kshoptionprint",
16593 "localoptions",
16594 "promptbang",
16595 "promptsubst",
16596 "singlelinezle",
16597 "interactivecomments",
16598 ];
16599 let unset = vec![
16600 "badpattern",
16601 "banghist",
16602 "bgnice",
16603 "equals",
16604 "functionargzero",
16605 "globalexport",
16606 "multios",
16607 "nomatch",
16608 "notify",
16609 "promptpercent",
16610 ];
16611 (set, unset)
16612 }
16613 "csh" => {
16614 (vec!["cshnullglob", "cshjunkiequotes"], vec!["nomatch"])
16616 }
16617 "bash" => {
16618 let set = vec![
16619 "ksharrays",
16620 "shwordsplit",
16621 "interactivecomments",
16622 "shfileexpansion",
16623 "globsubst",
16624 ];
16625 let unset = vec![
16626 "badpattern",
16627 "banghist",
16628 "functionargzero",
16629 "multios",
16630 "nomatch",
16631 "notify",
16632 "promptpercent",
16633 ];
16634 (set, unset)
16635 }
16636 _ => (vec![], vec![]),
16637 }
16638 }
16639
16640 fn builtin_exec(&mut self, args: &[String]) -> i32 {
16642 let mut clear_env = false;
16648 let mut login_shell = false;
16649 let mut argv0: Option<String> = None;
16650 let mut cmd_args: Vec<String> = Vec::new();
16651
16652 let mut i = 0;
16653 while i < args.len() {
16654 let arg = &args[i];
16655
16656 if arg == "-c" && cmd_args.is_empty() {
16657 clear_env = true;
16658 } else if arg == "-l" && cmd_args.is_empty() {
16659 login_shell = true;
16660 } else if arg == "-a" && cmd_args.is_empty() {
16661 i += 1;
16662 if i < args.len() {
16663 argv0 = Some(args[i].clone());
16664 }
16665 } else if arg.starts_with('-') && cmd_args.is_empty() {
16666 for ch in arg[1..].chars() {
16668 match ch {
16669 'c' => clear_env = true,
16670 'l' => login_shell = true,
16671 'a' => {
16672 i += 1;
16673 if i < args.len() {
16674 argv0 = Some(args[i].clone());
16675 }
16676 }
16677 _ => {}
16678 }
16679 }
16680 } else {
16681 cmd_args.push(arg.clone());
16682 }
16683 i += 1;
16684 }
16685
16686 if cmd_args.is_empty() {
16687 if clear_env {
16689 for (key, _) in env::vars() {
16690 env::remove_var(&key);
16691 }
16692 }
16693 return 0;
16694 }
16695
16696 let cmd = &cmd_args[0];
16697 let rest_args: Vec<&str> = cmd_args[1..].iter().map(|s| s.as_str()).collect();
16698
16699 let effective_argv0 = if let Some(a0) = argv0 {
16701 a0
16702 } else if login_shell {
16703 format!("-{}", cmd)
16704 } else {
16705 cmd.clone()
16706 };
16707
16708 use std::os::unix::process::CommandExt;
16709 let mut command = std::process::Command::new(cmd);
16710 command.arg0(&effective_argv0);
16711 command.args(&rest_args);
16712
16713 if clear_env {
16714 command.env_clear();
16715 }
16716
16717 let err = command.exec();
16718 eprintln!("exec: {}: {}", cmd, err);
16719 1
16720 }
16721
16722 fn builtin_float(&mut self, args: &[String]) -> i32 {
16724 for arg in args {
16725 if arg.starts_with('-') {
16726 continue;
16727 }
16728 if let Some(eq_pos) = arg.find('=') {
16729 let name = &arg[..eq_pos];
16730 let value = &arg[eq_pos + 1..];
16731 let float_val: f64 = value.parse().unwrap_or(0.0);
16732 self.variables
16733 .insert(name.to_string(), float_val.to_string());
16734 self.options.insert(format!("_float_{}", name), true);
16735 } else {
16736 self.variables.insert(arg.clone(), "0.0".to_string());
16737 self.options.insert(format!("_float_{}", arg), true);
16738 }
16739 }
16740 0
16741 }
16742
16743 fn builtin_integer(&mut self, args: &[String]) -> i32 {
16745 for arg in args {
16746 if arg.starts_with('-') {
16747 continue;
16748 }
16749 if let Some(eq_pos) = arg.find('=') {
16750 let name = &arg[..eq_pos];
16751 let value = &arg[eq_pos + 1..];
16752 let int_val: i64 = value.parse().unwrap_or(0);
16753 self.variables.insert(name.to_string(), int_val.to_string());
16754 self.options.insert(format!("_integer_{}", name), true);
16755 } else {
16756 self.variables.insert(arg.clone(), "0".to_string());
16757 self.options.insert(format!("_integer_{}", arg), true);
16758 }
16759 }
16760 0
16761 }
16762
16763 fn builtin_functions(&self, args: &[String]) -> i32 {
16765 let mut list_only = false;
16766 let mut show_trace = false;
16767 let mut names: Vec<&str> = Vec::new();
16768
16769 for arg in args {
16770 match arg.as_str() {
16771 "-l" => list_only = true,
16772 "-t" => show_trace = true,
16773 _ if arg.starts_with('-') => {}
16774 _ => names.push(arg),
16775 }
16776 }
16777
16778 if names.is_empty() {
16779 let mut func_names: Vec<_> = self.functions.keys().collect();
16781 func_names.sort();
16782 for name in func_names {
16783 if list_only {
16784 println!("{}", name);
16785 } else if let Some(func) = self.functions.get(name) {
16786 let body = crate::text::getpermtext(func);
16787 println!("{} () {{\n\t{}\n}}", name, body.trim());
16788 }
16789 }
16790 } else {
16791 for name in names {
16793 if let Some(func) = self.functions.get(name) {
16794 if show_trace {
16795 println!("functions -t {}", name);
16796 } else {
16797 let body = crate::text::getpermtext(func);
16798 println!("{} () {{\n\t{}\n}}", name, body.trim());
16799 }
16800 } else {
16801 eprintln!("functions: no such function: {}", name);
16802 return 1;
16803 }
16804 }
16805 }
16806 0
16807 }
16808
16809 fn builtin_print(&mut self, args: &[String]) -> i32 {
16811 let mut no_newline = false;
16814 let mut one_per_line = false;
16815 let mut interpret_escapes = true; let mut raw_mode = false;
16817 let mut prompt_expand = false;
16818 let mut fd: i32 = 1; let mut columns = 0usize;
16820 let mut null_terminate = false;
16821 let mut push_to_stack = false;
16822 let mut add_to_history = false;
16823 let mut sort_asc = false;
16824 let mut sort_desc = false;
16825 let mut named_dir_subst = false;
16826 let mut store_var: Option<String> = None;
16827 let mut format_string: Option<String> = None;
16828 let mut output_args: Vec<String> = Vec::new();
16829
16830 let mut i = 0;
16831 while i < args.len() {
16832 let arg = &args[i];
16833
16834 if arg == "--" {
16835 i += 1;
16836 while i < args.len() {
16837 output_args.push(args[i].clone());
16838 i += 1;
16839 }
16840 break;
16841 }
16842
16843 if arg.starts_with('-')
16844 && arg.len() > 1
16845 && !arg
16846 .chars()
16847 .nth(1)
16848 .map(|c| c.is_ascii_digit())
16849 .unwrap_or(false)
16850 {
16851 let mut chars = arg[1..].chars().peekable();
16852 while let Some(ch) = chars.next() {
16853 match ch {
16854 'n' => no_newline = true,
16855 'l' => one_per_line = true,
16856 'r' => {
16857 raw_mode = true;
16858 interpret_escapes = false;
16859 }
16860 'R' => {
16861 raw_mode = true;
16862 interpret_escapes = false;
16863 }
16864 'e' => interpret_escapes = true,
16865 'E' => interpret_escapes = false,
16866 'P' => prompt_expand = true,
16867 'N' => null_terminate = true,
16868 'z' => push_to_stack = true,
16869 's' => add_to_history = true,
16870 'o' => sort_asc = true,
16871 'O' => sort_desc = true,
16872 'D' => named_dir_subst = true,
16873 'c' => columns = 1,
16874 'a' | 'b' | 'i' | 'm' | 'p' | 'S' | 'x' | 'X' => {} 'u' => {
16876 let rest: String = chars.collect();
16878 if !rest.is_empty() {
16879 fd = rest.parse().unwrap_or(1);
16880 } else {
16881 i += 1;
16882 if i < args.len() {
16883 fd = args[i].parse().unwrap_or(1);
16884 }
16885 }
16886 break;
16887 }
16888 'C' => {
16889 let rest: String = chars.collect();
16891 if !rest.is_empty() {
16892 columns = rest.parse().unwrap_or(0);
16893 } else {
16894 i += 1;
16895 if i < args.len() {
16896 columns = args[i].parse().unwrap_or(0);
16897 }
16898 }
16899 break;
16900 }
16901 'v' => {
16902 let rest: String = chars.collect();
16904 if !rest.is_empty() {
16905 store_var = Some(rest);
16906 } else {
16907 i += 1;
16908 if i < args.len() {
16909 store_var = Some(args[i].clone());
16910 }
16911 }
16912 break;
16913 }
16914 'f' => {
16915 let rest: String = chars.collect();
16917 if !rest.is_empty() {
16918 format_string = Some(rest);
16919 } else {
16920 i += 1;
16921 if i < args.len() {
16922 format_string = Some(args[i].clone());
16923 }
16924 }
16925 break;
16926 }
16927 _ => {}
16928 }
16929 }
16930 } else {
16931 output_args.push(arg.clone());
16932 }
16933 i += 1;
16934 }
16935
16936 let _ = push_to_stack; let _ = fd; if sort_asc {
16941 output_args.sort();
16942 } else if sort_desc {
16943 output_args.sort_by(|a, b| b.cmp(a));
16944 }
16945
16946 if let Some(fmt) = format_string {
16948 let output = self.printf_format(&fmt, &output_args);
16949 if let Some(var) = store_var {
16950 self.variables.insert(var, output);
16951 } else {
16952 print!("{}", output);
16953 }
16954 return 0;
16955 }
16956
16957 let processed: Vec<String> = output_args
16959 .iter()
16960 .map(|s| {
16961 let mut result = s.clone();
16962 if prompt_expand {
16963 result = self.expand_prompt_string(&result);
16964 }
16965 if interpret_escapes && !raw_mode {
16966 result = self.expand_printf_escapes(&result);
16967 }
16968 if named_dir_subst {
16969 if let Ok(home) = env::var("HOME") {
16971 if result.starts_with(&home) {
16972 result = format!("~{}", &result[home.len()..]);
16973 }
16974 }
16975 for (name, path) in &self.named_dirs {
16977 let path_str = path.to_string_lossy();
16978 if result.starts_with(path_str.as_ref()) {
16979 result = format!("~{}{}", name, &result[path_str.len()..]);
16980 break;
16981 }
16982 }
16983 }
16984 result
16985 })
16986 .collect();
16987
16988 let separator = if one_per_line { "\n" } else { " " };
16990 let terminator = if null_terminate {
16991 "\0"
16992 } else if no_newline {
16993 ""
16994 } else {
16995 "\n"
16996 };
16997
16998 let output = if one_per_line {
17000 processed.join("\n")
17001 } else if columns > 0 {
17002 let mut result = String::new();
17004 let num_items = processed.len();
17005 let rows = (num_items + columns - 1) / columns;
17006 for row in 0..rows {
17007 let mut row_items = Vec::new();
17008 for col in 0..columns {
17009 let idx = row + col * rows;
17010 if idx < num_items {
17011 row_items.push(processed[idx].as_str());
17012 }
17013 }
17014 result.push_str(&row_items.join("\t"));
17015 if row < rows - 1 {
17016 result.push('\n');
17017 }
17018 }
17019 result
17020 } else {
17021 processed.join(separator)
17022 };
17023
17024 if add_to_history {
17026 if let Some(ref mut engine) = self.history {
17027 let _ = engine.add(&output, None);
17028 }
17029 }
17030
17031 if let Some(var) = store_var {
17033 self.variables.insert(var, output);
17034 } else {
17035 print!("{}{}", output, terminator);
17036 }
17037
17038 0
17039 }
17040
17041 fn printf_format(&self, format: &str, args: &[String]) -> String {
17042 let mut result = String::new();
17043 let mut arg_idx = 0;
17044 let mut chars = format.chars().peekable();
17045
17046 while let Some(ch) = chars.next() {
17047 if ch == '%' {
17048 if chars.peek() == Some(&'%') {
17049 chars.next();
17050 result.push('%');
17051 continue;
17052 }
17053
17054 let mut spec = String::from("%");
17056
17057 while let Some(&c) = chars.peek() {
17059 if c == '-' || c == '+' || c == ' ' || c == '#' || c == '0' {
17060 spec.push(c);
17061 chars.next();
17062 } else {
17063 break;
17064 }
17065 }
17066
17067 while let Some(&c) = chars.peek() {
17069 if c.is_ascii_digit() {
17070 spec.push(c);
17071 chars.next();
17072 } else {
17073 break;
17074 }
17075 }
17076
17077 if chars.peek() == Some(&'.') {
17079 spec.push('.');
17080 chars.next();
17081 while let Some(&c) = chars.peek() {
17082 if c.is_ascii_digit() {
17083 spec.push(c);
17084 chars.next();
17085 } else {
17086 break;
17087 }
17088 }
17089 }
17090
17091 if let Some(conv) = chars.next() {
17093 let arg = args.get(arg_idx).map(|s| s.as_str()).unwrap_or("");
17094 arg_idx += 1;
17095
17096 match conv {
17097 's' => result.push_str(arg),
17098 'd' | 'i' => {
17099 let n: i64 = arg.parse().unwrap_or(0);
17100 result.push_str(&n.to_string());
17101 }
17102 'u' => {
17103 let n: u64 = arg.parse().unwrap_or(0);
17104 result.push_str(&n.to_string());
17105 }
17106 'x' => {
17107 let n: i64 = arg.parse().unwrap_or(0);
17108 result.push_str(&format!("{:x}", n));
17109 }
17110 'X' => {
17111 let n: i64 = arg.parse().unwrap_or(0);
17112 result.push_str(&format!("{:X}", n));
17113 }
17114 'o' => {
17115 let n: i64 = arg.parse().unwrap_or(0);
17116 result.push_str(&format!("{:o}", n));
17117 }
17118 'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
17119 let n: f64 = arg.parse().unwrap_or(0.0);
17120 result.push_str(&format!("{}", n));
17121 }
17122 'c' => {
17123 if let Some(c) = arg.chars().next() {
17124 result.push(c);
17125 }
17126 }
17127 'b' => {
17128 result.push_str(&self.expand_printf_escapes(arg));
17129 }
17130 'n' => result.push('\n'),
17131 _ => {
17132 result.push('%');
17133 result.push(conv);
17134 }
17135 }
17136 }
17137 } else {
17138 result.push(ch);
17139 }
17140 }
17141
17142 result
17143 }
17144
17145 fn builtin_whence(&self, args: &[String]) -> i32 {
17147 let mut verbose = false;
17160 let mut csh_style = false;
17161 let mut word_type = false;
17162 let mut skip_functions = false;
17163 let mut path_only = false;
17164 let mut show_all = false;
17165 let mut pattern_mode = false;
17166 let mut show_symlink = false;
17167 let mut show_symlink_steps = false;
17168 let mut tab_expand: Option<usize> = None;
17169 let mut names: Vec<&str> = Vec::new();
17170
17171 let mut i = 0;
17172 while i < args.len() {
17173 let arg = &args[i];
17174
17175 if arg == "--" {
17176 i += 1;
17177 while i < args.len() {
17178 names.push(&args[i]);
17179 i += 1;
17180 }
17181 break;
17182 }
17183
17184 if arg.starts_with('-') && arg.len() > 1 {
17185 let mut chars = arg[1..].chars().peekable();
17186 while let Some(ch) = chars.next() {
17187 match ch {
17188 'v' => verbose = true,
17189 'c' => csh_style = true,
17190 'w' => word_type = true,
17191 'f' => skip_functions = true,
17192 'p' => path_only = true,
17193 'a' => show_all = true,
17194 'm' => pattern_mode = true,
17195 's' => show_symlink = true,
17196 'S' => show_symlink_steps = true,
17197 'x' => {
17198 let rest: String = chars.collect();
17200 if !rest.is_empty() {
17201 tab_expand = rest.parse().ok();
17202 } else {
17203 i += 1;
17204 if i < args.len() {
17205 tab_expand = args[i].parse().ok();
17206 }
17207 }
17208 break;
17209 }
17210 _ => {}
17211 }
17212 }
17213 } else {
17214 names.push(arg);
17215 }
17216 i += 1;
17217 }
17218
17219 let _ = csh_style; let _ = pattern_mode; let _ = tab_expand;
17222
17223 let mut status = 0;
17224 for name in names {
17225 let mut found = false;
17226 let mut word = "none";
17227
17228 if !path_only {
17229 if self.is_reserved_word(name) {
17231 found = true;
17232 word = "reserved";
17233 if word_type {
17234 println!("{}: {}", name, word);
17235 } else if verbose {
17236 println!("{} is a reserved word", name);
17237 } else {
17238 println!("{}", name);
17239 }
17240 if !show_all {
17241 continue;
17242 }
17243 }
17244
17245 if let Some(alias_val) = self.aliases.get(name) {
17247 found = true;
17248 word = "alias";
17249 if word_type {
17250 println!("{}: {}", name, word);
17251 } else if verbose {
17252 println!("{} is an alias for {}", name, alias_val);
17253 } else {
17254 println!("{}", alias_val);
17255 }
17256 if !show_all {
17257 continue;
17258 }
17259 }
17260
17261 if !skip_functions && self.functions.contains_key(name) {
17263 found = true;
17264 word = "function";
17265 if word_type {
17266 println!("{}: {}", name, word);
17267 } else if verbose {
17268 println!("{} is a shell function", name);
17269 } else {
17270 println!("{}", name);
17271 }
17272 if !show_all {
17273 continue;
17274 }
17275 }
17276
17277 if self.is_builtin(name) {
17279 found = true;
17280 word = "builtin";
17281 if word_type {
17282 println!("{}: {}", name, word);
17283 } else if verbose {
17284 println!("{} is a shell builtin", name);
17285 } else {
17286 println!("{}", name);
17287 }
17288 if !show_all {
17289 continue;
17290 }
17291 }
17292
17293 if let Some(path) = self.named_dirs.get(name) {
17296 found = true;
17297 word = "hashed";
17298 if word_type {
17299 println!("{}: {}", name, word);
17300 } else if verbose {
17301 println!("{} is hashed ({})", name, path.display());
17302 } else {
17303 println!("{}", path.display());
17304 }
17305 if !show_all {
17306 continue;
17307 }
17308 }
17309 }
17310
17311 if let Some(path) = self.find_in_path(name) {
17313 found = true;
17314 word = "command";
17315
17316 let display_path = if show_symlink || show_symlink_steps {
17318 let p = std::path::Path::new(&path);
17319 if show_symlink_steps {
17320 let mut current = p.to_path_buf();
17321 let mut steps = vec![path.clone()];
17322 while let Ok(target) = std::fs::read_link(¤t) {
17323 let resolved = if target.is_absolute() {
17324 target.clone()
17325 } else {
17326 current
17327 .parent()
17328 .unwrap_or(std::path::Path::new("/"))
17329 .join(&target)
17330 };
17331 steps.push(resolved.to_string_lossy().to_string());
17332 current = resolved;
17333 }
17334 steps.join(" -> ")
17335 } else {
17336 match p.canonicalize() {
17337 Ok(resolved) => format!("{} -> {}", path, resolved.display()),
17338 Err(_) => path.clone(),
17339 }
17340 }
17341 } else {
17342 path.clone()
17343 };
17344
17345 if word_type {
17346 println!("{}: {}", name, word);
17347 } else if verbose {
17348 println!("{} is {}", name, display_path);
17349 } else {
17350 println!("{}", display_path);
17351 }
17352 }
17353
17354 if !found {
17355 if word_type {
17356 println!("{}: none", name);
17357 } else if verbose {
17358 println!("{} not found", name);
17359 }
17360 status = 1;
17361 }
17362 }
17363 status
17364 }
17365
17366 fn is_reserved_word(&self, name: &str) -> bool {
17367 matches!(
17368 name,
17369 "if" | "then"
17370 | "else"
17371 | "elif"
17372 | "fi"
17373 | "case"
17374 | "esac"
17375 | "for"
17376 | "select"
17377 | "while"
17378 | "until"
17379 | "do"
17380 | "done"
17381 | "in"
17382 | "function"
17383 | "time"
17384 | "coproc"
17385 | "{"
17386 | "}"
17387 | "!"
17388 | "[["
17389 | "]]"
17390 | "(("
17391 | "))"
17392 )
17393 }
17394
17395 fn builtin_where(&self, args: &[String]) -> i32 {
17397 let mut new_args = vec!["-a".to_string(), "-v".to_string()];
17399 new_args.extend(args.iter().cloned());
17400 self.builtin_whence(&new_args)
17401 }
17402
17403 fn builtin_which(&self, args: &[String]) -> i32 {
17405 let mut new_args = vec!["-c".to_string()];
17407 new_args.extend(args.iter().cloned());
17408 self.builtin_whence(&new_args)
17409 }
17410
17411 fn is_builtin(&self, name: &str) -> bool {
17414 BUILTIN_SET.contains(name) || name.starts_with('_')
17415 }
17416
17417 fn find_in_path(&self, name: &str) -> Option<String> {
17419 if let Some(path) = self.command_hash.get(name) {
17421 return Some(path.clone());
17422 }
17423 let path_var = env::var("PATH").unwrap_or_default();
17425 for dir in path_var.split(':') {
17426 let full_path = format!("{}/{}", dir, name);
17427 if std::path::Path::new(&full_path).exists() {
17428 return Some(full_path);
17429 }
17430 }
17431 None
17432 }
17433
17434 fn builtin_ulimit(&self, args: &[String]) -> i32 {
17436 use libc::{getrlimit, rlimit, setrlimit};
17437 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
17438 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
17439
17440 let mut resource = RLIMIT_FSIZE; let mut hard = false;
17442 let mut soft = true;
17443 let mut value: Option<u64> = None;
17444
17445 let mut iter = args.iter();
17446 while let Some(arg) = iter.next() {
17447 match arg.as_str() {
17448 "-H" => {
17449 hard = true;
17450 soft = false;
17451 }
17452 "-S" => {
17453 soft = true;
17454 hard = false;
17455 }
17456 "-a" => {
17457 self.print_all_limits(soft);
17459 return 0;
17460 }
17461 "-c" => resource = RLIMIT_CORE,
17462 "-d" => resource = RLIMIT_DATA,
17463 "-f" => resource = RLIMIT_FSIZE,
17464 "-n" => resource = RLIMIT_NOFILE,
17465 "-s" => resource = RLIMIT_STACK,
17466 "-t" => resource = RLIMIT_CPU,
17467 "-u" => resource = RLIMIT_NPROC,
17468 "-v" => resource = RLIMIT_AS,
17469 "-m" => resource = RLIMIT_RSS,
17470 "unlimited" => value = Some(libc::RLIM_INFINITY as u64),
17471 _ if !arg.starts_with('-') => {
17472 value = arg.parse().ok();
17473 }
17474 _ => {}
17475 }
17476 }
17477
17478 let mut rlim = rlimit {
17479 rlim_cur: 0,
17480 rlim_max: 0,
17481 };
17482 unsafe {
17483 if getrlimit(resource, &mut rlim) != 0 {
17484 eprintln!("ulimit: cannot get limit");
17485 return 1;
17486 }
17487 }
17488
17489 if let Some(v) = value {
17490 if soft {
17492 rlim.rlim_cur = v as libc::rlim_t;
17493 }
17494 if hard {
17495 rlim.rlim_max = v as libc::rlim_t;
17496 }
17497 unsafe {
17498 if setrlimit(resource, &rlim) != 0 {
17499 eprintln!("ulimit: cannot set limit");
17500 return 1;
17501 }
17502 }
17503 } else {
17504 let limit = if hard { rlim.rlim_max } else { rlim.rlim_cur };
17506 if limit == libc::RLIM_INFINITY as libc::rlim_t {
17507 println!("unlimited");
17508 } else {
17509 println!("{}", limit);
17510 }
17511 }
17512 0
17513 }
17514
17515 fn print_all_limits(&self, soft: bool) {
17516 use libc::{getrlimit, rlimit};
17517 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
17518 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
17519
17520 let limits = [
17521 (RLIMIT_CORE, "core file size", "blocks", 512),
17522 (RLIMIT_DATA, "data seg size", "kbytes", 1024),
17523 (RLIMIT_FSIZE, "file size", "blocks", 512),
17524 (RLIMIT_NOFILE, "open files", "", 1),
17525 (RLIMIT_STACK, "stack size", "kbytes", 1024),
17526 (RLIMIT_CPU, "cpu time", "seconds", 1),
17527 (RLIMIT_NPROC, "max user processes", "", 1),
17528 (RLIMIT_AS, "virtual memory", "kbytes", 1024),
17529 (RLIMIT_RSS, "max memory size", "kbytes", 1024),
17530 ];
17531
17532 for (resource, name, unit, divisor) in limits {
17533 let mut rlim = rlimit {
17534 rlim_cur: 0,
17535 rlim_max: 0,
17536 };
17537 unsafe {
17538 if getrlimit(resource, &mut rlim) == 0 {
17539 let limit = if soft { rlim.rlim_cur } else { rlim.rlim_max };
17540 let unit_str = if unit.is_empty() {
17541 ""
17542 } else {
17543 &format!("({})", unit)
17544 };
17545 if limit == libc::RLIM_INFINITY as libc::rlim_t {
17546 println!("{:25} {} unlimited", name, unit_str);
17547 } else {
17548 println!("{:25} {} {}", name, unit_str, limit / divisor);
17549 }
17550 }
17551 }
17552 }
17553 }
17554
17555 fn builtin_limit(&self, args: &[String]) -> i32 {
17557 if args.is_empty() {
17559 use libc::{getrlimit, rlimit, RLIM_INFINITY};
17561 let resources = [
17562 (libc::RLIMIT_CPU, "cputime", 1, "seconds"),
17563 (libc::RLIMIT_FSIZE, "filesize", 1024, "kB"),
17564 (libc::RLIMIT_DATA, "datasize", 1024, "kB"),
17565 (libc::RLIMIT_STACK, "stacksize", 1024, "kB"),
17566 (libc::RLIMIT_CORE, "coredumpsize", 1024, "kB"),
17567 (libc::RLIMIT_RSS, "memoryuse", 1024, "kB"),
17568 #[cfg(target_os = "linux")]
17569 (libc::RLIMIT_NPROC, "maxproc", 1, ""),
17570 (libc::RLIMIT_NOFILE, "descriptors", 1, ""),
17571 ];
17572 for (res, name, divisor, unit) in resources {
17573 let mut rl: rlimit = unsafe { std::mem::zeroed() };
17574 unsafe {
17575 getrlimit(res, &mut rl);
17576 }
17577 let val = if rl.rlim_cur == RLIM_INFINITY as u64 {
17578 "unlimited".to_string()
17579 } else {
17580 let v = rl.rlim_cur as u64 / divisor;
17581 if unit.is_empty() {
17582 format!("{}", v)
17583 } else {
17584 format!("{}{}", v, unit)
17585 }
17586 };
17587 println!("{:<16}{}", name, val);
17588 }
17589 return 0;
17590 }
17591 self.builtin_ulimit(args)
17592 }
17593
17594 fn builtin_unlimit(&self, args: &[String]) -> i32 {
17596 let mut new_args = args.to_vec();
17597 new_args.push("unlimited".to_string());
17598 self.builtin_ulimit(&new_args)
17599 }
17600
17601 fn builtin_umask(&self, args: &[String]) -> i32 {
17603 use libc::umask;
17604
17605 let mut symbolic = false;
17606 let mut value: Option<&str> = None;
17607
17608 for arg in args {
17609 match arg.as_str() {
17610 "-S" => symbolic = true,
17611 _ if !arg.starts_with('-') => value = Some(arg),
17612 _ => {}
17613 }
17614 }
17615
17616 if let Some(v) = value {
17617 if let Ok(mask) = u32::from_str_radix(v, 8) {
17619 unsafe {
17620 umask(mask as libc::mode_t);
17621 }
17622 } else {
17623 eprintln!("umask: invalid mask: {}", v);
17624 return 1;
17625 }
17626 } else {
17627 let mask = unsafe {
17629 let m = umask(0);
17630 umask(m);
17631 m
17632 };
17633 if symbolic {
17634 let u = 7 - ((mask >> 6) & 7);
17635 let g = 7 - ((mask >> 3) & 7);
17636 let o = 7 - (mask & 7);
17637 println!(
17638 "u={}{}{}g={}{}{}o={}{}{}",
17639 if u & 4 != 0 { "r" } else { "" },
17640 if u & 2 != 0 { "w" } else { "" },
17641 if u & 1 != 0 { "x" } else { "" },
17642 if g & 4 != 0 { "r" } else { "" },
17643 if g & 2 != 0 { "w" } else { "" },
17644 if g & 1 != 0 { "x" } else { "" },
17645 if o & 4 != 0 { "r" } else { "" },
17646 if o & 2 != 0 { "w" } else { "" },
17647 if o & 1 != 0 { "x" } else { "" },
17648 );
17649 } else {
17650 println!("{:04o}", mask);
17651 }
17652 }
17653 0
17654 }
17655
17656 fn builtin_rehash(&mut self, args: &[String]) -> i32 {
17658 let mut rehash_dirs = false;
17664 let mut force = false;
17665 let mut verbose = false;
17666
17667 for arg in args {
17668 if arg.starts_with('-') {
17669 for ch in arg[1..].chars() {
17670 match ch {
17671 'd' => rehash_dirs = true,
17672 'f' => force = true,
17673 'v' => verbose = true,
17674 _ => {}
17675 }
17676 }
17677 }
17678 }
17679
17680 if rehash_dirs {
17681 self.named_dirs.clear();
17684 if let Ok(home) = env::var("HOME") {
17685 self.named_dirs.insert(String::new(), PathBuf::from(&home)); }
17687 return 0;
17688 }
17689
17690 self.command_hash.clear();
17692
17693 if force {
17694 if let Ok(path_var) = env::var("PATH") {
17697 let dirs: Vec<String> = path_var
17698 .split(':')
17699 .filter(|s| !s.is_empty())
17700 .map(|s| s.to_string())
17701 .collect();
17702
17703 let (tx, rx) = std::sync::mpsc::channel::<Vec<(String, String)>>();
17704
17705 for dir in dirs {
17706 let tx = tx.clone();
17707 self.worker_pool.submit(move || {
17708 let mut batch = Vec::new();
17709 if let Ok(entries) = std::fs::read_dir(&dir) {
17710 for entry in entries.flatten() {
17711 if let Ok(ft) = entry.file_type() {
17712 if ft.is_file() || ft.is_symlink() {
17713 if let Some(name) = entry.file_name().to_str() {
17714 let path = entry.path().to_string_lossy().to_string();
17715 batch.push((name.to_string(), path));
17716 }
17717 }
17718 }
17719 }
17720 }
17721 let _ = tx.send(batch);
17722 });
17723 }
17724 drop(tx);
17725
17726 for batch in rx {
17727 for (name, path) in batch {
17728 if verbose {
17729 println!("{}={}", name, path);
17730 }
17731 self.command_hash.insert(name, path);
17732 }
17733 }
17734 }
17735 }
17736
17737 0
17738 }
17739
17740 fn builtin_unhash(&mut self, args: &[String]) -> i32 {
17742 let mut remove_aliases = false;
17743 let mut remove_functions = false;
17744 let mut remove_dirs = false;
17745 let mut names: Vec<&str> = Vec::new();
17746
17747 for arg in args {
17748 match arg.as_str() {
17749 "-a" => remove_aliases = true,
17750 "-f" => remove_functions = true,
17751 "-d" => remove_dirs = true,
17752 "-m" => {} _ if arg.starts_with('-') => {}
17754 _ => names.push(arg),
17755 }
17756 }
17757
17758 for name in names {
17759 if remove_aliases {
17760 self.aliases.remove(name);
17761 }
17762 if remove_functions {
17763 self.functions.remove(name);
17764 }
17765 if remove_dirs {
17766 }
17768 }
17769 0
17770 }
17771
17772 fn builtin_times(&self, _args: &[String]) -> i32 {
17774 use libc::{getrusage, rusage, RUSAGE_CHILDREN, RUSAGE_SELF};
17775
17776 let mut self_usage: rusage = unsafe { std::mem::zeroed() };
17777 let mut child_usage: rusage = unsafe { std::mem::zeroed() };
17778
17779 unsafe {
17780 getrusage(RUSAGE_SELF, &mut self_usage);
17781 getrusage(RUSAGE_CHILDREN, &mut child_usage);
17782 }
17783
17784 let self_user =
17785 self_usage.ru_utime.tv_sec as f64 + self_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
17786 let self_sys =
17787 self_usage.ru_stime.tv_sec as f64 + self_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
17788 let child_user =
17789 child_usage.ru_utime.tv_sec as f64 + child_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
17790 let child_sys =
17791 child_usage.ru_stime.tv_sec as f64 + child_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
17792
17793 println!("{:.3}s {:.3}s", self_user, self_sys);
17794 println!("{:.3}s {:.3}s", child_user, child_sys);
17795 0
17796 }
17797
17798 fn builtin_zmodload(&mut self, args: &[String]) -> i32 {
17800 let mut list_loaded = false;
17801 let mut unload = false;
17802 let mut modules: Vec<&str> = Vec::new();
17803
17804 for arg in args {
17805 match arg.as_str() {
17806 "-l" | "-L" => list_loaded = true,
17807 "-u" => unload = true,
17808 "-a" | "-b" | "-c" | "-d" | "-e" | "-f" | "-i" | "-p" | "-s" => {}
17809 _ if arg.starts_with('-') => {}
17810 _ => modules.push(arg),
17811 }
17812 }
17813
17814 if list_loaded || modules.is_empty() {
17815 println!("zsh/complete");
17817 println!("zsh/complist");
17818 println!("zsh/parameter");
17819 println!("zsh/zutil");
17820 return 0;
17821 }
17822
17823 for module in modules {
17824 if unload {
17825 self.options.remove(&format!("_module_{}", module));
17827 } else {
17828 self.options.insert(format!("_module_{}", module), true);
17830 }
17831 }
17832 0
17833 }
17834
17835 fn builtin_r(&mut self, args: &[String]) -> i32 {
17837 let mut fc_args = vec!["-e".to_string(), "-".to_string()];
17838 fc_args.extend(args.iter().cloned());
17839 self.builtin_fc(&fc_args)
17840 }
17841
17842 fn builtin_ttyctl(&self, args: &[String]) -> i32 {
17844 for arg in args {
17845 match arg.as_str() {
17846 "-f" => {
17847 }
17850 "-u" => {
17851 }
17853 _ => {}
17854 }
17855 }
17856 0
17857 }
17858
17859 fn builtin_noglob(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
17861 if args.is_empty() {
17862 return 0;
17863 }
17864
17865 let saved = self.options.get("noglob").cloned();
17867 self.options.insert("noglob".to_string(), true);
17868
17869 let status = self.builtin_command(args, redirects);
17871
17872 if let Some(v) = saved {
17874 self.options.insert("noglob".to_string(), v);
17875 } else {
17876 self.options.remove("noglob");
17877 }
17878
17879 status
17880 }
17881
17882 fn builtin_zstat(&self, args: &[String]) -> i32 {
17888 use std::os::unix::fs::MetadataExt;
17889 use std::os::unix::fs::PermissionsExt;
17890
17891 let mut show_all = true;
17892 let mut symbolic_mode = false;
17893 let mut show_link = false;
17894 let mut _as_array = false;
17895 let mut _array_name = String::new();
17896 let mut format_time = String::new();
17897 let mut elements: Vec<String> = Vec::new();
17898 let mut files: Vec<&str> = Vec::new();
17899
17900 let mut iter = args.iter().peekable();
17901 while let Some(arg) = iter.next() {
17902 match arg.as_str() {
17903 "-s" => symbolic_mode = true,
17904 "-L" => show_link = true,
17905 "-N" => {} "-n" => {} "-o" => show_all = false,
17908 "-A" => {
17909 _as_array = true;
17910 if let Some(name) = iter.next() {
17911 _array_name = name.clone();
17912 }
17913 }
17914 "-F" => {
17915 if let Some(fmt) = iter.next() {
17916 format_time = fmt.clone();
17917 }
17918 }
17919 s if s.starts_with('+') => {
17920 elements.push(s[1..].to_string());
17921 show_all = false;
17922 }
17923 s if !s.starts_with('-') => files.push(s),
17924 _ => {}
17925 }
17926 }
17927
17928 if files.is_empty() {
17929 eprintln!("zstat: no files specified");
17930 return 1;
17931 }
17932
17933 for file in files {
17934 let meta = if show_link {
17935 std::fs::symlink_metadata(file)
17936 } else {
17937 std::fs::metadata(file)
17938 };
17939
17940 let meta = match meta {
17941 Ok(m) => m,
17942 Err(e) => {
17943 eprintln!("zstat: {}: {}", file, e);
17944 return 1;
17945 }
17946 };
17947
17948 let output_element = |name: &str, value: &str| {
17949 if _as_array {
17950 println!("{}={}", name, value);
17952 } else if show_all || elements.contains(&name.to_string()) {
17953 println!("{}: {}", name, value);
17954 }
17955 };
17956
17957 output_element("device", &meta.dev().to_string());
17958 output_element("inode", &meta.ino().to_string());
17959
17960 if symbolic_mode {
17961 let mode = meta.permissions().mode();
17962 let mode_str = format!(
17963 "{}{}{}{}{}{}{}{}{}{}",
17964 match mode & 0o170000 {
17965 0o040000 => 'd',
17966 0o120000 => 'l',
17967 0o100000 => '-',
17968 0o060000 => 'b',
17969 0o020000 => 'c',
17970 0o010000 => 'p',
17971 0o140000 => 's',
17972 _ => '?',
17973 },
17974 if mode & 0o400 != 0 { 'r' } else { '-' },
17975 if mode & 0o200 != 0 { 'w' } else { '-' },
17976 if mode & 0o4000 != 0 {
17977 's'
17978 } else if mode & 0o100 != 0 {
17979 'x'
17980 } else {
17981 '-'
17982 },
17983 if mode & 0o040 != 0 { 'r' } else { '-' },
17984 if mode & 0o020 != 0 { 'w' } else { '-' },
17985 if mode & 0o2000 != 0 {
17986 's'
17987 } else if mode & 0o010 != 0 {
17988 'x'
17989 } else {
17990 '-'
17991 },
17992 if mode & 0o004 != 0 { 'r' } else { '-' },
17993 if mode & 0o002 != 0 { 'w' } else { '-' },
17994 if mode & 0o1000 != 0 {
17995 't'
17996 } else if mode & 0o001 != 0 {
17997 'x'
17998 } else {
17999 '-'
18000 },
18001 );
18002 output_element("mode", &mode_str);
18003 } else {
18004 output_element("mode", &format!("{:o}", meta.permissions().mode()));
18005 }
18006
18007 output_element("nlink", &meta.nlink().to_string());
18008 output_element("uid", &meta.uid().to_string());
18009 output_element("gid", &meta.gid().to_string());
18010 output_element("rdev", &meta.rdev().to_string());
18011 output_element("size", &meta.len().to_string());
18012
18013 let format_timestamp = |secs: i64| -> String {
18014 if format_time.is_empty() {
18015 secs.to_string()
18016 } else {
18017 chrono::DateTime::from_timestamp(secs, 0)
18018 .map(|dt| dt.format(&format_time).to_string())
18019 .unwrap_or_else(|| secs.to_string())
18020 }
18021 };
18022
18023 output_element("atime", &format_timestamp(meta.atime()));
18024 output_element("mtime", &format_timestamp(meta.mtime()));
18025 output_element("ctime", &format_timestamp(meta.ctime()));
18026 output_element("blksize", &meta.blksize().to_string());
18027 output_element("blocks", &meta.blocks().to_string());
18028
18029 if show_link && meta.file_type().is_symlink() {
18030 if let Ok(target) = std::fs::read_link(file) {
18031 output_element("link", &target.to_string_lossy());
18032 }
18033 }
18034 }
18035
18036 0
18037 }
18038
18039 fn builtin_strftime(&self, args: &[String]) -> i32 {
18041 let mut format = "%c".to_string();
18042 let mut timestamp: Option<i64> = None;
18043 let mut to_var = false;
18044 let mut var_name = String::new();
18045
18046 let mut iter = args.iter();
18047 while let Some(arg) = iter.next() {
18048 match arg.as_str() {
18049 "-s" => {
18050 to_var = true;
18051 if let Some(name) = iter.next() {
18052 var_name = name.clone();
18053 }
18054 }
18055 "-r" => {
18056 if let Some(ts_str) = iter.next() {
18058 timestamp = ts_str.parse().ok();
18059 }
18060 }
18061 s if !s.starts_with('-') => {
18062 if format == "%c" {
18063 format = s.to_string();
18064 } else if timestamp.is_none() {
18065 timestamp = s.parse().ok();
18066 }
18067 }
18068 _ => {}
18069 }
18070 }
18071
18072 let ts = timestamp.unwrap_or_else(|| chrono::Local::now().timestamp());
18073
18074 let result = chrono::DateTime::from_timestamp(ts, 0)
18075 .map(|dt: chrono::DateTime<chrono::Utc>| {
18076 dt.with_timezone(&chrono::Local).format(&format).to_string()
18077 })
18078 .unwrap_or_else(|| "invalid timestamp".to_string());
18079
18080 if to_var && !var_name.is_empty() {
18081 println!("{}={}", var_name, result);
18083 } else {
18084 println!("{}", result);
18085 }
18086
18087 0
18088 }
18089
18090 fn builtin_zsleep(&self, args: &[String]) -> i32 {
18092 if args.is_empty() {
18093 eprintln!("zsleep: missing argument");
18094 return 1;
18095 }
18096
18097 let secs: f64 = match args[0].parse() {
18098 Ok(s) => s,
18099 Err(_) => {
18100 eprintln!("zsleep: invalid number: {}", args[0]);
18101 return 1;
18102 }
18103 };
18104
18105 std::thread::sleep(std::time::Duration::from_secs_f64(secs));
18106 0
18107 }
18108
18109 fn builtin_zsystem(&mut self, args: &[String]) -> i32 {
18112 if args.is_empty() {
18113 eprintln!("zsystem: subcommand expected");
18114 return 1;
18115 }
18116 match args[0].as_str() {
18117 "flock" => self.builtin_zsystem_flock(&args[1..]),
18118 "supports" => self.builtin_zsystem_supports(&args[1..]),
18119 _ => {
18120 eprintln!("zsystem: unknown subcommand: {}", args[0]);
18121 1
18122 }
18123 }
18124 }
18125
18126 fn builtin_zsystem_supports(&self, args: &[String]) -> i32 {
18128 if args.is_empty() {
18129 eprintln!("zsystem: supports: not enough arguments");
18130 return 255;
18131 }
18132 if args.len() > 1 {
18133 eprintln!("zsystem: supports: too many arguments");
18134 return 255;
18135 }
18136 match args[0].as_str() {
18137 "supports" | "flock" => 0,
18138 _ => 1,
18139 }
18140 }
18141
18142 fn builtin_zsystem_flock(&mut self, args: &[String]) -> i32 {
18144 #[cfg(unix)]
18145 {
18146 use std::os::unix::io::AsRawFd;
18147
18148 let mut cloexec = true;
18149 let mut readlock = false;
18150 let mut timeout: Option<f64> = None;
18151 let mut fdvar: Option<String> = None;
18152 let mut file: Option<&str> = None;
18153
18154 let mut i = 0;
18155 while i < args.len() {
18156 let arg = &args[i];
18157 if arg == "--" {
18158 i += 1;
18159 if i < args.len() {
18160 file = Some(&args[i]);
18161 }
18162 break;
18163 }
18164 if !arg.starts_with('-') {
18165 file = Some(arg);
18166 break;
18167 }
18168 let mut chars = arg[1..].chars().peekable();
18169 while let Some(c) = chars.next() {
18170 match c {
18171 'e' => cloexec = false,
18172 'r' => readlock = true,
18173 'u' => return 0,
18174 'f' => {
18175 let rest: String = chars.collect();
18176 if !rest.is_empty() {
18177 fdvar = Some(rest);
18178 } else {
18179 i += 1;
18180 if i < args.len() {
18181 fdvar = Some(args[i].clone());
18182 } else {
18183 eprintln!("zsystem: flock: option f requires a variable name");
18184 return 1;
18185 }
18186 }
18187 break;
18188 }
18189 't' => {
18190 let rest: String = chars.collect();
18191 let val = if !rest.is_empty() {
18192 rest
18193 } else {
18194 i += 1;
18195 if i < args.len() {
18196 args[i].clone()
18197 } else {
18198 eprintln!(
18199 "zsystem: flock: option t requires a numeric timeout"
18200 );
18201 return 1;
18202 }
18203 };
18204 match val.parse::<f64>() {
18205 Ok(t) => timeout = Some(t),
18206 Err(_) => {
18207 eprintln!("zsystem: flock: invalid timeout value: '{}'", val);
18208 return 1;
18209 }
18210 }
18211 break;
18212 }
18213 'i' => {
18214 let rest: String = chars.collect();
18215 if rest.is_empty() {
18216 i += 1;
18217 if i >= args.len() {
18218 eprintln!("zsystem: flock: option i requires a numeric retry interval");
18219 return 1;
18220 }
18221 }
18222 break;
18223 }
18224 _ => {
18225 eprintln!("zsystem: flock: unknown option: -{}", c);
18226 return 1;
18227 }
18228 }
18229 }
18230 i += 1;
18231 }
18232
18233 let filepath = match file {
18234 Some(f) => f,
18235 None => {
18236 eprintln!("zsystem: flock: not enough arguments");
18237 return 1;
18238 }
18239 };
18240
18241 use std::fs::OpenOptions;
18242 let file_handle = match OpenOptions::new()
18243 .read(true)
18244 .write(!readlock)
18245 .create(true)
18246 .truncate(false)
18247 .open(filepath)
18248 {
18249 Ok(f) => f,
18250 Err(e) => {
18251 eprintln!("zsystem: flock: {}: {}", filepath, e);
18252 return 1;
18253 }
18254 };
18255
18256 let lock_type = if readlock {
18257 libc::F_RDLCK as i16
18258 } else {
18259 libc::F_WRLCK as i16
18260 };
18261
18262 let mut flock = libc::flock {
18263 l_type: lock_type,
18264 l_whence: libc::SEEK_SET as i16,
18265 l_start: 0,
18266 l_len: 0,
18267 l_pid: 0,
18268 };
18269
18270 let cmd = if timeout.is_some() {
18271 libc::F_SETLK
18272 } else {
18273 libc::F_SETLKW
18274 };
18275 let start = std::time::Instant::now();
18276 let timeout_duration = timeout.map(|t| std::time::Duration::from_secs_f64(t));
18277
18278 loop {
18279 let ret = unsafe { libc::fcntl(file_handle.as_raw_fd(), cmd, &mut flock) };
18280 if ret == 0 {
18281 if let Some(ref var) = fdvar {
18282 let fd = file_handle.as_raw_fd();
18283 std::mem::forget(file_handle);
18284 self.variables.insert(var.clone(), fd.to_string());
18285 } else {
18286 std::mem::forget(file_handle);
18287 }
18288 let _ = cloexec;
18289 return 0;
18290 }
18291 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
18292 if errno != libc::EACCES && errno != libc::EAGAIN {
18293 eprintln!(
18294 "zsystem: flock: {}: {}",
18295 filepath,
18296 std::io::Error::last_os_error()
18297 );
18298 return 1;
18299 }
18300 if let Some(td) = timeout_duration {
18301 if start.elapsed() >= td {
18302 return 2;
18303 }
18304 std::thread::sleep(std::time::Duration::from_millis(100));
18305 } else {
18306 eprintln!(
18307 "zsystem: flock: {}: {}",
18308 filepath,
18309 std::io::Error::last_os_error()
18310 );
18311 return 1;
18312 }
18313 }
18314 }
18315 #[cfg(not(unix))]
18316 {
18317 eprintln!("zsystem: flock: not supported on this platform");
18318 1
18319 }
18320 }
18321
18322 fn builtin_sync(&self, _args: &[String]) -> i32 {
18325 #[cfg(unix)]
18326 unsafe {
18327 libc::sync();
18328 }
18329 0
18330 }
18331
18332 fn builtin_mkdir(&self, args: &[String]) -> i32 {
18335 let mut mode: u32 = 0o777;
18336 let mut parents = false;
18337 let mut dirs: Vec<&str> = Vec::new();
18338
18339 let mut i = 0;
18340 while i < args.len() {
18341 let arg = &args[i];
18342 if arg == "-p" {
18343 parents = true;
18344 } else if arg == "-m" && i + 1 < args.len() {
18345 i += 1;
18346 mode = u32::from_str_radix(&args[i], 8).unwrap_or(0o777);
18347 } else if arg.starts_with("-m") {
18348 mode = u32::from_str_radix(&arg[2..], 8).unwrap_or(0o777);
18349 } else if !arg.starts_with('-') || arg == "-" || arg == "--" {
18350 if arg == "--" {
18351 dirs.extend(args[i + 1..].iter().map(|s| s.as_str()));
18352 break;
18353 }
18354 dirs.push(arg);
18355 }
18356 i += 1;
18357 }
18358
18359 let mut err = 0;
18360 for dir in dirs {
18361 let path = std::path::Path::new(dir);
18362 let result = if parents {
18363 std::fs::create_dir_all(path)
18364 } else {
18365 std::fs::create_dir(path)
18366 };
18367 if let Err(e) = result {
18368 eprintln!("mkdir: cannot create directory '{}': {}", dir, e);
18369 err = 1;
18370 } else {
18371 #[cfg(unix)]
18372 {
18373 use std::os::unix::fs::PermissionsExt;
18374 let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode));
18375 }
18376 }
18377 }
18378 err
18379 }
18380
18381 fn builtin_rmdir(&self, args: &[String]) -> i32 {
18384 let mut err = 0;
18385 for arg in args {
18386 if arg.starts_with('-') {
18387 continue;
18388 }
18389 if let Err(e) = std::fs::remove_dir(arg) {
18390 eprintln!("rmdir: cannot remove '{}': {}", arg, e);
18391 err = 1;
18392 }
18393 }
18394 err
18395 }
18396
18397 fn builtin_ln(&self, args: &[String]) -> i32 {
18400 let mut symbolic = false;
18401 let mut force = false;
18402 let mut no_deref = false;
18403 let mut files: Vec<&str> = Vec::new();
18404
18405 for arg in args {
18406 match arg.as_str() {
18407 "-s" => symbolic = true,
18408 "-f" => force = true,
18409 "-n" | "-h" => no_deref = true,
18410 s if !s.starts_with('-') => files.push(s),
18411 _ => {}
18412 }
18413 }
18414
18415 if files.len() < 2 {
18416 if files.len() == 1 {
18417 let src = files[0];
18418 let target = std::path::Path::new(src)
18419 .file_name()
18420 .map(|n| n.to_string_lossy().to_string())
18421 .unwrap_or_else(|| src.to_string());
18422 files.push(Box::leak(target.into_boxed_str()));
18423 } else {
18424 eprintln!("ln: missing file operand");
18425 return 1;
18426 }
18427 }
18428
18429 let target = files.pop().unwrap();
18430 let target_path = std::path::Path::new(target);
18431 let is_dir = !no_deref && target_path.is_dir();
18432
18433 for src in files {
18434 let dest = if is_dir {
18435 format!(
18436 "{}/{}",
18437 target,
18438 std::path::Path::new(src)
18439 .file_name()
18440 .map(|n| n.to_string_lossy().to_string())
18441 .unwrap_or_else(|| src.to_string())
18442 )
18443 } else {
18444 target.to_string()
18445 };
18446
18447 let dest_path = std::path::Path::new(&dest);
18448 if force && dest_path.exists() {
18449 let _ = std::fs::remove_file(&dest);
18450 }
18451
18452 let result = if symbolic {
18453 #[cfg(unix)]
18454 {
18455 std::os::unix::fs::symlink(src, &dest)
18456 }
18457 #[cfg(not(unix))]
18458 {
18459 Err(std::io::Error::new(
18460 std::io::ErrorKind::Unsupported,
18461 "symlinks not supported",
18462 ))
18463 }
18464 } else {
18465 std::fs::hard_link(src, &dest)
18466 };
18467
18468 if let Err(e) = result {
18469 eprintln!("ln: cannot create link '{}' -> '{}': {}", dest, src, e);
18470 return 1;
18471 }
18472 }
18473 0
18474 }
18475
18476 fn builtin_mv(&self, args: &[String]) -> i32 {
18479 let mut force = false;
18480 let mut interactive = false;
18481 let mut verbose = false;
18482 let mut files: Vec<&str> = Vec::new();
18483
18484 for arg in args {
18485 match arg.as_str() {
18486 "-f" => force = true,
18487 "-i" => interactive = true,
18488 "-v" => verbose = true,
18489 s if !s.starts_with('-') => files.push(s),
18490 _ => {}
18491 }
18492 }
18493
18494 if files.len() < 2 {
18495 eprintln!("mv: missing file operand");
18496 return 1;
18497 }
18498
18499 let target = files.pop().unwrap();
18500 let target_path = std::path::Path::new(target);
18501 let is_dir = target_path.is_dir();
18502
18503 for src in files {
18504 let dest = if is_dir {
18505 format!(
18506 "{}/{}",
18507 target,
18508 std::path::Path::new(src)
18509 .file_name()
18510 .map(|n| n.to_string_lossy().to_string())
18511 .unwrap_or_else(|| src.to_string())
18512 )
18513 } else {
18514 target.to_string()
18515 };
18516
18517 let dest_path = std::path::Path::new(&dest);
18518 if dest_path.exists() && !force {
18519 if interactive {
18520 eprint!("mv: overwrite '{}'? ", dest);
18521 let mut response = String::new();
18522 if std::io::stdin().read_line(&mut response).is_err()
18523 || !response.trim().eq_ignore_ascii_case("y")
18524 {
18525 continue;
18526 }
18527 } else {
18528 eprintln!("mv: cannot overwrite '{}': File exists", dest);
18529 return 1;
18530 }
18531 }
18532
18533 if let Err(e) = std::fs::rename(src, &dest) {
18534 eprintln!("mv: cannot move '{}' to '{}': {}", src, dest, e);
18535 return 1;
18536 }
18537
18538 if verbose {
18539 println!("'{}' -> '{}'", src, dest);
18540 }
18541 }
18542 0
18543 }
18544
18545 fn builtin_cp(&self, args: &[String]) -> i32 {
18548 let mut recursive = false;
18549 let mut force = false;
18550 let mut interactive = false;
18551 let mut preserve = false;
18552 let mut verbose = false;
18553 let mut files: Vec<&str> = Vec::new();
18554
18555 for arg in args {
18556 match arg.as_str() {
18557 "-r" | "-R" => recursive = true,
18558 "-f" => force = true,
18559 "-i" => interactive = true,
18560 "-p" => preserve = true,
18561 "-v" => verbose = true,
18562 s if !s.starts_with('-') => files.push(s),
18563 _ => {}
18564 }
18565 }
18566
18567 let _ = preserve; if files.len() < 2 {
18570 eprintln!("cp: missing file operand");
18571 return 1;
18572 }
18573
18574 let target = files.pop().unwrap();
18575 let target_path = std::path::Path::new(target);
18576 let is_dir = target_path.is_dir();
18577
18578 for src in files {
18579 let src_path = std::path::Path::new(src);
18580 let dest = if is_dir {
18581 format!(
18582 "{}/{}",
18583 target,
18584 src_path
18585 .file_name()
18586 .map(|n| n.to_string_lossy().to_string())
18587 .unwrap_or_else(|| src.to_string())
18588 )
18589 } else {
18590 target.to_string()
18591 };
18592
18593 let dest_path = std::path::Path::new(&dest);
18594 if dest_path.exists() && !force {
18595 if interactive {
18596 eprint!("cp: overwrite '{}'? ", dest);
18597 let mut response = String::new();
18598 if std::io::stdin().read_line(&mut response).is_err()
18599 || !response.trim().eq_ignore_ascii_case("y")
18600 {
18601 continue;
18602 }
18603 }
18604 }
18605
18606 let result = if src_path.is_dir() {
18607 if recursive {
18608 Self::copy_dir_recursive(src_path, dest_path)
18609 } else {
18610 eprintln!("cp: -r not specified; omitting directory '{}'", src);
18611 continue;
18612 }
18613 } else {
18614 std::fs::copy(src, &dest).map(|_| ())
18615 };
18616
18617 if let Err(e) = result {
18618 eprintln!("cp: cannot copy '{}' to '{}': {}", src, dest, e);
18619 return 1;
18620 }
18621
18622 if verbose {
18623 println!("'{}' -> '{}'", src, dest);
18624 }
18625 }
18626 0
18627 }
18628
18629 fn copy_dir_recursive(src: &std::path::Path, dest: &std::path::Path) -> std::io::Result<()> {
18630 if !dest.exists() {
18631 std::fs::create_dir_all(dest)?;
18632 }
18633 for entry in std::fs::read_dir(src)? {
18634 let entry = entry?;
18635 let file_type = entry.file_type()?;
18636 let src_path = entry.path();
18637 let dest_path = dest.join(entry.file_name());
18638
18639 if file_type.is_dir() {
18640 Self::copy_dir_recursive(&src_path, &dest_path)?;
18641 } else {
18642 std::fs::copy(&src_path, &dest_path)?;
18643 }
18644 }
18645 Ok(())
18646 }
18647
18648 fn builtin_rm(&self, args: &[String]) -> i32 {
18650 let mut recursive = false;
18651 let mut force = false;
18652 let mut interactive = false;
18653 let mut verbose = false;
18654 let mut files: Vec<&str> = Vec::new();
18655
18656 for arg in args {
18657 match arg.as_str() {
18658 "-r" | "-R" => recursive = true,
18659 "-f" => force = true,
18660 "-i" => interactive = true,
18661 "-v" => verbose = true,
18662 "-rf" | "-fr" => {
18663 recursive = true;
18664 force = true;
18665 }
18666 s if !s.starts_with('-') => files.push(s),
18667 _ => {}
18668 }
18669 }
18670
18671 for file in files {
18672 let path = std::path::Path::new(file);
18673
18674 if !path.exists() {
18675 if !force {
18676 eprintln!("rm: cannot remove '{}': No such file or directory", file);
18677 return 1;
18678 }
18679 continue;
18680 }
18681
18682 if interactive {
18683 let file_type = if path.is_dir() { "directory" } else { "file" };
18684 eprint!("rm: remove {} '{}'? ", file_type, file);
18685 let mut response = String::new();
18686 if std::io::stdin().read_line(&mut response).is_err()
18687 || !response.trim().eq_ignore_ascii_case("y")
18688 {
18689 continue;
18690 }
18691 }
18692
18693 let result = if path.is_dir() {
18694 if recursive {
18695 std::fs::remove_dir_all(path)
18696 } else {
18697 eprintln!("rm: cannot remove '{}': Is a directory", file);
18698 return 1;
18699 }
18700 } else {
18701 std::fs::remove_file(path)
18702 };
18703
18704 if let Err(e) = result {
18705 if !force {
18706 eprintln!("rm: cannot remove '{}': {}", file, e);
18707 return 1;
18708 }
18709 } else if verbose {
18710 println!("removed '{}'", file);
18711 }
18712 }
18713 0
18714 }
18715
18716 #[cfg(unix)]
18718 fn builtin_chown(&self, args: &[String]) -> i32 {
18719 use std::os::unix::fs::MetadataExt;
18720
18721 let mut recursive = false;
18722 let mut positional: Vec<&str> = Vec::new();
18723
18724 for arg in args {
18725 match arg.as_str() {
18726 "-R" => recursive = true,
18727 "-h" => {} s if !s.starts_with('-') => positional.push(s),
18729 _ => {}
18730 }
18731 }
18732
18733 if positional.len() < 2 {
18734 eprintln!("chown: missing operand");
18735 return 1;
18736 }
18737
18738 let owner_spec = positional[0];
18739 let files = &positional[1..];
18740
18741 let (user, group) = if let Some(colon_pos) = owner_spec.find(':') {
18743 (&owner_spec[..colon_pos], Some(&owner_spec[colon_pos + 1..]))
18744 } else {
18745 (owner_spec, None)
18746 };
18747
18748 let uid: u32 = if user.is_empty() {
18749 u32::MAX
18750 } else if let Ok(id) = user.parse() {
18751 id
18752 } else {
18753 unsafe {
18755 let c_user = std::ffi::CString::new(user).unwrap();
18756 let pw = libc::getpwnam(c_user.as_ptr());
18757 if pw.is_null() {
18758 eprintln!("chown: invalid user: '{}'", user);
18759 return 1;
18760 }
18761 (*pw).pw_uid
18762 }
18763 };
18764
18765 let gid: u32 = match group {
18766 Some(g) if !g.is_empty() => {
18767 if let Ok(id) = g.parse() {
18768 id
18769 } else {
18770 unsafe {
18771 let c_group = std::ffi::CString::new(g).unwrap();
18772 let gr = libc::getgrnam(c_group.as_ptr());
18773 if gr.is_null() {
18774 eprintln!("chown: invalid group: '{}'", g);
18775 return 1;
18776 }
18777 (*gr).gr_gid
18778 }
18779 }
18780 }
18781 _ => u32::MAX,
18782 };
18783
18784 fn do_chown(path: &std::path::Path, uid: u32, gid: u32, recursive: bool) -> i32 {
18785 let c_path = match std::ffi::CString::new(path.to_string_lossy().as_bytes()) {
18786 Ok(p) => p,
18787 Err(_) => return 1,
18788 };
18789
18790 let ret = unsafe { libc::chown(c_path.as_ptr(), uid, gid) };
18791 if ret != 0 {
18792 eprintln!(
18793 "chown: changing ownership of '{}': {}",
18794 path.display(),
18795 std::io::Error::last_os_error()
18796 );
18797 return 1;
18798 }
18799
18800 if recursive && path.is_dir() {
18801 if let Ok(entries) = std::fs::read_dir(path) {
18802 for entry in entries.flatten() {
18803 if do_chown(&entry.path(), uid, gid, true) != 0 {
18804 return 1;
18805 }
18806 }
18807 }
18808 }
18809 0
18810 }
18811
18812 for file in files {
18813 if do_chown(std::path::Path::new(file), uid, gid, recursive) != 0 {
18814 return 1;
18815 }
18816 }
18817 0
18818 }
18819
18820 #[cfg(not(unix))]
18821 fn builtin_chown(&self, _args: &[String]) -> i32 {
18822 eprintln!("chown: not supported on this platform");
18823 1
18824 }
18825
18826 fn builtin_chmod(&self, args: &[String]) -> i32 {
18828 let mut recursive = false;
18829 let mut positional: Vec<&str> = Vec::new();
18830
18831 for arg in args {
18832 match arg.as_str() {
18833 "-R" => recursive = true,
18834 s if !s.starts_with('-') => positional.push(s),
18835 _ => {}
18836 }
18837 }
18838
18839 if positional.len() < 2 {
18840 eprintln!("chmod: missing operand");
18841 return 1;
18842 }
18843
18844 let mode_spec = positional[0];
18845 let files = &positional[1..];
18846
18847 let mode: Option<u32> = u32::from_str_radix(mode_spec, 8).ok();
18849
18850 if mode.is_none() {
18851 eprintln!("chmod: symbolic mode not implemented, use octal");
18853 return 1;
18854 }
18855
18856 let mode = mode.unwrap();
18857
18858 fn do_chmod(path: &std::path::Path, mode: u32, recursive: bool) -> i32 {
18859 #[cfg(unix)]
18860 {
18861 use std::os::unix::fs::PermissionsExt;
18862 if let Err(e) =
18863 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
18864 {
18865 eprintln!("chmod: changing permissions of '{}': {}", path.display(), e);
18866 return 1;
18867 }
18868
18869 if recursive && path.is_dir() {
18870 if let Ok(entries) = std::fs::read_dir(path) {
18871 for entry in entries.flatten() {
18872 if do_chmod(&entry.path(), mode, true) != 0 {
18873 return 1;
18874 }
18875 }
18876 }
18877 }
18878 }
18879 #[cfg(not(unix))]
18880 {
18881 let _ = (path, mode, recursive);
18882 }
18883 0
18884 }
18885
18886 for file in files {
18887 if do_chmod(std::path::Path::new(file), mode, recursive) != 0 {
18888 return 1;
18889 }
18890 }
18891 0
18892 }
18893
18894 fn builtin_zfiles(&self, cmd: &str, args: &[String]) -> i32 {
18896 let mut force = false;
18897 let mut verbose = false;
18898 let mut files: Vec<&str> = Vec::new();
18899
18900 for arg in args {
18901 match arg.as_str() {
18902 "-f" => force = true,
18903 "-v" => verbose = true,
18904 "-i" => {} s if !s.starts_with('-') => files.push(s),
18906 _ => {}
18907 }
18908 }
18909
18910 if files.len() < 2 {
18911 eprintln!("{}: missing operand", cmd);
18912 return 1;
18913 }
18914
18915 let target = files.pop().unwrap();
18916 let target_is_dir = std::path::Path::new(target).is_dir();
18917
18918 for src in files {
18919 let dest = if target_is_dir {
18920 format!(
18921 "{}/{}",
18922 target,
18923 std::path::Path::new(src)
18924 .file_name()
18925 .map(|n| n.to_string_lossy().to_string())
18926 .unwrap_or_else(|| src.to_string())
18927 )
18928 } else {
18929 target.to_string()
18930 };
18931
18932 if !force && std::path::Path::new(&dest).exists() {
18933 eprintln!("{}: '{}' already exists", cmd, dest);
18934 continue;
18935 }
18936
18937 let result = match cmd {
18938 "zln" => {
18939 #[cfg(unix)]
18940 {
18941 std::os::unix::fs::symlink(src, &dest)
18942 }
18943 #[cfg(not(unix))]
18944 {
18945 Err(std::io::Error::new(
18946 std::io::ErrorKind::Unsupported,
18947 "symlinks not supported",
18948 ))
18949 }
18950 }
18951 "zcp" => std::fs::copy(src, &dest).map(|_| ()),
18952 "zmv" => std::fs::rename(src, &dest),
18953 _ => Ok(()),
18954 };
18955
18956 match result {
18957 Ok(()) => {
18958 if verbose {
18959 println!("{} -> {}", src, dest);
18960 }
18961 }
18962 Err(e) => {
18963 eprintln!("{}: {}: {}", cmd, src, e);
18964 return 1;
18965 }
18966 }
18967 }
18968
18969 0
18970 }
18971
18972 fn builtin_coproc(&mut self, args: &[String]) -> i32 {
18974 if args.is_empty() {
18976 println!("(no coprocesses)");
18978 return 0;
18979 }
18980
18981 let cmd = args.join(" ");
18983 match std::process::Command::new("sh")
18984 .arg("-c")
18985 .arg(&cmd)
18986 .stdin(std::process::Stdio::piped())
18987 .stdout(std::process::Stdio::piped())
18988 .spawn()
18989 {
18990 Ok(child) => {
18991 println!("[coproc] {}", child.id());
18992 0
18993 }
18994 Err(e) => {
18995 eprintln!("coproc: {}", e);
18996 1
18997 }
18998 }
18999 }
19000
19001 fn builtin_zparseopts(&mut self, args: &[String]) -> i32 {
19003 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();
19011
19012 let mut iter = args.iter().peekable();
19013
19014 while let Some(arg) = iter.next() {
19016 match arg.as_str() {
19017 "-D" => remove_parsed = true,
19018 "-E" => keep_going = true,
19019 "-F" => fail_on_error = true,
19020 "-K" => keep_values = true,
19021 "-M" => _map_names = true,
19022 "-a" => {
19023 if let Some(name) = iter.next() {
19024 array_name = Some(name.clone());
19025 }
19026 }
19027 "-A" => {
19028 if let Some(name) = iter.next() {
19029 assoc_name = Some(name.clone());
19030 }
19031 }
19032 "-" | "--" => break,
19033 s if !s.starts_with('-') || s.contains('=') || s.contains(':') => {
19034 specs.push(s.to_string());
19035 }
19036 _ => specs.push(arg.clone()),
19037 }
19038 }
19039
19040 for arg in iter {
19042 specs.push(arg.clone());
19043 }
19044
19045 #[derive(Clone)]
19047 struct OptSpec {
19048 name: String,
19049 takes_arg: bool,
19050 optional_arg: bool,
19051 #[allow(dead_code)]
19052 append: bool,
19053 target_array: Option<String>,
19054 }
19055
19056 let mut opt_specs: Vec<OptSpec> = Vec::new();
19057 for spec in &specs {
19058 let mut s = spec.as_str();
19059 let mut target = None;
19060
19061 if let Some(eq_pos) = s.rfind('=') {
19063 if !s[eq_pos + 1..].contains(':') {
19064 target = Some(s[eq_pos + 1..].to_string());
19065 s = &s[..eq_pos];
19066 }
19067 }
19068
19069 let append = s.ends_with('+') || s.contains("+:");
19070 let s = s.trim_end_matches('+');
19071
19072 let (name, takes_arg, optional_arg) = if s.ends_with("::") {
19073 (s.trim_end_matches(':').trim_end_matches(':'), true, true)
19074 } else if s.ends_with(':') {
19075 (s.trim_end_matches(':'), true, false)
19076 } else {
19077 (s, false, false)
19078 };
19079
19080 opt_specs.push(OptSpec {
19081 name: name.to_string(),
19082 takes_arg,
19083 optional_arg,
19084 append,
19085 target_array: target,
19086 });
19087 }
19088
19089 let positionals: Vec<String> = (1..=99)
19091 .map(|i| self.get_variable(&i.to_string()))
19092 .take_while(|v| !v.is_empty())
19093 .collect();
19094
19095 let mut results: Vec<(String, Option<String>)> = Vec::new();
19097 let mut i = 0;
19098 let mut parsed_count = 0;
19099
19100 while i < positionals.len() {
19101 let arg = &positionals[i];
19102
19103 if arg == "-" || arg == "--" {
19104 parsed_count = i + 1;
19105 break;
19106 }
19107
19108 if !arg.starts_with('-') {
19109 if !keep_going {
19110 break;
19111 }
19112 i += 1;
19113 continue;
19114 }
19115
19116 let opt_name = arg.trim_start_matches('-');
19118 let mut matched = false;
19119
19120 for spec in &opt_specs {
19121 if opt_name == spec.name || opt_name.starts_with(&format!("{}=", spec.name)) {
19122 matched = true;
19123
19124 if spec.takes_arg {
19125 let arg_value = if opt_name.contains('=') {
19126 Some(opt_name.splitn(2, '=').nth(1).unwrap_or("").to_string())
19127 } else if i + 1 < positionals.len()
19128 && (!positionals[i + 1].starts_with('-') || spec.optional_arg)
19129 {
19130 i += 1;
19131 Some(positionals[i].clone())
19132 } else if spec.optional_arg {
19133 None
19134 } else if fail_on_error {
19135 eprintln!("zparseopts: missing argument for option: {}", spec.name);
19136 return 1;
19137 } else {
19138 None
19139 };
19140 results.push((format!("-{}", spec.name), arg_value));
19141 } else {
19142 results.push((format!("-{}", spec.name), None));
19143 }
19144 break;
19145 }
19146 }
19147
19148 if !matched && !keep_going {
19149 break;
19150 }
19151
19152 i += 1;
19153 parsed_count = i;
19154 }
19155
19156 if let Some(arr_name) = &array_name {
19158 let mut arr_values: Vec<String> = Vec::new();
19159 for (opt, val) in &results {
19160 arr_values.push(opt.clone());
19161 if let Some(v) = val {
19162 arr_values.push(v.clone());
19163 }
19164 }
19165 self.arrays.insert(arr_name.clone(), arr_values);
19166 }
19167
19168 if let Some(assoc) = &assoc_name {
19170 let mut map: HashMap<String, String> = HashMap::new();
19171 for (opt, val) in &results {
19172 map.insert(opt.clone(), val.clone().unwrap_or_default());
19173 }
19174 self.assoc_arrays.insert(assoc.clone(), map);
19175 }
19176
19177 for spec in &opt_specs {
19179 if let Some(target) = &spec.target_array {
19180 let values: Vec<String> = results
19181 .iter()
19182 .filter(|(opt, _)| opt.trim_start_matches('-') == spec.name)
19183 .flat_map(|(opt, val)| {
19184 let mut v = vec![opt.clone()];
19185 if let Some(arg) = val {
19186 v.push(arg.clone());
19187 }
19188 v
19189 })
19190 .collect();
19191 if !values.is_empty() || !keep_values {
19192 self.arrays.insert(target.clone(), values);
19193 }
19194 }
19195 }
19196
19197 if remove_parsed && parsed_count > 0 {
19199 for i in 1..=parsed_count {
19200 self.variables.remove(&i.to_string());
19201 std::env::remove_var(i.to_string());
19202 }
19203 let remaining: Vec<String> = ((parsed_count + 1)..=99)
19205 .map(|i| self.get_variable(&i.to_string()))
19206 .take_while(|v| !v.is_empty())
19207 .collect();
19208 for (i, val) in remaining.iter().enumerate() {
19209 self.variables.insert((i + 1).to_string(), val.clone());
19210 }
19211 }
19212
19213 0
19214 }
19215
19216 fn builtin_readonly(&mut self, args: &[String]) -> i32 {
19218 if args.is_empty() {
19219 for name in &self.readonly_vars {
19221 if let Some(val) = self.variables.get(name) {
19222 println!("readonly {}={}", name, val);
19223 }
19224 }
19225 return 0;
19226 }
19227
19228 for arg in args {
19229 if arg == "-p" {
19230 for name in &self.readonly_vars {
19231 if let Some(val) = self.variables.get(name) {
19232 println!("declare -r {}=\"{}\"", name, val);
19233 }
19234 }
19235 } else if let Some(eq_pos) = arg.find('=') {
19236 let name = &arg[..eq_pos];
19237 let value = &arg[eq_pos + 1..];
19238 self.variables.insert(name.to_string(), value.to_string());
19239 self.readonly_vars.insert(name.to_string());
19240 } else {
19241 self.readonly_vars.insert(arg.clone());
19242 }
19243 }
19244 0
19245 }
19246
19247 fn builtin_unfunction(&mut self, args: &[String]) -> i32 {
19249 for name in args {
19250 if self.functions.remove(name).is_none() {
19251 eprintln!("unfunction: no such function: {}", name);
19252 }
19253 }
19254 0
19255 }
19256
19257 fn builtin_getln(&mut self, args: &[String]) -> i32 {
19259 if args.is_empty() {
19260 eprintln!("getln: missing variable name");
19261 return 1;
19262 }
19263 let mut line = String::new();
19265 if std::io::stdin().read_line(&mut line).is_ok() {
19266 let line = line.trim_end_matches('\n');
19267 self.variables.insert(args[0].clone(), line.to_string());
19268 0
19269 } else {
19270 1
19271 }
19272 }
19273
19274 fn builtin_pushln(&mut self, args: &[String]) -> i32 {
19276 for arg in args {
19277 println!("{}", arg);
19278 }
19279 0
19280 }
19281
19282 fn builtin_bindkey(&mut self, args: &[String]) -> i32 {
19284 use crate::zle::{zle, KeymapName};
19285
19286 if args.is_empty() {
19287 let zle = zle();
19289 for (keys, widget) in zle
19290 .keymaps
19291 .get(&KeymapName::Main)
19292 .map(|km| km.list_bindings().collect::<Vec<_>>())
19293 .unwrap_or_default()
19294 {
19295 println!("\"{}\" {}", keys, widget);
19296 }
19297 return 0;
19298 }
19299
19300 let mut iter = args.iter().peekable();
19301 let mut keymap = KeymapName::Main;
19302 let mut list_mode = false;
19303 let mut list_all = false;
19304 let mut remove = false;
19305
19306 while let Some(arg) = iter.next() {
19307 match arg.as_str() {
19308 "-l" => {
19309 list_mode = true;
19310 }
19311 "-L" => {
19312 list_mode = true;
19313 list_all = true;
19314 }
19315 "-la" | "-lL" => {
19316 list_mode = true;
19317 list_all = true;
19318 }
19319 "-M" => {
19320 if let Some(name) = iter.next() {
19321 if let Some(km) = KeymapName::from_str(name) {
19322 keymap = km;
19323 }
19324 }
19325 }
19326 "-r" => {
19327 remove = true;
19328 }
19329 "-A" => {
19330 return 0;
19332 }
19333 "-N" => {
19334 return 0;
19336 }
19337 "-e" => {
19338 keymap = KeymapName::Emacs;
19339 }
19340 "-v" => {
19341 keymap = KeymapName::ViInsert;
19342 }
19343 "-a" => {
19344 keymap = KeymapName::ViCommand;
19345 }
19346 key if !key.starts_with('-') => {
19347 if let Some(widget) = iter.next() {
19349 let mut zle = zle();
19350 if remove {
19351 zle.unbind_key(keymap, key);
19352 } else {
19353 zle.bind_key(keymap, key, widget);
19354 }
19355 }
19356 return 0;
19357 }
19358 _ => {}
19359 }
19360 }
19361
19362 if list_mode {
19363 let zle = zle();
19364 if list_all {
19365 for km_name in &[
19366 KeymapName::Emacs,
19367 KeymapName::ViInsert,
19368 KeymapName::ViCommand,
19369 ] {
19370 println!("{}", km_name.as_str());
19371 }
19372 } else {
19373 if let Some(km) = zle.keymaps.get(&keymap) {
19374 for (keys, widget) in km.list_bindings() {
19375 println!("bindkey \"{}\" {}", keys, widget);
19376 }
19377 }
19378 }
19379 }
19380
19381 0
19382 }
19383
19384 fn builtin_zle(&mut self, args: &[String]) -> i32 {
19386 use crate::zle::zle;
19387
19388 if args.is_empty() {
19389 return 0;
19390 }
19391
19392 let mut iter = args.iter().peekable();
19393
19394 while let Some(arg) = iter.next() {
19395 match arg.as_str() {
19396 "-l" => {
19397 let zle = zle();
19399 let mut widgets: Vec<&str> = zle.list_widgets();
19400 widgets.sort();
19401 for w in widgets {
19402 println!("{}", w);
19403 }
19404 return 0;
19405 }
19406 "-la" | "-lL" => {
19407 let zle = zle();
19409 let mut widgets: Vec<&str> = zle.list_widgets();
19410 widgets.sort();
19411 for w in widgets {
19412 println!("{}", w);
19413 }
19414 return 0;
19415 }
19416 "-N" => {
19417 if let Some(widget_name) = iter.next() {
19419 let func_name = iter
19420 .next()
19421 .map(|s| s.as_str())
19422 .unwrap_or(widget_name.as_str());
19423 let mut zle = zle();
19424 zle.define_widget(widget_name, func_name);
19425 }
19426 return 0;
19427 }
19428 "-D" => {
19429 return 0;
19431 }
19432 "-A" => {
19433 return 0;
19435 }
19436 "-R" => {
19437 return 0;
19439 }
19440 "-U" => {
19441 return 0;
19443 }
19444 "-K" => {
19445 return 0;
19447 }
19448 "-F" => {
19449 return 0;
19451 }
19452 "-M" => {
19453 return 0;
19455 }
19456 "-I" => {
19457 return 0;
19459 }
19460 "-f" => {
19461 if let Some(name) = iter.next() {
19463 let zle = zle();
19464 return if zle.get_widget(name).is_some() { 0 } else { 1 };
19465 }
19466 return 1;
19467 }
19468 widget_name if !widget_name.starts_with('-') => {
19469 let mut zle = zle();
19471 match zle.execute_widget(widget_name, None) {
19472 crate::zle::WidgetResult::Ok => return 0,
19473 crate::zle::WidgetResult::Error(e) => {
19474 eprintln!("zle: {}", e);
19475 return 1;
19476 }
19477 crate::zle::WidgetResult::CallFunction(func) => {
19478 drop(zle);
19480 if let Some(f) = self.functions.get(&func).cloned() {
19481 return self.call_function(&f, &[]).unwrap_or(1);
19482 }
19483 return 1;
19484 }
19485 _ => return 0,
19486 }
19487 }
19488 _ => {}
19489 }
19490 }
19491
19492 0
19493 }
19494
19495 fn builtin_sched(&mut self, args: &[String]) -> i32 {
19497 use std::time::{Duration, SystemTime};
19498
19499 if args.is_empty() {
19500 if self.scheduled_commands.is_empty() {
19502 return 0;
19503 }
19504 let now = SystemTime::now();
19505 for cmd in &self.scheduled_commands {
19506 let remaining = cmd.run_at.duration_since(now).unwrap_or(Duration::ZERO);
19507 println!("{:3} +{:5} {}", cmd.id, remaining.as_secs(), cmd.command);
19508 }
19509 return 0;
19510 }
19511
19512 let mut i = 0;
19513 while i < args.len() {
19514 match args[i].as_str() {
19515 "-" => {
19516 i += 1;
19518 if i >= args.len() {
19519 eprintln!("sched: -: need item number");
19520 return 1;
19521 }
19522 if let Ok(id) = args[i].parse::<u32>() {
19523 self.scheduled_commands.retain(|c| c.id != id);
19524 return 0;
19525 } else {
19526 eprintln!("sched: invalid item number");
19527 return 1;
19528 }
19529 }
19530 "+" => {
19531 i += 1;
19533 if i >= args.len() {
19534 eprintln!("sched: +: need time");
19535 return 1;
19536 }
19537 let secs: u64 = args[i].parse().unwrap_or(0);
19538 i += 1;
19539 let command = args[i..].join(" ");
19540
19541 let id = self.scheduled_commands.len() as u32 + 1;
19542 self.scheduled_commands.push(ScheduledCommand {
19543 id,
19544 run_at: SystemTime::now() + Duration::from_secs(secs),
19545 command,
19546 });
19547 return 0;
19548 }
19549 time_str => {
19550 let parts: Vec<&str> = time_str.split(':').collect();
19552 if parts.len() >= 2 {
19553 let hour: u32 = parts[0].parse().unwrap_or(0);
19554 let min: u32 = parts[1].parse().unwrap_or(0);
19555 let sec: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
19556
19557 let now = SystemTime::now();
19559 let target_secs = (hour * 3600 + min * 60 + sec) as u64;
19560 let _day_secs = 86400u64;
19561
19562 let run_at = now + Duration::from_secs(target_secs);
19564
19565 i += 1;
19566 let command = args[i..].join(" ");
19567
19568 let id = self.scheduled_commands.len() as u32 + 1;
19569 self.scheduled_commands.push(ScheduledCommand {
19570 id,
19571 run_at,
19572 command,
19573 });
19574 return 0;
19575 } else {
19576 eprintln!("sched: invalid time format");
19577 return 1;
19578 }
19579 }
19580 }
19581 }
19582 0
19583 }
19584
19585 fn builtin_zcompile(&mut self, args: &[String]) -> i32 {
19587 use crate::zwc::{ZwcBuilder, ZwcFile};
19588
19589 let mut list_mode = false; let mut compile_current = false; let mut compile_auto = false; let mut files: Vec<String> = Vec::new();
19593
19594 let mut i = 0;
19595 while i < args.len() {
19596 let arg = &args[i];
19597 if arg.starts_with('-') && arg.len() > 1 {
19598 for c in arg[1..].chars() {
19599 match c {
19600 't' => list_mode = true,
19601 'c' => compile_current = true,
19602 'a' => compile_auto = true,
19603 'U' | 'M' | 'R' | 'm' | 'z' | 'k' => {} _ => {
19605 eprintln!("zcompile: unknown option: -{}", c);
19606 return 1;
19607 }
19608 }
19609 }
19610 } else {
19611 files.push(arg.clone());
19612 }
19613 i += 1;
19614 }
19615
19616 if files.is_empty() {
19617 eprintln!("zcompile: not enough arguments");
19618 return 1;
19619 }
19620
19621 if list_mode {
19623 let zwc_path = if files[0].ends_with(".zwc") {
19624 files[0].clone()
19625 } else {
19626 format!("{}.zwc", files[0])
19627 };
19628
19629 match ZwcFile::load(&zwc_path) {
19630 Ok(zwc) => {
19631 println!("zwc file for zshrs-{}", env!("CARGO_PKG_VERSION"));
19632 if files.len() > 1 {
19633 for name in &files[1..] {
19635 if zwc.get_function(name).is_some() {
19636 println!("{}", name);
19637 } else {
19638 eprintln!("zcompile: function not found: {}", name);
19639 return 1;
19640 }
19641 }
19642 } else {
19643 for name in zwc.list_functions() {
19645 println!("{}", name);
19646 }
19647 }
19648 return 0;
19649 }
19650 Err(e) => {
19651 eprintln!("zcompile: can't read zwc file: {}: {}", zwc_path, e);
19652 return 1;
19653 }
19654 }
19655 }
19656
19657 if compile_current || compile_auto {
19659 let zwc_path = if files[0].ends_with(".zwc") {
19660 files[0].clone()
19661 } else {
19662 format!("{}.zwc", files[0])
19663 };
19664
19665 let mut builder = ZwcBuilder::new();
19666
19667 if files.len() > 1 {
19668 for name in &files[1..] {
19670 if let Some(func) = self.functions.get(name) {
19671 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
19673 builder.add_source(name, &source);
19674 } else if compile_auto && self.autoload_pending.contains_key(name) {
19675 if let Some(path) = self.find_function_file(name) {
19677 if let Err(e) = builder.add_file(&path) {
19678 eprintln!("zcompile: can't read {}: {}", name, e);
19679 return 1;
19680 }
19681 }
19682 } else {
19683 eprintln!("zcompile: no such function: {}", name);
19684 return 1;
19685 }
19686 }
19687 } else {
19688 for (name, func) in &self.functions {
19690 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
19691 builder.add_source(name, &source);
19692 }
19693 }
19694
19695 if let Err(e) = builder.write(&zwc_path) {
19696 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
19697 return 1;
19698 }
19699 return 0;
19700 }
19701
19702 let zwc_path = if files[0].ends_with(".zwc") {
19704 files[0].clone()
19705 } else {
19706 format!("{}.zwc", files[0])
19707 };
19708
19709 let mut builder = ZwcBuilder::new();
19710
19711 let source_files = if files.len() == 1 {
19713 let path = std::path::Path::new(&files[0]);
19715 if path.is_dir() {
19716 match std::fs::read_dir(path) {
19718 Ok(entries) => {
19719 for entry in entries.flatten() {
19720 let p = entry.path();
19721 if p.is_file() && !p.extension().map_or(false, |e| e == "zwc") {
19722 if let Err(e) = builder.add_file(&p) {
19723 eprintln!("zcompile: can't read {:?}: {}", p, e);
19724 }
19725 }
19726 }
19727 }
19728 Err(e) => {
19729 eprintln!("zcompile: can't read directory: {}", e);
19730 return 1;
19731 }
19732 }
19733 vec![]
19734 } else {
19735 vec![files[0].clone()]
19736 }
19737 } else {
19738 files[1..].to_vec()
19739 };
19740
19741 for file in &source_files {
19742 let path = std::path::Path::new(file);
19743 if let Err(e) = builder.add_file(path) {
19744 eprintln!("zcompile: can't read {}: {}", file, e);
19745 return 1;
19746 }
19747 }
19748
19749 if let Err(e) = builder.write(&zwc_path) {
19750 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
19751 return 1;
19752 }
19753
19754 0
19755 }
19756
19757 fn builtin_zformat(&self, args: &[String]) -> i32 {
19759 if args.len() < 2 {
19760 eprintln!("zformat: not enough arguments");
19761 return 1;
19762 }
19763
19764 match args[0].as_str() {
19765 "-f" => {
19766 if args.len() < 3 {
19768 return 1;
19769 }
19770 let _var_name = &args[1];
19771 let format = &args[2];
19772 let specs: HashMap<char, &str> = args[3..]
19773 .iter()
19774 .filter_map(|s| {
19775 let mut chars = s.chars();
19776 let key = chars.next()?;
19777 if chars.next() == Some(':') {
19778 Some((key, &s[2..]))
19779 } else {
19780 None
19781 }
19782 })
19783 .collect();
19784
19785 let mut result = String::new();
19786 let mut chars = format.chars().peekable();
19787 while let Some(c) = chars.next() {
19788 if c == '%' {
19789 if let Some(&spec_char) = chars.peek() {
19790 if let Some(replacement) = specs.get(&spec_char) {
19791 result.push_str(replacement);
19792 chars.next();
19793 continue;
19794 }
19795 }
19796 }
19797 result.push(c);
19798 }
19799 println!("{}", result);
19800 }
19801 "-a" => {
19802 if args.len() < 4 {
19805 eprintln!("zformat -a: need array, separator, and specs");
19806 return 1;
19807 }
19808 let _array_name = &args[1];
19809 let sep = &args[2];
19810
19811 let mut results = Vec::new();
19812 for spec in &args[3..] {
19813 let parts: Vec<&str> = spec.splitn(3, ':').collect();
19814 if parts.len() >= 2 {
19815 let text = parts[0];
19816 let value = parts[1];
19817 let cond = parts.get(2).copied();
19818
19819 if let Some(c) = cond {
19821 if c.is_empty() || c == "0" {
19822 continue;
19823 }
19824 }
19825
19826 if !value.is_empty() {
19827 results.push(format!("{}{}{}", text, sep, value));
19828 }
19829 }
19830 }
19831
19832 for r in results {
19833 println!("{}", r);
19834 }
19835 }
19836 _ => {
19837 eprintln!("zformat: unknown option: {}", args[0]);
19838 return 1;
19839 }
19840 }
19841 0
19842 }
19843
19844 fn builtin_vared(&mut self, args: &[String]) -> i32 {
19846 if args.is_empty() {
19847 eprintln!("vared: not enough arguments");
19848 return 1;
19849 }
19850
19851 let mut var_name = String::new();
19852 let mut prompt = String::new();
19853 let mut rprompt = String::new();
19854 let mut _history = false; let mut i = 0;
19856
19857 while i < args.len() {
19858 match args[i].as_str() {
19859 "-p" if i + 1 < args.len() => {
19860 i += 1;
19861 prompt = args[i].clone();
19862 }
19863 "-r" if i + 1 < args.len() => {
19864 i += 1;
19865 rprompt = args[i].clone();
19866 }
19867 "-h" => _history = true,
19868 "-c" => {} "-e" => {} "-M" | "-m" => {
19871 i += 1;
19872 } "-a" | "-A" => {
19874 i += 1;
19875 } s if !s.starts_with('-') => {
19877 var_name = s.to_string();
19878 }
19879 _ => {}
19880 }
19881 i += 1;
19882 }
19883
19884 if var_name.is_empty() {
19885 eprintln!("vared: not enough arguments");
19886 return 1;
19887 }
19888
19889 let current = self.get_variable(&var_name);
19891
19892 if !prompt.is_empty() {
19894 eprint!("{}", prompt);
19895 }
19896 print!("{}", current);
19897 if !rprompt.is_empty() {
19898 eprint!("{}", rprompt);
19899 }
19900
19901 let mut input = String::new();
19902 if std::io::stdin().read_line(&mut input).is_ok() {
19903 let value = input.trim_end_matches('\n').to_string();
19904 self.variables.insert(var_name, value);
19905 return 0;
19906 }
19907 1
19908 }
19909
19910 fn builtin_echotc(&self, args: &[String]) -> i32 {
19912 if args.is_empty() {
19913 eprintln!("echotc: not enough arguments");
19914 return 1;
19915 }
19916
19917 match args[0].as_str() {
19919 "cl" => print!("\x1b[H\x1b[2J"), "cd" => print!("\x1b[J"), "ce" => print!("\x1b[K"), "cm" => {
19923 if args.len() >= 3 {
19925 if let (Ok(row), Ok(col)) = (args[1].parse::<u32>(), args[2].parse::<u32>()) {
19926 print!("\x1b[{};{}H", row + 1, col + 1);
19927 }
19928 }
19929 }
19930 "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" => {
19945 if args.len() >= 2 {
19947 if let Ok(color) = args[1].parse::<u32>() {
19948 print!("\x1b[38;5;{}m", color);
19949 }
19950 }
19951 }
19952 "AB" | "setab" => {
19953 if args.len() >= 2 {
19955 if let Ok(color) = args[1].parse::<u32>() {
19956 print!("\x1b[48;5;{}m", color);
19957 }
19958 }
19959 }
19960 "Co" | "colors" => {
19961 println!("256");
19963 }
19964 "co" | "cols" => {
19965 println!(
19967 "{}",
19968 std::env::var("COLUMNS")
19969 .ok()
19970 .and_then(|s| s.parse().ok())
19971 .unwrap_or(80u16)
19972 );
19973 }
19974 "li" | "lines" => {
19975 println!(
19977 "{}",
19978 std::env::var("LINES")
19979 .ok()
19980 .and_then(|s| s.parse().ok())
19981 .unwrap_or(24u16)
19982 );
19983 }
19984 cap => {
19985 eprintln!("echotc: unknown capability: {}", cap);
19986 return 1;
19987 }
19988 }
19989 use std::io::Write;
19990 let _ = std::io::stdout().flush();
19991 0
19992 }
19993
19994 fn builtin_echoti(&self, args: &[String]) -> i32 {
19996 self.builtin_echotc(args)
19999 }
20000
20001 fn builtin_zpty(&mut self, args: &[String]) -> i32 {
20003 use std::io::{Read, Write};
20004 use std::process::{Command, Stdio};
20005
20006 if args.is_empty() {
20007 if self.zptys.is_empty() {
20009 return 0;
20010 }
20011 for (name, state) in &self.zptys {
20012 println!("{}: {} (pid {})", name, state.cmd, state.pid);
20013 }
20014 return 0;
20015 }
20016
20017 let mut i = 0;
20018 while i < args.len() {
20019 match args[i].as_str() {
20020 "-d" => {
20021 i += 1;
20023 if i >= args.len() {
20024 eprintln!("zpty: -d requires pty name");
20025 return 1;
20026 }
20027 let name = &args[i];
20028 if let Some(mut state) = self.zptys.remove(name) {
20029 if let Some(ref mut child) = state.child {
20030 let _ = child.kill();
20031 }
20032 return 0;
20033 } else {
20034 eprintln!("zpty: no such pty: {}", name);
20035 return 1;
20036 }
20037 }
20038 "-w" => {
20039 i += 1;
20041 if i >= args.len() {
20042 eprintln!("zpty: -w requires pty name");
20043 return 1;
20044 }
20045 let name = args[i].clone();
20046 i += 1;
20047 let data = args[i..].join(" ") + "\n";
20048
20049 if let Some(state) = self.zptys.get_mut(&name) {
20050 if let Some(ref mut stdin) = state.stdin {
20051 if stdin.write_all(data.as_bytes()).is_ok() {
20052 let _ = stdin.flush();
20053 return 0;
20054 }
20055 }
20056 eprintln!("zpty: write failed");
20057 return 1;
20058 } else {
20059 eprintln!("zpty: no such pty: {}", name);
20060 return 1;
20061 }
20062 }
20063 "-r" => {
20064 i += 1;
20066 if i >= args.len() {
20067 eprintln!("zpty: -r requires pty name");
20068 return 1;
20069 }
20070 let name = args[i].clone();
20071 i += 1;
20072 let var_name = if i < args.len() {
20073 args[i].clone()
20074 } else {
20075 "REPLY".to_string()
20076 };
20077
20078 if let Some(state) = self.zptys.get_mut(&name) {
20079 if let Some(ref mut stdout) = state.stdout {
20080 let mut buf = vec![0u8; 4096];
20081 match stdout.read(&mut buf) {
20082 Ok(n) => {
20083 let data = String::from_utf8_lossy(&buf[..n]).to_string();
20084 self.variables.insert(var_name, data);
20085 return 0;
20086 }
20087 Err(_) => return 1,
20088 }
20089 }
20090 return 1;
20091 } else {
20092 eprintln!("zpty: no such pty: {}", name);
20093 return 1;
20094 }
20095 }
20096 "-t" => {
20097 i += 1;
20099 if i >= args.len() {
20100 return 1;
20101 }
20102 let name = &args[i];
20103 if self.zptys.contains_key(name) {
20104 return 0; }
20106 return 1;
20107 }
20108 "-L" => {
20109 for (name, state) in &self.zptys {
20111 println!("zpty {} {}", name, state.cmd);
20112 }
20113 return 0;
20114 }
20115 "-b" | "-e" => {
20116 i += 1;
20118 continue;
20119 }
20120 name if !name.starts_with('-') => {
20121 i += 1;
20123 if i >= args.len() {
20124 eprintln!("zpty: command required");
20125 return 1;
20126 }
20127 let cmd_str = args[i..].join(" ");
20128
20129 match Command::new("sh")
20130 .arg("-c")
20131 .arg(&cmd_str)
20132 .stdin(Stdio::piped())
20133 .stdout(Stdio::piped())
20134 .stderr(Stdio::piped())
20135 .spawn()
20136 {
20137 Ok(mut child) => {
20138 let pid = child.id();
20139 let stdin = child.stdin.take();
20140 let stdout = child.stdout.take();
20141
20142 self.zptys.insert(
20143 name.to_string(),
20144 ZptyState {
20145 pid,
20146 cmd: cmd_str,
20147 stdin,
20148 stdout,
20149 child: Some(child),
20150 },
20151 );
20152 return 0;
20153 }
20154 Err(e) => {
20155 eprintln!("zpty: failed to start: {}", e);
20156 return 1;
20157 }
20158 }
20159 }
20160 _ => {
20161 i += 1;
20162 }
20163 }
20164 i += 1;
20165 }
20166 0
20167 }
20168
20169 fn builtin_zprof(&mut self, args: &[String]) -> i32 {
20171 use crate::zprof::ZprofOptions;
20172
20173 let options = ZprofOptions {
20174 clear: args.iter().any(|a| a == "-c"),
20175 };
20176
20177 let (status, output) = crate::zprof::builtin_zprof(&mut self.profiler, &options);
20178 if !output.is_empty() {
20179 print!("{}", output);
20180 }
20181 status
20182 }
20183
20184 fn builtin_zsocket(&mut self, args: &[String]) -> i32 {
20186 use std::os::unix::net::{UnixListener, UnixStream};
20187
20188 if args.is_empty() {
20189 if self.unix_sockets.is_empty() {
20191 return 0;
20192 }
20193 for (fd, state) in &self.unix_sockets {
20194 let path = state
20195 .path
20196 .as_ref()
20197 .map(|p| p.display().to_string())
20198 .unwrap_or_default();
20199 let status = if state.listening {
20200 "listening"
20201 } else {
20202 "connected"
20203 };
20204 println!("{}: {} ({})", fd, path, status);
20205 }
20206 return 0;
20207 }
20208
20209 let mut i = 0;
20210 let mut verbose = false;
20211 let mut var_name = "REPLY".to_string();
20212
20213 while i < args.len() {
20214 match args[i].as_str() {
20215 "-v" => {
20216 verbose = true;
20217 i += 1;
20218 if i < args.len() && !args[i].starts_with('-') {
20219 var_name = args[i].clone();
20220 }
20221 }
20222 "-l" => {
20223 i += 1;
20225 if i >= args.len() {
20226 eprintln!("zsocket: -l requires path");
20227 return 1;
20228 }
20229 let path = PathBuf::from(&args[i]);
20230
20231 let _ = std::fs::remove_file(&path);
20233
20234 match UnixListener::bind(&path) {
20235 Ok(listener) => {
20236 let fd = self.next_fd;
20237 self.next_fd += 1;
20238
20239 self.unix_sockets.insert(
20240 fd,
20241 UnixSocketState {
20242 path: Some(path),
20243 listening: true,
20244 stream: None,
20245 listener: Some(listener),
20246 },
20247 );
20248
20249 if verbose {
20250 self.variables.insert(var_name.clone(), fd.to_string());
20251 }
20252 println!("{}", fd);
20253 return 0;
20254 }
20255 Err(e) => {
20256 eprintln!("zsocket: bind failed: {}", e);
20257 return 1;
20258 }
20259 }
20260 }
20261 "-a" => {
20262 i += 1;
20264 if i >= args.len() {
20265 eprintln!("zsocket: -a requires fd");
20266 return 1;
20267 }
20268 let listen_fd: i32 = args[i].parse().unwrap_or(-1);
20269
20270 if let Some(state) = self.unix_sockets.get(&listen_fd) {
20271 if let Some(ref listener) = state.listener {
20272 match listener.accept() {
20273 Ok((stream, _addr)) => {
20274 let new_fd = self.next_fd;
20275 self.next_fd += 1;
20276
20277 self.unix_sockets.insert(
20278 new_fd,
20279 UnixSocketState {
20280 path: None,
20281 listening: false,
20282 stream: Some(stream),
20283 listener: None,
20284 },
20285 );
20286
20287 if verbose {
20288 self.variables.insert(var_name.clone(), new_fd.to_string());
20289 }
20290 println!("{}", new_fd);
20291 return 0;
20292 }
20293 Err(e) => {
20294 eprintln!("zsocket: accept failed: {}", e);
20295 return 1;
20296 }
20297 }
20298 }
20299 }
20300 eprintln!("zsocket: invalid fd");
20301 return 1;
20302 }
20303 "-d" => {
20304 i += 1;
20306 if i >= args.len() {
20307 eprintln!("zsocket: -d requires fd");
20308 return 1;
20309 }
20310 let fd: i32 = args[i].parse().unwrap_or(-1);
20311
20312 if let Some(state) = self.unix_sockets.remove(&fd) {
20313 if let Some(path) = state.path {
20314 let _ = std::fs::remove_file(path);
20315 }
20316 return 0;
20317 }
20318 eprintln!("zsocket: no such fd");
20319 return 1;
20320 }
20321 path if !path.starts_with('-') => {
20322 match UnixStream::connect(path) {
20324 Ok(stream) => {
20325 let fd = self.next_fd;
20326 self.next_fd += 1;
20327
20328 self.unix_sockets.insert(
20329 fd,
20330 UnixSocketState {
20331 path: Some(PathBuf::from(path)),
20332 listening: false,
20333 stream: Some(stream),
20334 listener: None,
20335 },
20336 );
20337
20338 if verbose {
20339 self.variables.insert(var_name.clone(), fd.to_string());
20340 }
20341 println!("{}", fd);
20342 return 0;
20343 }
20344 Err(e) => {
20345 eprintln!("zsocket: connect failed: {}", e);
20346 return 1;
20347 }
20348 }
20349 }
20350 _ => {}
20351 }
20352 i += 1;
20353 }
20354 0
20355 }
20356
20357 fn builtin_ztcp(&mut self, args: &[String]) -> i32 {
20359 self.builtin_zsocket(args)
20361 }
20362
20363 fn builtin_zregexparse(&mut self, args: &[String]) -> i32 {
20365 if args.len() < 2 {
20366 eprintln!("zregexparse: usage: zregexparse var pattern [string]");
20367 return 1;
20368 }
20369
20370 let var_name = &args[0];
20371 let pattern = &args[1];
20372 let string = if args.len() > 2 {
20373 args[2].clone()
20374 } else {
20375 self.variables.get("REPLY").cloned().unwrap_or_default()
20376 };
20377
20378 match regex::Regex::new(pattern) {
20379 Ok(re) => {
20380 if let Some(captures) = re.captures(&string) {
20381 if let Some(m) = captures.get(0) {
20383 self.variables
20384 .insert(var_name.clone(), m.as_str().to_string());
20385 }
20386
20387 let mut match_array = Vec::new();
20389 let mut mbegin_array = Vec::new();
20390 let mut mend_array = Vec::new();
20391
20392 for (i, cap) in captures.iter().enumerate() {
20393 if let Some(c) = cap {
20394 match_array.push(c.as_str().to_string());
20395 mbegin_array.push((c.start() + 1).to_string());
20396 mend_array.push(c.end().to_string());
20397 self.variables
20398 .insert(format!("match[{}]", i), c.as_str().to_string());
20399 }
20400 }
20401 self.arrays.insert("match".to_string(), match_array);
20402 self.arrays.insert("mbegin".to_string(), mbegin_array);
20403 self.arrays.insert("mend".to_string(), mend_array);
20404
20405 if let Some(m) = captures.get(0) {
20407 self.variables
20408 .insert("MBEGIN".to_string(), (m.start() + 1).to_string());
20409 self.variables
20410 .insert("MEND".to_string(), m.end().to_string());
20411 }
20412
20413 0
20414 } else {
20415 1
20416 }
20417 }
20418 Err(e) => {
20419 eprintln!("zregexparse: invalid regex: {}", e);
20420 2
20421 }
20422 }
20423 }
20424
20425 fn builtin_clone(&mut self, args: &[String]) -> i32 {
20427 use std::process::Command;
20428
20429 let mut cmd =
20432 Command::new(std::env::current_exe().unwrap_or_else(|_| PathBuf::from("zshrs")));
20433
20434 if !args.is_empty() {
20435 cmd.arg("-c").arg(args.join(" "));
20437 }
20438
20439 for (k, v) in &self.variables {
20441 cmd.env(k, v);
20442 }
20443
20444 match cmd.spawn() {
20445 Ok(mut child) => match child.wait() {
20446 Ok(status) => status.code().unwrap_or(0),
20447 Err(_) => 1,
20448 },
20449 Err(e) => {
20450 eprintln!("clone: failed to spawn subshell: {}", e);
20451 1
20452 }
20453 }
20454 }
20455
20456 fn builtin_log(&mut self, args: &[String]) -> i32 {
20458 self.builtin_exit(args)
20459 }
20460
20461 fn builtin_comparguments(&mut self, _args: &[String]) -> i32 {
20465 0
20467 }
20468
20469 fn builtin_compcall(&mut self, _args: &[String]) -> i32 {
20471 0
20473 }
20474
20475 fn builtin_compctl(&mut self, args: &[String]) -> i32 {
20477 if args.is_empty() {
20478 println!("compctl: old-style completion system");
20479 println!("Use the new completion system (compsys) instead");
20480 return 0;
20481 }
20482 0
20484 }
20485
20486 fn builtin_compdescribe(&mut self, _args: &[String]) -> i32 {
20488 0
20489 }
20490
20491 fn builtin_compfiles(&mut self, _args: &[String]) -> i32 {
20493 0
20494 }
20495
20496 fn builtin_compgroups(&mut self, _args: &[String]) -> i32 {
20498 0
20499 }
20500
20501 fn builtin_compquote(&mut self, _args: &[String]) -> i32 {
20503 0
20504 }
20505
20506 fn builtin_comptags(&mut self, args: &[String]) -> i32 {
20508 if args.is_empty() {
20509 return 1;
20510 }
20511 match args[0].as_str() {
20512 "-i" => {
20513 0
20515 }
20516 "-S" => {
20517 0
20519 }
20520 _ => 1,
20521 }
20522 }
20523
20524 fn builtin_comptry(&mut self, _args: &[String]) -> i32 {
20526 1 }
20528
20529 fn builtin_compvalues(&mut self, _args: &[String]) -> i32 {
20531 0
20532 }
20533
20534 fn builtin_cap(&self, args: &[String]) -> i32 {
20536 if args.is_empty() {
20539 println!("cap: display/set capabilities");
20540 println!(" getcap file... - display capabilities");
20541 println!(" setcap caps file - set capabilities");
20542 return 0;
20543 }
20544
20545 #[cfg(target_os = "linux")]
20546 {
20547 let status = std::process::Command::new(&args[0])
20550 .args(&args[1..])
20551 .status();
20552 return status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
20553 }
20554
20555 #[cfg(not(target_os = "linux"))]
20556 {
20557 eprintln!("cap: capabilities not supported on this platform");
20558 1
20559 }
20560 }
20561
20562 fn builtin_zcurses(&mut self, args: &[String]) -> i32 {
20564 if args.is_empty() {
20565 eprintln!("zcurses: requires subcommand");
20566 return 1;
20567 }
20568
20569 match args[0].as_str() {
20570 "init" => {
20571 println!("zcurses: would initialize curses");
20573 0
20574 }
20575 "end" => {
20576 println!("zcurses: would end curses");
20578 0
20579 }
20580 "addwin" => {
20581 0
20583 }
20584 "delwin" => {
20585 0
20587 }
20588 "refresh" => {
20589 0
20591 }
20592 "move" => {
20593 0
20595 }
20596 "clear" => {
20597 0
20599 }
20600 "char" | "string" => {
20601 0
20603 }
20604 "border" => {
20605 0
20607 }
20608 "attr" => {
20609 0
20611 }
20612 "color" => {
20613 0
20615 }
20616 "scroll" => {
20617 0
20619 }
20620 "input" => {
20621 0
20623 }
20624 "mouse" => {
20625 0
20627 }
20628 "querychar" => {
20629 0
20631 }
20632 "resize" => {
20633 0
20635 }
20636 cmd => {
20637 eprintln!("zcurses: unknown subcommand: {}", cmd);
20638 1
20639 }
20640 }
20641 }
20642
20643 fn builtin_sysread(&mut self, args: &[String]) -> i32 {
20645 use std::io::Read;
20646
20647 let mut fd = 0i32; let mut count: Option<usize> = None;
20649 let mut var_name = "REPLY".to_string();
20650 let mut i = 0;
20651
20652 while i < args.len() {
20653 match args[i].as_str() {
20654 "-c" if i + 1 < args.len() => {
20655 i += 1;
20656 count = args[i].parse().ok();
20657 }
20658 "-i" if i + 1 < args.len() => {
20659 i += 1;
20660 fd = args[i].parse().unwrap_or(0);
20661 }
20662 "-o" if i + 1 < args.len() => {
20663 i += 1;
20664 var_name = args[i].clone();
20665 }
20666 "-t" if i + 1 < args.len() => {
20667 i += 1;
20668 }
20670 _ => {
20671 var_name = args[i].clone();
20672 }
20673 }
20674 i += 1;
20675 }
20676
20677 let mut buffer = vec![0u8; count.unwrap_or(8192)];
20678
20679 if fd == 0 {
20681 match std::io::stdin().read(&mut buffer) {
20682 Ok(n) => {
20683 buffer.truncate(n);
20684 let s = String::from_utf8_lossy(&buffer).to_string();
20685 self.variables.insert(var_name, s);
20686 0
20687 }
20688 Err(_) => 1,
20689 }
20690 } else {
20691 eprintln!("sysread: only fd 0 (stdin) supported");
20692 1
20693 }
20694 }
20695
20696 fn builtin_syswrite(&mut self, args: &[String]) -> i32 {
20698 use std::io::Write;
20699
20700 let mut fd = 1i32; let mut data = String::new();
20702 let mut i = 0;
20703
20704 while i < args.len() {
20705 match args[i].as_str() {
20706 "-o" if i + 1 < args.len() => {
20707 i += 1;
20708 fd = args[i].parse().unwrap_or(1);
20709 }
20710 "-c" if i + 1 < args.len() => {
20711 i += 1;
20712 }
20714 _ => {
20715 data = args[i].clone();
20716 }
20717 }
20718 i += 1;
20719 }
20720
20721 match fd {
20722 1 => {
20723 let _ = std::io::stdout().write_all(data.as_bytes());
20724 let _ = std::io::stdout().flush();
20725 0
20726 }
20727 2 => {
20728 let _ = std::io::stderr().write_all(data.as_bytes());
20729 let _ = std::io::stderr().flush();
20730 0
20731 }
20732 _ => {
20733 eprintln!("syswrite: only fd 1 (stdout) and 2 (stderr) supported");
20734 1
20735 }
20736 }
20737 }
20738
20739 fn builtin_syserror(&self, args: &[String]) -> i32 {
20741 let errno = if args.is_empty() {
20742 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
20744 } else {
20745 args[0].parse().unwrap_or(0)
20746 };
20747
20748 let err = std::io::Error::from_raw_os_error(errno);
20749 println!("{}", err);
20750 0
20751 }
20752
20753 fn builtin_sysopen(&mut self, args: &[String]) -> i32 {
20755 use std::fs::OpenOptions;
20756
20757 let mut filename = String::new();
20758 let mut var_name = "REPLY".to_string();
20759 let mut read = false;
20760 let mut write = false;
20761 let mut append = false;
20762 let mut create = false;
20763 let mut truncate = false;
20764
20765 let mut i = 0;
20766 while i < args.len() {
20767 match args[i].as_str() {
20768 "-r" => read = true,
20769 "-w" => write = true,
20770 "-a" => append = true,
20771 "-c" => create = true,
20772 "-t" => truncate = true,
20773 "-u" => {
20774 i += 1;
20775 if i < args.len() {
20776 var_name = args[i].clone();
20777 }
20778 }
20779 "-o" => {
20780 i += 1;
20781 }
20783 s if !s.starts_with('-') => {
20784 filename = s.to_string();
20785 }
20786 _ => {}
20787 }
20788 i += 1;
20789 }
20790
20791 if filename.is_empty() {
20792 eprintln!("sysopen: need filename");
20793 return 1;
20794 }
20795
20796 if !read && !write && !append {
20798 read = true;
20799 }
20800
20801 let file = OpenOptions::new()
20802 .read(read)
20803 .write(write || append || truncate)
20804 .append(append)
20805 .create(create || write)
20806 .truncate(truncate)
20807 .open(&filename);
20808
20809 match file {
20810 Ok(f) => {
20811 let fd = self.next_fd;
20812 self.next_fd += 1;
20813 self.open_fds.insert(fd, f);
20814 self.variables.insert(var_name, fd.to_string());
20815 0
20816 }
20817 Err(e) => {
20818 eprintln!("sysopen: {}: {}", filename, e);
20819 1
20820 }
20821 }
20822 }
20823
20824 fn builtin_sysseek(&mut self, args: &[String]) -> i32 {
20826 use std::io::{Seek, SeekFrom};
20827
20828 let mut fd = -1i32;
20829 let mut offset = 0i64;
20830 let mut whence = SeekFrom::Start(0);
20831
20832 let mut i = 0;
20833 while i < args.len() {
20834 match args[i].as_str() {
20835 "-u" => {
20836 i += 1;
20837 if i < args.len() {
20838 fd = args[i].parse().unwrap_or(-1);
20839 }
20840 }
20841 "-w" => {
20842 i += 1;
20843 if i < args.len() {
20844 whence = match args[i].as_str() {
20845 "start" | "set" | "0" => SeekFrom::Start(offset as u64),
20846 "current" | "cur" | "1" => SeekFrom::Current(offset),
20847 "end" | "2" => SeekFrom::End(offset),
20848 _ => SeekFrom::Start(offset as u64),
20849 };
20850 }
20851 }
20852 s if !s.starts_with('-') => {
20853 offset = s.parse().unwrap_or(0);
20854 }
20855 _ => {}
20856 }
20857 i += 1;
20858 }
20859
20860 if fd < 0 {
20861 eprintln!("sysseek: need fd (-u)");
20862 return 1;
20863 }
20864
20865 whence = match whence {
20867 SeekFrom::Start(_) => SeekFrom::Start(offset as u64),
20868 SeekFrom::Current(_) => SeekFrom::Current(offset),
20869 SeekFrom::End(_) => SeekFrom::End(offset),
20870 };
20871
20872 if let Some(file) = self.open_fds.get_mut(&fd) {
20873 match file.seek(whence) {
20874 Ok(pos) => {
20875 self.variables.insert("REPLY".to_string(), pos.to_string());
20876 0
20877 }
20878 Err(e) => {
20879 eprintln!("sysseek: {}", e);
20880 1
20881 }
20882 }
20883 } else {
20884 eprintln!("sysseek: bad fd: {}", fd);
20885 1
20886 }
20887 }
20888
20889 fn builtin_private(&mut self, args: &[String]) -> i32 {
20891 self.builtin_local(args)
20893 }
20894
20895 fn builtin_zattr(&self, cmd: &str, args: &[String]) -> i32 {
20897 match cmd {
20898 "zgetattr" => {
20899 if args.len() < 2 {
20900 eprintln!("zgetattr: need file and attribute name");
20901 return 1;
20902 }
20903 #[cfg(target_os = "macos")]
20904 {
20905 let output = std::process::Command::new("xattr")
20907 .arg("-p")
20908 .arg(&args[1])
20909 .arg(&args[0])
20910 .output();
20911 if let Ok(out) = output {
20912 print!("{}", String::from_utf8_lossy(&out.stdout));
20913 return if out.status.success() { 0 } else { 1 };
20914 }
20915 }
20916 #[cfg(target_os = "linux")]
20917 {
20918 let output = std::process::Command::new("getfattr")
20919 .arg("-n")
20920 .arg(&args[1])
20921 .arg(&args[0])
20922 .output();
20923 if let Ok(out) = output {
20924 print!("{}", String::from_utf8_lossy(&out.stdout));
20925 return if out.status.success() { 0 } else { 1 };
20926 }
20927 }
20928 1
20929 }
20930 "zsetattr" => {
20931 if args.len() < 3 {
20932 eprintln!("zsetattr: need file, attribute name, and value");
20933 return 1;
20934 }
20935 #[cfg(target_os = "macos")]
20936 {
20937 let status = std::process::Command::new("xattr")
20938 .arg("-w")
20939 .arg(&args[1])
20940 .arg(&args[2])
20941 .arg(&args[0])
20942 .status();
20943 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
20944 }
20945 #[cfg(target_os = "linux")]
20946 {
20947 let status = std::process::Command::new("setfattr")
20948 .arg("-n")
20949 .arg(&args[1])
20950 .arg("-v")
20951 .arg(&args[2])
20952 .arg(&args[0])
20953 .status();
20954 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
20955 }
20956 #[allow(unreachable_code)]
20957 1
20958 }
20959 "zdelattr" => {
20960 if args.len() < 2 {
20961 eprintln!("zdelattr: need file and attribute name");
20962 return 1;
20963 }
20964 #[cfg(target_os = "macos")]
20965 {
20966 let status = std::process::Command::new("xattr")
20967 .arg("-d")
20968 .arg(&args[1])
20969 .arg(&args[0])
20970 .status();
20971 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
20972 }
20973 #[cfg(target_os = "linux")]
20974 {
20975 let status = std::process::Command::new("setfattr")
20976 .arg("-x")
20977 .arg(&args[1])
20978 .arg(&args[0])
20979 .status();
20980 return status.map(|s| if s.success() { 0 } else { 1 }).unwrap_or(1);
20981 }
20982 #[allow(unreachable_code)]
20983 1
20984 }
20985 "zlistattr" => {
20986 if args.is_empty() {
20987 eprintln!("zlistattr: need file");
20988 return 1;
20989 }
20990 #[cfg(target_os = "macos")]
20991 {
20992 let output = std::process::Command::new("xattr").arg(&args[0]).output();
20993 if let Ok(out) = output {
20994 print!("{}", String::from_utf8_lossy(&out.stdout));
20995 return if out.status.success() { 0 } else { 1 };
20996 }
20997 }
20998 #[cfg(target_os = "linux")]
20999 {
21000 let output = std::process::Command::new("getfattr")
21001 .arg("-d")
21002 .arg(&args[0])
21003 .output();
21004 if let Ok(out) = output {
21005 print!("{}", String::from_utf8_lossy(&out.stdout));
21006 return if out.status.success() { 0 } else { 1 };
21007 }
21008 }
21009 1
21010 }
21011 _ => 1,
21012 }
21013 }
21014
21015 fn builtin_zftp(&mut self, args: &[String]) -> i32 {
21017 if args.is_empty() {
21018 println!("zftp: FTP client");
21019 println!(" zftp open host [port]");
21020 println!(" zftp login [user [password]]");
21021 println!(" zftp cd dir");
21022 println!(" zftp get file [localfile]");
21023 println!(" zftp put file [remotefile]");
21024 println!(" zftp ls [dir]");
21025 println!(" zftp close");
21026 return 0;
21027 }
21028
21029 match args[0].as_str() {
21030 "open" => {
21031 if args.len() < 2 {
21032 eprintln!("zftp open: need hostname");
21033 return 1;
21034 }
21035 println!("zftp: would connect to {}", args[1]);
21037 0
21038 }
21039 "login" => {
21040 println!("zftp: would login");
21042 0
21043 }
21044 "cd" => {
21045 if args.len() < 2 {
21046 eprintln!("zftp cd: need directory");
21047 return 1;
21048 }
21049 println!("zftp: would cd to {}", args[1]);
21050 0
21051 }
21052 "get" => {
21053 if args.len() < 2 {
21054 eprintln!("zftp get: need filename");
21055 return 1;
21056 }
21057 println!("zftp: would download {}", args[1]);
21058 0
21059 }
21060 "put" => {
21061 if args.len() < 2 {
21062 eprintln!("zftp put: need filename");
21063 return 1;
21064 }
21065 println!("zftp: would upload {}", args[1]);
21066 0
21067 }
21068 "ls" => {
21069 println!("zftp: would list directory");
21070 0
21071 }
21072 "close" | "quit" => {
21073 println!("zftp: would close connection");
21074 0
21075 }
21076 "params" => {
21077 println!("ZFTP_HOST=");
21079 println!("ZFTP_PORT=21");
21080 println!("ZFTP_USER=");
21081 println!("ZFTP_PWD=");
21082 println!("ZFTP_TYPE=A");
21083 0
21084 }
21085 cmd => {
21086 eprintln!("zftp: unknown command: {}", cmd);
21087 1
21088 }
21089 }
21090 }
21091
21092 fn builtin_promptinit(&mut self, _args: &[String]) -> i32 {
21094 self.arrays.insert(
21095 "prompt_themes".to_string(),
21096 vec![
21097 "adam1".to_string(),
21098 "adam2".to_string(),
21099 "bart".to_string(),
21100 "bigfade".to_string(),
21101 "clint".to_string(),
21102 "default".to_string(),
21103 "elite".to_string(),
21104 "elite2".to_string(),
21105 "fade".to_string(),
21106 "fire".to_string(),
21107 "minimal".to_string(),
21108 "off".to_string(),
21109 "oliver".to_string(),
21110 "pws".to_string(),
21111 "redhat".to_string(),
21112 "restore".to_string(),
21113 "suse".to_string(),
21114 "walters".to_string(),
21115 "zefram".to_string(),
21116 ],
21117 );
21118 self.variables
21119 .insert("prompt_theme".to_string(), "default".to_string());
21120 0
21121 }
21122
21123 fn builtin_prompt(&mut self, args: &[String]) -> i32 {
21125 if args.is_empty() {
21126 let theme = self
21127 .variables
21128 .get("prompt_theme")
21129 .cloned()
21130 .unwrap_or_else(|| "default".to_string());
21131 println!("Current prompt theme: {}", theme);
21132 return 0;
21133 }
21134 match args[0].as_str() {
21135 "-l" | "--list" => {
21136 println!("Available prompt themes:");
21137 if let Some(themes) = self.arrays.get("prompt_themes") {
21138 for theme in themes {
21139 println!(" {}", theme);
21140 }
21141 }
21142 }
21143 "-p" | "--preview" => {
21144 let theme = args.get(1).map(|s| s.as_str()).unwrap_or("default");
21145 self.apply_prompt_theme(theme, true);
21146 }
21147 "-h" | "--help" => {
21148 println!("prompt [options] [theme]");
21149 println!(" -l, --list List available themes");
21150 println!(" -p, --preview Preview a theme");
21151 println!(" -s, --setup Set up a theme");
21152 }
21153 _ => {
21154 let theme = if args[0].starts_with('-') {
21155 args.get(1).map(|s| s.as_str()).unwrap_or("default")
21156 } else {
21157 args[0].as_str()
21158 };
21159 self.apply_prompt_theme(theme, false);
21160 }
21161 }
21162 0
21163 }
21164
21165 fn apply_prompt_theme(&mut self, theme: &str, preview: bool) {
21166 let (ps1, rps1) = match theme {
21167 "minimal" => ("%# ", ""),
21168 "off" => ("$ ", ""),
21169 "adam1" => (
21170 "%B%F{cyan}%n@%m %F{blue}%~%f%b %# ",
21171 "%F{yellow}%D{%H:%M}%f",
21172 ),
21173 "redhat" => ("[%n@%m %~]$ ", ""),
21174 _ => ("%n@%m %~ %# ", ""),
21175 };
21176 if preview {
21177 println!("PS1={:?}", ps1);
21178 println!("RPS1={:?}", rps1);
21179 } else {
21180 self.variables.insert("PS1".to_string(), ps1.to_string());
21181 self.variables.insert("RPS1".to_string(), rps1.to_string());
21182 self.variables
21183 .insert("prompt_theme".to_string(), theme.to_string());
21184 }
21185 }
21186
21187 fn builtin_pcre_compile(&mut self, args: &[String]) -> i32 {
21189 use crate::pcre::{pcre_compile, PcreCompileOptions};
21190
21191 let mut pattern = String::new();
21192 let mut options = PcreCompileOptions::default();
21193
21194 for arg in args {
21195 match arg.as_str() {
21196 "-a" => options.anchored = true,
21197 "-i" => options.caseless = true,
21198 "-m" => options.multiline = true,
21199 "-s" => options.dotall = true,
21200 "-x" => options.extended = true,
21201 s if !s.starts_with('-') => pattern = s.to_string(),
21202 _ => {}
21203 }
21204 }
21205
21206 if pattern.is_empty() {
21207 eprintln!("pcre_compile: no pattern specified");
21208 return 1;
21209 }
21210
21211 match pcre_compile(&pattern, &options, &mut self.pcre_state) {
21212 Ok(()) => 0,
21213 Err(e) => {
21214 eprintln!("pcre_compile: {}", e);
21215 1
21216 }
21217 }
21218 }
21219
21220 fn builtin_pcre_match(&mut self, args: &[String]) -> i32 {
21222 use crate::pcre::{pcre_match, PcreMatchOptions};
21223
21224 let mut var_name = "MATCH".to_string();
21225 let mut array_name = "match".to_string();
21226 let mut string = String::new();
21227 let mut i = 0;
21228
21229 while i < args.len() {
21230 match args[i].as_str() {
21231 "-v" => {
21232 i += 1;
21233 if i < args.len() {
21234 var_name = args[i].clone();
21235 }
21236 }
21237 "-a" => {
21238 i += 1;
21239 if i < args.len() {
21240 array_name = args[i].clone();
21241 }
21242 }
21243 s if !s.starts_with('-') => string = s.to_string(),
21244 _ => {}
21245 }
21246 i += 1;
21247 }
21248
21249 let options = PcreMatchOptions {
21250 match_var: Some(var_name.clone()),
21251 array_var: Some(array_name.clone()),
21252 ..Default::default()
21253 };
21254
21255 match pcre_match(&string, &options, &self.pcre_state) {
21256 Ok(result) => {
21257 if result.matched {
21258 if let Some(m) = result.full_match {
21259 self.variables.insert(var_name, m);
21260 }
21261 let matches: Vec<String> =
21262 result.captures.into_iter().filter_map(|c| c).collect();
21263 self.arrays.insert(array_name, matches);
21264 0
21265 } else {
21266 1
21267 }
21268 }
21269 Err(e) => {
21270 eprintln!("pcre_match: {}", e);
21271 1
21272 }
21273 }
21274 }
21275
21276 fn builtin_pcre_study(&mut self, _args: &[String]) -> i32 {
21278 use crate::pcre::pcre_study;
21279
21280 match pcre_study(&self.pcre_state) {
21281 Ok(()) => 0,
21282 Err(e) => {
21283 eprintln!("pcre_study: {}", e);
21284 1
21285 }
21286 }
21287 }
21288
21289 pub fn zfork(&mut self, flags: ForkFlags) -> std::io::Result<ForkResult> {
21296 let can_background = self.options.get("monitor").copied().unwrap_or(false);
21298
21299 unsafe {
21300 match libc::fork() {
21301 -1 => Err(std::io::Error::last_os_error()),
21302 0 => {
21303 if !flags.contains(ForkFlags::NOJOB) && can_background {
21305 let pid = libc::getpid();
21307 if flags.contains(ForkFlags::NEWGRP) {
21308 libc::setpgid(0, 0);
21309 }
21310 if flags.contains(ForkFlags::FGTTY) {
21311 libc::tcsetpgrp(0, pid);
21312 }
21313 }
21314
21315 if !flags.contains(ForkFlags::KEEPSIGS) {
21317 self.reset_signals();
21318 }
21319
21320 Ok(ForkResult::Child)
21321 }
21322 pid => {
21323 if !flags.contains(ForkFlags::NOJOB) {
21325 self.add_child_process(pid);
21327 }
21328 Ok(ForkResult::Parent(pid))
21329 }
21330 }
21331 }
21332 }
21333
21334 fn add_child_process(&mut self, pid: i32) {
21336 self.variables.insert("!".to_string(), pid.to_string());
21338 }
21339
21340 fn reset_signals(&self) {
21342 unsafe {
21343 libc::signal(libc::SIGINT, libc::SIG_DFL);
21344 libc::signal(libc::SIGQUIT, libc::SIG_DFL);
21345 libc::signal(libc::SIGTERM, libc::SIG_DFL);
21346 libc::signal(libc::SIGTSTP, libc::SIG_DFL);
21347 libc::signal(libc::SIGTTIN, libc::SIG_DFL);
21348 libc::signal(libc::SIGTTOU, libc::SIG_DFL);
21349 libc::signal(libc::SIGCHLD, libc::SIG_DFL);
21350 }
21351 }
21352
21353 pub fn zexecve(&self, cmd: &str, args: &[String]) -> ! {
21356 use std::ffi::CString;
21357 use std::os::unix::ffi::OsStrExt;
21358
21359 let c_cmd = CString::new(cmd).expect("CString::new failed");
21360
21361 let c_args: Vec<CString> = std::iter::once(c_cmd.clone())
21363 .chain(args.iter().map(|s| CString::new(s.as_str()).unwrap()))
21364 .collect();
21365
21366 let c_argv: Vec<*const libc::c_char> = c_args
21367 .iter()
21368 .map(|s| s.as_ptr())
21369 .chain(std::iter::once(std::ptr::null()))
21370 .collect();
21371
21372 let env_vars: Vec<CString> = std::env::vars()
21374 .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
21375 .collect();
21376
21377 let c_envp: Vec<*const libc::c_char> = env_vars
21378 .iter()
21379 .map(|s| s.as_ptr())
21380 .chain(std::iter::once(std::ptr::null()))
21381 .collect();
21382
21383 unsafe {
21384 libc::execve(c_cmd.as_ptr(), c_argv.as_ptr(), c_envp.as_ptr());
21385 eprintln!(
21387 "zshrs: exec failed: {}: {}",
21388 cmd,
21389 std::io::Error::last_os_error()
21390 );
21391 std::process::exit(127);
21392 }
21393 }
21394
21395 pub fn entersubsh(&mut self, flags: SubshellFlags) {
21398 let level = self
21400 .get_variable("ZSH_SUBSHELL")
21401 .parse::<i32>()
21402 .unwrap_or(0);
21403 self.variables
21404 .insert("ZSH_SUBSHELL".to_string(), (level + 1).to_string());
21405
21406 if flags.contains(SubshellFlags::NOMONITOR) {
21408 self.options.insert("monitor".to_string(), false);
21409 }
21410
21411 if !flags.contains(SubshellFlags::KEEPFDS) {
21413 self.close_extra_fds();
21414 }
21415
21416 if !flags.contains(SubshellFlags::KEEPTRAPS) {
21418 self.reset_traps();
21419 }
21420 }
21421
21422 fn close_extra_fds(&self) {
21424 for fd in 10..256 {
21426 unsafe {
21427 libc::close(fd);
21428 }
21429 }
21430 }
21431
21432 fn reset_traps(&mut self) {
21434 self.traps.clear();
21435 }
21436
21437 pub fn doshfunc(
21440 &mut self,
21441 name: &str,
21442 func: &ShellCommand,
21443 args: &[String],
21444 ) -> Result<i32, String> {
21445 let old_argv = self.positional_params.clone();
21447 let old_funcstack = self.arrays.get("funcstack").cloned();
21448 let old_funcsourcetrace = self.arrays.get("funcsourcetrace").cloned();
21449
21450 self.positional_params = args.to_vec();
21452
21453 let mut funcstack = old_funcstack.clone().unwrap_or_default();
21455 funcstack.insert(0, name.to_string());
21456 self.arrays.insert("funcstack".to_string(), funcstack);
21457
21458 let result = self.execute_command(func);
21460
21461 self.positional_params = old_argv;
21463 if let Some(fs) = old_funcstack {
21464 self.arrays.insert("funcstack".to_string(), fs);
21465 } else {
21466 self.arrays.remove("funcstack");
21467 }
21468 if let Some(fst) = old_funcsourcetrace {
21469 self.arrays.insert("funcsourcetrace".to_string(), fst);
21470 }
21471
21472 result
21473 }
21474
21475 pub fn execarith(&mut self, expr: &str) -> i32 {
21478 let result = self.eval_arith_expr(expr);
21479 if result == 0 {
21480 1
21481 } else {
21482 0
21483 }
21484 }
21485
21486 pub fn execcond(&mut self, cond: &CondExpr) -> i32 {
21489 if self.eval_cond_expr(cond) {
21490 0
21491 } else {
21492 1
21493 }
21494 }
21495
21496 pub fn exectime(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
21499 use std::time::Instant;
21500
21501 let start = Instant::now();
21502 let result = self.execute_command(cmd);
21503 let elapsed = start.elapsed();
21504
21505 let user_time = elapsed.as_secs_f64() * 0.7; let sys_time = elapsed.as_secs_f64() * 0.1;
21508 let real_time = elapsed.as_secs_f64();
21509
21510 eprintln!(
21511 "{:.2}s user {:.2}s system {:.0}% cpu {:.3} total",
21512 user_time,
21513 sys_time,
21514 ((user_time + sys_time) / real_time * 100.0).min(100.0),
21515 real_time
21516 );
21517
21518 result
21519 }
21520
21521 pub fn findcmd(&self, name: &str, do_hash: bool) -> Option<String> {
21524 if do_hash {
21526 if let Some(path) = self.command_hash.get(name) {
21527 if std::path::Path::new(path).exists() {
21528 return Some(path.clone());
21529 }
21530 }
21531 }
21532
21533 if let Ok(path_var) = std::env::var("PATH") {
21535 for dir in path_var.split(':') {
21536 let full_path = format!("{}/{}", dir, name);
21537 if std::path::Path::new(&full_path).is_file() {
21538 return Some(full_path);
21539 }
21540 }
21541 }
21542
21543 None
21544 }
21545
21546 pub fn hashcmd(&mut self, name: &str, path: &str) {
21549 self.command_hash.insert(name.to_string(), path.to_string());
21550 }
21551
21552 pub fn iscom(&self, name: &str) -> bool {
21555 if self.is_builtin_cmd(name) {
21557 return true;
21558 }
21559
21560 if self.functions.contains_key(name) {
21562 return true;
21563 }
21564
21565 if self.aliases.contains_key(name) {
21567 return true;
21568 }
21569
21570 self.findcmd(name, true).is_some()
21572 }
21573
21574 fn is_builtin_cmd(&self, name: &str) -> bool {
21576 BUILTIN_SET.contains(name)
21577 }
21578
21579 pub fn closem(&self, exceptions: &[i32]) {
21582 for fd in 3..256 {
21583 if !exceptions.contains(&fd) {
21584 unsafe {
21585 libc::close(fd);
21586 }
21587 }
21588 }
21589 }
21590
21591 pub fn mpipe(&self) -> std::io::Result<(i32, i32)> {
21594 let mut fds = [0i32; 2];
21595 let result = unsafe { libc::pipe(fds.as_mut_ptr()) };
21596 if result == -1 {
21597 Err(std::io::Error::last_os_error())
21598 } else {
21599 Ok((fds[0], fds[1]))
21600 }
21601 }
21602
21603 pub fn addfd(&self, fd: i32, target_fd: i32, mode: RedirMode) -> std::io::Result<()> {
21606 match mode {
21607 RedirMode::Dup => {
21608 if fd != target_fd {
21609 unsafe {
21610 if libc::dup2(fd, target_fd) == -1 {
21611 return Err(std::io::Error::last_os_error());
21612 }
21613 }
21614 }
21615 }
21616 RedirMode::Close => unsafe {
21617 libc::close(target_fd);
21618 },
21619 }
21620 Ok(())
21621 }
21622
21623 pub fn gethere(&mut self, terminator: &str, strip_tabs: bool) -> String {
21626 let mut content = String::new();
21627
21628 if strip_tabs {
21632 content = content
21633 .lines()
21634 .map(|line| line.trim_start_matches('\t'))
21635 .collect::<Vec<_>>()
21636 .join("\n");
21637 }
21638
21639 content
21640 }
21641
21642 pub fn getherestr(&mut self, word: &str) -> String {
21645 let expanded = self.expand_string(word);
21646 format!("{}\n", expanded)
21647 }
21648
21649 pub fn resolvebuiltin(&self, name: &str) -> Option<BuiltinType> {
21652 if self.is_builtin_cmd(name) {
21653 Some(BuiltinType::Normal)
21654 } else {
21655 None
21657 }
21658 }
21659
21660 pub fn cancd(&self, path_str: &str) -> bool {
21663 use std::os::unix::fs::PermissionsExt;
21664
21665 let path = std::path::Path::new(path_str);
21666 if !path.is_dir() {
21667 return false;
21668 }
21669
21670 if let Ok(meta) = path.metadata() {
21671 let mode = meta.permissions().mode();
21672 let uid = unsafe { libc::getuid() };
21674 let gid = unsafe { libc::getgid() };
21675 let file_uid = meta.uid();
21676 let file_gid = meta.gid();
21677
21678 if uid == file_uid {
21679 return (mode & 0o100) != 0;
21680 } else if gid == file_gid {
21681 return (mode & 0o010) != 0;
21682 } else {
21683 return (mode & 0o001) != 0;
21684 }
21685 }
21686
21687 false
21688 }
21689
21690 pub fn commandnotfound(&mut self, name: &str, args: &[String]) -> i32 {
21693 if self.functions.contains_key("command_not_found_handler") {
21695 let mut handler_args = vec![name.to_string()];
21696 handler_args.extend(args.iter().cloned());
21697
21698 if let Some(func) = self.functions.get("command_not_found_handler").cloned() {
21699 if let Ok(code) = self.doshfunc("command_not_found_handler", &func, &handler_args) {
21700 return code;
21701 }
21702 }
21703 }
21704
21705 eprintln!("zshrs: command not found: {}", name);
21706 127
21707 }
21708}
21709
21710use std::os::unix::fs::MetadataExt;
21711
21712bitflags::bitflags! {
21713 #[derive(Debug, Clone, Copy, Default)]
21715 pub struct ForkFlags: u32 {
21716 const NOJOB = 1 << 0; const NEWGRP = 1 << 1; const FGTTY = 1 << 2; const KEEPSIGS = 1 << 3; }
21721}
21722
21723bitflags::bitflags! {
21724 #[derive(Debug, Clone, Copy, Default)]
21726 pub struct SubshellFlags: u32 {
21727 const NOMONITOR = 1 << 0; const KEEPFDS = 1 << 1; const KEEPTRAPS = 1 << 2; }
21731}
21732
21733#[derive(Debug)]
21735pub enum ForkResult {
21736 Parent(i32), Child,
21738}
21739
21740#[derive(Debug, Clone, Copy)]
21742pub enum RedirMode {
21743 Dup,
21744 Close,
21745}
21746
21747#[derive(Debug, Clone, Copy)]
21749pub enum BuiltinType {
21750 Normal,
21751 Disabled,
21752}