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 vm.register_builtin(BUILTIN_CAT, |vm, argc| {
818 let args = pop_args(vm, argc);
819 let status = with_executor(|exec| exec.builtin_cat(&args));
820 Value::Status(status)
821 });
822
823 vm.register_builtin(BUILTIN_HEAD, |vm, argc| {
824 let args = pop_args(vm, argc);
825 let status = with_executor(|exec| exec.builtin_head(&args));
826 Value::Status(status)
827 });
828
829 vm.register_builtin(BUILTIN_TAIL, |vm, argc| {
830 let args = pop_args(vm, argc);
831 let status = with_executor(|exec| exec.builtin_tail(&args));
832 Value::Status(status)
833 });
834
835 vm.register_builtin(BUILTIN_WC, |vm, argc| {
836 let args = pop_args(vm, argc);
837 let status = with_executor(|exec| exec.builtin_wc(&args));
838 Value::Status(status)
839 });
840
841 vm.register_builtin(BUILTIN_BASENAME, |vm, argc| {
842 let args = pop_args(vm, argc);
843 let status = with_executor(|exec| exec.builtin_basename(&args));
844 Value::Status(status)
845 });
846
847 vm.register_builtin(BUILTIN_DIRNAME, |vm, argc| {
848 let args = pop_args(vm, argc);
849 let status = with_executor(|exec| exec.builtin_dirname(&args));
850 Value::Status(status)
851 });
852
853 vm.register_builtin(BUILTIN_TOUCH, |vm, argc| {
854 let args = pop_args(vm, argc);
855 let status = with_executor(|exec| exec.builtin_touch(&args));
856 Value::Status(status)
857 });
858
859 vm.register_builtin(BUILTIN_REALPATH, |vm, argc| {
860 let args = pop_args(vm, argc);
861 let status = with_executor(|exec| exec.builtin_realpath(&args));
862 Value::Status(status)
863 });
864
865 vm.register_builtin(BUILTIN_SORT, |vm, argc| {
866 let args = pop_args(vm, argc);
867 let status = with_executor(|exec| exec.builtin_sort(&args));
868 Value::Status(status)
869 });
870
871 vm.register_builtin(BUILTIN_FIND, |vm, argc| {
872 let args = pop_args(vm, argc);
873 let status = with_executor(|exec| exec.builtin_find(&args));
874 Value::Status(status)
875 });
876
877 vm.register_builtin(BUILTIN_UNIQ, |vm, argc| {
878 let args = pop_args(vm, argc);
879 let status = with_executor(|exec| exec.builtin_uniq(&args));
880 Value::Status(status)
881 });
882
883 vm.register_builtin(BUILTIN_CUT, |vm, argc| {
884 let args = pop_args(vm, argc);
885 let status = with_executor(|exec| exec.builtin_cut(&args));
886 Value::Status(status)
887 });
888
889 vm.register_builtin(BUILTIN_TR, |vm, argc| {
890 let args = pop_args(vm, argc);
891 let status = with_executor(|exec| exec.builtin_tr(&args));
892 Value::Status(status)
893 });
894
895 vm.register_builtin(BUILTIN_SEQ, |vm, argc| {
896 let args = pop_args(vm, argc);
897 let status = with_executor(|exec| exec.builtin_seq(&args));
898 Value::Status(status)
899 });
900
901 vm.register_builtin(BUILTIN_REV, |vm, argc| {
902 let args = pop_args(vm, argc);
903 let status = with_executor(|exec| exec.builtin_rev(&args));
904 Value::Status(status)
905 });
906
907 vm.register_builtin(BUILTIN_TEE, |vm, argc| {
908 let args = pop_args(vm, argc);
909 let status = with_executor(|exec| exec.builtin_tee(&args));
910 Value::Status(status)
911 });
912
913 vm.register_builtin(BUILTIN_SLEEP, |vm, argc| {
914 let args = pop_args(vm, argc);
915 let status = with_executor(|exec| exec.builtin_sleep(&args));
916 Value::Status(status)
917 });
918
919 vm.register_builtin(BUILTIN_WHOAMI, |vm, argc| {
920 let args = pop_args(vm, argc);
921 let status = with_executor(|exec| exec.builtin_whoami(&args));
922 Value::Status(status)
923 });
924
925 vm.register_builtin(BUILTIN_ID, |vm, argc| {
926 let args = pop_args(vm, argc);
927 let status = with_executor(|exec| exec.builtin_id(&args));
928 Value::Status(status)
929 });
930
931 vm.register_builtin(BUILTIN_HOSTNAME, |vm, argc| {
932 let args = pop_args(vm, argc);
933 let status = with_executor(|exec| exec.builtin_hostname(&args));
934 Value::Status(status)
935 });
936
937 vm.register_builtin(BUILTIN_UNAME, |vm, argc| {
938 let args = pop_args(vm, argc);
939 let status = with_executor(|exec| exec.builtin_uname(&args));
940 Value::Status(status)
941 });
942
943 vm.register_builtin(BUILTIN_DATE, |vm, argc| {
944 let args = pop_args(vm, argc);
945 let status = with_executor(|exec| exec.builtin_date(&args));
946 Value::Status(status)
947 });
948
949 vm.register_builtin(BUILTIN_MKTEMP, |vm, argc| {
950 let args = pop_args(vm, argc);
951 let status = with_executor(|exec| exec.builtin_mktemp(&args));
952 Value::Status(status)
953 });
954}
955
956#[inline]
958fn pop_args(vm: &mut fusevm::VM, argc: u8) -> Vec<String> {
959 let mut args = Vec::with_capacity(argc as usize);
960 for _ in 0..argc {
961 args.push(vm.pop().to_str());
962 }
963 args.reverse(); args
965}
966
967fn intercept_matches(pattern: &str, cmd_name: &str, full_cmd: &str) -> bool {
970 if pattern == "*" || pattern == "all" {
971 return true;
972 }
973 if pattern == cmd_name {
974 return true;
975 }
976 if pattern.contains('*') || pattern.contains('?') {
978 if let Ok(pat) = glob::Pattern::new(pattern) {
979 return pat.matches(cmd_name) || pat.matches(full_cmd);
980 }
981 }
982 false
983}
984
985fn cached_regex(pattern: &str) -> Option<regex::Regex> {
987 let mut cache = REGEX_CACHE.lock();
988 if let Some(re) = cache.get(pattern) {
989 return Some(re.clone());
990 }
991 match regex::Regex::new(pattern) {
992 Ok(re) => {
993 cache.insert(pattern.to_string(), re.clone());
994 Some(re)
995 }
996 Err(_) => None,
997 }
998}
999
1000static ZSH_OPTIONS_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1002 [
1003 "aliases",
1004 "allexport",
1005 "alwayslastprompt",
1006 "alwaystoend",
1007 "appendcreate",
1008 "appendhistory",
1009 "autocd",
1010 "autocontinue",
1011 "autolist",
1012 "automenu",
1013 "autonamedirs",
1014 "autoparamkeys",
1015 "autoparamslash",
1016 "autopushd",
1017 "autoremoveslash",
1018 "autoresume",
1019 "badpattern",
1020 "banghist",
1021 "bareglobqual",
1022 "bashautolist",
1023 "bashrematch",
1024 "beep",
1025 "bgnice",
1026 "braceccl",
1027 "bsdecho",
1028 "caseglob",
1029 "casematch",
1030 "cbases",
1031 "cdablevars",
1032 "cdsilent",
1033 "chasedots",
1034 "chaselinks",
1035 "checkjobs",
1036 "checkrunningjobs",
1037 "clobber",
1038 "combiningchars",
1039 "completealiases",
1040 "completeinword",
1041 "continueonerror",
1042 "correct",
1043 "correctall",
1044 "cprecedences",
1045 "cshjunkiehistory",
1046 "cshjunkieloops",
1047 "cshjunkiequotes",
1048 "cshnullcmd",
1049 "cshnullglob",
1050 "debugbeforecmd",
1051 "dotglob",
1052 "dvorak",
1053 "emacs",
1054 "equals",
1055 "errexit",
1056 "errreturn",
1057 "evallineno",
1058 "exec",
1059 "extendedglob",
1060 "extendedhistory",
1061 "flowcontrol",
1062 "forcefloat",
1063 "functionargzero",
1064 "glob",
1065 "globassign",
1066 "globcomplete",
1067 "globdots",
1068 "globstarshort",
1069 "globsubst",
1070 "globalexport",
1071 "globalrcs",
1072 "hashall",
1073 "hashcmds",
1074 "hashdirs",
1075 "hashexecutablesonly",
1076 "hashlistall",
1077 "histallowclobber",
1078 "histappend",
1079 "histbeep",
1080 "histexpand",
1081 "histexpiredupsfirst",
1082 "histfcntllock",
1083 "histfindnodups",
1084 "histignorealldups",
1085 "histignoredups",
1086 "histignorespace",
1087 "histlexwords",
1088 "histnofunctions",
1089 "histnostore",
1090 "histreduceblanks",
1091 "histsavebycopy",
1092 "histsavenodups",
1093 "histsubstpattern",
1094 "histverify",
1095 "hup",
1096 "ignorebraces",
1097 "ignoreclosebraces",
1098 "ignoreeof",
1099 "incappendhistory",
1100 "incappendhistorytime",
1101 "interactive",
1102 "interactivecomments",
1103 "ksharrays",
1104 "kshautoload",
1105 "kshglob",
1106 "kshoptionprint",
1107 "kshtypeset",
1108 "kshzerosubscript",
1109 "listambiguous",
1110 "listbeep",
1111 "listpacked",
1112 "listrowsfirst",
1113 "listtypes",
1114 "localloops",
1115 "localoptions",
1116 "localpatterns",
1117 "localtraps",
1118 "log",
1119 "login",
1120 "longlistjobs",
1121 "magicequalsubst",
1122 "mailwarn",
1123 "mailwarning",
1124 "markdirs",
1125 "menucomplete",
1126 "monitor",
1127 "multibyte",
1128 "multifuncdef",
1129 "multios",
1130 "nomatch",
1131 "notify",
1132 "nullglob",
1133 "numericglobsort",
1134 "octalzeroes",
1135 "onecmd",
1136 "overstrike",
1137 "pathdirs",
1138 "pathscript",
1139 "physical",
1140 "pipefail",
1141 "posixaliases",
1142 "posixargzero",
1143 "posixbuiltins",
1144 "posixcd",
1145 "posixidentifiers",
1146 "posixjobs",
1147 "posixstrings",
1148 "posixtraps",
1149 "printeightbit",
1150 "printexitvalue",
1151 "privileged",
1152 "promptbang",
1153 "promptcr",
1154 "promptpercent",
1155 "promptsp",
1156 "promptsubst",
1157 "promptvars",
1158 "pushdignoredups",
1159 "pushdminus",
1160 "pushdsilent",
1161 "pushdtohome",
1162 "rcexpandparam",
1163 "rcquotes",
1164 "rcs",
1165 "recexact",
1166 "rematchpcre",
1167 "restricted",
1168 "rmstarsilent",
1169 "rmstarwait",
1170 "sharehistory",
1171 "shfileexpansion",
1172 "shglob",
1173 "shinstdin",
1174 "shnullcmd",
1175 "shoptionletters",
1176 "shortloops",
1177 "shortrepeat",
1178 "shwordsplit",
1179 "singlecommand",
1180 "singlelinezle",
1181 "sourcetrace",
1182 "stdin",
1183 "sunkeyboardhack",
1184 "trackall",
1185 "transientrprompt",
1186 "trapsasync",
1187 "typesetsilent",
1188 "typesettounset",
1189 "unset",
1190 "verbose",
1191 "vi",
1192 "warncreateglobal",
1193 "warnnestedvar",
1194 "xtrace",
1195 "zle",
1196 ]
1197 .into_iter()
1198 .collect()
1199});
1200
1201static BUILTIN_SET: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
1203 [
1204 "cd",
1205 "chdir",
1206 "pwd",
1207 "echo",
1208 "export",
1209 "unset",
1210 "source",
1211 "exit",
1212 "return",
1213 "bye",
1214 "logout",
1215 "log",
1216 "true",
1217 "false",
1218 "test",
1219 "local",
1220 "declare",
1221 "typeset",
1222 "read",
1223 "shift",
1224 "eval",
1225 "jobs",
1226 "fg",
1227 "bg",
1228 "kill",
1229 "disown",
1230 "wait",
1231 "autoload",
1232 "history",
1233 "fc",
1234 "trap",
1235 "suspend",
1236 "alias",
1237 "unalias",
1238 "set",
1239 "shopt",
1240 "setopt",
1241 "unsetopt",
1242 "getopts",
1243 "type",
1244 "hash",
1245 "command",
1246 "builtin",
1247 "let",
1248 "pushd",
1249 "popd",
1250 "dirs",
1251 "printf",
1252 "break",
1253 "continue",
1254 "disable",
1255 "enable",
1256 "emulate",
1257 "exec",
1258 "float",
1259 "integer",
1260 "functions",
1261 "print",
1262 "whence",
1263 "where",
1264 "which",
1265 "ulimit",
1266 "limit",
1267 "unlimit",
1268 "umask",
1269 "rehash",
1270 "unhash",
1271 "times",
1272 "zmodload",
1273 "r",
1274 "ttyctl",
1275 "noglob",
1276 "zstat",
1277 "stat",
1278 "strftime",
1279 "zsleep",
1280 "zln",
1281 "zmv",
1282 "zcp",
1283 "coproc",
1284 "zparseopts",
1285 "readonly",
1286 "unfunction",
1287 "getln",
1288 "pushln",
1289 "bindkey",
1290 "zle",
1291 "sched",
1292 "zformat",
1293 "zcompile",
1294 "vared",
1295 "echotc",
1296 "echoti",
1297 "zpty",
1298 "zprof",
1299 "zsocket",
1300 "ztcp",
1301 "zregexparse",
1302 "clone",
1303 "comparguments",
1304 "compcall",
1305 "compctl",
1306 "compdef",
1307 "compdescribe",
1308 "compfiles",
1309 "compgroups",
1310 "compinit",
1311 "compquote",
1312 "comptags",
1313 "comptry",
1314 "compvalues",
1315 "cdreplay",
1316 "cap",
1317 "getcap",
1318 "setcap",
1319 "zftp",
1320 "zcurses",
1321 "sysread",
1322 "syswrite",
1323 "syserror",
1324 "sysopen",
1325 "sysseek",
1326 "private",
1327 "zgetattr",
1328 "zsetattr",
1329 "zdelattr",
1330 "zlistattr",
1331 "[",
1332 ".",
1333 ":",
1334 "compgen",
1335 "complete",
1336 ]
1337 .into_iter()
1338 .collect()
1339});
1340
1341fn float_to_hex(val: f64, uppercase: bool) -> String {
1343 if val.is_nan() {
1344 return if uppercase { "NAN" } else { "nan" }.to_string();
1345 }
1346 if val.is_infinite() {
1347 return if val > 0.0 {
1348 if uppercase {
1349 "INF"
1350 } else {
1351 "inf"
1352 }
1353 } else {
1354 if uppercase {
1355 "-INF"
1356 } else {
1357 "-inf"
1358 }
1359 }
1360 .to_string();
1361 }
1362 if val == 0.0 {
1363 let sign = if val.is_sign_negative() { "-" } else { "" };
1364 return if uppercase {
1365 format!("{}0X0P+0", sign)
1366 } else {
1367 format!("{}0x0p+0", sign)
1368 };
1369 }
1370
1371 let sign = if val < 0.0 { "-" } else { "" };
1372 let abs_val = val.abs();
1373 let bits = abs_val.to_bits();
1374 let exponent = ((bits >> 52) & 0x7ff) as i32 - 1023;
1375 let mantissa = bits & 0xfffffffffffff;
1376
1377 let hex_mantissa = format!("{:013x}", mantissa);
1378 let hex_mantissa = hex_mantissa.trim_end_matches('0');
1379 let hex_mantissa = if hex_mantissa.is_empty() {
1380 "0"
1381 } else {
1382 hex_mantissa
1383 };
1384
1385 if uppercase {
1386 format!("{}0X1.{}P{:+}", sign, hex_mantissa.to_uppercase(), exponent)
1387 } else {
1388 format!("{}0x1.{}p{:+}", sign, hex_mantissa, exponent)
1389 }
1390}
1391
1392fn shell_quote(s: &str) -> String {
1394 if s.is_empty() {
1395 return "''".to_string();
1396 }
1397 let needs_quotes = s.chars().any(|c| {
1399 matches!(
1400 c,
1401 ' ' | '\t'
1402 | '\n'
1403 | '\''
1404 | '"'
1405 | '\\'
1406 | '$'
1407 | '`'
1408 | '!'
1409 | '*'
1410 | '?'
1411 | '['
1412 | ']'
1413 | '{'
1414 | '}'
1415 | '('
1416 | ')'
1417 | '<'
1418 | '>'
1419 | '|'
1420 | '&'
1421 | ';'
1422 | '#'
1423 | '~'
1424 )
1425 });
1426 if !needs_quotes {
1427 return s.to_string();
1428 }
1429 format!("'{}'", s.replace('\'', "'\\''"))
1431}
1432
1433fn shell_quote_value(s: &str) -> String {
1436 if s.is_empty() {
1437 return "''".to_string();
1438 }
1439 let needs_quotes = s.chars().any(|c| {
1440 matches!(
1441 c,
1442 ' ' | '\t'
1443 | '\n'
1444 | '\''
1445 | '"'
1446 | '\\'
1447 | '$'
1448 | '`'
1449 | '!'
1450 | '*'
1451 | '?'
1452 | '['
1453 | ']'
1454 | '{'
1455 | '}'
1456 | '('
1457 | ')'
1458 | '<'
1459 | '>'
1460 | '|'
1461 | '&'
1462 | ';'
1463 | '#'
1464 | '~'
1465 | '^'
1466 )
1467 });
1468 if !needs_quotes {
1469 return s.to_string();
1470 }
1471 format!("'{}'", s.replace('\'', "'\\''"))
1472}
1473
1474use crate::jobs::{continue_job, wait_for_child, wait_for_job, JobState, JobTable};
1475use crate::parser::{
1476 CaseTerminator, CompoundCommand, CondExpr, ListOp, Redirect, RedirectOp, ShellCommand,
1477 ShellParser, ShellWord, SimpleCommand, VarModifier, ZshParamFlag,
1478};
1479use crate::zwc::ZwcFile;
1480use std::collections::HashMap;
1481use std::env;
1482use std::fs::{self, File, OpenOptions};
1483use std::io;
1484use std::path::{Path, PathBuf};
1485use std::process::{Child, Command, Stdio};
1486
1487#[derive(Debug, Clone, Default)]
1489pub struct CompSpec {
1490 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>, }
1498
1499#[derive(Debug, Clone)]
1501pub struct CompMatch {
1502 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, }
1516
1517impl Default for CompMatch {
1518 fn default() -> Self {
1519 Self {
1520 word: String::new(),
1521 display: None,
1522 prefix: None,
1523 suffix: None,
1524 hidden_prefix: None,
1525 hidden_suffix: None,
1526 ignored_prefix: None,
1527 ignored_suffix: None,
1528 group: None,
1529 description: None,
1530 remove_suffix: None,
1531 file_match: false,
1532 quote_match: false,
1533 }
1534 }
1535}
1536
1537#[derive(Debug, Clone, Default)]
1539pub struct CompGroup {
1540 pub name: String,
1541 pub matches: Vec<CompMatch>,
1542 pub explanation: Option<String>,
1543 pub sorted: bool,
1544}
1545
1546#[derive(Debug, Clone, Default)]
1548pub struct CompState {
1549 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, }
1575
1576#[derive(Debug, Clone)]
1578pub struct ZStyle {
1579 pub pattern: String,
1580 pub style: String,
1581 pub values: Vec<String>,
1582}
1583
1584bitflags::bitflags! {
1585 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1587 pub struct AutoloadFlags: u32 {
1588 const NO_ALIAS = 0b00000001; const ZSH_STYLE = 0b00000010; const KSH_STYLE = 0b00000100; const TRACE = 0b00001000; const USE_CALLER_DIR = 0b00010000; const LOADED = 0b00100000; }
1595}
1596
1597pub struct ZptyState {
1599 pub pid: u32,
1600 pub cmd: String,
1601 pub stdin: Option<std::process::ChildStdin>,
1602 pub stdout: Option<std::process::ChildStdout>,
1603 pub child: Option<std::process::Child>,
1604}
1605
1606pub struct ScheduledCommand {
1608 pub id: u32,
1609 pub run_at: std::time::SystemTime,
1610 pub command: String,
1611}
1612
1613#[derive(Clone, Default)]
1615pub struct ProfileEntry {
1616 pub calls: u64,
1617 pub total_time_us: u64,
1618 pub self_time_us: u64,
1619}
1620
1621pub struct UnixSocketState {
1623 pub path: Option<PathBuf>,
1624 pub listening: bool,
1625 pub stream: Option<std::os::unix::net::UnixStream>,
1626 pub listener: Option<std::os::unix::net::UnixListener>,
1627}
1628
1629pub struct ShellExecutor {
1630 pub functions: HashMap<String, ShellCommand>,
1631 pub aliases: HashMap<String, String>,
1632 pub global_aliases: HashMap<String, String>, pub suffix_aliases: HashMap<String, String>, pub last_status: i32,
1635 pub variables: HashMap<String, String>,
1636 pub arrays: HashMap<String, Vec<String>>,
1637 pub assoc_arrays: HashMap<String, HashMap<String, String>>, pub jobs: JobTable,
1639 pub fpath: Vec<PathBuf>,
1640 pub zwc_cache: HashMap<PathBuf, ZwcFile>,
1641 pub positional_params: Vec<String>,
1642 pub history: Option<HistoryEngine>,
1643 process_sub_counter: u32,
1644 pub traps: HashMap<String, String>,
1645 pub options: HashMap<String, bool>,
1646 pub completions: HashMap<String, CompSpec>, pub dir_stack: Vec<PathBuf>,
1648 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>)>,
1662 pub local_scope_depth: usize,
1664 pub autoload_pending: HashMap<String, AutoloadFlags>, pub hook_functions: HashMap<String, Vec<String>>, pub named_dirs: HashMap<String, PathBuf>, pub zptys: HashMap<String, ZptyState>,
1671 pub open_fds: HashMap<i32, std::fs::File>,
1673 pub next_fd: i32,
1674 pub scheduled_commands: Vec<ScheduledCommand>,
1676 pub profile_data: HashMap<String, ProfileEntry>,
1678 pub profiling_enabled: bool,
1679 pub unix_sockets: HashMap<i32, UnixSocketState>,
1681 pub compsys_cache: Option<CompsysCache>,
1683 pub compinit_pending: Option<(
1685 std::sync::mpsc::Receiver<CompInitBgResult>,
1686 std::time::Instant,
1687 )>,
1688 pub plugin_cache: Option<crate::plugin_cache::PluginCache>,
1690 pub deferred_compdefs: Vec<Vec<String>>,
1692 pub command_hash: HashMap<String, String>,
1694 returning: Option<i32>, breaking: i32, continuing: i32, pub pcre_state: PcreState,
1700 pub tcp_sessions: TcpSessions,
1701 pub zftp: Zftp,
1702 pub profiler: Profiler,
1703 pub style_table: StyleTable,
1704 pub zsh_compat: bool,
1706 pub posix_mode: bool,
1708 pub worker_pool: std::sync::Arc<crate::worker::WorkerPool>,
1710 pub intercepts: Vec<Intercept>,
1713 pub async_jobs: HashMap<u32, crossbeam_channel::Receiver<(i32, String)>>,
1715 pub next_async_id: u32,
1717 pub defer_stack: Vec<Vec<String>>,
1719}
1720
1721impl ShellExecutor {
1722 pub fn new() -> Self {
1723 tracing::debug!("ShellExecutor::new() initializing");
1724 let fpath = env::var("FPATH")
1726 .unwrap_or_default()
1727 .split(':')
1728 .filter(|s| !s.is_empty())
1729 .map(PathBuf::from)
1730 .collect();
1731
1732 let history = HistoryEngine::new().ok();
1733
1734 let mut variables = HashMap::new();
1736 variables.insert("ZSH_VERSION".to_string(), "5.9".to_string());
1737 variables.insert(
1738 "ZSH_PATCHLEVEL".to_string(),
1739 "zsh-5.9-0-g73d3173".to_string(),
1740 );
1741 variables.insert("ZSH_NAME".to_string(), "zsh".to_string());
1742 variables.insert(
1743 "SHLVL".to_string(),
1744 env::var("SHLVL")
1745 .map(|v| {
1746 v.parse::<i32>()
1747 .map(|n| (n + 1).to_string())
1748 .unwrap_or_else(|_| "1".to_string())
1749 })
1750 .unwrap_or_else(|_| "1".to_string()),
1751 );
1752
1753 Self {
1754 functions: HashMap::new(),
1755 aliases: HashMap::new(),
1756 global_aliases: HashMap::new(),
1757 suffix_aliases: HashMap::new(),
1758 last_status: 0,
1759 variables,
1760 arrays: {
1761 let mut a = HashMap::new();
1762 let path_dirs: Vec<String> = env::var("PATH")
1764 .unwrap_or_default()
1765 .split(':')
1766 .map(|s| s.to_string())
1767 .collect();
1768 a.insert("path".to_string(), path_dirs);
1769 a
1770 },
1771 assoc_arrays: HashMap::new(),
1772 jobs: JobTable::new(),
1773 fpath,
1774 zwc_cache: HashMap::new(),
1775 positional_params: Vec::new(),
1776 history,
1777 completions: HashMap::new(),
1778 dir_stack: Vec::new(),
1779 process_sub_counter: 0,
1780 traps: HashMap::new(),
1781 options: Self::default_options(),
1782 comp_matches: Vec::new(),
1784 comp_groups: Vec::new(),
1785 comp_state: CompState::default(),
1786 zstyles: Vec::new(),
1787 comp_words: Vec::new(),
1788 comp_current: 0,
1789 comp_prefix: String::new(),
1790 comp_suffix: String::new(),
1791 comp_iprefix: String::new(),
1792 comp_isuffix: String::new(),
1793 readonly_vars: std::collections::HashSet::new(),
1794 local_save_stack: Vec::new(),
1795 local_scope_depth: 0,
1796 autoload_pending: HashMap::new(),
1797 hook_functions: HashMap::new(),
1798 named_dirs: HashMap::new(),
1799 zptys: HashMap::new(),
1800 open_fds: HashMap::new(),
1801 next_fd: 10,
1802 scheduled_commands: Vec::new(),
1803 profile_data: HashMap::new(),
1804 profiling_enabled: false,
1805 unix_sockets: HashMap::new(),
1806 compsys_cache: {
1807 let cache_path = compsys::cache::default_cache_path();
1808 if cache_path.exists() {
1809 let db_size = std::fs::metadata(&cache_path).map(|m| m.len()).unwrap_or(0);
1810 match CompsysCache::open(&cache_path) {
1811 Ok(c) => {
1812 tracing::info!(
1813 db_bytes = db_size,
1814 path = %cache_path.display(),
1815 "compsys: sqlite cache opened"
1816 );
1817 Some(c)
1818 }
1819 Err(e) => {
1820 tracing::warn!(error = %e, "compsys: failed to open cache");
1821 None
1822 }
1823 }
1824 } else {
1825 tracing::debug!("compsys: no cache at {}", cache_path.display());
1826 None
1827 }
1828 },
1829 compinit_pending: None, plugin_cache: {
1831 let pc_path = crate::plugin_cache::default_cache_path();
1832 if let Some(parent) = pc_path.parent() {
1833 let _ = std::fs::create_dir_all(parent);
1834 }
1835 match crate::plugin_cache::PluginCache::open(&pc_path) {
1836 Ok(pc) => {
1837 let (plugins, functions) = pc.stats();
1838 tracing::info!(
1839 plugins,
1840 cached_functions = functions,
1841 path = %pc_path.display(),
1842 "plugin_cache: sqlite opened"
1843 );
1844 Some(pc)
1845 }
1846 Err(e) => {
1847 tracing::warn!(error = %e, "plugin_cache: failed to open");
1848 None
1849 }
1850 }
1851 },
1852 deferred_compdefs: Vec::new(),
1853 command_hash: HashMap::new(),
1854 returning: None,
1855 breaking: 0,
1856 continuing: 0,
1857 pcre_state: PcreState::new(),
1858 tcp_sessions: TcpSessions::new(),
1859 zftp: Zftp::new(),
1860 profiler: Profiler::new(),
1861 style_table: StyleTable::new(),
1862 zsh_compat: false,
1863 posix_mode: false,
1864 worker_pool: {
1865 let config = crate::config::load();
1866 let pool_size = crate::config::resolve_pool_size(&config.worker_pool);
1867 std::sync::Arc::new(crate::worker::WorkerPool::new(pool_size))
1868 },
1869 intercepts: Vec::new(),
1870 async_jobs: HashMap::new(),
1871 next_async_id: 1,
1872 defer_stack: Vec::new(),
1873 }
1874 }
1875
1876 pub fn enter_posix_mode(&mut self) {
1879 self.posix_mode = true;
1880 self.plugin_cache = None;
1881 self.compsys_cache = None;
1882 self.compinit_pending = None;
1883 self.worker_pool = std::sync::Arc::new(crate::worker::WorkerPool::new(1));
1887 tracing::info!("POSIX strict mode: SQLite caches dropped, worker pool shrunk to 1");
1888 }
1889
1890 pub fn run_hooks(&mut self, hook_name: &str) {
1892 if let Some(funcs) = self.hook_functions.get(hook_name).cloned() {
1893 for func_name in funcs {
1894 if self.functions.contains_key(&func_name) {
1895 let _ = self.execute_script(&format!("{}", func_name));
1896 }
1897 }
1898 }
1899 let array_name = format!("{}_functions", hook_name);
1901 if let Some(funcs) = self.arrays.get(&array_name).cloned() {
1902 for func_name in funcs {
1903 if self.functions.contains_key(&func_name) {
1904 let _ = self.execute_script(&format!("{}", func_name));
1905 }
1906 }
1907 }
1908 }
1909
1910 pub fn add_hook(&mut self, hook_name: &str, func_name: &str) {
1912 self.hook_functions
1913 .entry(hook_name.to_string())
1914 .or_default()
1915 .push(func_name.to_string());
1916 }
1917
1918 pub fn add_named_dir(&mut self, name: &str, path: &str) {
1920 self.named_dirs
1921 .insert(name.to_string(), PathBuf::from(path));
1922 }
1923
1924 pub fn expand_tilde_named(&self, path: &str) -> String {
1926 if path.starts_with('~') {
1927 let rest = &path[1..];
1928 let (name, suffix) = if let Some(slash_pos) = rest.find('/') {
1930 (&rest[..slash_pos], &rest[slash_pos..])
1931 } else {
1932 (rest, "")
1933 };
1934
1935 if name.is_empty() {
1936 if let Ok(home) = std::env::var("HOME") {
1938 return format!("{}{}", home, suffix);
1939 }
1940 } else if let Some(dir) = self.named_dirs.get(name) {
1941 return format!("{}{}", dir.display(), suffix);
1942 }
1943 }
1944 path.to_string()
1945 }
1946
1947 fn all_zsh_options() -> &'static [&'static str] {
1948 &[
1949 "aliases",
1950 "aliasfuncdef",
1951 "allexport",
1952 "alwayslastprompt",
1953 "alwaystoend",
1954 "appendcreate",
1955 "appendhistory",
1956 "autocd",
1957 "autocontinue",
1958 "autolist",
1959 "automenu",
1960 "autonamedirs",
1961 "autoparamkeys",
1962 "autoparamslash",
1963 "autopushd",
1964 "autoremoveslash",
1965 "autoresume",
1966 "badpattern",
1967 "banghist",
1968 "bareglobqual",
1969 "bashautolist",
1970 "bashrematch",
1971 "beep",
1972 "bgnice",
1973 "braceccl",
1974 "braceexpand",
1975 "bsdecho",
1976 "caseglob",
1977 "casematch",
1978 "casepaths",
1979 "cbases",
1980 "cdablevars",
1981 "cdsilent",
1982 "chasedots",
1983 "chaselinks",
1984 "checkjobs",
1985 "checkrunningjobs",
1986 "clobber",
1987 "clobberempty",
1988 "combiningchars",
1989 "completealiases",
1990 "completeinword",
1991 "continueonerror",
1992 "correct",
1993 "correctall",
1994 "cprecedences",
1995 "cshjunkiehistory",
1996 "cshjunkieloops",
1997 "cshjunkiequotes",
1998 "cshnullcmd",
1999 "cshnullglob",
2000 "debugbeforecmd",
2001 "dotglob",
2002 "dvorak",
2003 "emacs",
2004 "equals",
2005 "errexit",
2006 "errreturn",
2007 "evallineno",
2008 "exec",
2009 "extendedglob",
2010 "extendedhistory",
2011 "flowcontrol",
2012 "forcefloat",
2013 "functionargzero",
2014 "glob",
2015 "globassign",
2016 "globcomplete",
2017 "globdots",
2018 "globstarshort",
2019 "globsubst",
2020 "globalexport",
2021 "globalrcs",
2022 "hashall",
2023 "hashcmds",
2024 "hashdirs",
2025 "hashexecutablesonly",
2026 "hashlistall",
2027 "histallowclobber",
2028 "histappend",
2029 "histbeep",
2030 "histexpand",
2031 "histexpiredupsfirst",
2032 "histfcntllock",
2033 "histfindnodups",
2034 "histignorealldups",
2035 "histignoredups",
2036 "histignorespace",
2037 "histlexwords",
2038 "histnofunctions",
2039 "histnostore",
2040 "histreduceblanks",
2041 "histsavebycopy",
2042 "histsavenodups",
2043 "histsubstpattern",
2044 "histverify",
2045 "hup",
2046 "ignorebraces",
2047 "ignoreclosebraces",
2048 "ignoreeof",
2049 "incappendhistory",
2050 "incappendhistorytime",
2051 "interactive",
2052 "interactivecomments",
2053 "ksharrays",
2054 "kshautoload",
2055 "kshglob",
2056 "kshoptionprint",
2057 "kshtypeset",
2058 "kshzerosubscript",
2059 "listambiguous",
2060 "listbeep",
2061 "listpacked",
2062 "listrowsfirst",
2063 "listtypes",
2064 "localloops",
2065 "localoptions",
2066 "localpatterns",
2067 "localtraps",
2068 "log",
2069 "login",
2070 "longlistjobs",
2071 "magicequalsubst",
2072 "mailwarn",
2073 "mailwarning",
2074 "markdirs",
2075 "menucomplete",
2076 "monitor",
2077 "multibyte",
2078 "multifuncdef",
2079 "multios",
2080 "nomatch",
2081 "notify",
2082 "nullglob",
2083 "numericglobsort",
2084 "octalzeroes",
2085 "onecmd",
2086 "overstrike",
2087 "pathdirs",
2088 "pathscript",
2089 "physical",
2090 "pipefail",
2091 "posixaliases",
2092 "posixargzero",
2093 "posixbuiltins",
2094 "posixcd",
2095 "posixidentifiers",
2096 "posixjobs",
2097 "posixstrings",
2098 "posixtraps",
2099 "printeightbit",
2100 "printexitvalue",
2101 "privileged",
2102 "promptbang",
2103 "promptcr",
2104 "promptpercent",
2105 "promptsp",
2106 "promptsubst",
2107 "promptvars",
2108 "pushdignoredups",
2109 "pushdminus",
2110 "pushdsilent",
2111 "pushdtohome",
2112 "rcexpandparam",
2113 "rcquotes",
2114 "rcs",
2115 "recexact",
2116 "rematchpcre",
2117 "restricted",
2118 "rmstarsilent",
2119 "rmstarwait",
2120 "sharehistory",
2121 "shfileexpansion",
2122 "shglob",
2123 "shinstdin",
2124 "shnullcmd",
2125 "shoptionletters",
2126 "shortloops",
2127 "shortrepeat",
2128 "shwordsplit",
2129 "singlecommand",
2130 "singlelinezle",
2131 "sourcetrace",
2132 "stdin",
2133 "sunkeyboardhack",
2134 "trackall",
2135 "transientrprompt",
2136 "trapsasync",
2137 "typesetsilent",
2138 "typesettounset",
2139 "unset",
2140 "verbose",
2141 "vi",
2142 "warncreateglobal",
2143 "warnnestedvar",
2144 "xtrace",
2145 "zle",
2146 ]
2147 }
2148
2149 fn default_options() -> HashMap<String, bool> {
2150 let mut opts = HashMap::new();
2151 for opt in Self::all_zsh_options() {
2153 opts.insert(opt.to_string(), false);
2154 }
2155 let defaults_on = [
2157 "aliases",
2158 "alwayslastprompt",
2159 "appendhistory",
2160 "autolist",
2161 "automenu",
2162 "autoparamkeys",
2163 "autoparamslash",
2164 "autoremoveslash",
2165 "badpattern",
2166 "banghist",
2167 "bareglobqual",
2168 "beep",
2169 "bgnice",
2170 "caseglob",
2171 "casematch",
2172 "checkjobs",
2173 "checkrunningjobs",
2174 "clobber",
2175 "debugbeforecmd",
2176 "equals",
2177 "evallineno",
2178 "exec",
2179 "flowcontrol",
2180 "functionargzero",
2181 "glob",
2182 "globalexport",
2183 "globalrcs",
2184 "hashcmds",
2185 "hashdirs",
2186 "hashlistall",
2187 "histbeep",
2188 "histsavebycopy",
2189 "hup",
2190 "interactive",
2191 "listambiguous",
2192 "listbeep",
2193 "listtypes",
2194 "monitor",
2195 "multibyte",
2196 "multifuncdef",
2197 "multios",
2198 "nomatch",
2199 "notify",
2200 "promptcr",
2201 "promptpercent",
2202 "promptsp",
2203 "rcs",
2204 "shinstdin",
2205 "shortloops",
2206 "unset",
2207 "zle",
2208 ];
2209 for opt in defaults_on {
2210 opts.insert(opt.to_string(), true);
2211 }
2212 opts
2213 }
2214
2215 fn normalize_option_name(name: &str) -> (String, bool) {
2217 let normalized = name.to_lowercase().replace(['-', '_'], "");
2218 if let Some(stripped) = normalized.strip_prefix("no") {
2219 if ZSH_OPTIONS_SET.contains(stripped) {
2221 return (stripped.to_string(), false);
2222 }
2223 }
2224 (normalized, true)
2225 }
2226
2227 fn option_matches_pattern(opt: &str, pattern: &str) -> bool {
2229 let pat = pattern.to_lowercase().replace(['-', '_'], "");
2230 let opt_lower = opt.to_lowercase();
2231
2232 if pat.contains('*') || pat.contains('?') || pat.contains('[') {
2233 let regex_pat = pat.replace('.', "\\.").replace('*', ".*").replace('?', ".");
2234 let full_pattern = format!("^{}$", regex_pat);
2235 cached_regex(&full_pattern)
2236 .map(|re| re.is_match(&opt_lower))
2237 .unwrap_or(false)
2238 } else {
2239 opt_lower == pat
2240 }
2241 }
2242
2243 pub fn autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
2245 if let Some(func) = self.functions.get(name) {
2247 return Some(func.clone());
2248 }
2249
2250 for i in 0..self.fpath.len() {
2252 let dir = self.fpath[i].clone();
2253 let zwc_path = dir.with_extension("zwc");
2255 if zwc_path.exists() {
2256 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
2257 return Some(func);
2258 }
2259 }
2260
2261 let func_zwc = dir.join(format!("{}.zwc", name));
2263 if func_zwc.exists() {
2264 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
2265 return Some(func);
2266 }
2267 }
2268
2269 if dir.is_dir() {
2271 if let Ok(entries) = fs::read_dir(&dir) {
2272 for entry in entries.flatten() {
2273 let path = entry.path();
2274 if path.extension().map_or(false, |e| e == "zwc") {
2275 if let Some(func) = self.load_function_from_zwc(&path, name) {
2276 return Some(func);
2277 }
2278 }
2279 }
2280 }
2281 }
2282 }
2283
2284 None
2285 }
2286
2287 fn load_function_from_zwc(&mut self, path: &Path, name: &str) -> Option<ShellCommand> {
2289 let zwc = if let Some(cached) = self.zwc_cache.get(path) {
2291 cached
2292 } else {
2293 let zwc = ZwcFile::load(path).ok()?;
2295 self.zwc_cache.insert(path.to_path_buf(), zwc);
2296 self.zwc_cache.get(path)?
2297 };
2298
2299 let func = zwc.get_function(name)?;
2301 let decoded = zwc.decode_function(func)?;
2302
2303 let shell_func = decoded.to_shell_function()?;
2305
2306 if let ShellCommand::FunctionDef(fname, body) = &shell_func {
2308 self.functions.insert(fname.clone(), (**body).clone());
2309 }
2310
2311 Some(shell_func)
2312 }
2313
2314 pub fn add_fpath(&mut self, path: PathBuf) {
2316 if !self.fpath.contains(&path) {
2317 self.fpath.insert(0, path);
2318 }
2319 }
2320
2321 fn glob_match(&self, s: &str, pattern: &str) -> bool {
2323 let mut regex_pattern = String::from("^");
2325 let mut chars = pattern.chars().peekable();
2326
2327 while let Some(c) = chars.next() {
2328 match c {
2329 '*' => regex_pattern.push_str(".*"),
2330 '?' => regex_pattern.push('.'),
2331 '[' => {
2332 regex_pattern.push('[');
2333 while let Some(cc) = chars.next() {
2335 if cc == ']' {
2336 regex_pattern.push(']');
2337 break;
2338 }
2339 regex_pattern.push(cc);
2340 }
2341 }
2342 '(' => {
2343 regex_pattern.push('(');
2345 }
2346 ')' => regex_pattern.push(')'),
2347 '|' => regex_pattern.push('|'),
2348 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
2349 regex_pattern.push('\\');
2350 regex_pattern.push(c);
2351 }
2352 _ => regex_pattern.push(c),
2353 }
2354 }
2355 regex_pattern.push('$');
2356
2357 regex::Regex::new(®ex_pattern)
2358 .map(|re| re.is_match(s))
2359 .unwrap_or(false)
2360 }
2361
2362 pub fn glob_match_static(s: &str, pattern: &str) -> bool {
2365 let mut regex_pattern = String::from("^");
2366 let mut chars = pattern.chars().peekable();
2367 while let Some(c) = chars.next() {
2368 match c {
2369 '*' => regex_pattern.push_str(".*"),
2370 '?' => regex_pattern.push('.'),
2371 '[' => {
2372 regex_pattern.push('[');
2373 while let Some(cc) = chars.next() {
2374 if cc == ']' {
2375 regex_pattern.push(']');
2376 break;
2377 }
2378 regex_pattern.push(cc);
2379 }
2380 }
2381 '(' => regex_pattern.push('('),
2382 ')' => regex_pattern.push(')'),
2383 '|' => regex_pattern.push('|'),
2384 '.' | '+' | '^' | '$' | '\\' | '{' | '}' => {
2385 regex_pattern.push('\\');
2386 regex_pattern.push(c);
2387 }
2388 _ => regex_pattern.push(c),
2389 }
2390 }
2391 regex_pattern.push('$');
2392 regex::Regex::new(®ex_pattern)
2393 .map(|re| re.is_match(s))
2394 .unwrap_or(false)
2395 }
2396
2397 pub fn execute_script_file(&mut self, file_path: &str) -> Result<i32, String> {
2400 use std::path::Path;
2401
2402 let path = Path::new(file_path);
2403 let abs_path = path
2404 .canonicalize()
2405 .unwrap_or_else(|_| path.to_path_buf())
2406 .to_string_lossy()
2407 .to_string();
2408
2409 if let Some(ref cache) = self.plugin_cache {
2411 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(path) {
2412 if let Some(bc_blob) = cache.check_bytecode(&abs_path, mt_s, mt_ns) {
2413 if let Ok(chunk) = bincode::deserialize::<fusevm::Chunk>(&bc_blob) {
2414 if !chunk.ops.is_empty() {
2415 tracing::trace!(
2416 path = %abs_path,
2417 ops = chunk.ops.len(),
2418 "execute_script_file: bytecode cache hit"
2419 );
2420 let mut vm = fusevm::VM::new(chunk);
2421 register_builtins(&mut vm);
2422 let _ctx = ExecutorContext::enter(self);
2423 match vm.run() {
2424 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
2425 self.last_status = vm.last_status;
2426 }
2427 fusevm::VMResult::Error(e) => {
2428 return Err(format!("VM error: {}", e));
2429 }
2430 }
2431 return Ok(self.last_status);
2432 }
2433 }
2434 }
2435 }
2436 }
2437
2438 let content =
2440 std::fs::read_to_string(file_path).map_err(|e| format!("{}: {}", file_path, e))?;
2441 let expanded = self.expand_history(&content);
2442 let mut parser = ShellParser::new(&expanded);
2443 let commands = parser.parse_script()?;
2444
2445 let compiler = crate::shell_compiler::ShellCompiler::new();
2446 let chunk = compiler.compile(&commands);
2447
2448 if let Some(ref cache) = self.plugin_cache {
2450 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(path) {
2451 if let Ok(blob) = bincode::serialize(&chunk) {
2452 let _ = cache.store_bytecode(&abs_path, mt_s, mt_ns, &blob);
2453 tracing::trace!(
2454 path = %abs_path,
2455 bytes = blob.len(),
2456 "execute_script_file: bytecode cached"
2457 );
2458 }
2459 }
2460 }
2461
2462 if !chunk.ops.is_empty() {
2464 let mut vm = fusevm::VM::new(chunk);
2465 register_builtins(&mut vm);
2466 let _ctx = ExecutorContext::enter(self);
2467 match vm.run() {
2468 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
2469 self.last_status = vm.last_status;
2470 }
2471 fusevm::VMResult::Error(e) => {
2472 return Err(format!("VM error: {}", e));
2473 }
2474 }
2475 }
2476
2477 Ok(self.last_status)
2478 }
2479
2480 #[tracing::instrument(skip(self, script), fields(len = script.len()))]
2481 pub fn execute_script(&mut self, script: &str) -> Result<i32, String> {
2482 let expanded = self.expand_history(script);
2484
2485 let mut parser = ShellParser::new(&expanded);
2486 let commands = parser.parse_script()?;
2487 tracing::trace!(cmds = commands.len(), "execute_script: parsed");
2488
2489 let compiler = crate::shell_compiler::ShellCompiler::new();
2493 let chunk = compiler.compile(&commands);
2494
2495 if !chunk.ops.is_empty() {
2496 if std::env::var("ZSHRS_DEBUG_OPS").is_ok() {
2497 eprintln!("[DEBUG] Compiled {} ops:", chunk.ops.len());
2498 for (i, op) in chunk.ops.iter().enumerate() {
2499 eprintln!(" {:3}: {:?}", i, op);
2500 }
2501 }
2502 let mut vm = fusevm::VM::new(chunk);
2503 register_builtins(&mut vm);
2504
2505 let _ctx = ExecutorContext::enter(self);
2507
2508 match vm.run() {
2509 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => {
2510 self.last_status = vm.last_status;
2511 }
2512 fusevm::VMResult::Error(e) => {
2513 return Err(format!("VM error: {}", e));
2514 }
2515 }
2516 }
2517
2518 if let Some(action) = self.traps.remove("EXIT") {
2521 tracing::debug!("firing EXIT trap");
2522 let _ = self.execute_script(&action);
2523 }
2524
2525 Ok(self.last_status)
2526 }
2527
2528 fn expand_history(&self, input: &str) -> String {
2530 let Some(ref engine) = self.history else {
2531 return input.to_string();
2532 };
2533
2534 if !input.contains('!') && !input.starts_with('^') {
2536 return input.to_string();
2537 }
2538
2539 let history_count = engine.count().unwrap_or(0) as usize;
2540 if history_count == 0 {
2541 return input.to_string();
2542 }
2543
2544 let chars: Vec<char> = input.chars().collect();
2545
2546 if chars.first() == Some(&'^') {
2548 if let Some(expanded) = self.history_quick_subst(&chars, engine) {
2549 return expanded;
2550 }
2551 }
2552
2553 let mut result = String::new();
2554 let mut i = 0;
2555 let mut in_single_quote = false;
2556 let mut in_brace = 0; let mut last_subst: Option<(String, String)> = None; while i < chars.len() {
2560 if chars[i] == '\'' && in_brace == 0 {
2562 in_single_quote = !in_single_quote;
2563 result.push(chars[i]);
2564 i += 1;
2565 continue;
2566 }
2567 if in_single_quote {
2568 result.push(chars[i]);
2569 i += 1;
2570 continue;
2571 }
2572
2573 if i + 1 < chars.len() && chars[i] == '$' && chars[i + 1] == '{' {
2575 in_brace += 1;
2576 result.push(chars[i]);
2577 i += 1;
2578 result.push(chars[i]);
2579 i += 1;
2580 continue;
2581 }
2582 if chars[i] == '}' && in_brace > 0 {
2583 in_brace -= 1;
2584 result.push(chars[i]);
2585 i += 1;
2586 continue;
2587 }
2588
2589 if chars[i] == '\\' && i + 1 < chars.len() && chars[i + 1] == '!' {
2591 result.push('!');
2592 i += 2;
2593 continue;
2594 }
2595
2596 if chars[i] == '!' && in_brace == 0 {
2597 if i + 1 >= chars.len() {
2598 result.push('!');
2600 i += 1;
2601 continue;
2602 }
2603
2604 let next = chars[i + 1];
2605 if next == ' ' || next == '\t' || next == '=' || next == '(' || next == '\n' {
2607 result.push('!');
2608 i += 1;
2609 continue;
2610 }
2611
2612 let (event_str, new_i) = self.history_resolve_event(&chars, i, engine, &result);
2614 if let Some(ev) = event_str {
2615 let (final_str, final_i) = self.history_apply_designators_and_modifiers(
2617 &chars,
2618 new_i,
2619 &ev,
2620 &mut last_subst,
2621 );
2622 result.push_str(&final_str);
2623 i = final_i;
2624 } else {
2625 result.push('!');
2627 i += 1;
2628 }
2629 continue;
2630 }
2631 result.push(chars[i]);
2632 i += 1;
2633 }
2634
2635 result
2636 }
2637
2638 fn history_quick_subst(
2641 &self,
2642 chars: &[char],
2643 engine: &crate::history::HistoryEngine,
2644 ) -> Option<String> {
2645 let mut i = 1; let mut old = String::new();
2647 while i < chars.len() && chars[i] != '^' {
2648 old.push(chars[i]);
2649 i += 1;
2650 }
2651 if i >= chars.len() {
2652 return None;
2653 }
2654 i += 1; let mut new = String::new();
2656 while i < chars.len() && chars[i] != '^' && chars[i] != '\n' {
2657 new.push(chars[i]);
2658 i += 1;
2659 }
2660 let prev = engine.get_by_offset(0).ok()??;
2661 Some(prev.command.replacen(&old, &new, 1))
2662 }
2663
2664 fn history_resolve_event(
2667 &self,
2668 chars: &[char],
2669 bang_pos: usize,
2670 engine: &crate::history::HistoryEngine,
2671 current_line: &str,
2672 ) -> (Option<String>, usize) {
2673 let mut i = bang_pos + 1; let in_brace = i < chars.len() && chars[i] == '{';
2677 if in_brace {
2678 i += 1;
2679 }
2680
2681 let c = if i < chars.len() {
2682 chars[i]
2683 } else {
2684 return (None, bang_pos);
2685 };
2686
2687 let (event, new_i) = match c {
2688 '!' => {
2689 let entry = engine.get_by_offset(0).ok().flatten();
2691 (entry.map(|e| e.command), i + 1)
2692 }
2693 '#' => {
2694 (Some(current_line.to_string()), i + 1)
2696 }
2697 '-' => {
2698 i += 1;
2700 let start = i;
2701 while i < chars.len() && chars[i].is_ascii_digit() {
2702 i += 1;
2703 }
2704 if i > start {
2705 let n: usize = chars[start..i]
2706 .iter()
2707 .collect::<String>()
2708 .parse()
2709 .unwrap_or(0);
2710 if n > 0 {
2711 let entry = engine.get_by_offset(n - 1).ok().flatten();
2712 (entry.map(|e| e.command), i)
2713 } else {
2714 (None, bang_pos)
2715 }
2716 } else {
2717 (None, bang_pos)
2718 }
2719 }
2720 '?' => {
2721 i += 1;
2723 let start = i;
2724 while i < chars.len() && chars[i] != '?' && chars[i] != '\n' {
2725 i += 1;
2726 }
2727 let search: String = chars[start..i].iter().collect();
2728 if i < chars.len() && chars[i] == '?' {
2729 i += 1;
2730 }
2731 let entry = engine
2732 .search(&search, 1)
2733 .ok()
2734 .and_then(|v| v.into_iter().next());
2735 (entry.map(|e| e.command), i)
2736 }
2737 c if c.is_ascii_digit() => {
2738 let start = i;
2740 while i < chars.len() && chars[i].is_ascii_digit() {
2741 i += 1;
2742 }
2743 let n: i64 = chars[start..i]
2744 .iter()
2745 .collect::<String>()
2746 .parse()
2747 .unwrap_or(0);
2748 if n > 0 {
2749 let entry = engine.get_by_number(n).ok().flatten();
2750 (entry.map(|e| e.command), i)
2751 } else {
2752 (None, bang_pos)
2753 }
2754 }
2755 '$' => {
2756 let entry = engine.get_by_offset(0).ok().flatten();
2758 let word =
2759 entry.and_then(|e| Self::history_split_words(&e.command).last().cloned());
2760 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2762 i + 2
2763 } else {
2764 i + 1
2765 };
2766 return (word, final_i);
2767 }
2768 '^' => {
2769 let entry = engine.get_by_offset(0).ok().flatten();
2771 let word = entry.and_then(|e| {
2772 let words = Self::history_split_words(&e.command);
2773 words.get(1).cloned()
2774 });
2775 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2776 i + 2
2777 } else {
2778 i + 1
2779 };
2780 return (word, final_i);
2781 }
2782 '*' => {
2783 let entry = engine.get_by_offset(0).ok().flatten();
2785 let word = entry.map(|e| {
2786 let words = Self::history_split_words(&e.command);
2787 if words.len() > 1 {
2788 words[1..].join(" ")
2789 } else {
2790 String::new()
2791 }
2792 });
2793 let final_i = if in_brace && i + 1 < chars.len() && chars[i + 1] == '}' {
2794 i + 2
2795 } else {
2796 i + 1
2797 };
2798 return (word, final_i);
2799 }
2800 c if c.is_alphabetic() || c == '_' || c == '/' || c == '.' => {
2801 let start = i;
2803 while i < chars.len()
2804 && !chars[i].is_whitespace()
2805 && chars[i] != ':'
2806 && chars[i] != '!'
2807 && chars[i] != '}'
2808 {
2809 i += 1;
2810 }
2811 let prefix: String = chars[start..i].iter().collect();
2812 let entry = engine
2813 .search_prefix(&prefix, 1)
2814 .ok()
2815 .and_then(|v| v.into_iter().next());
2816 (entry.map(|e| e.command), i)
2817 }
2818 _ => (None, bang_pos),
2819 };
2820
2821 let final_i = if in_brace && new_i < chars.len() && chars[new_i] == '}' {
2823 new_i + 1
2824 } else {
2825 new_i
2826 };
2827
2828 (event, final_i)
2829 }
2830
2831 fn history_split_words(cmd: &str) -> Vec<String> {
2833 let mut words = Vec::new();
2834 let mut current = String::new();
2835 let mut in_sq = false;
2836 let mut in_dq = false;
2837 let mut escaped = false;
2838
2839 for c in cmd.chars() {
2840 if escaped {
2841 current.push(c);
2842 escaped = false;
2843 continue;
2844 }
2845 if c == '\\' {
2846 current.push(c);
2847 escaped = true;
2848 continue;
2849 }
2850 if c == '\'' && !in_dq {
2851 in_sq = !in_sq;
2852 current.push(c);
2853 continue;
2854 }
2855 if c == '"' && !in_sq {
2856 in_dq = !in_dq;
2857 current.push(c);
2858 continue;
2859 }
2860 if c.is_whitespace() && !in_sq && !in_dq {
2861 if !current.is_empty() {
2862 words.push(std::mem::take(&mut current));
2863 }
2864 continue;
2865 }
2866 current.push(c);
2867 }
2868 if !current.is_empty() {
2869 words.push(current);
2870 }
2871 words
2872 }
2873
2874 fn history_apply_designators_and_modifiers(
2878 &self,
2879 chars: &[char],
2880 mut i: usize,
2881 event: &str,
2882 last_subst: &mut Option<(String, String)>,
2883 ) -> (String, usize) {
2884 let words = Self::history_split_words(event);
2885 let argc = words.len().saturating_sub(1); let mut sline = event.to_string();
2889
2890 if i < chars.len() && chars[i] == ':' {
2891 i += 1;
2892 if i < chars.len() {
2893 let (farg, larg, new_i) = self.history_parse_word_range(chars, i, argc);
2895 i = new_i;
2896 if farg.is_some() || larg.is_some() {
2897 let f = farg.unwrap_or(0);
2898 let l = larg.unwrap_or(argc);
2899 let selected: Vec<&String> = words
2900 .iter()
2901 .enumerate()
2902 .filter(|(idx, _)| *idx >= f && *idx <= l)
2903 .map(|(_, w)| w)
2904 .collect();
2905 sline = selected
2906 .iter()
2907 .map(|s| s.as_str())
2908 .collect::<Vec<_>>()
2909 .join(" ");
2910 }
2911 }
2912 } else if i < chars.len() && chars[i] == '*' {
2913 i += 1;
2915 if words.len() > 1 {
2916 sline = words[1..].join(" ");
2917 } else {
2918 sline = String::new();
2919 }
2920 }
2921
2922 while i < chars.len() && chars[i] == ':' {
2924 i += 1;
2925 if i >= chars.len() {
2926 break;
2927 }
2928 let mut global = false;
2929 if chars[i] == 'g' && i + 1 < chars.len() {
2930 global = true;
2931 i += 1;
2932 }
2933 match chars[i] {
2934 'h' => {
2935 i += 1;
2937 if let Some(pos) = sline.rfind('/') {
2938 if pos > 0 {
2939 sline = sline[..pos].to_string();
2940 } else {
2941 sline = "/".to_string();
2942 }
2943 }
2944 }
2945 't' => {
2946 i += 1;
2948 if let Some(pos) = sline.rfind('/') {
2949 sline = sline[pos + 1..].to_string();
2950 }
2951 }
2952 'r' => {
2953 i += 1;
2955 if let Some(pos) = sline.rfind('.') {
2956 if pos > 0 && sline[..pos].rfind('/').map_or(true, |sp| sp < pos) {
2957 sline = sline[..pos].to_string();
2958 }
2959 }
2960 }
2961 'e' => {
2962 i += 1;
2964 if let Some(pos) = sline.rfind('.') {
2965 sline = sline[pos + 1..].to_string();
2966 } else {
2967 sline = String::new();
2968 }
2969 }
2970 'l' => {
2971 i += 1;
2973 sline = sline.to_lowercase();
2974 }
2975 'u' => {
2976 i += 1;
2978 sline = sline.to_uppercase();
2979 }
2980 'p' => {
2981 i += 1;
2983 }
2985 'q' => {
2986 i += 1;
2988 sline = format!("'{}'", sline.replace('\'', "'\\''"));
2989 }
2990 'Q' => {
2991 i += 1;
2993 sline = sline.replace('\'', "").replace('"', "");
2994 }
2995 'a' => {
2996 i += 1;
2998 if !sline.starts_with('/') {
2999 if let Ok(cwd) = std::env::current_dir() {
3000 sline = format!("{}/{}", cwd.display(), sline);
3001 }
3002 }
3003 }
3004 'A' => {
3005 i += 1;
3007 if let Ok(real) = std::fs::canonicalize(&sline) {
3008 sline = real.to_string_lossy().to_string();
3009 }
3010 }
3011 's' | 'S' => {
3012 i += 1;
3014 if i < chars.len() {
3015 let delim = chars[i];
3016 i += 1;
3017 let mut old_s = String::new();
3018 while i < chars.len() && chars[i] != delim {
3019 old_s.push(chars[i]);
3020 i += 1;
3021 }
3022 if i < chars.len() {
3023 i += 1;
3024 } let mut new_s = String::new();
3026 while i < chars.len()
3027 && chars[i] != delim
3028 && chars[i] != ':'
3029 && chars[i] != ' '
3030 {
3031 new_s.push(chars[i]);
3032 i += 1;
3033 }
3034 if i < chars.len() && chars[i] == delim {
3035 i += 1;
3036 } *last_subst = Some((old_s.clone(), new_s.clone()));
3038 if global {
3039 sline = sline.replace(&old_s, &new_s);
3040 } else {
3041 sline = sline.replacen(&old_s, &new_s, 1);
3042 }
3043 }
3044 }
3045 '&' => {
3046 i += 1;
3048 if let Some((ref old_s, ref new_s)) = last_subst {
3049 if global {
3050 sline = sline.replace(old_s.as_str(), new_s.as_str());
3051 } else {
3052 sline = sline.replacen(old_s.as_str(), new_s.as_str(), 1);
3053 }
3054 }
3055 }
3056 _ => {
3057 if global {
3058 }
3061 break;
3062 }
3063 }
3064 }
3065
3066 (sline, i)
3067 }
3068
3069 fn history_parse_word_range(
3071 &self,
3072 chars: &[char],
3073 mut i: usize,
3074 argc: usize,
3075 ) -> (Option<usize>, Option<usize>, usize) {
3076 if i >= chars.len() {
3077 return (None, None, i);
3078 }
3079
3080 match chars[i] {
3082 'h' | 't' | 'r' | 'e' | 's' | 'S' | 'g' | 'p' | 'q' | 'Q' | 'l' | 'u' | 'a' | 'A'
3083 | '&' => {
3084 return (None, None, i - 1); }
3087 _ => {}
3088 }
3089
3090 let farg = if chars[i] == '^' {
3091 i += 1;
3092 Some(1usize)
3093 } else if chars[i] == '$' {
3094 i += 1;
3095 return (Some(argc), Some(argc), i);
3096 } else if chars[i] == '*' {
3097 i += 1;
3098 return (Some(1), Some(argc), i);
3099 } else if chars[i].is_ascii_digit() {
3100 let start = i;
3101 while i < chars.len() && chars[i].is_ascii_digit() {
3102 i += 1;
3103 }
3104 let n: usize = chars[start..i]
3105 .iter()
3106 .collect::<String>()
3107 .parse()
3108 .unwrap_or(0);
3109 Some(n)
3110 } else {
3111 None
3112 };
3113
3114 if i < chars.len() && chars[i] == '-' {
3116 i += 1;
3117 if i < chars.len() && chars[i] == '$' {
3118 i += 1;
3119 return (farg, Some(argc), i);
3120 } else if i < chars.len() && chars[i].is_ascii_digit() {
3121 let start = i;
3122 while i < chars.len() && chars[i].is_ascii_digit() {
3123 i += 1;
3124 }
3125 let m: usize = chars[start..i]
3126 .iter()
3127 .collect::<String>()
3128 .parse()
3129 .unwrap_or(0);
3130 return (farg, Some(m), i);
3131 } else {
3132 return (farg, Some(argc.saturating_sub(1)), i);
3134 }
3135 }
3136
3137 if farg.is_some() {
3138 (farg, farg, i)
3139 } else {
3140 (None, None, i)
3141 }
3142 }
3143
3144 #[tracing::instrument(level = "trace", skip_all)]
3145 pub fn execute_command(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
3146 match cmd {
3147 ShellCommand::Simple(simple) => self.execute_simple(simple),
3148 ShellCommand::Pipeline(cmds, negated) => {
3149 let status = self.execute_pipeline(cmds)?;
3150 if *negated {
3151 self.last_status = if status == 0 { 1 } else { 0 };
3152 } else {
3153 self.last_status = status;
3154 }
3155 Ok(self.last_status)
3156 }
3157 ShellCommand::List(items) => self.execute_list(items),
3158 ShellCommand::Compound(compound) => self.execute_compound(compound),
3159 ShellCommand::FunctionDef(name, body) => {
3160 if name.is_empty() {
3161 let result = self.execute_command(body);
3163 if let Some(ret) = self.returning.take() {
3165 self.last_status = ret;
3166 return Ok(ret);
3167 }
3168 result
3169 } else {
3170 self.functions.insert(name.clone(), (**body).clone());
3172 self.last_status = 0;
3173 Ok(0)
3174 }
3175 }
3176 }
3177 }
3178
3179 #[tracing::instrument(level = "trace", skip_all)]
3180 fn execute_simple(&mut self, cmd: &SimpleCommand) -> Result<i32, String> {
3181 for (var, val, is_append) in &cmd.assignments {
3183 match val {
3184 ShellWord::ArrayLiteral(elements) => {
3185 let new_elements: Vec<String> = elements
3190 .iter()
3191 .flat_map(|e| self.expand_word_split(e))
3192 .collect();
3193
3194 if self.assoc_arrays.contains_key(var) {
3196 if *is_append {
3198 let assoc = self.assoc_arrays.get_mut(var).unwrap();
3199 let mut iter = new_elements.iter();
3200 while let Some(key) = iter.next() {
3201 if let Some(val) = iter.next() {
3202 assoc.insert(key.clone(), val.clone());
3203 }
3204 }
3205 } else {
3206 let mut assoc = HashMap::new();
3207 let mut iter = new_elements.iter();
3208 while let Some(key) = iter.next() {
3209 if let Some(val) = iter.next() {
3210 assoc.insert(key.clone(), val.clone());
3211 }
3212 }
3213 self.assoc_arrays.insert(var.clone(), assoc);
3214 }
3215 } else if *is_append {
3216 let arr = self.arrays.entry(var.clone()).or_insert_with(Vec::new);
3218 arr.extend(new_elements);
3219 } else {
3220 self.arrays.insert(var.clone(), new_elements);
3221 }
3222 }
3223 _ => {
3224 let expanded = self.expand_word(val);
3225
3226 if let Some(bracket_pos) = var.find('[') {
3228 if var.ends_with(']') {
3229 let array_name = &var[..bracket_pos];
3230 let key = &var[bracket_pos + 1..var.len() - 1];
3231 let key = self.expand_string(key); if self.assoc_arrays.contains_key(array_name) {
3235 let assoc = self.assoc_arrays.get_mut(array_name).unwrap();
3236 if *is_append {
3237 let existing = assoc.get(&key).cloned().unwrap_or_default();
3238 assoc.insert(key, existing + &expanded);
3239 } else {
3240 assoc.insert(key, expanded);
3241 }
3242 } else if let Ok(idx) = key.parse::<i64>() {
3243 let idx = if idx < 0 { 0 } else { (idx - 1) as usize }; let arr = self
3246 .arrays
3247 .entry(array_name.to_string())
3248 .or_insert_with(Vec::new);
3249 while arr.len() <= idx {
3250 arr.push(String::new());
3251 }
3252 if *is_append {
3253 arr[idx].push_str(&expanded);
3254 } else {
3255 arr[idx] = expanded;
3256 }
3257 } else {
3258 let assoc = self
3260 .assoc_arrays
3261 .entry(array_name.to_string())
3262 .or_insert_with(HashMap::new);
3263 if *is_append {
3264 let existing = assoc.get(&key).cloned().unwrap_or_default();
3265 assoc.insert(key, existing + &expanded);
3266 } else {
3267 assoc.insert(key, expanded);
3268 }
3269 }
3270 continue;
3271 }
3272 }
3273
3274 let final_value = if *is_append {
3276 let existing = self.variables.get(var).cloned().unwrap_or_default();
3277 existing + &expanded
3278 } else {
3279 expanded
3280 };
3281
3282 if self.readonly_vars.contains(var) {
3283 eprintln!("zshrs: read-only variable: {}", var);
3284 self.last_status = 1;
3285 return Ok(1);
3286 }
3287 if cmd.words.is_empty() {
3288 env::set_var(var, &final_value);
3290 }
3291 self.variables.insert(var.clone(), final_value);
3292 }
3293 }
3294 }
3295
3296 if cmd.words.is_empty() {
3297 self.last_status = 0;
3298 return Ok(0);
3299 }
3300
3301 let is_noglob = cmd
3303 .words
3304 .first()
3305 .map(|w| self.expand_word(w) == "noglob")
3306 .unwrap_or(false);
3307 let saved_noglob = if is_noglob {
3308 let saved = self.options.get("noglob").copied();
3309 self.options.insert("noglob".to_string(), true);
3310 saved
3311 } else {
3312 None
3313 };
3314
3315 let preflight = self.preflight_command_subs(&cmd.words);
3319
3320 let mut words: Vec<String> = cmd
3321 .words
3322 .iter()
3323 .enumerate()
3324 .flat_map(|(i, w)| {
3325 if let Some(rx) = &preflight[i] {
3326 vec![rx.recv().unwrap_or_default()]
3328 } else {
3329 self.expand_word_glob(w)
3330 }
3331 })
3332 .collect();
3333
3334 if is_noglob {
3336 match saved_noglob {
3337 Some(v) => {
3338 self.options.insert("noglob".to_string(), v);
3339 }
3340 None => {
3341 self.options.remove("noglob");
3342 }
3343 }
3344 }
3345 if words.is_empty() {
3346 self.last_status = 0;
3347 return Ok(0);
3348 }
3349
3350 if !self.global_aliases.is_empty() {
3352 let global_aliases = self.global_aliases.clone();
3353 words = words
3354 .into_iter()
3355 .map(|w| global_aliases.get(&w).cloned().unwrap_or(w))
3356 .collect();
3357 }
3358
3359 if self.options.get("xtrace").copied().unwrap_or(false) {
3361 let ps4 = self
3362 .variables
3363 .get("PS4")
3364 .cloned()
3365 .unwrap_or_else(|| "+".to_string());
3366 eprintln!("{}{}", ps4, words.join(" "));
3367 }
3368
3369 let cmd_name = &words[0];
3371 if let Some(alias_value) = self.aliases.get(cmd_name).cloned() {
3372 let expanded_cmd = if words.len() > 1 {
3374 format!("{} {}", alias_value, words[1..].join(" "))
3375 } else {
3376 alias_value
3377 };
3378 return self.execute_script(&expanded_cmd);
3380 }
3381
3382 if !self.suffix_aliases.is_empty() {
3384 let cmd_path = std::path::Path::new(cmd_name);
3385 if let Some(ext) = cmd_path.extension().and_then(|e| e.to_str()) {
3386 if let Some(handler) = self.suffix_aliases.get(ext).cloned() {
3387 let expanded_cmd = format!("{} {}", handler, words.join(" "));
3389 return self.execute_script(&expanded_cmd);
3390 }
3391 }
3392 }
3393
3394 let args = &words[1..];
3395
3396 let is_exec_with_redirects_only =
3399 cmd_name == "exec" && args.is_empty() && !cmd.redirects.is_empty();
3400
3401 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
3403 for redirect in &cmd.redirects {
3404 let target = self.expand_word(&redirect.target);
3405
3406 if let Some(ref var_name) = redirect.fd_var {
3408 use std::os::unix::io::IntoRawFd;
3409 let file_result = match redirect.op {
3410 RedirectOp::Write | RedirectOp::Clobber => std::fs::File::create(&target),
3411 RedirectOp::Append => std::fs::OpenOptions::new()
3412 .create(true)
3413 .append(true)
3414 .open(&target),
3415 RedirectOp::Read => std::fs::File::open(&target),
3416 _ => continue,
3417 };
3418 match file_result {
3419 Ok(file) => {
3420 let new_fd = file.into_raw_fd();
3421 self.variables.insert(var_name.clone(), new_fd.to_string());
3422 if !is_exec_with_redirects_only {
3424 }
3426 }
3427 Err(e) => {
3428 eprintln!("{}: {}: {}", cmd_name, target, e);
3429 return Ok(1);
3430 }
3431 }
3432 continue;
3433 }
3434
3435 let fd = redirect.fd.unwrap_or(match redirect.op {
3436 RedirectOp::Read
3437 | RedirectOp::HereDoc
3438 | RedirectOp::HereString
3439 | RedirectOp::ReadWrite => 0,
3440 _ => 1,
3441 });
3442
3443 match redirect.op {
3444 RedirectOp::Write | RedirectOp::Clobber => {
3445 use std::os::unix::io::IntoRawFd;
3446 if !is_exec_with_redirects_only {
3447 let saved = unsafe { libc::dup(fd) };
3448 if saved >= 0 {
3449 saved_fds.push((fd, saved));
3450 }
3451 }
3452 if let Ok(file) = std::fs::File::create(&target) {
3453 let new_fd = file.into_raw_fd();
3454 unsafe {
3455 libc::dup2(new_fd, fd);
3456 }
3457 unsafe {
3458 libc::close(new_fd);
3459 }
3460 }
3461 }
3462 RedirectOp::Append => {
3463 use std::os::unix::io::IntoRawFd;
3464 if !is_exec_with_redirects_only {
3465 let saved = unsafe { libc::dup(fd) };
3466 if saved >= 0 {
3467 saved_fds.push((fd, saved));
3468 }
3469 }
3470 if let Ok(file) = std::fs::OpenOptions::new()
3471 .create(true)
3472 .append(true)
3473 .open(&target)
3474 {
3475 let new_fd = file.into_raw_fd();
3476 unsafe {
3477 libc::dup2(new_fd, fd);
3478 }
3479 unsafe {
3480 libc::close(new_fd);
3481 }
3482 }
3483 }
3484 RedirectOp::Read => {
3485 use std::os::unix::io::IntoRawFd;
3486 if !is_exec_with_redirects_only {
3487 let saved = unsafe { libc::dup(fd) };
3488 if saved >= 0 {
3489 saved_fds.push((fd, saved));
3490 }
3491 }
3492 if let Ok(file) = std::fs::File::open(&target) {
3493 let new_fd = file.into_raw_fd();
3494 unsafe {
3495 libc::dup2(new_fd, fd);
3496 }
3497 unsafe {
3498 libc::close(new_fd);
3499 }
3500 }
3501 }
3502 RedirectOp::DupWrite | RedirectOp::DupRead => {
3503 if let Ok(target_fd) = target.parse::<i32>() {
3504 if !is_exec_with_redirects_only {
3505 let saved = unsafe { libc::dup(fd) };
3506 if saved >= 0 {
3507 saved_fds.push((fd, saved));
3508 }
3509 }
3510 unsafe {
3511 libc::dup2(target_fd, fd);
3512 }
3513 }
3514 }
3515 _ => {}
3516 }
3517 }
3518
3519 if is_exec_with_redirects_only {
3521 self.last_status = 0;
3522 return Ok(0);
3523 }
3524
3525 let status = match cmd_name.as_str() {
3527 "cd" => self.builtin_cd(args),
3528 "pwd" => self.builtin_pwd(&cmd.redirects),
3529 "echo" => self.builtin_echo(args, &cmd.redirects),
3530 "export" => self.builtin_export(args),
3531 "unset" => self.builtin_unset(args),
3532 "source" | "." => self.builtin_source(args),
3533 "exit" | "bye" | "logout" => self.builtin_exit(args),
3534 "return" => self.builtin_return(args),
3535 "true" => 0,
3536 "false" => 1,
3537 ":" => 0,
3538 "chdir" => self.builtin_cd(args),
3539 "test" | "[" => self.builtin_test(args),
3540 "local" => self.builtin_local(args),
3541 "declare" | "typeset" => self.builtin_declare(args),
3542 "read" => self.builtin_read(args),
3543 "shift" => self.builtin_shift(args),
3544 "eval" => self.builtin_eval(args),
3545 "jobs" => self.builtin_jobs(args),
3546 "fg" => self.builtin_fg(args),
3547 "bg" => self.builtin_bg(args),
3548 "kill" => self.builtin_kill(args),
3549 "disown" => self.builtin_disown(args),
3550 "wait" => self.builtin_wait(args),
3551 "autoload" => self.builtin_autoload(args),
3552 "history" => self.builtin_history(args),
3553 "fc" => self.builtin_fc(args),
3554 "trap" => self.builtin_trap(args),
3555 "suspend" => self.builtin_suspend(args),
3556 "alias" => self.builtin_alias(args),
3557 "unalias" => self.builtin_unalias(args),
3558 "set" => self.builtin_set(args),
3559 "shopt" => self.builtin_shopt(args),
3560 "bind" => self.builtin_bindkey(args),
3562 "caller" => self.builtin_caller(args),
3563 "help" => self.builtin_help(args),
3564 "doctor" => self.builtin_doctor(args),
3565 "dbview" => self.builtin_dbview(args),
3566 "profile" => self.builtin_profile(args),
3567 "intercept" => self.builtin_intercept(args),
3568 "intercept_proceed" => self.builtin_intercept_proceed(args),
3569 "async" => self.builtin_async(args),
3571 "await" => self.builtin_await(args),
3572 "pmap" => self.builtin_pmap(args),
3573 "pgrep" => self.builtin_pgrep(args),
3574 "peach" => self.builtin_peach(args),
3575 "barrier" => self.builtin_barrier(args),
3576 "readarray" | "mapfile" => self.builtin_readarray(args),
3577 "setopt" => self.builtin_setopt(args),
3578 "unsetopt" => self.builtin_unsetopt(args),
3579 "getopts" => self.builtin_getopts(args),
3580 "type" => self.builtin_type(args),
3581 "hash" => self.builtin_hash(args),
3582 "add-zsh-hook" => self.builtin_add_zsh_hook(args),
3583 "command" => self.builtin_command(args, &cmd.redirects),
3584 "builtin" => self.builtin_builtin(args, &cmd.redirects),
3585 "let" => self.builtin_let(args),
3586 "compgen" => self.builtin_compgen(args),
3587 "complete" => self.builtin_complete(args),
3588 "compopt" => self.builtin_compopt(args),
3589 "compadd" => self.builtin_compadd(args),
3590 "compset" => self.builtin_compset(args),
3591 "compdef" => self.builtin_compdef(args),
3592 "compinit" => self.builtin_compinit(args),
3593 "cdreplay" => self.builtin_cdreplay(args),
3594 "zstyle" => self.builtin_zstyle(args),
3595 "ztie" => self.builtin_ztie(args),
3597 "zuntie" => self.builtin_zuntie(args),
3598 "zgdbmpath" => self.builtin_zgdbmpath(args),
3599 "pushd" => self.builtin_pushd(args),
3600 "popd" => self.builtin_popd(args),
3601 "dirs" => self.builtin_dirs(args),
3602 "printf" => self.builtin_printf(args),
3603 "break" => self.builtin_break(args),
3605 "continue" => self.builtin_continue(args),
3606 "disable" => self.builtin_disable(args),
3608 "enable" => self.builtin_enable(args),
3609 "emulate" => self.builtin_emulate(args),
3611 "promptinit" => self.builtin_promptinit(args),
3613 "prompt" => self.builtin_prompt(args),
3614 "pcre_compile" => self.builtin_pcre_compile(args),
3616 "pcre_match" => self.builtin_pcre_match(args),
3617 "pcre_study" => self.builtin_pcre_study(args),
3618 "exec" => self.builtin_exec(args),
3620 "float" => self.builtin_float(args),
3622 "integer" => self.builtin_integer(args),
3623 "functions" => self.builtin_functions(args),
3625 "print" => self.builtin_print(args),
3627 "whence" => self.builtin_whence(args),
3629 "where" => self.builtin_where(args),
3630 "which" => self.builtin_which(args),
3631 "ulimit" => self.builtin_ulimit(args),
3633 "limit" => self.builtin_limit(args),
3634 "unlimit" => self.builtin_unlimit(args),
3635 "umask" => self.builtin_umask(args),
3637 "rehash" => self.builtin_rehash(args),
3639 "unhash" => self.builtin_unhash(args),
3640 "times" => self.builtin_times(args),
3642 "zmodload" => self.builtin_zmodload(args),
3644 "r" => self.builtin_r(args),
3646 "ttyctl" => self.builtin_ttyctl(args),
3648 "noglob" => self.builtin_noglob(args, &cmd.redirects),
3650 "zstat" | "stat" => self.builtin_zstat(args),
3652 "strftime" => self.builtin_strftime(args),
3654 "zsleep" => self.builtin_zsleep(args),
3656 "zsystem" => self.builtin_zsystem(args),
3658 "sync" => self.builtin_sync(args),
3660 "mkdir" => self.builtin_mkdir(args),
3661 "rmdir" => self.builtin_rmdir(args),
3662 "ln" => self.builtin_ln(args),
3663 "mv" => self.builtin_mv(args),
3664 "cp" => self.builtin_cp(args),
3665 "rm" => self.builtin_rm(args),
3666 "chown" => self.builtin_chown(args),
3667 "chmod" => self.builtin_chmod(args),
3668 "zln" | "zmv" | "zcp" => self.builtin_zfiles(cmd_name, args),
3669 "coproc" => self.builtin_coproc(args),
3671 "zparseopts" => self.builtin_zparseopts(args),
3673 "readonly" => self.builtin_readonly(args),
3675 "unfunction" => self.builtin_unfunction(args),
3676 "getln" => self.builtin_getln(args),
3678 "pushln" => self.builtin_pushln(args),
3679 "bindkey" => self.builtin_bindkey(args),
3681 "zle" => self.builtin_zle(args),
3683 "sched" => self.builtin_sched(args),
3685 "zformat" => self.builtin_zformat(args),
3687 "zcompile" => self.builtin_zcompile(args),
3689 "vared" => self.builtin_vared(args),
3691 "echotc" => self.builtin_echotc(args),
3693 "echoti" => self.builtin_echoti(args),
3694 "zpty" => self.builtin_zpty(args),
3696 "zprof" => self.builtin_zprof(args),
3697 "zsocket" => self.builtin_zsocket(args),
3698 "ztcp" => self.builtin_ztcp(args),
3699 "zregexparse" => self.builtin_zregexparse(args),
3700 "clone" => self.builtin_clone(args),
3701 "log" => self.builtin_log(args),
3702 "comparguments" => self.builtin_comparguments(args),
3704 "compcall" => self.builtin_compcall(args),
3705 "compctl" => self.builtin_compctl(args),
3706 "compdescribe" => self.builtin_compdescribe(args),
3707 "compfiles" => self.builtin_compfiles(args),
3708 "compgroups" => self.builtin_compgroups(args),
3709 "compquote" => self.builtin_compquote(args),
3710 "comptags" => self.builtin_comptags(args),
3711 "comptry" => self.builtin_comptry(args),
3712 "compvalues" => self.builtin_compvalues(args),
3713 "cap" | "getcap" | "setcap" => self.builtin_cap(args),
3715 "zftp" => self.builtin_zftp(args),
3717 "zcurses" => self.builtin_zcurses(args),
3719 "sysread" => self.builtin_sysread(args),
3721 "syswrite" => self.builtin_syswrite(args),
3722 "syserror" => self.builtin_syserror(args),
3723 "sysopen" => self.builtin_sysopen(args),
3724 "sysseek" => self.builtin_sysseek(args),
3725 "mapfile" => 0, "private" => self.builtin_private(args),
3729 "zgetattr" | "zsetattr" | "zdelattr" | "zlistattr" => {
3731 self.builtin_zattr(cmd_name, args)
3732 }
3733 "_arguments" | "_describe" | "_description" | "_message" | "_tags" | "_requested"
3736 | "_all_labels" | "_next_label" | "_files" | "_path_files" | "_directories" | "_cd"
3737 | "_default" | "_dispatch" | "_complete" | "_main_complete" | "_normal"
3738 | "_approximate" | "_correct" | "_expand" | "_history" | "_match" | "_menu"
3739 | "_oldlist" | "_list" | "_prefix" | "_generic" | "_wanted" | "_alternative"
3740 | "_values" | "_sequence" | "_sep_parts" | "_multi_parts" | "_combination"
3741 | "_parameters" | "_command" | "_command_names" | "_commands" | "_functions"
3742 | "_aliases" | "_builtins" | "_jobs" | "_pids" | "_process_names" | "_signals"
3743 | "_users" | "_groups" | "_hosts" | "_domains" | "_urls" | "_email_addresses"
3744 | "_options" | "_contexts" | "_set_options" | "_unset_options" | "_vars"
3745 | "_env_variables" | "_shell_variables" | "_arrays" | "_globflags" | "_globquals"
3746 | "_globqual_delims" | "_subscript" | "_history_modifiers" | "_brace_parameter"
3747 | "_tilde" | "_style" | "_cache_invalid" | "_store_cache" | "_retrieve_cache"
3748 | "_call_function" | "_call_program" | "_pick_variant" | "_setup"
3749 | "_comp_priv_prefix" | "_regex_arguments" | "_regex_words" | "_guard"
3750 | "_gnu_generic" | "_long_options" | "_x_arguments" | "_sub_commands"
3751 | "_cmdstring" | "_cmdambivalent" | "_first" | "_precommand" | "_user_at_host"
3752 | "_user_expand" | "_path_commands" | "_globbed_files" | "_have_glob_qual" => {
3753 0
3756 }
3757 _ => {
3758 if !self.intercepts.is_empty() {
3762 let full_cmd = if args.is_empty() {
3763 cmd_name.to_string()
3764 } else {
3765 format!("{} {}", cmd_name, args.join(" "))
3766 };
3767 if let Some(result) = self.run_intercepts(cmd_name, &full_cmd, args) {
3768 return result;
3769 }
3770 }
3771
3772 if let Some(func) = self.functions.get(cmd_name).cloned() {
3774 return self.call_function(&func, args);
3775 }
3776
3777 if self.maybe_autoload(cmd_name) {
3779 if let Some(func) = self.functions.get(cmd_name).cloned() {
3780 return self.call_function(&func, args);
3781 }
3782 }
3783
3784 if self.autoload_function(cmd_name).is_some() {
3786 if let Some(func) = self.functions.get(cmd_name).cloned() {
3787 return self.call_function(&func, args);
3788 }
3789 }
3790
3791 self.execute_external(cmd_name, args, &cmd.redirects)?
3793 }
3794 };
3795
3796 for (fd, saved) in saved_fds.into_iter().rev() {
3798 unsafe {
3799 libc::dup2(saved, fd);
3800 libc::close(saved);
3801 }
3802 }
3803
3804 self.last_status = status;
3805 Ok(status)
3806 }
3807
3808 #[tracing::instrument(level = "debug", skip_all)]
3810 fn call_function(&mut self, func: &ShellCommand, args: &[String]) -> Result<i32, String> {
3811 let saved_params = std::mem::take(&mut self.positional_params);
3813
3814 let saved_local_vars = self.local_save_stack.len();
3817 self.local_scope_depth += 1;
3818
3819 self.positional_params = args.to_vec();
3821
3822 let result = self.execute_command(func);
3824
3825 let final_result = if let Some(ret) = self.returning.take() {
3827 self.last_status = ret;
3828 Ok(ret)
3829 } else {
3830 result
3831 };
3832
3833 self.local_scope_depth -= 1;
3835 while self.local_save_stack.len() > saved_local_vars {
3836 if let Some((name, old_val)) = self.local_save_stack.pop() {
3837 match old_val {
3838 Some(v) => {
3839 self.variables.insert(name, v);
3840 }
3841 None => {
3842 self.variables.remove(&name);
3843 }
3844 }
3845 }
3846 }
3847
3848 self.positional_params = saved_params;
3850
3851 final_result
3852 }
3853
3854 fn execute_external(
3855 &mut self,
3856 cmd: &str,
3857 args: &[String],
3858 redirects: &[Redirect],
3859 ) -> Result<i32, String> {
3860 self.execute_external_bg(cmd, args, redirects, false)
3861 }
3862
3863 fn execute_external_bg(
3864 &mut self,
3865 cmd: &str,
3866 args: &[String],
3867 redirects: &[Redirect],
3868 background: bool,
3869 ) -> Result<i32, String> {
3870 tracing::trace!(cmd, bg = background, "exec external");
3871 let mut command = Command::new(cmd);
3872 command.args(args);
3873
3874 for redir in redirects {
3876 let target = self.expand_word(&redir.target);
3877 match redir.op {
3878 RedirectOp::Read => match File::open(&target) {
3879 Ok(f) => {
3880 command.stdin(Stdio::from(f));
3881 }
3882 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3883 },
3884 RedirectOp::Write => match File::create(&target) {
3885 Ok(f) => {
3886 command.stdout(Stdio::from(f));
3887 }
3888 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
3889 },
3890 RedirectOp::Append => {
3891 match OpenOptions::new().create(true).append(true).open(&target) {
3892 Ok(f) => {
3893 command.stdout(Stdio::from(f));
3894 }
3895 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3896 }
3897 }
3898 RedirectOp::WriteBoth => match File::create(&target) {
3899 Ok(f) => {
3900 let f2 = f
3901 .try_clone()
3902 .map_err(|e| format!("Cannot clone fd: {}", e))?;
3903 command.stdout(Stdio::from(f));
3904 command.stderr(Stdio::from(f2));
3905 }
3906 Err(e) => return Err(format!("Cannot create {}: {}", target, e)),
3907 },
3908 RedirectOp::AppendBoth => {
3909 match OpenOptions::new().create(true).append(true).open(&target) {
3910 Ok(f) => {
3911 let f2 = f
3912 .try_clone()
3913 .map_err(|e| format!("Cannot clone fd: {}", e))?;
3914 command.stdout(Stdio::from(f));
3915 command.stderr(Stdio::from(f2));
3916 }
3917 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3918 }
3919 }
3920 RedirectOp::HereDoc => {
3921 if let Some(ref content) = redir.heredoc_content {
3923 let expanded = self.expand_string(content);
3925 command.stdin(Stdio::piped());
3926 use std::io::Write;
3929 let mut temp_file = tempfile::NamedTempFile::new()
3930 .map_err(|e| format!("Cannot create temp file: {}", e))?;
3931 temp_file
3932 .write_all(expanded.as_bytes())
3933 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
3934 let temp_path = temp_file.into_temp_path();
3935 let f = File::open(&temp_path)
3936 .map_err(|e| format!("Cannot open temp file: {}", e))?;
3937 command.stdin(Stdio::from(f));
3938 }
3939 }
3940 RedirectOp::HereString => {
3941 use std::io::Write;
3943 let content = format!("{}\n", target);
3944 let mut temp_file = tempfile::NamedTempFile::new()
3945 .map_err(|e| format!("Cannot create temp file: {}", e))?;
3946 temp_file
3947 .write_all(content.as_bytes())
3948 .map_err(|e| format!("Cannot write to temp file: {}", e))?;
3949 let temp_path = temp_file.into_temp_path();
3950 let f = File::open(&temp_path)
3951 .map_err(|e| format!("Cannot open temp file: {}", e))?;
3952 command.stdin(Stdio::from(f));
3953 }
3954 _ => {
3955 }
3957 }
3958
3959 if let Some(ref var_name) = redir.fd_var {
3961 #[cfg(unix)]
3964 {
3965 use std::os::unix::io::AsRawFd;
3966 let fd = match redir.op {
3967 RedirectOp::Write | RedirectOp::Append => {
3968 let f = if redir.op == RedirectOp::Write {
3969 File::create(&target)
3970 } else {
3971 OpenOptions::new().create(true).append(true).open(&target)
3972 };
3973 match f {
3974 Ok(file) => {
3975 let raw_fd = file.as_raw_fd();
3976 std::mem::forget(file); raw_fd
3978 }
3979 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3980 }
3981 }
3982 RedirectOp::Read => match File::open(&target) {
3983 Ok(file) => {
3984 let raw_fd = file.as_raw_fd();
3985 std::mem::forget(file);
3986 raw_fd
3987 }
3988 Err(e) => return Err(format!("Cannot open {}: {}", target, e)),
3989 },
3990 _ => continue,
3991 };
3992 self.variables.insert(var_name.clone(), fd.to_string());
3993 }
3994 }
3995 }
3996
3997 if background {
3998 match command.spawn() {
3999 Ok(child) => {
4000 let pid = child.id();
4001 let cmd_str = format!("{} {}", cmd, args.join(" "));
4002 let job_id = self.jobs.add_job(child, cmd_str, JobState::Running);
4003 println!("[{}] {}", job_id, pid);
4004 Ok(0)
4005 }
4006 Err(e) => {
4007 if e.kind() == io::ErrorKind::NotFound {
4008 eprintln!("zshrs: command not found: {}", cmd);
4009 Ok(127)
4010 } else {
4011 Err(format!("zshrs: {}: {}", cmd, e))
4012 }
4013 }
4014 }
4015 } else {
4016 match command.status() {
4017 Ok(status) => Ok(status.code().unwrap_or(1)),
4018 Err(e) => {
4019 if e.kind() == io::ErrorKind::NotFound {
4020 eprintln!("zshrs: command not found: {}", cmd);
4021 Ok(127)
4022 } else {
4023 Err(format!("zshrs: {}: {}", cmd, e))
4024 }
4025 }
4026 }
4027 }
4028 }
4029
4030 #[tracing::instrument(level = "trace", skip_all, fields(stages = cmds.len()))]
4031 fn execute_pipeline(&mut self, cmds: &[ShellCommand]) -> Result<i32, String> {
4032 if cmds.len() == 1 {
4033 return self.execute_command(&cmds[0]);
4034 }
4035
4036 let mut children: Vec<Child> = Vec::new();
4037 let mut prev_stdout: Option<std::process::ChildStdout> = None;
4038
4039 for (i, cmd) in cmds.iter().enumerate() {
4040 if let ShellCommand::Simple(simple) = cmd {
4041 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
4042 if words.is_empty() {
4043 continue;
4044 }
4045
4046 let mut command = Command::new(&words[0]);
4047 command.args(&words[1..]);
4048
4049 if let Some(stdout) = prev_stdout.take() {
4050 command.stdin(Stdio::from(stdout));
4051 }
4052
4053 if i < cmds.len() - 1 {
4054 command.stdout(Stdio::piped());
4055 }
4056
4057 match command.spawn() {
4058 Ok(mut child) => {
4059 prev_stdout = child.stdout.take();
4060 children.push(child);
4061 }
4062 Err(e) => {
4063 eprintln!("zshrs: {}: {}", words[0], e);
4064 return Ok(127);
4065 }
4066 }
4067 }
4068 }
4069
4070 let mut last_status = 0;
4072 for mut child in children {
4073 if let Ok(status) = child.wait() {
4074 last_status = status.code().unwrap_or(1);
4075 }
4076 }
4077
4078 Ok(last_status)
4079 }
4080
4081 fn execute_list(&mut self, items: &[(ShellCommand, ListOp)]) -> Result<i32, String> {
4082 for (cmd, op) in items {
4083 let background = matches!(op, ListOp::Amp);
4085
4086 let status = if background {
4087 self.execute_command_bg(cmd)?
4088 } else {
4089 self.execute_command(cmd)?
4090 };
4091
4092 if self.returning.is_some() || self.breaking > 0 || self.continuing > 0 {
4094 return Ok(status);
4095 }
4096
4097 match op {
4098 ListOp::And => {
4099 if status != 0 {
4100 return Ok(status);
4101 }
4102 }
4103 ListOp::Or => {
4104 if status == 0 {
4105 return Ok(0);
4106 }
4107 }
4108 ListOp::Amp => {
4109 }
4111 ListOp::Semi | ListOp::Newline => {
4112 }
4114 }
4115 }
4116
4117 Ok(self.last_status)
4118 }
4119
4120 fn execute_command_bg(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
4121 if let ShellCommand::Simple(simple) = cmd {
4123 if simple.words.is_empty() {
4124 return Ok(0);
4125 }
4126 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
4127 let cmd_name = &words[0];
4128 let args: Vec<String> = words[1..].to_vec();
4129 return self.execute_external_bg(cmd_name, &args, &simple.redirects, true);
4130 }
4131 self.execute_command(cmd)
4133 }
4134
4135 #[tracing::instrument(level = "trace", skip_all)]
4136 fn execute_compound(&mut self, compound: &CompoundCommand) -> Result<i32, String> {
4137 match compound {
4138 CompoundCommand::BraceGroup(cmds) => {
4139 for cmd in cmds {
4140 self.execute_command(cmd)?;
4141 if self.returning.is_some() {
4142 break;
4143 }
4144 }
4145 Ok(self.last_status)
4146 }
4147 CompoundCommand::Subshell(cmds) => {
4148 let saved_vars = self.variables.clone();
4151 let saved_arrays = self.arrays.clone();
4152 let saved_assoc = self.assoc_arrays.clone();
4153 let saved_params = self.positional_params.clone();
4154
4155 for cmd in cmds {
4156 self.execute_command(cmd)?;
4157 if self.returning.is_some() {
4158 break;
4159 }
4160 }
4161 let status = self.last_status;
4162
4163 self.variables = saved_vars;
4165 self.arrays = saved_arrays;
4166 self.assoc_arrays = saved_assoc;
4167 self.positional_params = saved_params;
4168 self.last_status = status;
4169
4170 Ok(status)
4171 }
4172
4173 CompoundCommand::If {
4174 conditions,
4175 else_part,
4176 } => {
4177 for (cond, body) in conditions {
4178 for cmd in cond {
4180 self.execute_command(cmd)?;
4181 }
4182
4183 if self.last_status == 0 {
4184 for cmd in body {
4186 self.execute_command(cmd)?;
4187 }
4188 return Ok(self.last_status);
4189 }
4190 }
4191
4192 if let Some(else_cmds) = else_part {
4194 for cmd in else_cmds {
4195 self.execute_command(cmd)?;
4196 }
4197 }
4198
4199 Ok(self.last_status)
4200 }
4201
4202 CompoundCommand::For { var, words, body } => {
4203 let items: Vec<String> = if let Some(words) = words {
4204 words
4205 .iter()
4206 .flat_map(|w| self.expand_word_split(w))
4207 .collect()
4208 } else {
4209 self.positional_params.clone()
4211 };
4212
4213 for item in items {
4214 env::set_var(var, &item);
4215 self.variables.insert(var.clone(), item);
4216
4217 for cmd in body {
4218 self.execute_command(cmd)?;
4219 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4220 break;
4221 }
4222 }
4223
4224 if self.continuing > 0 {
4225 self.continuing -= 1;
4226 if self.continuing > 0 {
4227 break;
4228 }
4229 continue;
4230 }
4231 if self.breaking > 0 {
4232 self.breaking -= 1;
4233 break;
4234 }
4235 if self.returning.is_some() {
4236 break;
4237 }
4238 }
4239
4240 Ok(self.last_status)
4241 }
4242
4243 CompoundCommand::ForArith {
4244 init,
4245 cond,
4246 step,
4247 body,
4248 } => {
4249 if !init.is_empty() {
4252 self.evaluate_arithmetic_expr(init);
4253 }
4254
4255 loop {
4257 if !cond.is_empty() {
4259 let cond_result = self.eval_arith_expr(cond);
4260 if cond_result == 0 {
4261 break;
4262 }
4263 }
4264
4265 for cmd in body {
4267 self.execute_command(cmd)?;
4268 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4269 break;
4270 }
4271 }
4272
4273 if self.continuing > 0 {
4274 self.continuing -= 1;
4275 if self.continuing > 0 {
4276 break;
4277 }
4278 continue;
4279 }
4280 if self.breaking > 0 {
4281 self.breaking -= 1;
4282 break;
4283 }
4284 if self.returning.is_some() {
4285 break;
4286 }
4287
4288 if !step.is_empty() {
4290 self.evaluate_arithmetic_expr(step);
4291 }
4292 }
4293 Ok(self.last_status)
4294 }
4295
4296 CompoundCommand::While { condition, body } => {
4297 loop {
4298 for cmd in condition {
4299 self.execute_command(cmd)?;
4300 if self.breaking > 0 || self.returning.is_some() {
4301 break;
4302 }
4303 }
4304
4305 if self.last_status != 0 || self.breaking > 0 || self.returning.is_some() {
4306 break;
4307 }
4308
4309 for cmd in body {
4310 self.execute_command(cmd)?;
4311 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4312 break;
4313 }
4314 }
4315
4316 if self.continuing > 0 {
4317 self.continuing -= 1;
4318 if self.continuing > 0 {
4319 break;
4320 }
4321 continue;
4322 }
4323 if self.breaking > 0 {
4324 self.breaking -= 1;
4325 break;
4326 }
4327 }
4328 Ok(self.last_status)
4329 }
4330
4331 CompoundCommand::Until { condition, body } => {
4332 loop {
4333 for cmd in condition {
4334 self.execute_command(cmd)?;
4335 if self.breaking > 0 || self.returning.is_some() {
4336 break;
4337 }
4338 }
4339
4340 if self.last_status == 0 || self.breaking > 0 || self.returning.is_some() {
4341 break;
4342 }
4343
4344 for cmd in body {
4345 self.execute_command(cmd)?;
4346 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4347 break;
4348 }
4349 }
4350
4351 if self.continuing > 0 {
4352 self.continuing -= 1;
4353 if self.continuing > 0 {
4354 break;
4355 }
4356 continue;
4357 }
4358 if self.breaking > 0 {
4359 self.breaking -= 1;
4360 break;
4361 }
4362 }
4363 Ok(self.last_status)
4364 }
4365
4366 CompoundCommand::Case { word, cases } => {
4367 let value = self.expand_word(word);
4368
4369 for (patterns, body, term) in cases {
4370 for pattern in patterns {
4371 let pat = self.expand_word(pattern);
4372 if self.matches_pattern(&value, &pat) {
4373 for cmd in body {
4374 self.execute_command(cmd)?;
4375 }
4376
4377 match term {
4378 CaseTerminator::Break => return Ok(self.last_status),
4379 CaseTerminator::Fallthrough => {
4380 }
4382 CaseTerminator::Continue => {
4383 break;
4385 }
4386 }
4387 }
4388 }
4389 }
4390
4391 Ok(self.last_status)
4392 }
4393
4394 CompoundCommand::Select { var, words, body } => {
4395 if let Some(words) = words {
4397 if let Some(first) = words.first() {
4398 let val = self.expand_word(first);
4399 env::set_var(var, &val);
4400 for cmd in body {
4401 self.execute_command(cmd)?;
4402 }
4403 }
4404 }
4405 Ok(self.last_status)
4406 }
4407
4408 CompoundCommand::Repeat { count, body } => {
4409 let n: i64 = self
4410 .expand_word(&ShellWord::Literal(count.clone()))
4411 .parse()
4412 .unwrap_or(0);
4413
4414 for _ in 0..n {
4415 for cmd in body {
4416 self.execute_command(cmd)?;
4417 if self.breaking > 0 || self.continuing > 0 || self.returning.is_some() {
4418 break;
4419 }
4420 }
4421
4422 if self.continuing > 0 {
4423 self.continuing -= 1;
4424 if self.continuing > 0 {
4425 break;
4426 }
4427 continue;
4428 }
4429 if self.breaking > 0 {
4430 self.breaking -= 1;
4431 break;
4432 }
4433 if self.returning.is_some() {
4434 break;
4435 }
4436 }
4437
4438 Ok(self.last_status)
4439 }
4440
4441 CompoundCommand::Try {
4442 try_body,
4443 always_body,
4444 } => {
4445 for cmd in try_body {
4448 if let Err(_e) = self.execute_command(cmd) {
4449 break;
4450 }
4451 if self.returning.is_some() {
4452 break;
4453 }
4454 }
4455
4456 let endval = self.last_status;
4458
4459 let save_returning = self.returning.take();
4461 let save_breaking = self.breaking;
4462 let save_continuing = self.continuing;
4463 self.breaking = 0;
4464 self.continuing = 0;
4465
4466 for cmd in always_body {
4468 let _ = self.execute_command(cmd);
4469 }
4470
4471 if self.returning.is_none() {
4474 self.returning = save_returning;
4475 }
4476 if self.breaking == 0 {
4477 self.breaking = save_breaking;
4478 }
4479 if self.continuing == 0 {
4480 self.continuing = save_continuing;
4481 }
4482
4483 self.last_status = endval;
4484 Ok(endval)
4485 }
4486
4487 CompoundCommand::Cond(expr) => {
4488 let result = self.eval_cond_expr(expr);
4489 self.last_status = if result { 0 } else { 1 };
4490 Ok(self.last_status)
4491 }
4492
4493 CompoundCommand::Arith(expr) => {
4494 let result = self.evaluate_arithmetic_expr(expr);
4496 self.last_status = if result != 0 { 0 } else { 1 };
4498 Ok(self.last_status)
4499 }
4500
4501 CompoundCommand::Coproc { name, body } => {
4502 let (stdin_read, stdin_write) =
4504 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
4505 let (stdout_read, stdout_write) =
4506 os_pipe::pipe().map_err(|e| format!("Cannot create pipe: {}", e))?;
4507
4508 let cmd_str = match body.as_ref() {
4510 ShellCommand::Simple(simple) => simple
4511 .words
4512 .iter()
4513 .map(|w| self.expand_word(w))
4514 .collect::<Vec<_>>()
4515 .join(" "),
4516 ShellCommand::Compound(CompoundCommand::BraceGroup(_cmds)) => {
4517 "bash -c 'true'".to_string()
4520 }
4521 _ => "true".to_string(),
4522 };
4523
4524 let parts: Vec<&str> = cmd_str.split_whitespace().collect();
4526 if parts.is_empty() {
4527 return Ok(0);
4528 }
4529
4530 let mut command = Command::new(parts[0]);
4531 if parts.len() > 1 {
4532 command.args(&parts[1..]);
4533 }
4534
4535 use std::os::unix::io::{FromRawFd, IntoRawFd};
4536
4537 command.stdin(unsafe { Stdio::from_raw_fd(stdin_read.into_raw_fd()) });
4538 command.stdout(unsafe { Stdio::from_raw_fd(stdout_write.into_raw_fd()) });
4539
4540 match command.spawn() {
4541 Ok(child) => {
4542 let pid = child.id();
4543 let coproc_name = name.clone().unwrap_or_else(|| "COPROC".to_string());
4544
4545 let read_fd = stdout_read.into_raw_fd();
4549 let write_fd = stdin_write.into_raw_fd();
4550
4551 self.arrays.insert(
4552 coproc_name.clone(),
4553 vec![read_fd.to_string(), write_fd.to_string()],
4554 );
4555
4556 self.variables
4558 .insert(format!("{}_PID", coproc_name), pid.to_string());
4559
4560 let cmd_str_clone = cmd_str.clone();
4561 self.jobs.add_job(child, cmd_str_clone, JobState::Running);
4562
4563 Ok(0)
4564 }
4565 Err(e) => {
4566 if e.kind() == io::ErrorKind::NotFound {
4567 eprintln!("zshrs: command not found: {}", parts[0]);
4568 Ok(127)
4569 } else {
4570 Err(format!("zshrs: coproc: {}: {}", parts[0], e))
4571 }
4572 }
4573 }
4574 }
4575
4576 CompoundCommand::WithRedirects(cmd, redirects) => {
4577 let mut saved_fds: Vec<(i32, i32)> = Vec::new();
4579
4580 for redirect in redirects {
4582 let fd = redirect.fd.unwrap_or(match redirect.op {
4583 RedirectOp::Read
4584 | RedirectOp::HereDoc
4585 | RedirectOp::HereString
4586 | RedirectOp::ReadWrite => 0,
4587 _ => 1,
4588 });
4589
4590 let target = self.expand_word(&redirect.target);
4591
4592 match redirect.op {
4593 RedirectOp::Write | RedirectOp::Clobber => {
4594 use std::os::unix::io::IntoRawFd;
4595 let saved = unsafe { libc::dup(fd) };
4596 if saved >= 0 {
4597 saved_fds.push((fd, saved));
4598 }
4599 if let Ok(file) = std::fs::File::create(&target) {
4600 let new_fd = file.into_raw_fd();
4601 unsafe {
4602 libc::dup2(new_fd, fd);
4603 }
4604 unsafe {
4605 libc::close(new_fd);
4606 }
4607 }
4608 }
4609 RedirectOp::Append => {
4610 use std::os::unix::io::IntoRawFd;
4611 let saved = unsafe { libc::dup(fd) };
4612 if saved >= 0 {
4613 saved_fds.push((fd, saved));
4614 }
4615 if let Ok(file) = std::fs::OpenOptions::new()
4616 .create(true)
4617 .append(true)
4618 .open(&target)
4619 {
4620 let new_fd = file.into_raw_fd();
4621 unsafe {
4622 libc::dup2(new_fd, fd);
4623 }
4624 unsafe {
4625 libc::close(new_fd);
4626 }
4627 }
4628 }
4629 RedirectOp::Read => {
4630 use std::os::unix::io::IntoRawFd;
4631 let saved = unsafe { libc::dup(fd) };
4632 if saved >= 0 {
4633 saved_fds.push((fd, saved));
4634 }
4635 if let Ok(file) = std::fs::File::open(&target) {
4636 let new_fd = file.into_raw_fd();
4637 unsafe {
4638 libc::dup2(new_fd, fd);
4639 }
4640 unsafe {
4641 libc::close(new_fd);
4642 }
4643 }
4644 }
4645 RedirectOp::DupWrite | RedirectOp::DupRead => {
4646 if let Ok(target_fd) = target.parse::<i32>() {
4647 let saved = unsafe { libc::dup(fd) };
4648 if saved >= 0 {
4649 saved_fds.push((fd, saved));
4650 }
4651 unsafe {
4652 libc::dup2(target_fd, fd);
4653 }
4654 }
4655 }
4656 _ => {}
4657 }
4658 }
4659
4660 let result = self.execute_command(cmd);
4662
4663 for (fd, saved) in saved_fds.into_iter().rev() {
4665 unsafe {
4666 libc::dup2(saved, fd);
4667 libc::close(saved);
4668 }
4669 }
4670
4671 result
4672 }
4673 }
4674 }
4675
4676 #[tracing::instrument(level = "trace", skip_all)]
4678 fn expand_word_glob(&mut self, word: &ShellWord) -> Vec<String> {
4679 match word {
4680 ShellWord::SingleQuoted(s) => vec![s.clone()],
4681 ShellWord::DoubleQuoted(parts) => {
4682 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
4684 }
4685 _ => {
4686 let expanded = self.expand_word(word);
4687
4688 let brace_expanded = self.expand_braces(&expanded);
4690
4691 let noglob = self.options.get("noglob").copied().unwrap_or(false)
4693 || self.options.get("GLOB").map(|v| !v).unwrap_or(false);
4694 brace_expanded
4695 .into_iter()
4696 .flat_map(|s| {
4697 if !noglob
4698 && (s.contains('*')
4699 || s.contains('?')
4700 || s.contains('[')
4701 || self.has_extglob_pattern(&s))
4702 {
4703 self.expand_glob(&s)
4704 } else {
4705 vec![s]
4706 }
4707 })
4708 .collect()
4709 }
4710 }
4711 }
4712
4713 fn expand_braces(&self, s: &str) -> Vec<String> {
4715 let mut depth = 0;
4717 let mut brace_start = None;
4718
4719 for (i, c) in s.char_indices() {
4720 match c {
4721 '{' => {
4722 if depth == 0 {
4723 brace_start = Some(i);
4724 }
4725 depth += 1;
4726 }
4727 '}' => {
4728 depth -= 1;
4729 if depth == 0 {
4730 if let Some(start) = brace_start {
4731 let prefix = &s[..start];
4732 let content = &s[start + 1..i];
4733 let suffix = &s[i + 1..];
4734
4735 let expansions = if content.contains("..") {
4737 self.expand_brace_sequence(content)
4738 } else if content.contains(',') {
4739 self.expand_brace_list(content)
4740 } else {
4741 return vec![s.to_string()];
4743 };
4744
4745 let mut results = Vec::new();
4747 for exp in expansions {
4748 let combined = format!("{}{}{}", prefix, exp, suffix);
4749 results.extend(self.expand_braces(&combined));
4751 }
4752 return results;
4753 }
4754 }
4755 }
4756 _ => {}
4757 }
4758 }
4759
4760 vec![s.to_string()]
4762 }
4763
4764 fn expand_brace_list(&self, content: &str) -> Vec<String> {
4766 let mut parts = Vec::new();
4768 let mut current = String::new();
4769 let mut depth = 0;
4770
4771 for c in content.chars() {
4772 match c {
4773 '{' => {
4774 depth += 1;
4775 current.push(c);
4776 }
4777 '}' => {
4778 depth -= 1;
4779 current.push(c);
4780 }
4781 ',' if depth == 0 => {
4782 parts.push(current.clone());
4783 current.clear();
4784 }
4785 _ => current.push(c),
4786 }
4787 }
4788 parts.push(current);
4789
4790 parts
4791 }
4792
4793 fn expand_brace_sequence(&self, content: &str) -> Vec<String> {
4795 let parts: Vec<&str> = content.splitn(3, "..").collect();
4796 if parts.len() < 2 {
4797 return vec![content.to_string()];
4798 }
4799
4800 let start = parts[0];
4801 let end = parts[1];
4802 let step: i64 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(1);
4803
4804 if let (Ok(start_num), Ok(end_num)) = (start.parse::<i64>(), end.parse::<i64>()) {
4806 let mut results = Vec::new();
4807 if start_num <= end_num {
4808 let mut i = start_num;
4809 while i <= end_num {
4810 results.push(i.to_string());
4811 i += step;
4812 }
4813 } else {
4814 let mut i = start_num;
4815 while i >= end_num {
4816 results.push(i.to_string());
4817 i -= step;
4818 }
4819 }
4820 return results;
4821 }
4822
4823 if start.len() == 1 && end.len() == 1 {
4825 let start_char = start.chars().next().unwrap();
4826 let end_char = end.chars().next().unwrap();
4827 let mut results = Vec::new();
4828
4829 if start_char <= end_char {
4830 let mut c = start_char;
4831 while c <= end_char {
4832 results.push(c.to_string());
4833 c = (c as u8 + step as u8) as char;
4834 if c as u8 > end_char as u8 {
4835 break;
4836 }
4837 }
4838 } else {
4839 let mut c = start_char;
4840 while c >= end_char {
4841 results.push(c.to_string());
4842 if (c as u8) < step as u8 {
4843 break;
4844 }
4845 c = (c as u8 - step as u8) as char;
4846 }
4847 }
4848 return results;
4849 }
4850
4851 vec![content.to_string()]
4852 }
4853
4854 fn expand_glob(&self, pattern: &str) -> Vec<String> {
4856 let (glob_pattern, qualifiers) = self.parse_glob_qualifiers(pattern);
4858
4859 if self.has_extglob_pattern(&glob_pattern) {
4861 let expanded = self.expand_extglob(&glob_pattern);
4862 return self.filter_by_qualifiers(expanded, &qualifiers);
4863 }
4864
4865 let nullglob = self.options.get("nullglob").copied().unwrap_or(false);
4866 let dotglob = self.options.get("dotglob").copied().unwrap_or(false);
4867 let nocaseglob = self.options.get("nocaseglob").copied().unwrap_or(false);
4868
4869 let expanded = if glob_pattern.contains("**/") {
4874 self.expand_glob_parallel(&glob_pattern, dotglob, nocaseglob)
4875 } else {
4876 let options = glob::MatchOptions {
4877 case_sensitive: !nocaseglob,
4878 require_literal_separator: false,
4879 require_literal_leading_dot: !dotglob,
4880 };
4881 match glob::glob_with(&glob_pattern, options) {
4882 Ok(paths) => paths
4883 .filter_map(|p| p.ok())
4884 .map(|p| p.to_string_lossy().to_string())
4885 .collect(),
4886 Err(_) => vec![],
4887 }
4888 };
4889
4890 let mut expanded = self.filter_by_qualifiers(expanded, &qualifiers);
4891 expanded.sort();
4892
4893 if expanded.is_empty() {
4894 if nullglob {
4895 vec![]
4896 } else {
4897 vec![pattern.to_string()]
4898 }
4899 } else {
4900 expanded
4901 }
4902 }
4903
4904 fn expand_glob_parallel(&self, pattern: &str, dotglob: bool, nocaseglob: bool) -> Vec<String> {
4910 use walkdir::WalkDir;
4911
4912 let (base, file_glob) = if let Some(pos) = pattern.find("**/") {
4916 let base = if pos == 0 {
4917 "."
4918 } else {
4919 &pattern[..pos.saturating_sub(1)]
4920 };
4921 let rest = &pattern[pos + 3..]; (base.to_string(), rest.to_string())
4923 } else {
4924 return vec![];
4925 };
4926
4927 if file_glob.contains("**/") {
4930 let options = glob::MatchOptions {
4931 case_sensitive: !nocaseglob,
4932 require_literal_separator: false,
4933 require_literal_leading_dot: !dotglob,
4934 };
4935 return match glob::glob_with(pattern, options) {
4936 Ok(paths) => paths
4937 .filter_map(|p| p.ok())
4938 .map(|p| p.to_string_lossy().to_string())
4939 .collect(),
4940 Err(_) => vec![],
4941 };
4942 }
4943
4944 let match_opts = glob::MatchOptions {
4946 case_sensitive: !nocaseglob,
4947 require_literal_separator: false,
4948 require_literal_leading_dot: !dotglob,
4949 };
4950 let file_pat = match glob::Pattern::new(&file_glob) {
4951 Ok(p) => p,
4952 Err(_) => return vec![],
4953 };
4954
4955 let top_entries: Vec<std::path::PathBuf> = match std::fs::read_dir(&base) {
4957 Ok(rd) => rd.filter_map(|e| e.ok()).map(|e| e.path()).collect(),
4958 Err(_) => return vec![],
4959 };
4960
4961 let mut results: Vec<String> = Vec::new();
4963 for entry in &top_entries {
4964 if entry.is_file() || entry.is_symlink() {
4965 if let Some(name) = entry.file_name().and_then(|n| n.to_str()) {
4966 if file_pat.matches_with(name, match_opts) {
4967 results.push(entry.to_string_lossy().to_string());
4968 }
4969 }
4970 }
4971 }
4972
4973 let subdirs: Vec<std::path::PathBuf> = top_entries
4975 .into_iter()
4976 .filter(|p| p.is_dir())
4977 .filter(|p| {
4978 dotglob
4979 || !p
4980 .file_name()
4981 .and_then(|n| n.to_str())
4982 .map(|n| n.starts_with('.'))
4983 .unwrap_or(false)
4984 })
4985 .collect();
4986
4987 if subdirs.is_empty() {
4988 return results;
4989 }
4990
4991 let (tx, rx) = std::sync::mpsc::channel::<Vec<String>>();
4992
4993 for subdir in &subdirs {
4994 let tx = tx.clone();
4995 let subdir = subdir.clone();
4996 let file_pat = file_pat.clone();
4997 let skip_dot = !dotglob;
4998 self.worker_pool.submit(move || {
4999 let mut matches = Vec::new();
5000 let walker = WalkDir::new(&subdir)
5001 .follow_links(false)
5002 .into_iter()
5003 .filter_entry(move |e| {
5004 if skip_dot {
5006 if let Some(name) = e.file_name().to_str() {
5007 if name.starts_with('.') && e.depth() > 0 {
5008 return false;
5009 }
5010 }
5011 }
5012 true
5013 });
5014 for entry in walker.filter_map(|e| e.ok()) {
5015 if entry.file_type().is_file() || entry.file_type().is_symlink() {
5016 if let Some(name) = entry.file_name().to_str() {
5017 if file_pat.matches_with(name, match_opts) {
5018 matches.push(entry.path().to_string_lossy().to_string());
5019 }
5020 }
5021 }
5022 }
5023 let _ = tx.send(matches);
5024 });
5025 }
5026
5027 drop(tx);
5029
5030 for batch in rx {
5032 results.extend(batch);
5033 }
5034
5035 results
5036 }
5037
5038 fn parse_glob_qualifiers(&self, pattern: &str) -> (String, String) {
5041 if !pattern.ends_with(')') {
5044 return (pattern.to_string(), String::new());
5045 }
5046
5047 let chars: Vec<char> = pattern.chars().collect();
5049 let mut depth = 0;
5050 let mut qual_start = None;
5051
5052 for i in (0..chars.len()).rev() {
5053 match chars[i] {
5054 ')' => depth += 1,
5055 '(' => {
5056 depth -= 1;
5057 if depth == 0 {
5058 qual_start = Some(i);
5059 break;
5060 }
5061 }
5062 _ => {}
5063 }
5064 }
5065
5066 if let Some(start) = qual_start {
5067 let qual_content: String = chars[start + 1..chars.len() - 1].iter().collect();
5068
5069 if !qual_content.contains('|') && self.looks_like_glob_qualifiers(&qual_content) {
5073 let base_pattern: String = chars[..start].iter().collect();
5074 return (base_pattern, qual_content);
5075 }
5076 }
5077
5078 (pattern.to_string(), String::new())
5079 }
5080
5081 fn looks_like_glob_qualifiers(&self, s: &str) -> bool {
5083 if s.is_empty() {
5084 return false;
5085 }
5086 let valid_chars = "./@=p*%brwxAIERWXsStfedDLNnMmcaou^-+:0123456789,[]FT";
5089 s.chars()
5090 .all(|c| valid_chars.contains(c) || c.is_whitespace())
5091 }
5092
5093 fn prefetch_metadata(
5098 &self,
5099 files: &[String],
5100 ) -> HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)> {
5101 if files.len() < 32 {
5102 return files
5104 .iter()
5105 .map(|f| {
5106 let meta = std::fs::metadata(f).ok();
5107 let symlink_meta = std::fs::symlink_metadata(f).ok();
5108 (f.clone(), (meta, symlink_meta))
5109 })
5110 .collect();
5111 }
5112
5113 let pool_size = self.worker_pool.size();
5114 let chunk_size = (files.len() + pool_size - 1) / pool_size;
5115 let (tx, rx) = std::sync::mpsc::channel();
5116
5117 for chunk in files.chunks(chunk_size) {
5118 let tx = tx.clone();
5119 let chunk: Vec<String> = chunk.to_vec();
5120 self.worker_pool.submit(move || {
5121 let batch: Vec<(
5122 String,
5123 (Option<std::fs::Metadata>, Option<std::fs::Metadata>),
5124 )> = chunk
5125 .into_iter()
5126 .map(|f| {
5127 let meta = std::fs::metadata(&f).ok();
5128 let symlink_meta = std::fs::symlink_metadata(&f).ok();
5129 (f, (meta, symlink_meta))
5130 })
5131 .collect();
5132 let _ = tx.send(batch);
5133 });
5134 }
5135 drop(tx);
5136
5137 let mut map = HashMap::with_capacity(files.len());
5138 for batch in rx {
5139 for (path, metas) in batch {
5140 map.insert(path, metas);
5141 }
5142 }
5143 map
5144 }
5145
5146 fn filter_by_qualifiers(&self, files: Vec<String>, qualifiers: &str) -> Vec<String> {
5147 if qualifiers.is_empty() {
5148 return files;
5149 }
5150
5151 let meta_cache = self.prefetch_metadata(&files);
5154
5155 let mut result = files;
5156 let mut negate = false;
5157 let mut chars = qualifiers.chars().peekable();
5158
5159 while let Some(c) = chars.next() {
5160 match c {
5161 '^' => negate = !negate,
5163
5164 '.' => {
5166 result = result
5167 .into_iter()
5168 .filter(|f| {
5169 let is_file = meta_cache
5170 .get(f)
5171 .and_then(|(m, _)| m.as_ref())
5172 .map(|m| m.is_file())
5173 .unwrap_or(false);
5174 if negate {
5175 !is_file
5176 } else {
5177 is_file
5178 }
5179 })
5180 .collect();
5181 negate = false;
5182 }
5183 '/' => {
5184 result = result
5185 .into_iter()
5186 .filter(|f| {
5187 let is_dir = meta_cache
5188 .get(f)
5189 .and_then(|(m, _)| m.as_ref())
5190 .map(|m| m.is_dir())
5191 .unwrap_or(false);
5192 if negate {
5193 !is_dir
5194 } else {
5195 is_dir
5196 }
5197 })
5198 .collect();
5199 negate = false;
5200 }
5201 '@' => {
5202 result = result
5203 .into_iter()
5204 .filter(|f| {
5205 let is_link = meta_cache
5206 .get(f)
5207 .and_then(|(_, sm)| sm.as_ref())
5208 .map(|m| m.file_type().is_symlink())
5209 .unwrap_or(false);
5210 if negate {
5211 !is_link
5212 } else {
5213 is_link
5214 }
5215 })
5216 .collect();
5217 negate = false;
5218 }
5219 '=' => {
5220 use std::os::unix::fs::FileTypeExt;
5222 result = result
5223 .into_iter()
5224 .filter(|f| {
5225 let is_socket = meta_cache
5226 .get(f)
5227 .and_then(|(_, sm)| sm.as_ref())
5228 .map(|m| m.file_type().is_socket())
5229 .unwrap_or(false);
5230 if negate {
5231 !is_socket
5232 } else {
5233 is_socket
5234 }
5235 })
5236 .collect();
5237 negate = false;
5238 }
5239 'p' => {
5240 use std::os::unix::fs::FileTypeExt;
5242 result = result
5243 .into_iter()
5244 .filter(|f| {
5245 let is_fifo = meta_cache
5246 .get(f)
5247 .and_then(|(_, sm)| sm.as_ref())
5248 .map(|m| m.file_type().is_fifo())
5249 .unwrap_or(false);
5250 if negate {
5251 !is_fifo
5252 } else {
5253 is_fifo
5254 }
5255 })
5256 .collect();
5257 negate = false;
5258 }
5259 '*' => {
5260 use std::os::unix::fs::PermissionsExt;
5262 result = result
5263 .into_iter()
5264 .filter(|f| {
5265 let is_exec = meta_cache
5266 .get(f)
5267 .and_then(|(m, _)| m.as_ref())
5268 .map(|m| m.is_file() && (m.permissions().mode() & 0o111) != 0)
5269 .unwrap_or(false);
5270 if negate {
5271 !is_exec
5272 } else {
5273 is_exec
5274 }
5275 })
5276 .collect();
5277 negate = false;
5278 }
5279 '%' => {
5280 use std::os::unix::fs::FileTypeExt;
5282 let next = chars.peek().copied();
5283 result = result
5284 .into_iter()
5285 .filter(|f| {
5286 let is_device = meta_cache
5287 .get(f)
5288 .and_then(|(_, sm)| sm.as_ref())
5289 .map(|m| match next {
5290 Some('b') => m.file_type().is_block_device(),
5291 Some('c') => m.file_type().is_char_device(),
5292 _ => {
5293 m.file_type().is_block_device()
5294 || m.file_type().is_char_device()
5295 }
5296 })
5297 .unwrap_or(false);
5298 if negate {
5299 !is_device
5300 } else {
5301 is_device
5302 }
5303 })
5304 .collect();
5305 if next == Some('b') || next == Some('c') {
5306 chars.next();
5307 }
5308 negate = false;
5309 }
5310
5311 'r' => {
5313 result = self.filter_by_permission(result, 0o400, negate, &meta_cache);
5314 negate = false;
5315 }
5316 'w' => {
5317 result = self.filter_by_permission(result, 0o200, negate, &meta_cache);
5318 negate = false;
5319 }
5320 'x' => {
5321 result = self.filter_by_permission(result, 0o100, negate, &meta_cache);
5322 negate = false;
5323 }
5324 'A' => {
5325 result = self.filter_by_permission(result, 0o040, negate, &meta_cache);
5326 negate = false;
5327 }
5328 'I' => {
5329 result = self.filter_by_permission(result, 0o020, negate, &meta_cache);
5330 negate = false;
5331 }
5332 'E' => {
5333 result = self.filter_by_permission(result, 0o010, negate, &meta_cache);
5334 negate = false;
5335 }
5336 'R' => {
5337 result = self.filter_by_permission(result, 0o004, negate, &meta_cache);
5338 negate = false;
5339 }
5340 'W' => {
5341 result = self.filter_by_permission(result, 0o002, negate, &meta_cache);
5342 negate = false;
5343 }
5344 'X' => {
5345 result = self.filter_by_permission(result, 0o001, negate, &meta_cache);
5346 negate = false;
5347 }
5348 's' => {
5349 result = self.filter_by_permission(result, 0o4000, negate, &meta_cache);
5350 negate = false;
5351 }
5352 'S' => {
5353 result = self.filter_by_permission(result, 0o2000, negate, &meta_cache);
5354 negate = false;
5355 }
5356 't' => {
5357 result = self.filter_by_permission(result, 0o1000, negate, &meta_cache);
5358 negate = false;
5359 }
5360
5361 'F' => {
5363 result = result
5365 .into_iter()
5366 .filter(|f| {
5367 let path = std::path::Path::new(f);
5368 let is_nonempty = path.is_dir()
5369 && std::fs::read_dir(path)
5370 .map(|mut d| d.next().is_some())
5371 .unwrap_or(false);
5372 if negate {
5373 !is_nonempty
5374 } else {
5375 is_nonempty
5376 }
5377 })
5378 .collect();
5379 negate = false;
5380 }
5381
5382 'U' => {
5384 let euid = unsafe { libc::geteuid() };
5386 result = result
5387 .into_iter()
5388 .filter(|f| {
5389 use std::os::unix::fs::MetadataExt;
5390 let is_owned = meta_cache
5391 .get(f)
5392 .and_then(|(m, _)| m.as_ref())
5393 .map(|m| m.uid() == euid)
5394 .unwrap_or(false);
5395 if negate {
5396 !is_owned
5397 } else {
5398 is_owned
5399 }
5400 })
5401 .collect();
5402 negate = false;
5403 }
5404 'G' => {
5405 let egid = unsafe { libc::getegid() };
5407 result = result
5408 .into_iter()
5409 .filter(|f| {
5410 use std::os::unix::fs::MetadataExt;
5411 let is_owned = meta_cache
5412 .get(f)
5413 .and_then(|(m, _)| m.as_ref())
5414 .map(|m| m.gid() == egid)
5415 .unwrap_or(false);
5416 if negate {
5417 !is_owned
5418 } else {
5419 is_owned
5420 }
5421 })
5422 .collect();
5423 negate = false;
5424 }
5425
5426 'o' => {
5428 if chars.peek() == Some(&'n') {
5430 chars.next();
5431 result.sort();
5433 } else if chars.peek() == Some(&'L') {
5434 chars.next();
5435 result.sort_by_key(|f| {
5437 meta_cache
5438 .get(f)
5439 .and_then(|(m, _)| m.as_ref())
5440 .map(|m| m.len())
5441 .unwrap_or(0)
5442 });
5443 } else if chars.peek() == Some(&'m') {
5444 chars.next();
5445 result.sort_by_key(|f| {
5447 meta_cache
5448 .get(f)
5449 .and_then(|(m, _)| m.as_ref())
5450 .and_then(|m| m.modified().ok())
5451 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5452 });
5453 } else if chars.peek() == Some(&'a') {
5454 chars.next();
5455 result.sort_by_key(|f| {
5457 meta_cache
5458 .get(f)
5459 .and_then(|(m, _)| m.as_ref())
5460 .and_then(|m| m.accessed().ok())
5461 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5462 });
5463 }
5464 }
5465 'O' => {
5466 if chars.peek() == Some(&'n') {
5468 chars.next();
5469 result.sort();
5470 result.reverse();
5471 } else if chars.peek() == Some(&'L') {
5472 chars.next();
5473 result.sort_by_key(|f| {
5474 meta_cache
5475 .get(f)
5476 .and_then(|(m, _)| m.as_ref())
5477 .map(|m| m.len())
5478 .unwrap_or(0)
5479 });
5480 result.reverse();
5481 } else if chars.peek() == Some(&'m') {
5482 chars.next();
5483 result.sort_by_key(|f| {
5484 meta_cache
5485 .get(f)
5486 .and_then(|(m, _)| m.as_ref())
5487 .and_then(|m| m.modified().ok())
5488 .unwrap_or(std::time::SystemTime::UNIX_EPOCH)
5489 });
5490 result.reverse();
5491 } else {
5492 result.reverse();
5494 }
5495 }
5496
5497 '[' => {
5499 let mut range_str = String::new();
5500 while let Some(&ch) = chars.peek() {
5501 if ch == ']' {
5502 chars.next();
5503 break;
5504 }
5505 range_str.push(chars.next().unwrap());
5506 }
5507
5508 if let Some((start, end)) = self.parse_subscript_range(&range_str, result.len())
5509 {
5510 result = result.into_iter().skip(start).take(end - start).collect();
5511 }
5512 }
5513
5514 'D' => {
5516 }
5518 'N' => {
5519 }
5521
5522 _ => {}
5524 }
5525 }
5526
5527 result
5528 }
5529
5530 fn filter_by_permission(
5532 &self,
5533 files: Vec<String>,
5534 mode: u32,
5535 negate: bool,
5536 meta_cache: &HashMap<String, (Option<std::fs::Metadata>, Option<std::fs::Metadata>)>,
5537 ) -> Vec<String> {
5538 use std::os::unix::fs::PermissionsExt;
5539 files
5540 .into_iter()
5541 .filter(|f| {
5542 let has_perm = meta_cache
5543 .get(f)
5544 .and_then(|(m, _)| m.as_ref())
5545 .map(|m| (m.permissions().mode() & mode) != 0)
5546 .unwrap_or(false);
5547 if negate {
5548 !has_perm
5549 } else {
5550 has_perm
5551 }
5552 })
5553 .collect()
5554 }
5555
5556 fn parse_subscript_range(&self, s: &str, len: usize) -> Option<(usize, usize)> {
5558 if s.is_empty() || len == 0 {
5559 return None;
5560 }
5561
5562 let parts: Vec<&str> = s.split(',').collect();
5563
5564 let parse_idx = |idx_str: &str| -> Option<usize> {
5565 let idx: i64 = idx_str.trim().parse().ok()?;
5566 if idx < 0 {
5567 let abs = (-idx) as usize;
5569 if abs > len {
5570 None
5571 } else {
5572 Some(len - abs)
5573 }
5574 } else if idx == 0 {
5575 Some(0)
5576 } else {
5577 Some((idx as usize).saturating_sub(1).min(len))
5579 }
5580 };
5581
5582 match parts.len() {
5583 1 => {
5584 let idx = parse_idx(parts[0])?;
5586 Some((idx, idx + 1))
5587 }
5588 2 => {
5589 let start = parse_idx(parts[0])?;
5591 let end = parse_idx(parts[1])?.saturating_add(1);
5592 Some((start.min(end), start.max(end)))
5593 }
5594 _ => None,
5595 }
5596 }
5597
5598 fn has_extglob_pattern(&self, pattern: &str) -> bool {
5600 let chars: Vec<char> = pattern.chars().collect();
5601 for i in 0..chars.len().saturating_sub(1) {
5602 if (chars[i] == '?'
5603 || chars[i] == '*'
5604 || chars[i] == '+'
5605 || chars[i] == '@'
5606 || chars[i] == '!')
5607 && chars[i + 1] == '('
5608 {
5609 return true;
5610 }
5611 }
5612 false
5613 }
5614
5615 fn extglob_to_regex(&self, pattern: &str) -> String {
5617 let mut regex = String::from("^");
5618 let chars: Vec<char> = pattern.chars().collect();
5619 let mut i = 0;
5620
5621 while i < chars.len() {
5622 let c = chars[i];
5623
5624 if i + 1 < chars.len() && chars[i + 1] == '(' {
5626 match c {
5627 '?' => {
5628 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5630 let inner_regex = self.extglob_inner_to_regex(&inner);
5631 regex.push_str(&format!("({})?", inner_regex));
5632 i = end + 1;
5633 continue;
5634 }
5635 '*' => {
5636 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5638 let inner_regex = self.extglob_inner_to_regex(&inner);
5639 regex.push_str(&format!("({})*", inner_regex));
5640 i = end + 1;
5641 continue;
5642 }
5643 '+' => {
5644 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5646 let inner_regex = self.extglob_inner_to_regex(&inner);
5647 regex.push_str(&format!("({})+", inner_regex));
5648 i = end + 1;
5649 continue;
5650 }
5651 '@' => {
5652 let (inner, end) = self.extract_extglob_inner(&chars, i + 2);
5654 let inner_regex = self.extglob_inner_to_regex(&inner);
5655 regex.push_str(&format!("({})", inner_regex));
5656 i = end + 1;
5657 continue;
5658 }
5659 '!' => {
5660 let (_, end) = self.extract_extglob_inner(&chars, i + 2);
5663 regex.push_str(".*"); i = end + 1;
5665 continue;
5666 }
5667 _ => {}
5668 }
5669 }
5670
5671 match c {
5673 '*' => regex.push_str(".*"),
5674 '?' => regex.push('.'),
5675 '.' => regex.push_str("\\."),
5676 '[' => {
5677 regex.push('[');
5678 i += 1;
5679 while i < chars.len() && chars[i] != ']' {
5680 if chars[i] == '!' && regex.ends_with('[') {
5681 regex.push('^');
5682 } else {
5683 regex.push(chars[i]);
5684 }
5685 i += 1;
5686 }
5687 regex.push(']');
5688 }
5689 '^' | '$' | '(' | ')' | '{' | '}' | '|' | '\\' => {
5690 regex.push('\\');
5691 regex.push(c);
5692 }
5693 _ => regex.push(c),
5694 }
5695 i += 1;
5696 }
5697
5698 regex.push('$');
5699 regex
5700 }
5701
5702 fn extract_extglob_inner(&self, chars: &[char], start: usize) -> (String, usize) {
5704 let mut inner = String::new();
5705 let mut depth = 1;
5706 let mut i = start;
5707
5708 while i < chars.len() && depth > 0 {
5709 if chars[i] == '(' {
5710 depth += 1;
5711 } else if chars[i] == ')' {
5712 depth -= 1;
5713 if depth == 0 {
5714 return (inner, i);
5715 }
5716 }
5717 inner.push(chars[i]);
5718 i += 1;
5719 }
5720
5721 (inner, i)
5722 }
5723
5724 fn extglob_inner_to_regex(&self, inner: &str) -> String {
5726 let alternatives: Vec<String> = inner
5728 .split('|')
5729 .map(|alt| {
5730 let mut result = String::new();
5731 for c in alt.chars() {
5732 match c {
5733 '*' => result.push_str(".*"),
5734 '?' => result.push('.'),
5735 '.' => result.push_str("\\."),
5736 '^' | '$' | '(' | ')' | '{' | '}' | '\\' => {
5737 result.push('\\');
5738 result.push(c);
5739 }
5740 _ => result.push(c),
5741 }
5742 }
5743 result
5744 })
5745 .collect();
5746
5747 alternatives.join("|")
5748 }
5749
5750 fn expand_extglob(&self, pattern: &str) -> Vec<String> {
5752 let (search_dir, file_pattern) = if let Some(last_slash) = pattern.rfind('/') {
5754 (&pattern[..last_slash], &pattern[last_slash + 1..])
5755 } else {
5756 (".", pattern)
5757 };
5758
5759 if let Some((neg_pat, suffix)) = self.extract_neg_extglob(file_pattern) {
5761 return self.expand_neg_extglob(search_dir, &neg_pat, &suffix, pattern);
5762 }
5763
5764 let regex_str = self.extglob_to_regex(file_pattern);
5766
5767 let re = match cached_regex(®ex_str) {
5768 Some(r) => r,
5769 None => return vec![pattern.to_string()],
5770 };
5771
5772 let mut results = Vec::new();
5773
5774 if let Ok(entries) = std::fs::read_dir(search_dir) {
5775 for entry in entries.flatten() {
5776 let name = entry.file_name().to_string_lossy().to_string();
5777 if name.starts_with('.') && !file_pattern.starts_with('.') {
5779 continue;
5780 }
5781
5782 if re.is_match(&name) {
5783 let full_path = if search_dir == "." {
5784 name
5785 } else {
5786 format!("{}/{}", search_dir, name)
5787 };
5788 results.push(full_path);
5789 }
5790 }
5791 }
5792
5793 if results.is_empty() {
5794 vec![pattern.to_string()]
5795 } else {
5796 results.sort();
5797 results
5798 }
5799 }
5800
5801 fn expand_neg_extglob(
5803 &self,
5804 search_dir: &str,
5805 neg_pat: &str,
5806 suffix: &str,
5807 original_pattern: &str,
5808 ) -> Vec<String> {
5809 let mut results = Vec::new();
5810
5811 if let Ok(entries) = std::fs::read_dir(search_dir) {
5812 for entry in entries.flatten() {
5813 let name = entry.file_name().to_string_lossy().to_string();
5814 if name.starts_with('.') {
5816 continue;
5817 }
5818
5819 if !name.ends_with(suffix) {
5821 continue;
5822 }
5823
5824 let basename = &name[..name.len() - suffix.len()];
5825 let alts: Vec<&str> = neg_pat.split('|').collect();
5827 let matches_neg = alts.iter().any(|alt| {
5828 if alt.contains('*') || alt.contains('?') {
5829 let alt_re = self.extglob_inner_to_regex(alt);
5830 let full_pattern = format!("^{}$", alt_re);
5831 if let Some(r) = cached_regex(&full_pattern) {
5832 r.is_match(basename)
5833 } else {
5834 *alt == basename
5835 }
5836 } else {
5837 *alt == basename
5838 }
5839 });
5840
5841 if !matches_neg {
5842 let full_path = if search_dir == "." {
5843 name
5844 } else {
5845 format!("{}/{}", search_dir, name)
5846 };
5847 results.push(full_path);
5848 }
5849 }
5850 }
5851
5852 if results.is_empty() {
5853 vec![original_pattern.to_string()]
5854 } else {
5855 results.sort();
5856 results
5857 }
5858 }
5859
5860 fn extract_neg_extglob(&self, pattern: &str) -> Option<(String, String)> {
5862 let chars: Vec<char> = pattern.chars().collect();
5863 if chars.len() >= 3 && chars[0] == '!' && chars[1] == '(' {
5864 let mut depth = 1;
5865 let mut i = 2;
5866 while i < chars.len() && depth > 0 {
5867 if chars[i] == '(' {
5868 depth += 1;
5869 } else if chars[i] == ')' {
5870 depth -= 1;
5871 }
5872 i += 1;
5873 }
5874 if depth == 0 {
5875 let inner: String = chars[2..i - 1].iter().collect();
5876 let suffix: String = chars[i..].iter().collect();
5877 return Some((inner, suffix));
5878 }
5879 }
5880 None
5881 }
5882
5883 fn expand_word_split(&mut self, word: &ShellWord) -> Vec<String> {
5885 match word {
5886 ShellWord::Literal(s) => {
5887 let brace_expanded = self.expand_braces(s);
5889 brace_expanded
5890 .into_iter()
5891 .flat_map(|item| self.expand_string_split(&item))
5892 .collect()
5893 }
5894 ShellWord::SingleQuoted(s) => vec![s.clone()],
5895 ShellWord::DoubleQuoted(parts) => {
5896 vec![parts.iter().map(|p| self.expand_word(p)).collect()]
5898 }
5899 ShellWord::Variable(name) => {
5900 let val = env::var(name).unwrap_or_default();
5901 self.split_words(&val)
5902 }
5903 ShellWord::VariableBraced(name, modifier) => {
5904 let val = env::var(name).ok();
5905 let expanded = self.apply_var_modifier(name, val, modifier.as_deref());
5906 self.split_words(&expanded)
5907 }
5908 ShellWord::ArrayVar(name, index) => {
5909 let idx_str = self.expand_word(index);
5910 if idx_str == "@" || idx_str == "*" {
5911 self.arrays.get(name).cloned().unwrap_or_default()
5913 } else {
5914 vec![self.expand_array_access(name, index)]
5915 }
5916 }
5917 ShellWord::Glob(pattern) => match glob::glob(pattern) {
5918 Ok(paths) => {
5919 let expanded: Vec<String> = paths
5920 .filter_map(|p| p.ok())
5921 .map(|p| p.to_string_lossy().to_string())
5922 .collect();
5923 if expanded.is_empty() {
5924 vec![pattern.clone()]
5925 } else {
5926 expanded
5927 }
5928 }
5929 Err(_) => vec![pattern.clone()],
5930 },
5931 ShellWord::CommandSub(_) => {
5932 let val = self.expand_word(word);
5934 self.split_words(&val)
5935 }
5936 ShellWord::Concat(parts) => {
5937 let val = self.expand_concat_parallel(parts);
5939 self.split_words(&val)
5940 }
5941 _ => vec![self.expand_word(word)],
5942 }
5943 }
5944
5945 fn expand_string_split(&mut self, s: &str) -> Vec<String> {
5947 let mut results: Vec<String> = Vec::new();
5948 let mut current = String::new();
5949 let mut chars = s.chars().peekable();
5950
5951 while let Some(c) = chars.next() {
5952 if c == '$' {
5953 if chars.peek() == Some(&'{') {
5954 chars.next(); let mut brace_content = String::new();
5956 let mut depth = 1;
5957 while let Some(ch) = chars.next() {
5958 if ch == '{' {
5959 depth += 1;
5960 brace_content.push(ch);
5961 } else if ch == '}' {
5962 depth -= 1;
5963 if depth == 0 {
5964 break;
5965 }
5966 brace_content.push(ch);
5967 } else {
5968 brace_content.push(ch);
5969 }
5970 }
5971
5972 if let Some(bracket_start) = brace_content.find('[') {
5974 let var_name = &brace_content[..bracket_start];
5975 let bracket_content = &brace_content[bracket_start + 1..];
5976 if let Some(bracket_end) = bracket_content.find(']') {
5977 let index = &bracket_content[..bracket_end];
5978 if (index == "@" || index == "*")
5979 && bracket_end + 1 == bracket_content.len()
5980 {
5981 if !current.is_empty() {
5983 results.push(current.clone());
5984 current.clear();
5985 }
5986 if let Some(arr) = self.arrays.get(var_name) {
5987 results.extend(arr.clone());
5988 }
5989 continue;
5990 }
5991 }
5992 }
5993
5994 current.push_str(&self.expand_braced_variable(&brace_content));
5996 } else {
5997 let mut var_name = String::new();
5999 while let Some(&ch) = chars.peek() {
6000 if ch.is_alphanumeric() || ch == '_' {
6001 var_name.push(chars.next().unwrap());
6002 } else {
6003 break;
6004 }
6005 }
6006 let val = self.get_variable(&var_name);
6007 if !current.is_empty() {
6009 results.push(current.clone());
6010 current.clear();
6011 }
6012 results.extend(self.split_words(&val));
6013 }
6014 } else {
6015 current.push(c);
6016 }
6017 }
6018
6019 if !current.is_empty() {
6020 results.push(current);
6021 }
6022
6023 if results.is_empty() {
6024 results.push(String::new());
6025 }
6026
6027 results
6028 }
6029
6030 fn split_words(&self, s: &str) -> Vec<String> {
6032 let ifs = self
6033 .variables
6034 .get("IFS")
6035 .cloned()
6036 .or_else(|| env::var("IFS").ok())
6037 .unwrap_or_else(|| " \t\n".to_string());
6038
6039 if ifs.is_empty() {
6040 return vec![s.to_string()];
6041 }
6042
6043 s.split(|c: char| ifs.contains(c))
6044 .filter(|s| !s.is_empty())
6045 .map(|s| s.to_string())
6046 .collect()
6047 }
6048
6049 #[tracing::instrument(level = "trace", skip_all)]
6050 fn expand_word(&mut self, word: &ShellWord) -> String {
6051 match word {
6052 ShellWord::Literal(s) => {
6053 let expanded = self.expand_string(s);
6054 expanded
6056 }
6057 ShellWord::SingleQuoted(s) => s.clone(),
6058 ShellWord::DoubleQuoted(parts) => parts.iter().map(|p| self.expand_word(p)).collect(),
6059 ShellWord::Variable(name) => self.get_variable(name),
6060 ShellWord::VariableBraced(name, modifier) => {
6061 let val = env::var(name).ok();
6062 self.apply_var_modifier(name, val, modifier.as_deref())
6063 }
6064 ShellWord::Tilde(user) => {
6065 if let Some(u) = user {
6066 format!("/home/{}", u)
6068 } else {
6069 dirs::home_dir()
6070 .map(|p| p.to_string_lossy().to_string())
6071 .unwrap_or_else(|| "~".to_string())
6072 }
6073 }
6074 ShellWord::Glob(pattern) => {
6075 match glob::glob(pattern) {
6077 Ok(paths) => {
6078 let expanded: Vec<String> = paths
6079 .filter_map(|p| p.ok())
6080 .map(|p| p.to_string_lossy().to_string())
6081 .collect();
6082 if expanded.is_empty() {
6083 pattern.clone()
6084 } else {
6085 expanded.join(" ")
6086 }
6087 }
6088 Err(_) => pattern.clone(),
6089 }
6090 }
6091 ShellWord::Concat(parts) => self.expand_concat_parallel(parts),
6092 ShellWord::CommandSub(cmd) => self.execute_command_substitution(cmd),
6093 ShellWord::ProcessSubIn(cmd) => self.execute_process_sub_in(cmd),
6094 ShellWord::ProcessSubOut(cmd) => self.execute_process_sub_out(cmd),
6095 ShellWord::ArithSub(expr) => self.evaluate_arithmetic(expr),
6096 ShellWord::ArrayVar(name, index) => self.expand_array_access(name, index),
6097 ShellWord::ArrayLiteral(elements) => elements
6098 .iter()
6099 .map(|e| self.expand_word(e))
6100 .collect::<Vec<_>>()
6101 .join(" "),
6102 }
6103 }
6104
6105 fn preflight_command_subs(
6108 &mut self,
6109 words: &[ShellWord],
6110 ) -> Vec<Option<crossbeam_channel::Receiver<String>>> {
6111 use crate::parser::ShellWord;
6112 use std::process::{Command, Stdio};
6113
6114 let mut receivers = Vec::with_capacity(words.len());
6115
6116 let external_count = words
6118 .iter()
6119 .filter(|w| {
6120 if let ShellWord::CommandSub(cmd) = w {
6121 if let ShellCommand::Simple(simple) = cmd.as_ref() {
6122 if let Some(first) = simple.words.first() {
6123 let name = self.expand_word(first);
6124 return !self.functions.contains_key(&name) && !self.is_builtin(&name);
6125 }
6126 }
6127 }
6128 false
6129 })
6130 .count();
6131
6132 if external_count < 2 {
6133 return vec![None; words.len()];
6135 }
6136
6137 for word in words {
6138 if let ShellWord::CommandSub(cmd) = word {
6139 if let ShellCommand::Simple(simple) = cmd.as_ref() {
6140 let first = simple.words.first().map(|w| self.expand_word(w));
6141 if let Some(ref name) = first {
6142 if !self.functions.contains_key(name) && !self.is_builtin(name) {
6143 let expanded: Vec<String> =
6144 simple.words.iter().map(|w| self.expand_word(w)).collect();
6145 let rx = self.worker_pool.submit_with_result(move || {
6146 let output = Command::new(&expanded[0])
6147 .args(&expanded[1..])
6148 .stdout(Stdio::piped())
6149 .stderr(Stdio::inherit())
6150 .output();
6151 match output {
6152 Ok(out) => String::from_utf8_lossy(&out.stdout)
6153 .trim_end_matches('\n')
6154 .to_string(),
6155 Err(_) => String::new(),
6156 }
6157 });
6158 receivers.push(Some(rx));
6159 continue;
6160 }
6161 }
6162 }
6163 }
6164 receivers.push(None);
6165 }
6166
6167 receivers
6168 }
6169
6170 fn expand_concat_parallel(&mut self, parts: &[ShellWord]) -> String {
6173 use crate::parser::ShellWord;
6174 use std::process::{Command, Stdio};
6175
6176 let mut preflight: Vec<Option<crossbeam_channel::Receiver<String>>> =
6178 Vec::with_capacity(parts.len());
6179
6180 for part in parts {
6181 if let ShellWord::CommandSub(cmd) = part {
6182 if let ShellCommand::Simple(simple) = cmd.as_ref() {
6183 let first = simple.words.first().map(|w| self.expand_word(w));
6184 if let Some(ref name) = first {
6185 if !self.functions.contains_key(name) && !self.is_builtin(name) {
6186 let words: Vec<String> =
6188 simple.words.iter().map(|w| self.expand_word(w)).collect();
6189 let rx = self.worker_pool.submit_with_result(move || {
6190 let output = Command::new(&words[0])
6191 .args(&words[1..])
6192 .stdout(Stdio::piped())
6193 .stderr(Stdio::inherit())
6194 .output();
6195 match output {
6196 Ok(out) => String::from_utf8_lossy(&out.stdout)
6197 .trim_end_matches('\n')
6198 .to_string(),
6199 Err(_) => String::new(),
6200 }
6201 });
6202 preflight.push(Some(rx));
6203 continue;
6204 }
6205 }
6206 }
6207 }
6208 preflight.push(None); }
6210
6211 let mut result = String::new();
6213 for (i, part) in parts.iter().enumerate() {
6214 if let Some(rx) = preflight[i].take() {
6215 result.push_str(&rx.recv().unwrap_or_default());
6217 } else {
6218 result.push_str(&self.expand_word(part));
6220 }
6221 }
6222 result
6223 }
6224
6225 fn expand_braced_variable(&mut self, content: &str) -> String {
6226 if content.starts_with("${") {
6228 let mut depth = 0;
6230 let mut inner_end = 0;
6231 for (i, c) in content.char_indices() {
6232 match c {
6233 '{' => depth += 1,
6234 '}' => {
6235 depth -= 1;
6236 if depth == 0 {
6237 inner_end = i;
6238 break;
6239 }
6240 }
6241 _ => {}
6242 }
6243 }
6244
6245 if inner_end > 0 {
6246 let inner_content = &content[2..inner_end];
6248 let inner_result = self.expand_braced_variable(inner_content);
6249
6250 let rest = &content[inner_end + 1..];
6252 if rest.starts_with('[') {
6253 if let Some(bracket_end) = rest.find(']') {
6255 let index = &rest[1..bracket_end];
6256 if let Ok(idx) = index.parse::<i64>() {
6257 let chars: Vec<char> = inner_result.chars().collect();
6258 let actual_idx = if idx < 0 {
6259 (chars.len() as i64 + idx).max(0) as usize
6260 } else if idx > 0 {
6261 (idx - 1) as usize
6262 } else {
6263 0
6264 };
6265 return chars
6266 .get(actual_idx)
6267 .map(|c| c.to_string())
6268 .unwrap_or_default();
6269 }
6270 }
6271 }
6272
6273 return inner_result;
6274 }
6275 }
6276
6277 if content.starts_with('(') {
6279 if let Some(close_paren) = content.find(')') {
6280 let flags_str = &content[1..close_paren];
6281 let rest = &content[close_paren + 1..];
6282 let flags = self.parse_zsh_flags(flags_str);
6283
6284 let has_match_flag = flags.iter().any(|f| matches!(f, ZshParamFlag::Match));
6286
6287 if let Some(filter_pos) = rest.find(":#") {
6289 let var_name = &rest[..filter_pos];
6290 let pattern = &rest[filter_pos + 2..];
6291
6292 if let Some(arr) = self.arrays.get(var_name).cloned() {
6294 let filtered: Vec<String> = if arr.len() >= 1000 {
6295 tracing::trace!(
6296 count = arr.len(),
6297 pattern,
6298 "using parallel filter (rayon) for large array"
6299 );
6300 use rayon::prelude::*;
6301 let pattern = pattern.to_string();
6302 arr.into_par_iter()
6303 .filter(|elem| {
6304 let m = Self::glob_match_static(elem, &pattern);
6305 if has_match_flag {
6306 m
6307 } else {
6308 !m
6309 }
6310 })
6311 .collect()
6312 } else {
6313 arr.into_iter()
6314 .filter(|elem| {
6315 let m = self.glob_match(elem, pattern);
6316 if has_match_flag {
6317 m
6318 } else {
6319 !m
6320 }
6321 })
6322 .collect()
6323 };
6324 return filtered.join(" ");
6325 }
6326
6327 let val = self.get_variable(var_name);
6329 let matches = self.glob_match(&val, pattern);
6330
6331 return if has_match_flag {
6332 if matches {
6333 val
6334 } else {
6335 String::new()
6336 }
6337 } else {
6338 if matches {
6339 String::new()
6340 } else {
6341 val
6342 }
6343 };
6344 }
6345
6346 let (var_name, default_val) = if rest.starts_with(":-") {
6349 ("", Some(&rest[2..]))
6351 } else if let Some(pos) = rest.find(":-") {
6352 (&rest[..pos], Some(&rest[pos + 2..]))
6354 } else if rest.starts_with(':') {
6355 ("", None)
6357 } else {
6358 let vn = rest
6360 .split(|c: char| !c.is_alphanumeric() && c != '_')
6361 .next()
6362 .unwrap_or("");
6363 (vn, None)
6364 };
6365
6366 let mut val = self.get_variable(var_name);
6367
6368 if val.is_empty() {
6370 if let Some(def) = default_val {
6371 val = self.expand_string(def);
6373 }
6374 }
6375
6376 for flag in &flags {
6378 val = self.apply_zsh_param_flag(&val, var_name, flag);
6379 }
6380 return val;
6381 }
6382 }
6383
6384 if content.starts_with('#') {
6386 let rest = &content[1..];
6387 if let Some(bracket_start) = rest.find('[') {
6388 let var_name = &rest[..bracket_start];
6389 let bracket_content = &rest[bracket_start + 1..];
6390 if let Some(bracket_end) = bracket_content.find(']') {
6391 let index = &bracket_content[..bracket_end];
6392 if index == "@" || index == "*" {
6393 return self
6395 .arrays
6396 .get(var_name)
6397 .map(|arr| arr.len().to_string())
6398 .unwrap_or_else(|| "0".to_string());
6399 }
6400 }
6401 }
6402 if self.arrays.contains_key(rest) {
6404 return self
6405 .arrays
6406 .get(rest)
6407 .map(|arr| arr.len().to_string())
6408 .unwrap_or_else(|| "0".to_string());
6409 }
6410 if self.assoc_arrays.contains_key(rest) {
6412 return self
6413 .assoc_arrays
6414 .get(rest)
6415 .map(|h| h.len().to_string())
6416 .unwrap_or_else(|| "0".to_string());
6417 }
6418 let val = self.get_variable(rest);
6420 return val.len().to_string();
6421 }
6422
6423 if content.starts_with('+') {
6425 let rest = &content[1..];
6426
6427 if let Some(bracket_start) = rest.find('[') {
6429 let var_name = &rest[..bracket_start];
6430 let bracket_content = &rest[bracket_start + 1..];
6431 if let Some(bracket_end) = bracket_content.find(']') {
6432 let key = &bracket_content[..bracket_end];
6433
6434 if let Some(val) = self.get_special_array_value(var_name, key) {
6436 return if val.is_empty() {
6437 "0".to_string()
6438 } else {
6439 "1".to_string()
6440 };
6441 }
6442
6443 if self.assoc_arrays.contains_key(var_name) {
6445 let expanded_key = self.expand_string(key);
6446 let has_key = self
6447 .assoc_arrays
6448 .get(var_name)
6449 .map(|a| a.contains_key(&expanded_key))
6450 .unwrap_or(false);
6451 return if has_key {
6452 "1".to_string()
6453 } else {
6454 "0".to_string()
6455 };
6456 }
6457
6458 if let Some(arr) = self.arrays.get(var_name) {
6460 if let Ok(idx) = key.parse::<usize>() {
6461 let actual_idx = if idx > 0 { idx - 1 } else { 0 };
6462 return if arr.get(actual_idx).is_some() {
6463 "1".to_string()
6464 } else {
6465 "0".to_string()
6466 };
6467 }
6468 }
6469
6470 return "0".to_string();
6471 }
6472 }
6473
6474 let is_set = self.variables.contains_key(rest)
6476 || self.arrays.contains_key(rest)
6477 || self.assoc_arrays.contains_key(rest)
6478 || std::env::var(rest).is_ok()
6479 || self.functions.contains_key(rest);
6480 return if is_set {
6481 "1".to_string()
6482 } else {
6483 "0".to_string()
6484 };
6485 }
6486
6487 if let Some(bracket_start) = content.find('[') {
6489 let var_name = &content[..bracket_start];
6490 let bracket_content = &content[bracket_start + 1..];
6491 if let Some(bracket_end) = bracket_content.find(']') {
6492 let index = &bracket_content[..bracket_end];
6493
6494 if let Some(val) = self.get_special_array_value(var_name, index) {
6496 return val;
6497 }
6498
6499 if self.assoc_arrays.contains_key(var_name) {
6501 if index == "@" || index == "*" {
6502 return self
6504 .assoc_arrays
6505 .get(var_name)
6506 .map(|a| a.values().cloned().collect::<Vec<_>>().join(" "))
6507 .unwrap_or_default();
6508 } else {
6509 let key = self.expand_string(index);
6511 return self
6512 .assoc_arrays
6513 .get(var_name)
6514 .and_then(|a| a.get(&key).cloned())
6515 .unwrap_or_default();
6516 }
6517 }
6518
6519 if index == "@" || index == "*" {
6521 return self
6523 .arrays
6524 .get(var_name)
6525 .map(|arr| arr.join(" "))
6526 .unwrap_or_default();
6527 }
6528
6529 use crate::subscript::{
6531 get_array_by_subscript, get_array_element_by_subscript, getindex,
6532 };
6533 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
6534
6535 if let Ok(v) = getindex(index, false, ksh_arrays) {
6536 if let Some(arr) = self.arrays.get(var_name) {
6538 if v.is_all() {
6539 return arr.join(" ");
6540 }
6541 let is_range = index.contains(',');
6545 if is_range {
6546 return get_array_by_subscript(arr, &v, ksh_arrays).join(" ");
6548 } else {
6549 return get_array_element_by_subscript(arr, &v, ksh_arrays)
6551 .unwrap_or_default();
6552 }
6553 }
6554
6555 let val = self.get_variable(var_name);
6557 if !val.is_empty() {
6558 let chars: Vec<char> = val.chars().collect();
6559 let idx = v.start;
6560 let actual_idx = if idx < 0 {
6561 (chars.len() as i64 + idx).max(0) as usize
6562 } else if idx > 0 {
6563 (idx - 1) as usize } else {
6565 0
6566 };
6567
6568 if v.end > v.start + 1 {
6569 let end_idx = if v.end < 0 {
6571 (chars.len() as i64 + v.end + 1).max(0) as usize
6572 } else {
6573 v.end as usize
6574 };
6575 let end_idx = end_idx.min(chars.len());
6576 return chars[actual_idx..end_idx].iter().collect();
6577 } else {
6578 return chars
6579 .get(actual_idx)
6580 .map(|c| c.to_string())
6581 .unwrap_or_default();
6582 }
6583 }
6584 return String::new();
6585 }
6586
6587 return String::new();
6589 }
6590 }
6591
6592 if let Some(colon_pos) = content.find(':') {
6594 let var_name = &content[..colon_pos];
6595 let rest = &content[colon_pos + 1..];
6596 let val = self.get_variable(var_name);
6597 let val_opt = if val.is_empty() {
6598 None
6599 } else {
6600 Some(val.clone())
6601 };
6602
6603 if rest.starts_with('-') {
6604 return match val_opt {
6606 Some(v) if !v.is_empty() => v,
6607 _ => self.expand_string(&rest[1..]),
6608 };
6609 } else if rest.starts_with('=') {
6610 return match val_opt {
6612 Some(v) if !v.is_empty() => v,
6613 _ => {
6614 let default = self.expand_string(&rest[1..]);
6615 self.variables.insert(var_name.to_string(), default.clone());
6616 default
6617 }
6618 };
6619 } else if rest.starts_with('?') {
6620 return match val_opt {
6622 Some(v) if !v.is_empty() => v,
6623 _ => {
6624 let msg = self.expand_string(&rest[1..]);
6625 eprintln!("zshrs: {}: {}", var_name, msg);
6626 String::new()
6627 }
6628 };
6629 } else if rest.starts_with('+') {
6630 return match val_opt {
6632 Some(v) if !v.is_empty() => self.expand_string(&rest[1..]),
6633 _ => String::new(),
6634 };
6635 } else if rest.starts_with('#') {
6636 let pattern = &rest[1..];
6639 if self.glob_match(&val, pattern) {
6641 return String::new();
6642 } else {
6643 return val;
6644 }
6645 } else if self.is_history_modifier(rest) {
6646 return self.apply_history_modifiers(&val, rest);
6649 } else if rest
6650 .chars()
6651 .next()
6652 .map(|c| c.is_ascii_digit() || c == '-')
6653 .unwrap_or(false)
6654 {
6655 let parts: Vec<&str> = rest.splitn(2, ':').collect();
6657 let offset: i64 = parts[0].parse().unwrap_or(0);
6658 let length: Option<usize> = parts.get(1).and_then(|s| s.parse().ok());
6659
6660 let start = if offset < 0 {
6661 (val.len() as i64 + offset).max(0) as usize
6662 } else {
6663 (offset as usize).min(val.len())
6664 };
6665
6666 return if let Some(len) = length {
6667 val.chars().skip(start).take(len).collect()
6668 } else {
6669 val.chars().skip(start).collect()
6670 };
6671 }
6672 }
6673
6674 if let Some(slash_pos) = content.find('/') {
6677 let var_name = &content[..slash_pos];
6678 if !var_name.is_empty()
6680 && var_name
6681 .chars()
6682 .next()
6683 .map(|c| c.is_alphabetic() || c == '_')
6684 .unwrap_or(false)
6685 && var_name.chars().all(|c| c.is_alphanumeric() || c == '_')
6686 {
6687 let rest = &content[slash_pos + 1..];
6688 let val = self.get_variable(var_name);
6689
6690 let replace_all = rest.starts_with('/');
6691 let rest = if replace_all { &rest[1..] } else { rest };
6692
6693 let parts: Vec<&str> = rest.splitn(2, '/').collect();
6694 let pattern = parts.get(0).unwrap_or(&"");
6695 let replacement = parts.get(1).unwrap_or(&"");
6696
6697 return if replace_all {
6698 val.replace(pattern, replacement)
6699 } else {
6700 val.replacen(pattern, replacement, 1)
6701 };
6702 }
6703 }
6704
6705 if let Some(hash_pos) = content.find('#') {
6708 if hash_pos > 0 {
6709 let var_name = &content[..hash_pos];
6710 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
6712 let rest = &content[hash_pos + 1..];
6713 let val = self.get_variable(var_name);
6714
6715 let long = rest.starts_with('#');
6716 let pattern = if long { &rest[1..] } else { rest };
6717
6718 let pattern_regex = regex::escape(pattern)
6720 .replace(r"\*", ".*")
6721 .replace(r"\?", ".");
6722 let full_pattern = format!("^{}", pattern_regex);
6723
6724 if let Some(re) = cached_regex(&full_pattern) {
6725 if long {
6726 let mut longest_end = 0;
6728 for m in re.find_iter(&val) {
6729 if m.end() > longest_end {
6730 longest_end = m.end();
6731 }
6732 }
6733 if longest_end > 0 {
6734 return val[longest_end..].to_string();
6735 }
6736 } else {
6737 if let Some(m) = re.find(&val) {
6739 return val[m.end()..].to_string();
6740 }
6741 }
6742 }
6743 return val;
6744 }
6745 }
6746 }
6747
6748 if let Some(pct_pos) = content.find('%') {
6750 if pct_pos > 0 {
6751 let var_name = &content[..pct_pos];
6752 if var_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
6753 let rest = &content[pct_pos + 1..];
6754 let val = self.get_variable(var_name);
6755
6756 let long = rest.starts_with('%');
6757 let pattern = if long { &rest[1..] } else { rest };
6758
6759 if let Ok(glob) = glob::Pattern::new(pattern) {
6761 if long {
6762 for i in 0..=val.len() {
6764 if glob.matches(&val[i..]) {
6765 return val[..i].to_string();
6766 }
6767 }
6768 } else {
6769 for i in (0..=val.len()).rev() {
6771 if glob.matches(&val[i..]) {
6772 return val[..i].to_string();
6773 }
6774 }
6775 }
6776 }
6777 return val;
6778 }
6779 }
6780 }
6781
6782 if let Some(caret_pos) = content.find('^') {
6784 let var_name = &content[..caret_pos];
6785 let val = self.get_variable(var_name);
6786 let all = content[caret_pos + 1..].starts_with('^');
6787
6788 return if all {
6789 val.to_uppercase()
6790 } else {
6791 let mut chars = val.chars();
6792 match chars.next() {
6793 Some(first) => first.to_uppercase().to_string() + chars.as_str(),
6794 None => String::new(),
6795 }
6796 };
6797 }
6798
6799 if let Some(comma_pos) = content.find(',') {
6801 let var_name = &content[..comma_pos];
6802 let val = self.get_variable(var_name);
6803 let all = content[comma_pos + 1..].starts_with(',');
6804
6805 return if all {
6806 val.to_lowercase()
6807 } else {
6808 let mut chars = val.chars();
6809 match chars.next() {
6810 Some(first) => first.to_lowercase().to_string() + chars.as_str(),
6811 None => String::new(),
6812 }
6813 };
6814 }
6815
6816 if content.starts_with('!') {
6818 let rest = &content[1..];
6819 if rest.ends_with('*') || rest.ends_with('@') {
6820 let prefix = &rest[..rest.len() - 1];
6821 let mut matches: Vec<String> = self
6822 .variables
6823 .keys()
6824 .filter(|k| k.starts_with(prefix))
6825 .cloned()
6826 .collect();
6827 for k in self.arrays.keys() {
6829 if k.starts_with(prefix) && !matches.contains(k) {
6830 matches.push(k.clone());
6831 }
6832 }
6833 matches.sort();
6834 return matches.join(" ");
6835 }
6836
6837 let var_name = self.get_variable(rest);
6839 return self.get_variable(&var_name);
6840 }
6841
6842 self.get_variable(content)
6844 }
6845
6846 fn expand_array_access(&mut self, name: &str, index: &ShellWord) -> String {
6847 use crate::subscript::{get_array_by_subscript, get_array_element_by_subscript, getindex};
6848
6849 let idx_str = self.expand_word(index);
6850 let ksh_arrays = self.options.get("ksh_arrays").copied().unwrap_or(false);
6851
6852 match getindex(&idx_str, false, ksh_arrays) {
6854 Ok(v) => {
6855 if let Some(arr) = self.arrays.get(name) {
6856 if v.is_all() {
6857 arr.join(" ")
6858 } else if v.start == v.end - 1 {
6859 get_array_element_by_subscript(arr, &v, ksh_arrays).unwrap_or_default()
6861 } else {
6862 get_array_by_subscript(arr, &v, ksh_arrays).join(" ")
6864 }
6865 } else {
6866 String::new()
6867 }
6868 }
6869 Err(_) => String::new(),
6870 }
6871 }
6872
6873 #[tracing::instrument(level = "trace", skip_all)]
6874 fn expand_string(&mut self, s: &str) -> String {
6875 let mut result = String::new();
6876 let mut chars = s.chars().peekable();
6877
6878 while let Some(c) = chars.next() {
6879 if c == '\x00' {
6881 if let Some(literal_char) = chars.next() {
6882 result.push(literal_char);
6883 }
6884 continue;
6885 }
6886 if c == '$' {
6887 if chars.peek() == Some(&'(') {
6888 chars.next(); if chars.peek() == Some(&'(') {
6892 chars.next(); let expr = Self::collect_until_double_paren(&mut chars);
6894 result.push_str(&self.evaluate_arithmetic(&expr));
6895 } else {
6896 let cmd_str = Self::collect_until_paren(&mut chars);
6898 result.push_str(&self.run_command_substitution(&cmd_str));
6899 }
6900 } else if chars.peek() == Some(&'{') {
6901 chars.next();
6902 let mut brace_content = String::new();
6904 let mut depth = 1;
6905 while let Some(c) = chars.next() {
6906 if c == '{' {
6907 depth += 1;
6908 brace_content.push(c);
6909 } else if c == '}' {
6910 depth -= 1;
6911 if depth == 0 {
6912 break;
6913 }
6914 brace_content.push(c);
6915 } else {
6916 brace_content.push(c);
6917 }
6918 }
6919 result.push_str(&self.expand_braced_variable(&brace_content));
6920 } else {
6921 if matches!(chars.peek(), Some(&'$') | Some(&'!') | Some(&'-')) {
6923 let sc = chars.next().unwrap();
6924 result.push_str(&self.get_variable(&sc.to_string()));
6925 continue;
6926 }
6927 if chars.peek() == Some(&'#') {
6929 let mut peek_iter = chars.clone();
6930 peek_iter.next(); if peek_iter
6932 .peek()
6933 .map(|c| c.is_alphabetic() || *c == '_')
6934 .unwrap_or(false)
6935 {
6936 chars.next(); let mut name = String::new();
6938 while let Some(&c) = chars.peek() {
6939 if c.is_alphanumeric() || c == '_' {
6940 name.push(chars.next().unwrap());
6941 } else {
6942 break;
6943 }
6944 }
6945 let len = if let Some(arr) = self.arrays.get(&name) {
6947 arr.len()
6948 } else {
6949 self.get_variable(&name).len()
6950 };
6951 result.push_str(&len.to_string());
6952 continue;
6953 }
6954 }
6955 let mut var_name = String::new();
6956 while let Some(&c) = chars.peek() {
6957 if c.is_alphanumeric()
6958 || c == '_'
6959 || c == '@'
6960 || c == '*'
6961 || c == '#'
6962 || c == '?'
6963 {
6964 var_name.push(chars.next().unwrap());
6965 if matches!(
6967 var_name.as_str(),
6968 "@" | "*"
6969 | "#"
6970 | "?"
6971 | "$"
6972 | "!"
6973 | "-"
6974 | "0"
6975 | "1"
6976 | "2"
6977 | "3"
6978 | "4"
6979 | "5"
6980 | "6"
6981 | "7"
6982 | "8"
6983 | "9"
6984 ) {
6985 break;
6986 }
6987 } else {
6988 break;
6989 }
6990 }
6991 result.push_str(&self.get_variable(&var_name));
6992 }
6993 } else if c == '`' {
6994 let cmd_str: String = chars.by_ref().take_while(|&c| c != '`').collect();
6996 result.push_str(&self.run_command_substitution(&cmd_str));
6997 } else if c == '<' && chars.peek() == Some(&'(') {
6998 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
7001 result.push_str(&self.run_process_sub_in(&cmd_str));
7002 } else if c == '>' && chars.peek() == Some(&'(') {
7003 chars.next(); let cmd_str = Self::collect_until_paren(&mut chars);
7006 result.push_str(&self.run_process_sub_out(&cmd_str));
7007 } else if c == '~' && result.is_empty() {
7008 if let Some(home) = dirs::home_dir() {
7009 result.push_str(&home.to_string_lossy());
7010 } else {
7011 result.push(c);
7012 }
7013 } else {
7014 result.push(c);
7015 }
7016 }
7017
7018 result
7019 }
7020
7021 fn collect_until_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
7022 let mut result = String::new();
7023 let mut depth = 1;
7024
7025 while let Some(c) = chars.next() {
7026 if c == '(' {
7027 depth += 1;
7028 result.push(c);
7029 } else if c == ')' {
7030 depth -= 1;
7031 if depth == 0 {
7032 break;
7033 }
7034 result.push(c);
7035 } else {
7036 result.push(c);
7037 }
7038 }
7039
7040 result
7041 }
7042
7043 fn collect_until_double_paren(chars: &mut std::iter::Peekable<std::str::Chars>) -> String {
7044 let mut result = String::new();
7045 let mut arith_depth = 1; let mut paren_depth = 0; while let Some(c) = chars.next() {
7049 if c == '(' {
7050 if paren_depth == 0 && chars.peek() == Some(&'(') {
7051 paren_depth += 1;
7054 result.push(c);
7055 } else {
7056 paren_depth += 1;
7057 result.push(c);
7058 }
7059 } else if c == ')' {
7060 if paren_depth > 0 {
7061 paren_depth -= 1;
7063 result.push(c);
7064 } else if chars.peek() == Some(&')') {
7065 chars.next();
7067 arith_depth -= 1;
7068 if arith_depth == 0 {
7069 break;
7070 }
7071 result.push_str("))");
7072 } else {
7073 result.push(c);
7075 }
7076 } else {
7077 result.push(c);
7078 }
7079 }
7080
7081 result
7082 }
7083
7084 fn run_process_sub_in(&mut self, cmd_str: &str) -> String {
7085 use std::fs;
7086 use std::process::Stdio;
7087
7088 let mut parser = ShellParser::new(cmd_str);
7090 let commands = match parser.parse_script() {
7091 Ok(cmds) => cmds,
7092 Err(_) => return String::new(),
7093 };
7094
7095 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
7097 let fifo_counter = self.process_sub_counter;
7098 self.process_sub_counter += 1;
7099 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
7100
7101 let _ = fs::remove_file(&fifo_path);
7103 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
7104 return String::new();
7105 }
7106
7107 let fifo_clone = fifo_path.clone();
7109 if let Some(cmd) = commands.first() {
7110 if let ShellCommand::Simple(simple) = cmd {
7111 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7112 if !words.is_empty() {
7113 let cmd_name = words[0].clone();
7114 let args: Vec<String> = words[1..].to_vec();
7115
7116 self.worker_pool.submit(move || {
7117 if let Ok(fifo) = fs::OpenOptions::new().write(true).open(&fifo_clone) {
7119 let _ = Command::new(&cmd_name)
7120 .args(&args)
7121 .stdout(fifo)
7122 .stderr(Stdio::inherit())
7123 .status();
7124 }
7125 let _ = fs::remove_file(&fifo_clone);
7127 });
7128 }
7129 }
7130 }
7131
7132 fifo_path
7133 }
7134
7135 fn run_process_sub_out(&mut self, cmd_str: &str) -> String {
7136 use std::fs;
7137 use std::process::Stdio;
7138
7139 let mut parser = ShellParser::new(cmd_str);
7141 let commands = match parser.parse_script() {
7142 Ok(cmds) => cmds,
7143 Err(_) => return String::new(),
7144 };
7145
7146 let fifo_path = format!("/tmp/zshrs_psub_{}", std::process::id());
7148 let fifo_counter = self.process_sub_counter;
7149 self.process_sub_counter += 1;
7150 let fifo_path = format!("{}_{}", fifo_path, fifo_counter);
7151
7152 let _ = fs::remove_file(&fifo_path);
7154 if let Err(_) = nix::unistd::mkfifo(fifo_path.as_str(), nix::sys::stat::Mode::S_IRWXU) {
7155 return String::new();
7156 }
7157
7158 let fifo_clone = fifo_path.clone();
7160 if let Some(cmd) = commands.first() {
7161 if let ShellCommand::Simple(simple) = cmd {
7162 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7163 if !words.is_empty() {
7164 let cmd_name = words[0].clone();
7165 let args: Vec<String> = words[1..].to_vec();
7166
7167 self.worker_pool.submit(move || {
7168 if let Ok(fifo) = fs::File::open(&fifo_clone) {
7170 let _ = Command::new(&cmd_name)
7171 .args(&args)
7172 .stdin(fifo)
7173 .stdout(Stdio::inherit())
7174 .stderr(Stdio::inherit())
7175 .status();
7176 }
7177 let _ = fs::remove_file(&fifo_clone);
7179 });
7180 }
7181 }
7182 }
7183
7184 fifo_path
7185 }
7186
7187 fn run_command_substitution(&mut self, cmd_str: &str) -> String {
7188 use std::process::Stdio;
7189
7190 let mut parser = ShellParser::new(cmd_str);
7196 let commands = match parser.parse_script() {
7197 Ok(cmds) => cmds,
7198 Err(_) => return String::new(),
7199 };
7200
7201 if commands.is_empty() {
7202 return String::new();
7203 }
7204
7205 let is_internal = if let ShellCommand::Simple(simple) = &commands[0] {
7208 let first = simple.words.first().map(|w| self.expand_word(w));
7209 if let Some(ref name) = first {
7210 self.functions.contains_key(name) || self.is_builtin(name)
7211 } else {
7212 true
7213 }
7214 } else {
7215 true };
7217
7218 if is_internal {
7219 let (read_fd, write_fd) = {
7221 let mut fds = [0i32; 2];
7222 if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
7223 return String::new();
7224 }
7225 (fds[0], fds[1])
7226 };
7227
7228 let saved_stdout = unsafe { libc::dup(1) };
7230 unsafe {
7231 libc::dup2(write_fd, 1);
7232 }
7233 unsafe {
7234 libc::close(write_fd);
7235 }
7236
7237 for cmd in &commands {
7239 let _ = self.execute_command(cmd);
7240 }
7241
7242 use std::io::Write;
7244 let _ = io::stdout().flush();
7245
7246 unsafe {
7248 libc::dup2(saved_stdout, 1);
7249 }
7250 unsafe {
7251 libc::close(saved_stdout);
7252 }
7253
7254 use std::os::unix::io::FromRawFd;
7256 let mut output = String::new();
7257 let read_file = unsafe { std::fs::File::from_raw_fd(read_fd) };
7258 use std::io::Read;
7259 let _ = std::io::BufReader::new(read_file).read_to_string(&mut output);
7260
7261 output.trim_end_matches('\n').to_string()
7262 } else {
7263 if let ShellCommand::Simple(simple) = &commands[0] {
7265 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7266 if words.is_empty() {
7267 return String::new();
7268 }
7269
7270 let output = Command::new(&words[0])
7271 .args(&words[1..])
7272 .stdout(Stdio::piped())
7273 .stderr(Stdio::inherit())
7274 .output();
7275
7276 match output {
7277 Ok(out) => String::from_utf8_lossy(&out.stdout)
7278 .trim_end_matches('\n')
7279 .to_string(),
7280 Err(_) => String::new(),
7281 }
7282 } else {
7283 String::new()
7284 }
7285 }
7286 }
7287
7288 fn execute_process_sub_in(&mut self, cmd: &ShellCommand) -> String {
7290 if let ShellCommand::Simple(simple) = cmd {
7291 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7292 let cmd_str = words.join(" ");
7293 self.run_process_sub_in(&cmd_str)
7294 } else {
7295 String::new()
7296 }
7297 }
7298
7299 fn execute_process_sub_out(&mut self, cmd: &ShellCommand) -> String {
7301 if let ShellCommand::Simple(simple) = cmd {
7302 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
7303 let cmd_str = words.join(" ");
7304 self.run_process_sub_out(&cmd_str)
7305 } else {
7306 String::new()
7307 }
7308 }
7309
7310 fn get_special_array_value(&self, array_name: &str, key: &str) -> Option<String> {
7313 match array_name {
7314 "options" => {
7316 if key == "@" || key == "*" {
7317 let opts: Vec<String> = self
7319 .options
7320 .iter()
7321 .map(|(k, v)| format!("{}={}", k, if *v { "on" } else { "off" }))
7322 .collect();
7323 return Some(opts.join(" "));
7324 }
7325 let opt_name = key.to_lowercase().replace('_', "");
7326 let is_on = self.options.get(&opt_name).copied().unwrap_or(false);
7327 Some(if is_on {
7328 "on".to_string()
7329 } else {
7330 "off".to_string()
7331 })
7332 }
7333
7334 "aliases" => {
7336 if key == "@" || key == "*" {
7337 let vals: Vec<String> = self.aliases.values().cloned().collect();
7338 return Some(vals.join(" "));
7339 }
7340 Some(self.aliases.get(key).cloned().unwrap_or_default())
7341 }
7342 "galiases" => {
7343 if key == "@" || key == "*" {
7344 let vals: Vec<String> = self.global_aliases.values().cloned().collect();
7345 return Some(vals.join(" "));
7346 }
7347 Some(self.global_aliases.get(key).cloned().unwrap_or_default())
7348 }
7349 "saliases" => {
7350 if key == "@" || key == "*" {
7351 let vals: Vec<String> = self.suffix_aliases.values().cloned().collect();
7352 return Some(vals.join(" "));
7353 }
7354 Some(self.suffix_aliases.get(key).cloned().unwrap_or_default())
7355 }
7356
7357 "functions" => {
7359 if key == "@" || key == "*" {
7360 let names: Vec<String> = self.functions.keys().cloned().collect();
7361 return Some(names.join(" "));
7362 }
7363 if let Some(body) = self.functions.get(key) {
7364 Some(format!("{:?}", body))
7365 } else {
7366 Some(String::new())
7367 }
7368 }
7369 "functions_source" => {
7370 Some(String::new())
7372 }
7373
7374 "commands" => {
7376 if key == "@" || key == "*" {
7377 return Some(String::new()); }
7379 if let Some(path) = self.find_in_path(key) {
7381 Some(path)
7382 } else {
7383 Some(String::new())
7384 }
7385 }
7386
7387 "builtins" => {
7389 let builtins = Self::get_builtin_names();
7390 if key == "@" || key == "*" {
7391 return Some(builtins.join(" "));
7392 }
7393 if builtins.contains(&key) {
7394 Some("defined".to_string())
7395 } else {
7396 Some(String::new())
7397 }
7398 }
7399
7400 "parameters" => {
7402 if key == "@" || key == "*" {
7403 let mut names: Vec<String> = self.variables.keys().cloned().collect();
7404 names.extend(self.arrays.keys().cloned());
7405 names.extend(self.assoc_arrays.keys().cloned());
7406 return Some(names.join(" "));
7407 }
7408 if self.assoc_arrays.contains_key(key) {
7410 Some("association".to_string())
7411 } else if self.arrays.contains_key(key) {
7412 Some("array".to_string())
7413 } else if self.variables.contains_key(key) || std::env::var(key).is_ok() {
7414 Some("scalar".to_string())
7415 } else {
7416 Some(String::new())
7417 }
7418 }
7419
7420 "nameddirs" => {
7422 if key == "@" || key == "*" {
7423 let vals: Vec<String> = self
7424 .named_dirs
7425 .values()
7426 .map(|p| p.display().to_string())
7427 .collect();
7428 return Some(vals.join(" "));
7429 }
7430 Some(
7431 self.named_dirs
7432 .get(key)
7433 .map(|p| p.display().to_string())
7434 .unwrap_or_default(),
7435 )
7436 }
7437
7438 "userdirs" => {
7440 if key == "@" || key == "*" {
7441 return Some(String::new());
7442 }
7443 #[cfg(unix)]
7445 {
7446 use std::ffi::CString;
7447 if let Ok(name) = CString::new(key) {
7448 unsafe {
7449 let pwd = libc::getpwnam(name.as_ptr());
7450 if !pwd.is_null() {
7451 let dir = std::ffi::CStr::from_ptr((*pwd).pw_dir);
7452 return Some(dir.to_string_lossy().to_string());
7453 }
7454 }
7455 }
7456 }
7457 Some(String::new())
7458 }
7459
7460 "usergroups" => {
7462 if key == "@" || key == "*" {
7463 return Some(String::new());
7464 }
7465 #[cfg(unix)]
7467 {
7468 use std::ffi::CString;
7469 if let Ok(name) = CString::new(key) {
7470 unsafe {
7471 let grp = libc::getgrnam(name.as_ptr());
7472 if !grp.is_null() {
7473 return Some((*grp).gr_gid.to_string());
7474 }
7475 }
7476 }
7477 }
7478 Some(String::new())
7479 }
7480
7481 "dirstack" => {
7483 if key == "@" || key == "*" {
7484 let dirs: Vec<String> = self
7485 .dir_stack
7486 .iter()
7487 .map(|p| p.display().to_string())
7488 .collect();
7489 return Some(dirs.join(" "));
7490 }
7491 if let Ok(idx) = key.parse::<usize>() {
7492 Some(
7493 self.dir_stack
7494 .get(idx)
7495 .map(|p| p.display().to_string())
7496 .unwrap_or_default(),
7497 )
7498 } else {
7499 Some(String::new())
7500 }
7501 }
7502
7503 "jobstates" => {
7505 if key == "@" || key == "*" {
7506 let states: Vec<String> = self
7507 .jobs
7508 .iter()
7509 .map(|(id, job)| format!("{}:{:?}", id, job.state))
7510 .collect();
7511 return Some(states.join(" "));
7512 }
7513 if let Ok(id) = key.parse::<usize>() {
7514 if let Some(job) = self.jobs.get(id) {
7515 return Some(format!("{:?}", job.state));
7516 }
7517 }
7518 Some(String::new())
7519 }
7520 "jobtexts" => {
7521 if key == "@" || key == "*" {
7522 let texts: Vec<String> = self
7523 .jobs
7524 .iter()
7525 .map(|(_, job)| job.command.clone())
7526 .collect();
7527 return Some(texts.join(" "));
7528 }
7529 if let Ok(id) = key.parse::<usize>() {
7530 if let Some(job) = self.jobs.get(id) {
7531 return Some(job.command.clone());
7532 }
7533 }
7534 Some(String::new())
7535 }
7536 "jobdirs" => {
7537 if key == "@" || key == "*" {
7539 return Some(String::new());
7540 }
7541 Some(String::new())
7542 }
7543
7544 "history" => {
7546 if key == "@" || key == "*" {
7547 if let Some(ref engine) = self.history {
7549 if let Ok(entries) = engine.recent(100) {
7550 let cmds: Vec<String> =
7551 entries.iter().map(|e| e.command.clone()).collect();
7552 return Some(cmds.join("\n"));
7553 }
7554 }
7555 return Some(String::new());
7556 }
7557 if let Ok(num) = key.parse::<usize>() {
7558 if let Some(ref engine) = self.history {
7559 if let Ok(Some(entry)) = engine.get_by_offset(num.saturating_sub(1)) {
7560 return Some(entry.command);
7561 }
7562 }
7563 }
7564 Some(String::new())
7565 }
7566 "historywords" => {
7567 Some(String::new())
7569 }
7570
7571 "modules" => {
7573 if key == "@" || key == "*" {
7576 return Some("zsh/parameter zsh/zutil".to_string());
7577 }
7578 match key {
7579 "zsh/parameter" | "zsh/zutil" | "zsh/complete" | "zsh/complist" => {
7580 Some("loaded".to_string())
7581 }
7582 _ => Some(String::new()),
7583 }
7584 }
7585
7586 "reswords" => {
7588 let reswords = [
7589 "do",
7590 "done",
7591 "esac",
7592 "then",
7593 "elif",
7594 "else",
7595 "fi",
7596 "for",
7597 "case",
7598 "if",
7599 "while",
7600 "function",
7601 "repeat",
7602 "time",
7603 "until",
7604 "select",
7605 "coproc",
7606 "nocorrect",
7607 "foreach",
7608 "end",
7609 "in",
7610 ];
7611 if key == "@" || key == "*" {
7612 return Some(reswords.join(" "));
7613 }
7614 if let Ok(idx) = key.parse::<usize>() {
7615 Some(reswords.get(idx).map(|s| s.to_string()).unwrap_or_default())
7616 } else {
7617 Some(String::new())
7618 }
7619 }
7620
7621 "patchars" => {
7623 let patchars = ["?", "*", "[", "]", "^", "#", "~", "(", ")", "|"];
7624 if key == "@" || key == "*" {
7625 return Some(patchars.join(" "));
7626 }
7627 if let Ok(idx) = key.parse::<usize>() {
7628 Some(patchars.get(idx).map(|s| s.to_string()).unwrap_or_default())
7629 } else {
7630 Some(String::new())
7631 }
7632 }
7633
7634 "funcstack" | "functrace" | "funcfiletrace" | "funcsourcetrace" => {
7636 Some(String::new())
7638 }
7639
7640 "dis_aliases"
7642 | "dis_galiases"
7643 | "dis_saliases"
7644 | "dis_functions"
7645 | "dis_functions_source"
7646 | "dis_builtins"
7647 | "dis_reswords"
7648 | "dis_patchars" => {
7649 Some(String::new())
7651 }
7652
7653 _ => None,
7655 }
7656 }
7657
7658 fn get_builtin_names() -> Vec<&'static str> {
7660 vec![
7661 ".",
7662 ":",
7663 "[",
7664 "alias",
7665 "autoload",
7666 "bg",
7667 "bind",
7668 "bindkey",
7669 "break",
7670 "builtin",
7671 "bye",
7672 "caller",
7673 "cd",
7674 "cdreplay",
7675 "chdir",
7676 "clone",
7677 "command",
7678 "compadd",
7679 "comparguments",
7680 "compcall",
7681 "compctl",
7682 "compdef",
7683 "compdescribe",
7684 "compfiles",
7685 "compgen",
7686 "compgroups",
7687 "compinit",
7688 "complete",
7689 "compopt",
7690 "compquote",
7691 "compset",
7692 "comptags",
7693 "comptry",
7694 "compvalues",
7695 "continue",
7696 "coproc",
7697 "declare",
7698 "dirs",
7699 "disable",
7700 "disown",
7701 "echo",
7702 "echotc",
7703 "echoti",
7704 "emulate",
7705 "enable",
7706 "eval",
7707 "exec",
7708 "exit",
7709 "export",
7710 "false",
7711 "fc",
7712 "fg",
7713 "float",
7714 "functions",
7715 "getln",
7716 "getopts",
7717 "hash",
7718 "help",
7719 "history",
7720 "integer",
7721 "jobs",
7722 "kill",
7723 "let",
7724 "limit",
7725 "local",
7726 "log",
7727 "logout",
7728 "mapfile",
7729 "noglob",
7730 "popd",
7731 "print",
7732 "printf",
7733 "private",
7734 "prompt",
7735 "promptinit",
7736 "pushd",
7737 "pushln",
7738 "pwd",
7739 "r",
7740 "read",
7741 "readarray",
7742 "readonly",
7743 "rehash",
7744 "return",
7745 "sched",
7746 "set",
7747 "setopt",
7748 "shift",
7749 "shopt",
7750 "source",
7751 "stat",
7752 "strftime",
7753 "suspend",
7754 "test",
7755 "times",
7756 "trap",
7757 "true",
7758 "ttyctl",
7759 "type",
7760 "typeset",
7761 "ulimit",
7762 "umask",
7763 "unalias",
7764 "unfunction",
7765 "unhash",
7766 "unlimit",
7767 "unset",
7768 "unsetopt",
7769 "vared",
7770 "wait",
7771 "whence",
7772 "where",
7773 "which",
7774 "zcompile",
7775 "zcurses",
7776 "zformat",
7777 "zle",
7778 "zmodload",
7779 "zparseopts",
7780 "zprof",
7781 "zpty",
7782 "zregexparse",
7783 "zsocket",
7784 "zstyle",
7785 "ztcp",
7786 "add-zsh-hook",
7787 ]
7788 }
7789
7790 fn get_variable(&self, name: &str) -> String {
7791 match name {
7793 "" => String::new(), "$" => std::process::id().to_string(),
7795 "@" | "*" => self.positional_params.join(" "),
7796 "#" => self.positional_params.len().to_string(),
7797 "?" => self.last_status.to_string(),
7798 "0" => self
7799 .variables
7800 .get("0")
7801 .cloned()
7802 .unwrap_or_else(|| env::args().next().unwrap_or_default()),
7803 n if !n.is_empty() && n.chars().all(|c| c.is_ascii_digit()) => {
7804 let idx: usize = n.parse().unwrap_or(0);
7805 if idx == 0 {
7806 env::args().next().unwrap_or_default()
7807 } else {
7808 self.positional_params
7809 .get(idx - 1)
7810 .cloned()
7811 .unwrap_or_default()
7812 }
7813 }
7814 _ => {
7815 self.variables
7817 .get(name)
7818 .cloned()
7819 .or_else(|| {
7820 self.arrays.get(name).map(|a| a.join(" "))
7822 })
7823 .or_else(|| env::var(name).ok())
7824 .unwrap_or_default()
7825 }
7826 }
7827 }
7828
7829 fn apply_var_modifier(
7830 &mut self,
7831 name: &str,
7832 val: Option<String>,
7833 modifier: Option<&VarModifier>,
7834 ) -> String {
7835 match modifier {
7836 None => val.unwrap_or_default(),
7837
7838 Some(VarModifier::Default(word)) => match &val {
7840 Some(v) if !v.is_empty() => v.clone(),
7841 _ => self.expand_word(word),
7842 },
7843
7844 Some(VarModifier::DefaultAssign(word)) => match &val {
7846 Some(v) if !v.is_empty() => v.clone(),
7847 _ => self.expand_word(word),
7848 },
7849
7850 Some(VarModifier::Error(word)) => match &val {
7852 Some(v) if !v.is_empty() => v.clone(),
7853 _ => {
7854 let msg = self.expand_word(word);
7855 eprintln!("zshrs: {}", msg);
7856 String::new()
7857 }
7858 },
7859
7860 Some(VarModifier::Alternate(word)) => match &val {
7862 Some(v) if !v.is_empty() => self.expand_word(word),
7863 _ => String::new(),
7864 },
7865
7866 Some(VarModifier::Length) => val
7868 .map(|v| v.len().to_string())
7869 .unwrap_or_else(|| "0".to_string()),
7870
7871 Some(VarModifier::Substring(offset, length)) => {
7873 let v = val.unwrap_or_default();
7874 let start = if *offset < 0 {
7875 (v.len() as i64 + offset).max(0) as usize
7876 } else {
7877 (*offset as usize).min(v.len())
7878 };
7879
7880 if let Some(len) = length {
7881 let len = (*len as usize).min(v.len().saturating_sub(start));
7882 v.chars().skip(start).take(len).collect()
7883 } else {
7884 v.chars().skip(start).collect()
7885 }
7886 }
7887
7888 Some(VarModifier::RemovePrefix(pattern)) => {
7890 let v = val.unwrap_or_default();
7891 let pat = self.expand_word(pattern);
7892 if v.starts_with(&pat) {
7893 v[pat.len()..].to_string()
7894 } else {
7895 v
7896 }
7897 }
7898
7899 Some(VarModifier::RemovePrefixLong(pattern)) => {
7901 let v = val.unwrap_or_default();
7902 let pat = self.expand_word(pattern);
7903 if let Ok(glob) = glob::Pattern::new(&pat) {
7905 for i in (0..=v.len()).rev() {
7906 if glob.matches(&v[..i]) {
7907 return v[i..].to_string();
7908 }
7909 }
7910 }
7911 v
7912 }
7913
7914 Some(VarModifier::RemoveSuffix(pattern)) => {
7916 let v = val.unwrap_or_default();
7917 let pat = self.expand_word(pattern);
7918 if let Ok(glob) = glob::Pattern::new(&pat) {
7920 for i in (0..=v.len()).rev() {
7921 if glob.matches(&v[i..]) {
7922 return v[..i].to_string();
7923 }
7924 }
7925 } else if v.ends_with(&pat) {
7926 return v[..v.len() - pat.len()].to_string();
7927 }
7928 v
7929 }
7930
7931 Some(VarModifier::RemoveSuffixLong(pattern)) => {
7933 let v = val.unwrap_or_default();
7934 let pat = self.expand_word(pattern);
7935 if let Ok(glob) = glob::Pattern::new(&pat) {
7937 for i in 0..=v.len() {
7938 if glob.matches(&v[i..]) {
7939 return v[..i].to_string();
7940 }
7941 }
7942 }
7943 v
7944 }
7945
7946 Some(VarModifier::Replace(pattern, replacement)) => {
7948 let v = val.unwrap_or_default();
7949 let pat = self.expand_word(pattern);
7950 let repl = self.expand_word(replacement);
7951 v.replacen(&pat, &repl, 1)
7952 }
7953
7954 Some(VarModifier::ReplaceAll(pattern, replacement)) => {
7956 let v = val.unwrap_or_default();
7957 let pat = self.expand_word(pattern);
7958 let repl = self.expand_word(replacement);
7959 v.replace(&pat, &repl)
7960 }
7961
7962 Some(VarModifier::Upper) => val.map(|v| v.to_uppercase()).unwrap_or_default(),
7964
7965 Some(VarModifier::Lower) => val.map(|v| v.to_lowercase()).unwrap_or_default(),
7967
7968 Some(VarModifier::ZshFlags(flags)) => {
7970 let mut result = val.unwrap_or_default();
7971 for flag in flags {
7972 result = self.apply_zsh_param_flag(&result, name, flag);
7973 }
7974 result
7975 }
7976
7977 Some(VarModifier::ArrayLength)
7979 | Some(VarModifier::ArrayIndex(_))
7980 | Some(VarModifier::ArrayAll) => val.unwrap_or_default(),
7981 }
7982 }
7983
7984 fn is_history_modifier(&self, s: &str) -> bool {
7986 if s.is_empty() {
7987 return false;
7988 }
7989 let first = s.chars().next().unwrap();
7990 matches!(
7991 first,
7992 'A' | 'a' | 'h' | 't' | 'r' | 'e' | 'l' | 'u' | 'q' | 'Q' | 'P'
7993 )
7994 }
7995
7996 fn apply_history_modifiers(&self, val: &str, modifiers: &str) -> String {
7999 let mut result = val.to_string();
8000 let mut chars = modifiers.chars().peekable();
8001
8002 while let Some(c) = chars.next() {
8003 match c {
8004 ':' => continue,
8005 'A' => {
8006 if let Ok(abs) = std::fs::canonicalize(&result) {
8007 result = abs.to_string_lossy().to_string();
8008 } else if !result.starts_with('/') {
8009 if let Ok(cwd) = std::env::current_dir() {
8010 result = cwd.join(&result).to_string_lossy().to_string();
8011 }
8012 }
8013 }
8014 'a' => {
8015 if !result.starts_with('/') {
8016 if let Ok(cwd) = std::env::current_dir() {
8017 result = cwd.join(&result).to_string_lossy().to_string();
8018 }
8019 }
8020 }
8021 'h' => {
8022 if let Some(pos) = result.rfind('/') {
8023 if pos == 0 {
8024 result = "/".to_string();
8025 } else {
8026 result = result[..pos].to_string();
8027 }
8028 } else {
8029 result = ".".to_string();
8030 }
8031 }
8032 't' => {
8033 if let Some(pos) = result.rfind('/') {
8034 result = result[pos + 1..].to_string();
8035 }
8036 }
8037 'r' => {
8038 if let Some(dot_pos) = result.rfind('.') {
8039 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
8040 if dot_pos > slash_pos {
8041 result = result[..dot_pos].to_string();
8042 }
8043 }
8044 }
8045 'e' => {
8046 if let Some(dot_pos) = result.rfind('.') {
8047 let slash_pos = result.rfind('/').map(|p| p + 1).unwrap_or(0);
8048 if dot_pos > slash_pos {
8049 result = result[dot_pos + 1..].to_string();
8050 } else {
8051 result = String::new();
8052 }
8053 } else {
8054 result = String::new();
8055 }
8056 }
8057 'l' => result = result.to_lowercase(),
8058 'u' => result = result.to_uppercase(),
8059 'q' => result = format!("'{}'", result.replace('\'', "'\\''")),
8060 'Q' => {
8061 if result.starts_with('\'') && result.ends_with('\'') && result.len() >= 2 {
8062 result = result[1..result.len() - 1].to_string();
8063 } else if result.starts_with('"') && result.ends_with('"') && result.len() >= 2
8064 {
8065 result = result[1..result.len() - 1].to_string();
8066 }
8067 }
8068 'P' => {
8069 if let Ok(real) = std::fs::canonicalize(&result) {
8070 result = real.to_string_lossy().to_string();
8071 }
8072 }
8073 _ => break,
8074 }
8075 }
8076 result
8077 }
8078
8079 fn parse_zsh_flags(&self, s: &str) -> Vec<ZshParamFlag> {
8081 let mut flags = Vec::new();
8082 let mut chars = s.chars().peekable();
8083
8084 while let Some(c) = chars.next() {
8085 match c {
8086 'L' => flags.push(ZshParamFlag::Lower),
8087 'U' => flags.push(ZshParamFlag::Upper),
8088 'C' => flags.push(ZshParamFlag::Capitalize),
8089 'j' => {
8090 if let Some(&delim) = chars.peek() {
8092 chars.next(); let mut sep = String::new();
8094 while let Some(&ch) = chars.peek() {
8095 if ch == delim {
8096 chars.next();
8097 break;
8098 }
8099 sep.push(chars.next().unwrap());
8100 }
8101 flags.push(ZshParamFlag::Join(sep));
8102 }
8103 }
8104 'F' => flags.push(ZshParamFlag::JoinNewline),
8105 's' => {
8106 if chars.peek() == Some(&':') {
8108 chars.next();
8109 let mut sep = String::new();
8110 while let Some(&ch) = chars.peek() {
8111 if ch == ':' {
8112 chars.next();
8113 break;
8114 }
8115 sep.push(chars.next().unwrap());
8116 }
8117 flags.push(ZshParamFlag::Split(sep));
8118 }
8119 }
8120 'f' => flags.push(ZshParamFlag::SplitLines),
8121 'z' => flags.push(ZshParamFlag::SplitWords),
8122 't' => flags.push(ZshParamFlag::Type),
8123 'w' => flags.push(ZshParamFlag::Words),
8124 'b' => flags.push(ZshParamFlag::QuoteBackslash),
8125 'q' => {
8126 if chars.peek() == Some(&'q') {
8127 chars.next();
8128 flags.push(ZshParamFlag::DoubleQuote);
8129 } else {
8130 flags.push(ZshParamFlag::Quote);
8131 }
8132 }
8133 'u' => flags.push(ZshParamFlag::Unique),
8134 'O' => flags.push(ZshParamFlag::Reverse),
8135 'o' => flags.push(ZshParamFlag::Sort),
8136 'n' => flags.push(ZshParamFlag::NumericSort),
8137 'a' => flags.push(ZshParamFlag::IndexSort),
8138 'k' => flags.push(ZshParamFlag::Keys),
8139 'v' => flags.push(ZshParamFlag::Values),
8140 '#' => flags.push(ZshParamFlag::Length),
8141 'c' => flags.push(ZshParamFlag::CountChars),
8142 'e' => flags.push(ZshParamFlag::Expand),
8143 '%' => {
8144 if chars.peek() == Some(&'%') {
8145 chars.next();
8146 flags.push(ZshParamFlag::PromptExpandFull);
8147 } else {
8148 flags.push(ZshParamFlag::PromptExpand);
8149 }
8150 }
8151 'V' => flags.push(ZshParamFlag::Visible),
8152 'D' => flags.push(ZshParamFlag::Directory),
8153 'M' => flags.push(ZshParamFlag::Match),
8154 'R' => flags.push(ZshParamFlag::Remove),
8155 'S' => flags.push(ZshParamFlag::Subscript),
8156 'P' => flags.push(ZshParamFlag::Parameter),
8157 '~' => flags.push(ZshParamFlag::Glob),
8158 'l' => {
8159 if chars.peek() == Some(&':') {
8161 chars.next();
8162 let mut len_str = String::new();
8163 while let Some(&ch) = chars.peek() {
8164 if ch == ':' {
8165 chars.next();
8166 break;
8167 }
8168 len_str.push(chars.next().unwrap());
8169 }
8170 let mut fill = ' ';
8171 if let Some(&ch) = chars.peek() {
8172 if ch != ':' {
8173 fill = chars.next().unwrap();
8174 if chars.peek() == Some(&':') {
8175 chars.next();
8176 }
8177 }
8178 }
8179 if let Ok(len) = len_str.parse() {
8180 flags.push(ZshParamFlag::PadLeft(len, fill));
8181 }
8182 }
8183 }
8184 'r' => {
8185 if chars.peek() == Some(&':') {
8187 chars.next();
8188 let mut len_str = String::new();
8189 while let Some(&ch) = chars.peek() {
8190 if ch == ':' {
8191 chars.next();
8192 break;
8193 }
8194 len_str.push(chars.next().unwrap());
8195 }
8196 let mut fill = ' ';
8197 if let Some(&ch) = chars.peek() {
8198 if ch != ':' {
8199 fill = chars.next().unwrap();
8200 if chars.peek() == Some(&':') {
8201 chars.next();
8202 }
8203 }
8204 }
8205 if let Ok(len) = len_str.parse() {
8206 flags.push(ZshParamFlag::PadRight(len, fill));
8207 }
8208 }
8209 }
8210 'm' => {
8211 let mut width_str = String::new();
8213 while let Some(&ch) = chars.peek() {
8214 if ch.is_ascii_digit() {
8215 width_str.push(chars.next().unwrap());
8216 } else {
8217 break;
8218 }
8219 }
8220 if let Ok(w) = width_str.parse() {
8221 flags.push(ZshParamFlag::Width(w));
8222 }
8223 }
8224 _ => {}
8225 }
8226 }
8227 flags
8228 }
8229
8230 fn apply_zsh_param_flag(&self, val: &str, name: &str, flag: &ZshParamFlag) -> String {
8232 match flag {
8233 ZshParamFlag::Lower => val.to_lowercase(),
8234 ZshParamFlag::Upper => val.to_uppercase(),
8235 ZshParamFlag::Capitalize => val
8236 .split_whitespace()
8237 .map(|word| {
8238 let mut c = word.chars();
8239 match c.next() {
8240 None => String::new(),
8241 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
8242 }
8243 })
8244 .collect::<Vec<_>>()
8245 .join(" "),
8246 ZshParamFlag::Join(sep) => {
8247 if let Some(arr) = self.arrays.get(name) {
8248 arr.join(sep)
8249 } else {
8250 val.to_string()
8251 }
8252 }
8253 ZshParamFlag::Split(sep) => val.split(sep).collect::<Vec<_>>().join(" "),
8254 ZshParamFlag::SplitLines => val.lines().collect::<Vec<_>>().join(" "),
8255 ZshParamFlag::Type => {
8256 if self.arrays.contains_key(name) {
8257 "array".to_string()
8258 } else if self.assoc_arrays.contains_key(name) {
8259 "association".to_string()
8260 } else if self.functions.contains_key(name) {
8261 "function".to_string()
8262 } else if std::env::var(name).is_ok() || self.variables.contains_key(name) {
8263 "scalar".to_string()
8264 } else {
8265 "".to_string()
8266 }
8267 }
8268 ZshParamFlag::Words => val.split_whitespace().collect::<Vec<_>>().join(" "),
8269 ZshParamFlag::Quote => format!("'{}'", val.replace('\'', "'\\''")),
8270 ZshParamFlag::DoubleQuote => format!("\"{}\"", val.replace('"', "\\\"")),
8271 ZshParamFlag::Unique => {
8272 let words: Vec<&str> = val.split_whitespace().collect();
8275 let mut seen = std::collections::HashSet::with_capacity(if words.len() >= 1000 {
8276 words.len()
8277 } else {
8278 0
8279 });
8280 if words.len() >= 1000 {
8281 tracing::trace!(
8282 count = words.len(),
8283 "unique on large array ({} elements)",
8284 words.len()
8285 );
8286 }
8287 words
8288 .into_iter()
8289 .filter(|s| seen.insert(*s))
8290 .collect::<Vec<_>>()
8291 .join(" ")
8292 }
8293 ZshParamFlag::Reverse => {
8294 let mut words: Vec<&str> = val.split_whitespace().collect();
8296 if words.len() >= 1000 {
8297 tracing::trace!(
8298 count = words.len(),
8299 "using parallel reverse sort (rayon) for large array"
8300 );
8301 use rayon::prelude::*;
8302 words.par_sort_unstable_by(|a, b| b.cmp(a));
8303 } else {
8304 words.sort_unstable_by(|a, b| b.cmp(a));
8305 }
8306 words.join(" ")
8307 }
8308 ZshParamFlag::Sort => {
8309 let mut words: Vec<&str> = val.split_whitespace().collect();
8310 if words.len() >= 1000 {
8311 tracing::trace!(
8312 count = words.len(),
8313 "using parallel sort (rayon) for large array"
8314 );
8315 use rayon::prelude::*;
8316 words.par_sort_unstable();
8317 } else {
8318 words.sort_unstable();
8319 }
8320 words.join(" ")
8321 }
8322 ZshParamFlag::NumericSort => {
8323 let mut words: Vec<&str> = val.split_whitespace().collect();
8324 let cmp = |a: &&str, b: &&str| {
8325 let na: i64 = a.parse().unwrap_or(0);
8326 let nb: i64 = b.parse().unwrap_or(0);
8327 na.cmp(&nb)
8328 };
8329 if words.len() >= 1000 {
8330 tracing::trace!(
8331 count = words.len(),
8332 "using parallel numeric sort (rayon) for large array"
8333 );
8334 use rayon::prelude::*;
8335 words.par_sort_unstable_by(cmp);
8336 } else {
8337 words.sort_unstable_by(cmp);
8338 }
8339 words.join(" ")
8340 }
8341 ZshParamFlag::Keys => {
8342 if let Some(assoc) = self.assoc_arrays.get(name) {
8343 assoc.keys().cloned().collect::<Vec<_>>().join(" ")
8344 } else {
8345 String::new()
8346 }
8347 }
8348 ZshParamFlag::Values => {
8349 if let Some(assoc) = self.assoc_arrays.get(name) {
8350 assoc.values().cloned().collect::<Vec<_>>().join(" ")
8351 } else {
8352 val.to_string()
8353 }
8354 }
8355 ZshParamFlag::Length => val.len().to_string(),
8356 ZshParamFlag::Head(n) => val
8357 .split_whitespace()
8358 .take(*n)
8359 .collect::<Vec<_>>()
8360 .join(" "),
8361 ZshParamFlag::Tail(n) => {
8362 let words: Vec<&str> = val.split_whitespace().collect();
8363 if words.len() > *n {
8364 words[words.len() - n..].join(" ")
8365 } else {
8366 val.to_string()
8367 }
8368 }
8369 ZshParamFlag::JoinNewline => {
8370 if let Some(arr) = self.arrays.get(name) {
8371 arr.join("\n")
8372 } else {
8373 val.to_string()
8374 }
8375 }
8376 ZshParamFlag::SplitWords => {
8377 val.split_whitespace().collect::<Vec<_>>().join(" ")
8379 }
8380 ZshParamFlag::QuoteBackslash => {
8381 let mut result = String::new();
8383 for c in val.chars() {
8384 if "\\*?[]{}()".contains(c) {
8385 result.push('\\');
8386 }
8387 result.push(c);
8388 }
8389 result
8390 }
8391 ZshParamFlag::IndexSort => {
8392 val.to_string()
8394 }
8395 ZshParamFlag::CountChars => {
8396 val.chars().count().to_string()
8398 }
8399 ZshParamFlag::Expand => {
8400 val.to_string()
8402 }
8403 ZshParamFlag::PromptExpand => {
8404 self.expand_prompt_string(val)
8406 }
8407 ZshParamFlag::PromptExpandFull => {
8408 self.expand_prompt_string(val)
8410 }
8411 ZshParamFlag::Visible => {
8412 val.chars()
8414 .map(|c| {
8415 if c.is_control() {
8416 format!("^{}", (c as u8 + 64) as char)
8417 } else {
8418 c.to_string()
8419 }
8420 })
8421 .collect()
8422 }
8423 ZshParamFlag::Directory => {
8424 if let Some(home) = dirs::home_dir() {
8426 let home_str = home.to_string_lossy();
8427 if val.starts_with(home_str.as_ref()) {
8428 format!("~{}", &val[home_str.len()..])
8429 } else {
8430 val.to_string()
8431 }
8432 } else {
8433 val.to_string()
8434 }
8435 }
8436 ZshParamFlag::PadLeft(len, fill) => {
8437 if val.len() >= *len {
8438 val.to_string()
8439 } else {
8440 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
8441 format!("{}{}", padding, val)
8442 }
8443 }
8444 ZshParamFlag::PadRight(len, fill) => {
8445 if val.len() >= *len {
8446 val.to_string()
8447 } else {
8448 let padding: String = std::iter::repeat(*fill).take(len - val.len()).collect();
8449 format!("{}{}", val, padding)
8450 }
8451 }
8452 ZshParamFlag::Width(_) => {
8453 val.to_string()
8455 }
8456 ZshParamFlag::Match => {
8457 val.to_string()
8460 }
8461 ZshParamFlag::Remove => {
8462 val.to_string()
8464 }
8465 ZshParamFlag::Subscript => {
8466 val.to_string()
8468 }
8469 ZshParamFlag::Parameter => {
8470 self.get_variable(val)
8472 }
8473 ZshParamFlag::Glob => {
8474 val.to_string()
8476 }
8477 }
8478 }
8479
8480 fn expand_prompt_string(&self, s: &str) -> String {
8482 let ctx = self.build_prompt_context();
8483 expand_prompt(s, &ctx)
8484 }
8485
8486 fn build_prompt_context(&self) -> PromptContext {
8488 let pwd = env::current_dir()
8489 .map(|p| p.to_string_lossy().to_string())
8490 .unwrap_or_else(|_| "/".to_string());
8491
8492 let home = env::var("HOME").unwrap_or_default();
8493
8494 let user = env::var("USER")
8495 .or_else(|_| env::var("LOGNAME"))
8496 .unwrap_or_else(|_| "user".to_string());
8497
8498 let host = hostname::get()
8499 .map(|h| h.to_string_lossy().to_string())
8500 .unwrap_or_else(|_| "localhost".to_string());
8501
8502 let host_short = host.split('.').next().unwrap_or(&host).to_string();
8503
8504 let shlvl = env::var("SHLVL")
8505 .ok()
8506 .and_then(|s| s.parse().ok())
8507 .unwrap_or(1);
8508
8509 PromptContext {
8510 pwd,
8511 home,
8512 user,
8513 host,
8514 host_short,
8515 tty: String::new(),
8516 lastval: self.last_status,
8517 histnum: self
8518 .history
8519 .as_ref()
8520 .and_then(|h| h.count().ok())
8521 .unwrap_or(1),
8522 shlvl,
8523 num_jobs: self.jobs.list().len() as i32,
8524 is_root: unsafe { libc::geteuid() } == 0,
8525 cmd_stack: Vec::new(),
8526 psvar: self.get_psvar(),
8527 term_width: self.get_term_width(),
8528 lineno: 1,
8529 }
8530 }
8531
8532 fn get_psvar(&self) -> Vec<String> {
8533 if let Some(arr) = self.arrays.get("psvar") {
8534 arr.clone()
8535 } else {
8536 Vec::new()
8537 }
8538 }
8539
8540 fn get_term_width(&self) -> usize {
8541 env::var("COLUMNS")
8542 .ok()
8543 .and_then(|s| s.parse().ok())
8544 .unwrap_or(80)
8545 }
8546
8547 fn execute_command_substitution(&mut self, cmd: &ShellCommand) -> String {
8549 match self.execute_command_capture(cmd) {
8550 Ok(output) => output.trim_end_matches('\n').to_string(),
8551 Err(_) => String::new(),
8552 }
8553 }
8554
8555 fn execute_command_capture(&mut self, cmd: &ShellCommand) -> Result<String, String> {
8557 if let ShellCommand::Simple(simple) = cmd {
8559 let words: Vec<String> = simple.words.iter().map(|w| self.expand_word(w)).collect();
8560 if words.is_empty() {
8561 return Ok(String::new());
8562 }
8563
8564 let cmd_name = &words[0];
8565 let args = &words[1..];
8566
8567 match cmd_name.as_str() {
8569 "echo" => {
8570 let output = args.join(" ");
8571 return Ok(format!("{}\n", output));
8572 }
8573 "printf" => {
8574 if !args.is_empty() {
8575 let format = &args[0];
8577 let result = if args.len() > 1 {
8578 let mut out = format.clone();
8580 for (i, arg) in args[1..].iter().enumerate() {
8581 out = out.replacen("%s", arg, 1);
8582 out = out.replacen(&format!("${}", i + 1), arg, 1);
8583 }
8584 out
8585 } else {
8586 format.clone()
8587 };
8588 return Ok(result);
8589 }
8590 return Ok(String::new());
8591 }
8592 "pwd" => {
8593 return Ok(env::current_dir()
8594 .map(|p| format!("{}\n", p.display()))
8595 .unwrap_or_default());
8596 }
8597 _ => {}
8598 }
8599
8600 let output = Command::new(cmd_name)
8602 .args(args)
8603 .stdout(Stdio::piped())
8604 .stderr(Stdio::inherit())
8605 .output();
8606
8607 match output {
8608 Ok(output) => {
8609 self.last_status = output.status.code().unwrap_or(1);
8610 Ok(String::from_utf8_lossy(&output.stdout).to_string())
8611 }
8612 Err(e) => {
8613 self.last_status = 127;
8614 Err(format!("{}: {}", cmd_name, e))
8615 }
8616 }
8617 } else if let ShellCommand::Pipeline(cmds, _negated) = cmd {
8618 if let Some(last) = cmds.last() {
8621 return self.execute_command_capture(last);
8622 }
8623 Ok(String::new())
8624 } else {
8625 let _ = self.execute_command(cmd);
8628 Ok(String::new())
8629 }
8630 }
8631
8632 fn evaluate_arithmetic(&mut self, expr: &str) -> String {
8634 let expr = self.expand_string(expr);
8635 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
8636 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8637 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8638
8639 let mut evaluator = MathEval::new(&expr)
8640 .with_string_variables(&self.variables)
8641 .with_force_float(force_float)
8642 .with_c_precedences(c_prec)
8643 .with_octal_zeroes(octal);
8644
8645 match evaluator.evaluate() {
8646 Ok(result) => {
8647 for (k, v) in evaluator.extract_string_variables() {
8648 self.variables.insert(k.clone(), v.clone());
8649 env::set_var(&k, &v);
8650 }
8651 match result {
8652 crate::math::MathNum::Integer(i) => i.to_string(),
8653 crate::math::MathNum::Float(f) => {
8654 if f.fract() == 0.0 && f.abs() < i64::MAX as f64 {
8655 (f as i64).to_string()
8656 } else {
8657 f.to_string()
8658 }
8659 }
8660 crate::math::MathNum::Unset => "0".to_string(),
8661 }
8662 }
8663 Err(_) => "0".to_string(),
8664 }
8665 }
8666
8667 fn eval_arith_expr(&mut self, expr: &str) -> i64 {
8668 let expr_expanded = self.expand_string(expr);
8669 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8670 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8671
8672 let mut evaluator = MathEval::new(&expr_expanded)
8673 .with_string_variables(&self.variables)
8674 .with_c_precedences(c_prec)
8675 .with_octal_zeroes(octal);
8676
8677 match evaluator.evaluate() {
8678 Ok(result) => {
8679 for (k, v) in evaluator.extract_string_variables() {
8680 self.variables.insert(k.clone(), v.clone());
8681 env::set_var(&k, &v);
8682 }
8683 result.to_int()
8684 }
8685 Err(_) => 0,
8686 }
8687 }
8688
8689 fn eval_arith_expr_float(&mut self, expr: &str) -> f64 {
8690 let expr_expanded = self.expand_string(expr);
8691 let force_float = self.options.get("forcefloat").copied().unwrap_or(false);
8692 let c_prec = self.options.get("cprecedences").copied().unwrap_or(false);
8693 let octal = self.options.get("octalzeroes").copied().unwrap_or(false);
8694
8695 let mut evaluator = MathEval::new(&expr_expanded)
8696 .with_string_variables(&self.variables)
8697 .with_force_float(force_float)
8698 .with_c_precedences(c_prec)
8699 .with_octal_zeroes(octal);
8700
8701 match evaluator.evaluate() {
8702 Ok(result) => {
8703 for (k, v) in evaluator.extract_string_variables() {
8704 self.variables.insert(k.clone(), v.clone());
8705 env::set_var(&k, &v);
8706 }
8707 result.to_float()
8708 }
8709 Err(_) => 0.0,
8710 }
8711 }
8712
8713 fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
8714 if pattern == "*" {
8716 return true;
8717 }
8718 if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
8719 glob::Pattern::new(pattern)
8721 .map(|p| p.matches(value))
8722 .unwrap_or(false)
8723 } else {
8724 value == pattern
8725 }
8726 }
8727
8728 fn eval_cond_expr(&mut self, expr: &CondExpr) -> bool {
8729 match expr {
8730 CondExpr::FileExists(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8731 CondExpr::FileRegular(w) => std::path::Path::new(&self.expand_word(w)).is_file(),
8732 CondExpr::FileDirectory(w) => std::path::Path::new(&self.expand_word(w)).is_dir(),
8733 CondExpr::FileSymlink(w) => std::path::Path::new(&self.expand_word(w)).is_symlink(),
8734 CondExpr::FileReadable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8735 CondExpr::FileWritable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8736 CondExpr::FileExecutable(w) => std::path::Path::new(&self.expand_word(w)).exists(),
8737 CondExpr::FileNonEmpty(w) => std::fs::metadata(&self.expand_word(w))
8738 .map(|m| m.len() > 0)
8739 .unwrap_or(false),
8740 CondExpr::StringEmpty(w) => self.expand_word(w).is_empty(),
8741 CondExpr::StringNonEmpty(w) => !self.expand_word(w).is_empty(),
8742 CondExpr::StringEqual(a, b) => {
8743 let left = self.expand_word(a);
8744 let right = self.expand_word(b);
8745 if right.contains('*') || right.contains('?') || right.contains('[') {
8747 crate::glob::pattern_match(&right, &left, true, true)
8748 } else {
8749 left == right
8750 }
8751 }
8752 CondExpr::StringNotEqual(a, b) => {
8753 let left = self.expand_word(a);
8754 let right = self.expand_word(b);
8755 if right.contains('*') || right.contains('?') || right.contains('[') {
8756 !crate::glob::pattern_match(&right, &left, true, true)
8757 } else {
8758 left != right
8759 }
8760 }
8761 CondExpr::StringMatch(a, b) => {
8762 let val = self.expand_word(a);
8763 let pattern = self.expand_word(b);
8764 if let Some(re) = cached_regex(&pattern) {
8765 if let Some(caps) = re.captures(&val) {
8766 if let Some(m) = caps.get(0) {
8768 self.variables
8769 .insert("MATCH".to_string(), m.as_str().to_string());
8770 }
8771 let mut match_arr = Vec::new();
8773 for i in 1..caps.len() {
8774 if let Some(g) = caps.get(i) {
8775 match_arr.push(g.as_str().to_string());
8776 }
8777 }
8778 if !match_arr.is_empty() {
8779 self.arrays.insert("match".to_string(), match_arr);
8780 }
8781 true
8782 } else {
8783 self.variables.remove("MATCH");
8784 self.arrays.remove("match");
8785 false
8786 }
8787 } else {
8788 false
8789 }
8790 }
8791 CondExpr::StringLess(a, b) => self.expand_word(a) < self.expand_word(b),
8792 CondExpr::StringGreater(a, b) => self.expand_word(a) > self.expand_word(b),
8793 CondExpr::NumEqual(a, b) => {
8794 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8795 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8796 a_val == b_val
8797 }
8798 CondExpr::NumNotEqual(a, b) => {
8799 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8800 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8801 a_val != b_val
8802 }
8803 CondExpr::NumLess(a, b) => {
8804 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8805 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8806 a_val < b_val
8807 }
8808 CondExpr::NumLessEqual(a, b) => {
8809 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8810 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8811 a_val <= b_val
8812 }
8813 CondExpr::NumGreater(a, b) => {
8814 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8815 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8816 a_val > b_val
8817 }
8818 CondExpr::NumGreaterEqual(a, b) => {
8819 let a_val = self.expand_word(a).parse::<i64>().unwrap_or(0);
8820 let b_val = self.expand_word(b).parse::<i64>().unwrap_or(0);
8821 a_val >= b_val
8822 }
8823 CondExpr::Not(inner) => !self.eval_cond_expr(inner),
8824 CondExpr::And(a, b) => self.eval_cond_expr(a) && self.eval_cond_expr(b),
8825 CondExpr::Or(a, b) => self.eval_cond_expr(a) || self.eval_cond_expr(b),
8826 }
8827 }
8828
8829 fn builtin_cd(&mut self, args: &[String]) -> i32 {
8836 let mut quiet = false;
8840 let mut use_cdpath = false;
8841 let mut logical = true; let mut positional_args: Vec<&str> = Vec::new();
8843
8844 for arg in args {
8845 if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
8846 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
8848 positional_args.push(arg);
8849 continue;
8850 }
8851 for ch in arg[1..].chars() {
8852 match ch {
8853 'q' => quiet = true,
8854 's' => use_cdpath = true,
8855 'L' => logical = true,
8856 'P' => logical = false,
8857 _ => {
8858 eprintln!("cd: bad option: -{}", ch);
8859 return 1;
8860 }
8861 }
8862 }
8863 } else if arg.starts_with('+')
8864 && arg.len() > 1
8865 && arg[1..].chars().all(|c| c.is_ascii_digit())
8866 {
8867 positional_args.push(arg);
8869 } else {
8870 positional_args.push(arg);
8871 }
8872 }
8873
8874 if positional_args.len() == 2 {
8876 if let Ok(cwd) = env::current_dir() {
8877 let cwd_str = cwd.to_string_lossy();
8878 let old = positional_args[0];
8879 let new = positional_args[1];
8880 if cwd_str.contains(old) {
8881 let new_path = cwd_str.replace(old, new);
8882 if !quiet {
8883 println!("{}", new_path);
8884 }
8885 positional_args = vec![];
8886 return self.do_cd(&new_path, quiet, use_cdpath, logical);
8887 }
8888 }
8889 }
8890
8891 let path_arg = positional_args.first().map(|s| *s).unwrap_or("~");
8892
8893 if path_arg.starts_with('+') || path_arg.starts_with('-') {
8895 if let Ok(n) = path_arg[1..].parse::<usize>() {
8896 let idx = if path_arg.starts_with('+') {
8897 n
8898 } else {
8899 self.dir_stack.len().saturating_sub(n)
8900 };
8901 if let Some(dir) = self.dir_stack.get(idx) {
8902 let dir_path = dir.to_string_lossy().to_string();
8903 return self.do_cd(&dir_path, quiet, use_cdpath, logical);
8904 } else {
8905 eprintln!("cd: no such entry in dir stack");
8906 return 1;
8907 }
8908 }
8909 }
8910
8911 self.do_cd(path_arg, quiet, use_cdpath, logical)
8912 }
8913
8914 fn do_cd(&mut self, path_arg: &str, quiet: bool, use_cdpath: bool, physical: bool) -> i32 {
8915 let path = if path_arg == "~" || path_arg.is_empty() {
8916 dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
8917 } else if path_arg.starts_with("~/") {
8918 dirs::home_dir()
8919 .unwrap_or_else(|| PathBuf::from("."))
8920 .join(&path_arg[2..])
8921 } else if path_arg == "-" {
8922 if let Ok(oldpwd) = env::var("OLDPWD") {
8923 if !quiet {
8924 println!("{}", oldpwd);
8925 }
8926 PathBuf::from(oldpwd)
8927 } else {
8928 eprintln!("cd: OLDPWD not set");
8929 return 1;
8930 }
8931 } else if use_cdpath && !path_arg.starts_with('/') && !path_arg.starts_with('.') {
8932 let cdpath = env::var("CDPATH").unwrap_or_default();
8934 let mut found = None;
8935 for dir in cdpath.split(':') {
8936 let candidate = if dir.is_empty() {
8937 PathBuf::from(path_arg)
8938 } else {
8939 PathBuf::from(dir).join(path_arg)
8940 };
8941 if candidate.is_dir() {
8942 found = Some(candidate);
8943 break;
8944 }
8945 }
8946 found.unwrap_or_else(|| PathBuf::from(path_arg))
8947 } else {
8948 PathBuf::from(path_arg)
8949 };
8950
8951 if let Ok(cwd) = env::current_dir() {
8952 env::set_var("OLDPWD", &cwd);
8953 }
8954
8955 let target = if !physical {
8957 if let Ok(resolved) = path.canonicalize() {
8958 resolved
8959 } else {
8960 path.clone()
8961 }
8962 } else {
8963 path.clone()
8964 };
8965
8966 match env::set_current_dir(&target) {
8967 Ok(_) => {
8968 if let Ok(cwd) = env::current_dir() {
8969 env::set_var("PWD", &cwd);
8970 self.variables
8971 .insert("PWD".to_string(), cwd.to_string_lossy().to_string());
8972 }
8973 0
8974 }
8975 Err(e) => {
8976 eprintln!("cd: {}: {}", path.display(), e);
8977 1
8978 }
8979 }
8980 }
8981
8982 fn builtin_pwd(&mut self, _redirects: &[Redirect]) -> i32 {
8983 match env::current_dir() {
8984 Ok(path) => {
8985 println!("{}", path.display());
8986 0
8987 }
8988 Err(e) => {
8989 eprintln!("pwd: {}", e);
8990 1
8991 }
8992 }
8993 }
8994
8995 fn builtin_echo(&mut self, args: &[String], _redirects: &[Redirect]) -> i32 {
8996 let mut newline = true;
8997 let mut interpret_escapes = false;
8998 let mut start = 0;
8999
9000 for (i, arg) in args.iter().enumerate() {
9001 match arg.as_str() {
9002 "-n" => {
9003 newline = false;
9004 start = i + 1;
9005 }
9006 "-e" => {
9007 interpret_escapes = true;
9008 start = i + 1;
9009 }
9010 "-E" => {
9011 interpret_escapes = false;
9012 start = i + 1;
9013 }
9014 _ => break,
9015 }
9016 }
9017
9018 let output = args[start..].join(" ");
9019 if interpret_escapes {
9020 print!("{}", output.replace("\\n", "\n").replace("\\t", "\t"));
9021 } else {
9022 print!("{}", output);
9023 }
9024
9025 if newline {
9026 println!();
9027 }
9028 0
9029 }
9030
9031 fn builtin_export(&mut self, args: &[String]) -> i32 {
9032 for arg in args {
9033 if let Some((key, value)) = arg.split_once('=') {
9034 self.variables.insert(key.to_string(), value.to_string());
9035 env::set_var(key, value);
9036 } else {
9037 let val = self.get_variable(arg);
9039 env::set_var(arg, &val);
9040 }
9041 }
9042 0
9043 }
9044
9045 fn builtin_unset(&mut self, args: &[String]) -> i32 {
9046 for arg in args {
9047 env::remove_var(arg);
9048 self.variables.remove(arg);
9049 }
9050 0
9051 }
9052
9053 fn builtin_source(&mut self, args: &[String]) -> i32 {
9054 if args.is_empty() {
9055 eprintln!("source: filename argument required");
9056 return 1;
9057 }
9058
9059 let path = &args[0];
9060
9061 let abs_path = if path.starts_with('/') {
9063 path.clone()
9064 } else if path.starts_with("~/") {
9065 if let Some(home) = dirs::home_dir() {
9066 home.join(&path[2..]).to_string_lossy().to_string()
9067 } else {
9068 path.clone()
9069 }
9070 } else {
9071 std::env::current_dir()
9072 .map(|cwd| cwd.join(path).to_string_lossy().to_string())
9073 .unwrap_or_else(|_| path.clone())
9074 };
9075
9076 let saved_zero = self.variables.get("0").cloned();
9078 self.variables.insert("0".to_string(), abs_path.clone());
9079
9080 let result;
9081
9082 if self.posix_mode {
9083 result = match std::fs::read_to_string(&abs_path) {
9085 Ok(content) => match self.execute_script(&content) {
9086 Ok(status) => status,
9087 Err(e) => {
9088 eprintln!("source: {}: {}", path, e);
9089 1
9090 }
9091 },
9092 Err(e) => {
9093 eprintln!("source: {}: {}", path, e);
9094 1
9095 }
9096 };
9097 } else {
9098 let file_path = std::path::Path::new(&abs_path);
9100
9101 if let Some(ref cache) = self.plugin_cache {
9103 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
9104 if let Some(plugin_id) = cache.check(&abs_path, mt_s, mt_ns) {
9105 if let Ok(delta) = cache.load(plugin_id) {
9106 let t0 = std::time::Instant::now();
9107 self.replay_plugin_delta(&delta);
9108 tracing::info!(
9109 path = %abs_path,
9110 replay_us = t0.elapsed().as_micros() as u64,
9111 funcs = delta.functions.len(),
9112 aliases = delta.aliases.len(),
9113 vars = delta.variables.len() + delta.exports.len(),
9114 "source: cache hit, replayed"
9115 );
9116 if let Some(z) = saved_zero {
9118 self.variables.insert("0".to_string(), z);
9119 } else {
9120 self.variables.remove("0");
9121 }
9122 return 0;
9123 }
9124 }
9125 }
9126 }
9127
9128 let snapshot = self.snapshot_state();
9130 let t0 = std::time::Instant::now();
9131 tracing::debug!(path = %abs_path, "source: cache miss, executing via AST-cached path");
9132 result = match self.execute_script_file(&abs_path) {
9133 Ok(status) => status,
9134 Err(e) => {
9135 tracing::warn!(path = %abs_path, error = %e, "source: execution failed");
9136 eprintln!("source: {}: {}", path, e);
9137 1
9138 }
9139 };
9140 let source_ms = t0.elapsed().as_millis() as u64;
9141
9142 if result == 0 {
9144 if let Some((mt_s, mt_ns)) = crate::plugin_cache::file_mtime(file_path) {
9145 let delta = self.diff_state(&snapshot);
9146 let store_path = abs_path.clone();
9147 tracing::info!(
9148 path = %abs_path, source_ms,
9149 funcs = delta.functions.len(),
9150 aliases = delta.aliases.len(),
9151 vars = delta.variables.len() + delta.exports.len(),
9152 "source: caching delta on worker"
9153 );
9154 let cache_db_path = crate::plugin_cache::default_cache_path();
9155 self.worker_pool.submit(move || {
9156 match crate::plugin_cache::PluginCache::open(&cache_db_path) {
9157 Ok(cache) => {
9158 if let Err(e) = cache.store(&store_path, mt_s, mt_ns, source_ms, &delta) {
9159 tracing::error!(path = %store_path, error = %e, "plugin_cache: store failed");
9160 } else {
9161 tracing::debug!(path = %store_path, "plugin_cache: stored");
9162 }
9163 }
9164 Err(e) => tracing::error!(error = %e, "plugin_cache: open for write failed"),
9165 }
9166 });
9167 }
9168 }
9169 }
9170
9171 let final_result = if let Some(ret) = self.returning.take() {
9173 ret
9174 } else {
9175 result
9176 };
9177
9178 if let Some(z) = saved_zero {
9180 self.variables.insert("0".to_string(), z);
9181 } else {
9182 self.variables.remove("0");
9183 }
9184
9185 final_result
9186 }
9187
9188 fn snapshot_state(&self) -> PluginSnapshot {
9190 PluginSnapshot {
9191 functions: self.functions.keys().cloned().collect(),
9192 aliases: self.aliases.keys().cloned().collect(),
9193 global_aliases: self.global_aliases.keys().cloned().collect(),
9194 suffix_aliases: self.suffix_aliases.keys().cloned().collect(),
9195 variables: self.variables.clone(),
9196 arrays: self.arrays.keys().cloned().collect(),
9197 assoc_arrays: self.assoc_arrays.keys().cloned().collect(),
9198 fpath: self.fpath.clone(),
9199 options: self.options.clone(),
9200 hooks: self.hook_functions.clone(),
9201 autoloads: self.autoload_pending.keys().cloned().collect(),
9202 }
9203 }
9204
9205 fn diff_state(&self, snap: &PluginSnapshot) -> crate::plugin_cache::PluginDelta {
9207 use crate::plugin_cache::{AliasKind, PluginDelta};
9208 let mut delta = PluginDelta::default();
9209
9210 for (name, body) in &self.functions {
9212 if !snap.functions.contains(name) {
9213 if let Ok(bytes) = bincode::serialize(body) {
9214 delta.functions.push((name.clone(), bytes));
9215 }
9216 }
9217 }
9218
9219 for (name, value) in &self.aliases {
9221 if !snap.aliases.contains(name) {
9222 delta
9223 .aliases
9224 .push((name.clone(), value.clone(), AliasKind::Regular));
9225 }
9226 }
9227 for (name, value) in &self.global_aliases {
9228 if !snap.global_aliases.contains(name) {
9229 delta
9230 .aliases
9231 .push((name.clone(), value.clone(), AliasKind::Global));
9232 }
9233 }
9234 for (name, value) in &self.suffix_aliases {
9235 if !snap.suffix_aliases.contains(name) {
9236 delta
9237 .aliases
9238 .push((name.clone(), value.clone(), AliasKind::Suffix));
9239 }
9240 }
9241
9242 for (name, value) in &self.variables {
9244 if name == "0" {
9245 continue;
9246 } match snap.variables.get(name) {
9248 Some(old) if old == value => {} _ => {
9250 if env::var(name).ok().as_ref() == Some(value) {
9252 delta.exports.push((name.clone(), value.clone()));
9253 } else {
9254 delta.variables.push((name.clone(), value.clone()));
9255 }
9256 }
9257 }
9258 }
9259
9260 for (name, values) in &self.arrays {
9262 if !snap.arrays.contains(name) {
9263 delta.arrays.push((name.clone(), values.clone()));
9264 }
9265 }
9266
9267 for p in &self.fpath {
9269 if !snap.fpath.contains(p) {
9270 delta.fpath_additions.push(p.to_string_lossy().to_string());
9271 }
9272 }
9273
9274 for (name, value) in &self.options {
9276 match snap.options.get(name) {
9277 Some(old) if old == value => {}
9278 _ => delta.options_changed.push((name.clone(), *value)),
9279 }
9280 }
9281
9282 for (hook, funcs) in &self.hook_functions {
9284 let old_funcs = snap.hooks.get(hook);
9285 for f in funcs {
9286 let is_new = old_funcs.map_or(true, |old| !old.contains(f));
9287 if is_new {
9288 delta.hooks.push((hook.clone(), f.clone()));
9289 }
9290 }
9291 }
9292
9293 for (name, flags) in &self.autoload_pending {
9295 if !snap.autoloads.contains(name) {
9296 delta.autoloads.push((name.clone(), format!("{:?}", flags)));
9297 }
9298 }
9299
9300 delta
9301 }
9302
9303 fn replay_plugin_delta(&mut self, delta: &crate::plugin_cache::PluginDelta) {
9305 use crate::plugin_cache::AliasKind;
9306
9307 for (name, value, kind) in &delta.aliases {
9309 match kind {
9310 AliasKind::Regular => {
9311 self.aliases.insert(name.clone(), value.clone());
9312 }
9313 AliasKind::Global => {
9314 self.global_aliases.insert(name.clone(), value.clone());
9315 }
9316 AliasKind::Suffix => {
9317 self.suffix_aliases.insert(name.clone(), value.clone());
9318 }
9319 }
9320 }
9321
9322 for (name, value) in &delta.variables {
9324 self.variables.insert(name.clone(), value.clone());
9325 }
9326
9327 for (name, value) in &delta.exports {
9329 self.variables.insert(name.clone(), value.clone());
9330 env::set_var(name, value);
9331 }
9332
9333 for (name, values) in &delta.arrays {
9335 self.arrays.insert(name.clone(), values.clone());
9336 }
9337
9338 for p in &delta.fpath_additions {
9340 let pb = PathBuf::from(p);
9341 if !self.fpath.contains(&pb) {
9342 self.fpath.push(pb);
9343 }
9344 }
9345
9346 for (cmd, func) in &delta.completions {
9348 if let Some(ref mut comps) = self.assoc_arrays.get_mut("_comps") {
9349 comps.insert(cmd.clone(), func.clone());
9350 }
9351 }
9352
9353 for (name, enabled) in &delta.options_changed {
9355 self.options.insert(name.clone(), *enabled);
9356 }
9357
9358 for (hook, func) in &delta.hooks {
9360 self.hook_functions
9361 .entry(hook.clone())
9362 .or_insert_with(Vec::new)
9363 .push(func.clone());
9364 }
9365
9366 for (name, bytes) in &delta.functions {
9368 if let Ok(ast) = bincode::deserialize::<crate::parser::ShellCommand>(bytes) {
9369 self.functions.insert(name.clone(), ast);
9370 }
9371 }
9372 }
9373
9374 fn builtin_exit(&mut self, args: &[String]) -> i32 {
9375 let code = args
9376 .first()
9377 .and_then(|s| s.parse::<i32>().ok())
9378 .unwrap_or(self.last_status);
9379 std::process::exit(code);
9380 }
9381
9382 fn builtin_return(&mut self, args: &[String]) -> i32 {
9383 let status = args
9384 .first()
9385 .and_then(|s| s.parse::<i32>().ok())
9386 .unwrap_or(self.last_status);
9387 self.returning = Some(status);
9388 status
9389 }
9390
9391 fn builtin_test(&mut self, args: &[String]) -> i32 {
9392 if args.is_empty() {
9393 return 1;
9394 }
9395
9396 let args: Vec<&str> = args
9398 .iter()
9399 .map(|s| s.as_str())
9400 .filter(|&s| s != "]")
9401 .collect();
9402
9403 let mut meta_cache: HashMap<String, Option<std::fs::Metadata>> = HashMap::new();
9406 for arg in &args {
9407 if !arg.starts_with('-') && !arg.starts_with('!') && *arg != "(" && *arg != ")" {
9408 let path_str = arg.to_string();
9409 if !meta_cache.contains_key(&path_str) {
9410 meta_cache.insert(path_str, std::fs::metadata(arg).ok());
9411 }
9412 }
9413 }
9414
9415 let get_meta = |path: &str| -> Option<std::fs::Metadata> {
9417 meta_cache
9418 .get(path)
9419 .cloned()
9420 .unwrap_or_else(|| std::fs::metadata(path).ok())
9421 };
9422
9423 match args.as_slice() {
9424 ["-z", s] => {
9426 if s.is_empty() {
9427 0
9428 } else {
9429 1
9430 }
9431 }
9432 ["-n", s] => {
9433 if !s.is_empty() {
9434 0
9435 } else {
9436 1
9437 }
9438 }
9439
9440 ["-a", path] | ["-e", path] => {
9442 if std::path::Path::new(path).exists() {
9443 0
9444 } else {
9445 1
9446 }
9447 }
9448 ["-f", path] => {
9449 if std::path::Path::new(path).is_file() {
9450 0
9451 } else {
9452 1
9453 }
9454 }
9455 ["-d", path] => {
9456 if std::path::Path::new(path).is_dir() {
9457 0
9458 } else {
9459 1
9460 }
9461 }
9462 ["-b", path] => {
9463 use std::os::unix::fs::FileTypeExt;
9464 if std::fs::symlink_metadata(path)
9465 .map(|m| m.file_type().is_block_device())
9466 .unwrap_or(false)
9467 {
9468 0
9469 } else {
9470 1
9471 }
9472 }
9473 ["-c", path] => {
9474 use std::os::unix::fs::FileTypeExt;
9475 if std::fs::symlink_metadata(path)
9476 .map(|m| m.file_type().is_char_device())
9477 .unwrap_or(false)
9478 {
9479 0
9480 } else {
9481 1
9482 }
9483 }
9484 ["-p", path] => {
9485 use std::os::unix::fs::FileTypeExt;
9486 if std::fs::symlink_metadata(path)
9487 .map(|m| m.file_type().is_fifo())
9488 .unwrap_or(false)
9489 {
9490 0
9491 } else {
9492 1
9493 }
9494 }
9495 ["-S", path] => {
9496 use std::os::unix::fs::FileTypeExt;
9497 if std::fs::symlink_metadata(path)
9498 .map(|m| m.file_type().is_socket())
9499 .unwrap_or(false)
9500 {
9501 0
9502 } else {
9503 1
9504 }
9505 }
9506 ["-h", path] | ["-L", path] => {
9507 if std::path::Path::new(path).is_symlink() {
9508 0
9509 } else {
9510 1
9511 }
9512 }
9513
9514 ["-r", path] => {
9516 use std::os::unix::fs::MetadataExt;
9517 if let Some(meta) = get_meta(path) {
9518 let mode = meta.mode();
9519 let uid = unsafe { libc::geteuid() };
9520 let gid = unsafe { libc::getegid() };
9521 let readable = if meta.uid() == uid {
9522 mode & 0o400 != 0
9523 } else if meta.gid() == gid {
9524 mode & 0o040 != 0
9525 } else {
9526 mode & 0o004 != 0
9527 };
9528 if readable {
9529 0
9530 } else {
9531 1
9532 }
9533 } else {
9534 1
9535 }
9536 }
9537 ["-w", path] => {
9538 use std::os::unix::fs::MetadataExt;
9539 if let Some(meta) = get_meta(path) {
9540 let mode = meta.mode();
9541 let uid = unsafe { libc::geteuid() };
9542 let gid = unsafe { libc::getegid() };
9543 let writable = if meta.uid() == uid {
9544 mode & 0o200 != 0
9545 } else if meta.gid() == gid {
9546 mode & 0o020 != 0
9547 } else {
9548 mode & 0o002 != 0
9549 };
9550 if writable {
9551 0
9552 } else {
9553 1
9554 }
9555 } else {
9556 1
9557 }
9558 }
9559 ["-x", path] => {
9560 use std::os::unix::fs::MetadataExt;
9561 if let Some(meta) = get_meta(path) {
9562 let mode = meta.mode();
9563 let uid = unsafe { libc::geteuid() };
9564 let gid = unsafe { libc::getegid() };
9565 let executable = if meta.uid() == uid {
9566 mode & 0o100 != 0
9567 } else if meta.gid() == gid {
9568 mode & 0o010 != 0
9569 } else {
9570 mode & 0o001 != 0
9571 };
9572 if executable {
9573 0
9574 } else {
9575 1
9576 }
9577 } else {
9578 1
9579 }
9580 }
9581
9582 ["-g", path] => {
9584 use std::os::unix::fs::MetadataExt;
9585 if get_meta(path)
9586 .map(|m| m.mode() & 0o2000 != 0)
9587 .unwrap_or(false)
9588 {
9589 0
9590 } else {
9591 1
9592 }
9593 }
9594 ["-k", path] => {
9595 use std::os::unix::fs::MetadataExt;
9596 if get_meta(path)
9597 .map(|m| m.mode() & 0o1000 != 0)
9598 .unwrap_or(false)
9599 {
9600 0
9601 } else {
9602 1
9603 }
9604 }
9605 ["-u", path] => {
9606 use std::os::unix::fs::MetadataExt;
9607 if get_meta(path)
9608 .map(|m| m.mode() & 0o4000 != 0)
9609 .unwrap_or(false)
9610 {
9611 0
9612 } else {
9613 1
9614 }
9615 }
9616
9617 ["-s", path] => {
9619 if get_meta(path).map(|m| m.len() > 0).unwrap_or(false) {
9620 0
9621 } else {
9622 1
9623 }
9624 }
9625
9626 ["-O", path] => {
9628 use std::os::unix::fs::MetadataExt;
9629 if get_meta(path)
9630 .map(|m| m.uid() == unsafe { libc::geteuid() })
9631 .unwrap_or(false)
9632 {
9633 0
9634 } else {
9635 1
9636 }
9637 }
9638 ["-G", path] => {
9639 use std::os::unix::fs::MetadataExt;
9640 if get_meta(path)
9641 .map(|m| m.gid() == unsafe { libc::getegid() })
9642 .unwrap_or(false)
9643 {
9644 0
9645 } else {
9646 1
9647 }
9648 }
9649
9650 ["-N", path] => {
9652 use std::os::unix::fs::MetadataExt;
9653 if let Some(meta) = get_meta(path) {
9654 if meta.mtime() > meta.atime() {
9655 0
9656 } else {
9657 1
9658 }
9659 } else {
9660 1
9661 }
9662 }
9663
9664 ["-t", fd] => {
9666 if let Ok(fd_num) = fd.parse::<i32>() {
9667 if unsafe { libc::isatty(fd_num) } == 1 {
9668 0
9669 } else {
9670 1
9671 }
9672 } else {
9673 1
9674 }
9675 }
9676
9677 ["-v", varname] => {
9679 if self.variables.contains_key(*varname) || std::env::var(varname).is_ok() {
9680 0
9681 } else {
9682 1
9683 }
9684 }
9685
9686 ["-o", opt] => {
9688 let (name, _) = Self::normalize_option_name(opt);
9689 if self.options.get(&name).copied().unwrap_or(false) {
9690 0
9691 } else {
9692 1
9693 }
9694 }
9695
9696 [a, "=", b] | [a, "==", b] => {
9698 if a == b {
9699 0
9700 } else {
9701 1
9702 }
9703 }
9704 [a, "!=", b] => {
9705 if a != b {
9706 0
9707 } else {
9708 1
9709 }
9710 }
9711 [a, "<", b] => {
9712 if *a < *b {
9713 0
9714 } else {
9715 1
9716 }
9717 }
9718 [a, ">", b] => {
9719 if *a > *b {
9720 0
9721 } else {
9722 1
9723 }
9724 }
9725
9726 [a, "-eq", b] => {
9728 let a: i64 = a.parse().unwrap_or(0);
9729 let b: i64 = b.parse().unwrap_or(0);
9730 if a == b {
9731 0
9732 } else {
9733 1
9734 }
9735 }
9736 [a, "-ne", b] => {
9737 let a: i64 = a.parse().unwrap_or(0);
9738 let b: i64 = b.parse().unwrap_or(0);
9739 if a != b {
9740 0
9741 } else {
9742 1
9743 }
9744 }
9745 [a, "-lt", b] => {
9746 let a: i64 = a.parse().unwrap_or(0);
9747 let b: i64 = b.parse().unwrap_or(0);
9748 if a < b {
9749 0
9750 } else {
9751 1
9752 }
9753 }
9754 [a, "-le", b] => {
9755 let a: i64 = a.parse().unwrap_or(0);
9756 let b: i64 = b.parse().unwrap_or(0);
9757 if a <= b {
9758 0
9759 } else {
9760 1
9761 }
9762 }
9763 [a, "-gt", b] => {
9764 let a: i64 = a.parse().unwrap_or(0);
9765 let b: i64 = b.parse().unwrap_or(0);
9766 if a > b {
9767 0
9768 } else {
9769 1
9770 }
9771 }
9772 [a, "-ge", b] => {
9773 let a: i64 = a.parse().unwrap_or(0);
9774 let b: i64 = b.parse().unwrap_or(0);
9775 if a >= b {
9776 0
9777 } else {
9778 1
9779 }
9780 }
9781
9782 [f1, "-nt", f2] => {
9784 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
9785 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
9786 match (m1, m2) {
9787 (Some(t1), Some(t2)) => {
9788 if t1 > t2 {
9789 0
9790 } else {
9791 1
9792 }
9793 }
9794 (Some(_), None) => 0,
9795 _ => 1,
9796 }
9797 }
9798 [f1, "-ot", f2] => {
9799 let m1 = std::fs::metadata(f1).and_then(|m| m.modified()).ok();
9800 let m2 = std::fs::metadata(f2).and_then(|m| m.modified()).ok();
9801 match (m1, m2) {
9802 (Some(t1), Some(t2)) => {
9803 if t1 < t2 {
9804 0
9805 } else {
9806 1
9807 }
9808 }
9809 (None, Some(_)) => 0,
9810 _ => 1,
9811 }
9812 }
9813 [f1, "-ef", f2] => {
9814 use std::os::unix::fs::MetadataExt;
9815 let m1 = std::fs::metadata(f1).ok();
9816 let m2 = std::fs::metadata(f2).ok();
9817 match (m1, m2) {
9818 (Some(a), Some(b)) => {
9819 if a.dev() == b.dev() && a.ino() == b.ino() {
9820 0
9821 } else {
9822 1
9823 }
9824 }
9825 _ => 1,
9826 }
9827 }
9828
9829 [s] => {
9831 if !s.is_empty() {
9832 0
9833 } else {
9834 1
9835 }
9836 }
9837
9838 _ => 1,
9839 }
9840 }
9841
9842 fn builtin_local(&mut self, args: &[String]) -> i32 {
9843 self.builtin_typeset(args)
9844 }
9845
9846 fn builtin_declare(&mut self, args: &[String]) -> i32 {
9847 self.builtin_typeset(args)
9848 }
9849
9850 fn builtin_typeset(&mut self, args: &[String]) -> i32 {
9851 if self.local_scope_depth > 0 {
9854 for arg in args {
9855 if arg.starts_with('-') || arg.starts_with('+') {
9856 continue;
9857 }
9858 let name = arg.split('=').next().unwrap_or(arg);
9859 if !name.is_empty() {
9860 let old_val = self.variables.get(name).cloned();
9861 self.local_save_stack.push((name.to_string(), old_val));
9862 }
9863 }
9864 }
9865
9866 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;
9894 let mut precision: Option<usize> = None;
9895 let mut var_args: Vec<String> = Vec::new();
9896
9897 let mut i = 0;
9898 while i < args.len() {
9899 let arg = &args[i];
9900
9901 if arg == "--" {
9902 i += 1;
9903 while i < args.len() {
9904 var_args.push(args[i].clone());
9905 i += 1;
9906 }
9907 break;
9908 }
9909
9910 if arg == "+" {
9911 plus_mode = true;
9912 i += 1;
9913 continue;
9914 }
9915
9916 if arg.starts_with('+') && arg.len() > 1 {
9917 plus_mode = true;
9918 for c in arg[1..].chars() {
9919 match c {
9920 'a' => is_array = false,
9921 'A' => is_assoc = false,
9922 'x' => is_export = false,
9923 'i' => is_integer = false,
9924 'r' => is_readonly = false,
9925 'l' => is_lower = false,
9926 'u' => is_upper = false,
9927 'L' => is_left_pad = false,
9928 'R' => is_right_pad = false,
9929 'Z' => is_zero_pad = false,
9930 'F' => is_float = false,
9931 'E' => is_float_exp = false,
9932 'f' => is_function = false,
9933 'g' => is_global = false,
9934 'T' => is_tied = false,
9935 'H' => is_hidden = false,
9936 'h' => is_hide_val = false,
9937 't' => is_trace = false,
9938 'p' => print_mode = false,
9939 'm' => pattern_match = false,
9940 _ => {}
9941 }
9942 }
9943 } else if arg.starts_with('-') && arg.len() > 1 {
9944 let mut chars = arg[1..].chars().peekable();
9945 while let Some(c) = chars.next() {
9946 match c {
9947 'a' => is_array = true,
9948 'A' => is_assoc = true,
9949 'x' => is_export = true,
9950 'i' => is_integer = true,
9951 'r' => is_readonly = true,
9952 'l' => is_lower = true,
9953 'u' => is_upper = true,
9954 'L' => {
9955 is_left_pad = true;
9956 let rest: String = chars.clone().collect();
9958 if !rest.is_empty()
9959 && rest
9960 .chars()
9961 .next()
9962 .map(|c| c.is_ascii_digit())
9963 .unwrap_or(false)
9964 {
9965 let num: String =
9966 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9967 width = num.parse().ok();
9968 }
9969 }
9970 'R' => {
9971 is_right_pad = true;
9972 let rest: String = chars.clone().collect();
9973 if !rest.is_empty()
9974 && rest
9975 .chars()
9976 .next()
9977 .map(|c| c.is_ascii_digit())
9978 .unwrap_or(false)
9979 {
9980 let num: String =
9981 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9982 width = num.parse().ok();
9983 }
9984 }
9985 'Z' => {
9986 is_zero_pad = true;
9987 let rest: String = chars.clone().collect();
9988 if !rest.is_empty()
9989 && rest
9990 .chars()
9991 .next()
9992 .map(|c| c.is_ascii_digit())
9993 .unwrap_or(false)
9994 {
9995 let num: String =
9996 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
9997 width = num.parse().ok();
9998 }
9999 }
10000 'F' => {
10001 is_float = true;
10002 let rest: String = chars.clone().collect();
10003 if !rest.is_empty()
10004 && rest
10005 .chars()
10006 .next()
10007 .map(|c| c.is_ascii_digit())
10008 .unwrap_or(false)
10009 {
10010 let num: String =
10011 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
10012 precision = num.parse().ok();
10013 }
10014 }
10015 'E' => {
10016 is_float_exp = true;
10017 let rest: String = chars.clone().collect();
10018 if !rest.is_empty()
10019 && rest
10020 .chars()
10021 .next()
10022 .map(|c| c.is_ascii_digit())
10023 .unwrap_or(false)
10024 {
10025 let num: String =
10026 chars.by_ref().take_while(|c| c.is_ascii_digit()).collect();
10027 precision = num.parse().ok();
10028 }
10029 }
10030 'f' => is_function = true,
10031 'g' => is_global = true,
10032 'T' => is_tied = true,
10033 'H' => is_hidden = true,
10034 'h' => is_hide_val = true,
10035 't' => is_trace = true,
10036 'p' => print_mode = true,
10037 'm' => pattern_match = true,
10038 _ => {}
10039 }
10040 }
10041 } else {
10042 var_args.push(arg.clone());
10043 }
10044 i += 1;
10045 }
10046
10047 let _ = is_global;
10048 let _ = is_tied;
10049 let _ = is_hidden;
10050 let _ = is_hide_val;
10051 let _ = is_trace;
10052 let _ = pattern_match;
10053 let _ = precision;
10054
10055 if is_function && var_args.is_empty() {
10057 let mut func_names: Vec<_> = self.functions.keys().cloned().collect();
10058 func_names.sort();
10059 for name in &func_names {
10060 if let Some(func) = self.functions.get(name) {
10061 if print_mode {
10062 let body = crate::text::getpermtext(func);
10063 println!("{} () {{\n\t{}\n}}", name, body.trim());
10064 } else {
10065 let body = crate::text::getpermtext(func);
10066 println!("{} () {{\n\t{}\n}}", name, body.trim());
10067 }
10068 }
10069 }
10070 return 0;
10071 }
10072
10073 if is_function {
10075 for name in &var_args {
10076 if let Some(func) = self.functions.get(name) {
10077 if print_mode {
10078 let body = crate::text::getpermtext(func);
10079 println!("{} () {{\n\t{}\n}}", name, body.trim());
10080 } else {
10081 let body = crate::text::getpermtext(func);
10082 println!("{} () {{\n\t{}\n}}", name, body.trim());
10083 }
10084 }
10085 }
10086 return 0;
10087 }
10088
10089 if var_args.is_empty() {
10091 list_mode = true;
10092 }
10093
10094 if list_mode {
10095 let mut sorted_names: Vec<_> = self.variables.keys().cloned().collect();
10096 sorted_names.sort();
10097 for name in &sorted_names {
10098 let val = self.variables.get(name).cloned().unwrap_or_default();
10099 let mut attrs = String::new();
10100 if is_export || env::var(name).is_ok() {
10101 attrs.push('x');
10102 }
10103 let is_arr = self.arrays.contains_key(name);
10104 let is_hash = self.assoc_arrays.contains_key(name);
10105 if is_arr {
10106 attrs.push('a');
10107 }
10108 if is_hash {
10109 attrs.push('A');
10110 }
10111 if print_mode {
10112 let prefix = if attrs.is_empty() {
10114 "typeset".to_string()
10115 } else {
10116 format!("typeset -{}", attrs)
10117 };
10118 if is_hash {
10119 if let Some(assoc) = self.assoc_arrays.get(name) {
10120 let mut pairs: Vec<_> = assoc.iter().collect();
10121 pairs.sort_by_key(|(k, _)| (*k).clone());
10122 let formatted: Vec<String> = pairs
10123 .iter()
10124 .map(|(k, v)| {
10125 format!("[{}]={}", shell_quote_value(k), shell_quote_value(v))
10126 })
10127 .collect();
10128 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
10129 }
10130 } else if is_arr {
10131 if let Some(arr) = self.arrays.get(name) {
10132 let formatted: Vec<String> =
10133 arr.iter().map(|v| shell_quote_value(v)).collect();
10134 println!("{} {}=( {} )", prefix, name, formatted.join(" "));
10135 }
10136 } else {
10137 println!("{} {}={}", prefix, name, shell_quote_value(&val));
10138 }
10139 } else if is_hide_val {
10140 println!("{}={}", name, "*".repeat(val.len().min(8)));
10141 } else {
10142 println!("{}={}", name, val);
10143 }
10144 }
10145 return 0;
10146 }
10147
10148 for arg in var_args {
10150 if let Some(eq_pos) = arg.find('=') {
10152 let name = &arg[..eq_pos];
10153 let rest = &arg[eq_pos + 1..];
10154
10155 if rest.starts_with('(') {
10156 let mut elements = Vec::new();
10158 let current = rest[1..].to_string(); if let Some(close_pos) = current.find(')') {
10162 let content = ¤t[..close_pos];
10163 if !content.is_empty() {
10164 elements.extend(content.split_whitespace().map(|s| s.to_string()));
10165 }
10166 } else {
10167 if !current.is_empty() {
10169 let trimmed = current.trim_end_matches(')');
10170 elements.extend(trimmed.split_whitespace().map(|s| s.to_string()));
10171 }
10172 }
10173
10174 if is_assoc {
10176 let mut assoc = std::collections::HashMap::new();
10177 let mut iter = elements.iter();
10178 while let Some(key) = iter.next() {
10179 if let Some(val) = iter.next() {
10180 assoc.insert(key.clone(), val.clone());
10181 }
10182 }
10183 self.assoc_arrays.insert(name.to_string(), assoc);
10184 } else {
10185 self.arrays.insert(name.to_string(), elements);
10186 }
10187 self.variables.insert(name.to_string(), String::new());
10188 } else {
10189 let mut value = rest.to_string();
10191
10192 if is_integer {
10193 value = self.evaluate_arithmetic(&value).to_string();
10195 }
10196 if is_lower {
10197 value = value.to_lowercase();
10198 }
10199 if is_upper {
10200 value = value.to_uppercase();
10201 }
10202 if let Some(w) = width {
10203 if is_left_pad {
10204 value = format!("{:<width$}", value, width = w);
10205 value.truncate(w);
10206 } else if is_right_pad || is_zero_pad {
10207 let pad_char = if is_zero_pad { '0' } else { ' ' };
10208 if value.len() < w {
10209 value = format!(
10210 "{}{}",
10211 pad_char.to_string().repeat(w - value.len()),
10212 value
10213 );
10214 }
10215 if value.len() > w {
10216 value = value[value.len() - w..].to_string();
10217 }
10218 }
10219 }
10220 if is_float || is_float_exp {
10221 if let Ok(f) = value.parse::<f64>() {
10222 let prec = precision.unwrap_or(10);
10223 value = if is_float_exp {
10224 format!("{:.prec$e}", f, prec = prec)
10225 } else {
10226 format!("{:.prec$}", f, prec = prec)
10227 };
10228 }
10229 }
10230
10231 self.variables.insert(name.to_string(), value.clone());
10232
10233 if is_export {
10234 env::set_var(name, &value);
10235 }
10236 }
10237 } else if is_array || is_assoc {
10238 if is_assoc {
10240 self.assoc_arrays
10241 .insert(arg.clone(), std::collections::HashMap::new());
10242 } else {
10243 self.arrays.insert(arg.clone(), Vec::new());
10244 }
10245 self.variables.insert(arg.clone(), String::new());
10246 } else {
10247 self.variables.insert(arg.clone(), String::new());
10248 if is_export {
10249 env::set_var(&arg, "");
10250 }
10251 }
10252
10253 if is_readonly {
10255 let name = if let Some(eq_pos) = arg.find('=') {
10256 arg[..eq_pos].to_string()
10257 } else {
10258 arg.clone()
10259 };
10260 self.readonly_vars.insert(name);
10261 }
10262 }
10263 0
10264 }
10265
10266 fn builtin_read(&mut self, args: &[String]) -> i32 {
10267 use std::io::{BufRead, Read as IoRead};
10270
10271 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();
10282
10283 let mut i = 0;
10284 while i < args.len() {
10285 let arg = &args[i];
10286
10287 if arg == "--" {
10288 i += 1;
10289 while i < args.len() {
10290 var_names.push(args[i].clone());
10291 i += 1;
10292 }
10293 break;
10294 }
10295
10296 if arg.starts_with('-') && arg.len() > 1 {
10297 let mut chars = arg[1..].chars().peekable();
10298 while let Some(ch) = chars.next() {
10299 match ch {
10300 'r' => raw_mode = true,
10301 's' => silent = true,
10302 'z' => to_history = true,
10303 'A' => use_array = true,
10304 'c' | 'l' | 'n' | 'e' | 'E' => {} 'q' => quiet = true,
10306 't' => {
10307 let rest: String = chars.collect();
10308 if !rest.is_empty() {
10309 timeout = rest.parse().ok();
10310 } else {
10311 i += 1;
10312 if i < args.len() {
10313 timeout = args[i].parse().ok();
10314 }
10315 }
10316 break;
10317 }
10318 'd' => {
10319 let rest: String = chars.collect();
10320 if !rest.is_empty() {
10321 delimiter = rest.chars().next().unwrap_or('\n');
10322 } else {
10323 i += 1;
10324 if i < args.len() {
10325 delimiter = args[i].chars().next().unwrap_or('\n');
10326 }
10327 }
10328 break;
10329 }
10330 'k' => {
10331 let rest: String = chars.collect();
10332 if !rest.is_empty() {
10333 nchars = Some(rest.parse().unwrap_or(1));
10334 } else if i + 1 < args.len()
10335 && args[i + 1].chars().all(|c| c.is_ascii_digit())
10336 {
10337 i += 1;
10338 nchars = Some(args[i].parse().unwrap_or(1));
10339 } else {
10340 nchars = Some(1);
10341 }
10342 break;
10343 }
10344 'u' => {
10345 let rest: String = chars.collect();
10346 if !rest.is_empty() {
10347 fd = rest.parse().unwrap_or(0);
10348 } else {
10349 i += 1;
10350 if i < args.len() {
10351 fd = args[i].parse().unwrap_or(0);
10352 }
10353 }
10354 break;
10355 }
10356 'p' => {
10357 let rest: String = chars.collect();
10358 if !rest.is_empty() {
10359 prompt_str = Some(rest);
10360 } else {
10361 i += 1;
10362 if i < args.len() {
10363 prompt_str = Some(args[i].clone());
10364 }
10365 }
10366 break;
10367 }
10368 _ => {}
10369 }
10370 }
10371 } else {
10372 if let Some(pos) = arg.find('?') {
10373 var_names.push(arg[..pos].to_string());
10374 prompt_str = Some(arg[pos + 1..].to_string());
10375 } else {
10376 var_names.push(arg.clone());
10377 }
10378 }
10379 i += 1;
10380 }
10381
10382 if var_names.is_empty() {
10383 var_names.push("REPLY".to_string());
10384 }
10385
10386 if let Some(ref p) = prompt_str {
10387 eprint!("{}", p);
10388 let _ = std::io::stderr().flush();
10389 }
10390
10391 let _ = to_history;
10392 let _ = fd;
10393 let _ = silent;
10394
10395 let input = if let Some(n) = nchars {
10396 let mut buf = vec![0u8; n];
10397 let stdin = io::stdin();
10398 if let Some(_t) = timeout {
10399 }
10401 match stdin.lock().read_exact(&mut buf) {
10402 Ok(_) => String::from_utf8_lossy(&buf).to_string(),
10403 Err(_) => return 1,
10404 }
10405 } else {
10406 let stdin = io::stdin();
10407 let mut input = String::new();
10408 if delimiter == '\n' {
10409 match stdin.lock().read_line(&mut input) {
10410 Ok(0) => return 1,
10411 Ok(_) => {}
10412 Err(_) => return 1,
10413 }
10414 } else {
10415 let mut byte = [0u8; 1];
10416 loop {
10417 match stdin.lock().read_exact(&mut byte) {
10418 Ok(_) => {
10419 let c = byte[0] as char;
10420 if c == delimiter {
10421 break;
10422 }
10423 input.push(c);
10424 }
10425 Err(_) => break,
10426 }
10427 }
10428 }
10429 input
10430 .trim_end_matches('\n')
10431 .trim_end_matches('\r')
10432 .to_string()
10433 };
10434
10435 let processed = if raw_mode {
10436 input
10437 } else {
10438 input.replace("\\\n", "")
10439 };
10440
10441 if quiet {
10442 return if processed.is_empty() { 1 } else { 0 };
10443 }
10444
10445 if use_array {
10446 let var = &var_names[0];
10447 let words: Vec<String> = processed.split_whitespace().map(String::from).collect();
10448 self.arrays.insert(var.clone(), words);
10449 } else if var_names.len() == 1 {
10450 let var = &var_names[0];
10451 env::set_var(var, &processed);
10452 self.variables.insert(var.clone(), processed);
10453 } else {
10454 let ifs = self
10455 .variables
10456 .get("IFS")
10457 .map(|s| s.as_str())
10458 .unwrap_or(" \t\n");
10459 let words: Vec<&str> = processed
10460 .split(|c| ifs.contains(c))
10461 .filter(|s| !s.is_empty())
10462 .collect();
10463
10464 for (j, var) in var_names.iter().enumerate() {
10465 if j < words.len() {
10466 if j == var_names.len() - 1 && words.len() > var_names.len() {
10467 let remaining = words[j..].join(" ");
10468 env::set_var(var, &remaining);
10469 self.variables.insert(var.clone(), remaining);
10470 } else {
10471 env::set_var(var, words[j]);
10472 self.variables.insert(var.clone(), words[j].to_string());
10473 }
10474 } else {
10475 env::set_var(var, "");
10476 self.variables.insert(var.clone(), String::new());
10477 }
10478 }
10479 }
10480
10481 0
10482 }
10483
10484 fn builtin_shift(&mut self, args: &[String]) -> i32 {
10485 let mut from_end = false;
10491 let mut count = 1usize;
10492 let mut array_names: Vec<String> = Vec::new();
10493
10494 let mut i = 0;
10495 while i < args.len() {
10496 let arg = &args[i];
10497 if arg == "-p" {
10498 from_end = true;
10499 } else if arg.chars().all(|c| c.is_ascii_digit()) {
10500 count = arg.parse().unwrap_or(1);
10501 } else {
10502 array_names.push(arg.clone());
10503 }
10504 i += 1;
10505 }
10506
10507 if array_names.is_empty() {
10508 if from_end {
10510 for _ in 0..count {
10511 if !self.positional_params.is_empty() {
10512 self.positional_params.pop();
10513 }
10514 }
10515 } else {
10516 for _ in 0..count.min(self.positional_params.len()) {
10517 self.positional_params.remove(0);
10518 }
10519 }
10520 } else {
10521 for name in array_names {
10523 if let Some(arr) = self.arrays.get_mut(&name) {
10524 if from_end {
10525 for _ in 0..count {
10526 if !arr.is_empty() {
10527 arr.pop();
10528 }
10529 }
10530 } else {
10531 for _ in 0..count {
10532 if !arr.is_empty() {
10533 arr.remove(0);
10534 }
10535 }
10536 }
10537 }
10538 }
10539 }
10540
10541 0
10542 }
10543
10544 #[tracing::instrument(level = "debug", skip(self))]
10545 fn builtin_eval(&mut self, args: &[String]) -> i32 {
10546 let code = args.join(" ");
10547 match self.execute_script(&code) {
10548 Ok(status) => status,
10549 Err(e) => {
10550 eprintln!("eval: {}", e);
10551 1
10552 }
10553 }
10554 }
10555
10556 fn builtin_autoload(&mut self, args: &[String]) -> i32 {
10557 let mut functions = Vec::new();
10561 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;
10569
10570 let mut i = 0;
10571 while i < args.len() {
10572 let arg = &args[i];
10573
10574 if arg == "--" {
10575 i += 1;
10576 break;
10577 }
10578
10579 if arg.starts_with('+') {
10580 let flags = &arg[1..];
10581 for c in flags.chars() {
10582 match c {
10583 'U' => no_alias = false,
10584 'z' => zsh_style = false,
10585 'k' => ksh_style = false,
10586 't' => trace = false,
10587 'd' => use_caller_dir = false,
10588 _ => {}
10589 }
10590 }
10591 } else if arg.starts_with('-') {
10592 let flags = &arg[1..];
10593 if flags.is_empty() {
10594 i += 1;
10596 break;
10597 }
10598 for c in flags.chars() {
10599 match c {
10600 'U' => no_alias = true,
10601 'z' => zsh_style = true,
10602 'k' => ksh_style = true,
10603 'X' => execute_now = true,
10604 'r' | 'R' => resolve = true,
10605 't' => trace = true,
10606 'T' => {} 'W' => {} 'd' => use_caller_dir = true,
10609 'w' => {} 'm' => {} _ => {}
10612 }
10613 }
10614 } else {
10615 functions.push(arg.clone());
10616 }
10617 i += 1;
10618 }
10619
10620 while i < args.len() {
10622 functions.push(args[i].clone());
10623 i += 1;
10624 }
10625
10626 if functions.is_empty() && !execute_now {
10628 for (name, _) in &self.autoload_pending {
10629 if no_alias && zsh_style {
10630 println!("autoload -Uz {}", name);
10631 } else if no_alias {
10632 println!("autoload -U {}", name);
10633 } else {
10634 println!("autoload {}", name);
10635 }
10636 }
10637 return 0;
10638 }
10639
10640 if execute_now {
10644 for func_name in &functions {
10645 if let Some(loaded) = self.load_autoload_function(func_name) {
10647 let body = match loaded {
10649 ShellCommand::FunctionDef(_, body) => (*body).clone(),
10650 other => other,
10651 };
10652 self.functions.insert(func_name.clone(), body);
10654 self.autoload_pending.remove(func_name);
10656 } else {
10657 eprintln!(
10658 "autoload: {}: function definition file not found",
10659 func_name
10660 );
10661 return 1;
10662 }
10663 }
10664 return 0;
10665 }
10666
10667 for func_name in &functions {
10669 let mut flags = AutoloadFlags::empty();
10671 if no_alias {
10672 flags |= AutoloadFlags::NO_ALIAS;
10673 }
10674 if zsh_style {
10675 flags |= AutoloadFlags::ZSH_STYLE;
10676 }
10677 if ksh_style {
10678 flags |= AutoloadFlags::KSH_STYLE;
10679 }
10680 if trace {
10681 flags |= AutoloadFlags::TRACE;
10682 }
10683 if use_caller_dir {
10684 flags |= AutoloadFlags::USE_CALLER_DIR;
10685 }
10686
10687 self.autoload_pending.insert(func_name.clone(), flags);
10688
10689 let autoload_opts = if zsh_style && no_alias {
10692 "-XUz"
10693 } else if zsh_style {
10694 "-Xz"
10695 } else if no_alias {
10696 "-XU"
10697 } else {
10698 "-X"
10699 };
10700
10701 let stub = ShellCommand::List(vec![
10703 (
10704 ShellCommand::Simple(SimpleCommand {
10705 assignments: vec![],
10706 words: vec![
10707 ShellWord::Literal("builtin".to_string()),
10708 ShellWord::Literal("autoload".to_string()),
10709 ShellWord::Literal(autoload_opts.to_string()),
10710 ShellWord::Literal(func_name.clone()),
10711 ],
10712 redirects: vec![],
10713 }),
10714 ListOp::And,
10715 ),
10716 (
10717 ShellCommand::Simple(SimpleCommand {
10718 assignments: vec![],
10719 words: vec![
10720 ShellWord::Literal(func_name.clone()),
10721 ShellWord::DoubleQuoted(vec![ShellWord::Variable("@".to_string())]),
10722 ],
10723 redirects: vec![],
10724 }),
10725 ListOp::Semi,
10726 ),
10727 ]);
10728
10729 self.functions.insert(func_name.clone(), stub);
10730
10731 if resolve {
10733 if self.find_function_file(func_name).is_none() {
10734 eprintln!(
10735 "autoload: {}: function definition file not found",
10736 func_name
10737 );
10738 }
10739 }
10740 }
10741
10742 if functions.len() >= 4 && !resolve && !execute_now {
10746 let fpath_dirs: Vec<PathBuf> = self.fpath.clone();
10747 let names: Vec<String> = functions.clone();
10748 let pool = std::sync::Arc::clone(&self.worker_pool);
10749
10750 tracing::debug!(
10751 count = names.len(),
10752 fpath_dirs = fpath_dirs.len(),
10753 "batch autoload: pre-resolving fpath lookups on worker pool"
10754 );
10755
10756 let resolved = std::sync::Arc::new(parking_lot::Mutex::new(
10759 HashMap::<String, PathBuf>::with_capacity(names.len()),
10760 ));
10761
10762 for name in names {
10763 let dirs = fpath_dirs.clone();
10764 let resolved = std::sync::Arc::clone(&resolved);
10765 pool.submit(move || {
10766 for dir in &dirs {
10767 let path = dir.join(&name);
10768 if path.exists() && path.is_file() {
10769 let _ = std::fs::read(&path);
10772 resolved.lock().insert(name.clone(), path);
10773 tracing::trace!(func = %name, "autoload batch: pre-resolved");
10774 break;
10775 }
10776 }
10777 });
10778 }
10779 }
10780
10781 0
10782 }
10783
10784 fn find_function_file(&self, name: &str) -> Option<PathBuf> {
10786 for dir in &self.fpath {
10787 let path = dir.join(name);
10788 if path.exists() && path.is_file() {
10789 return Some(path);
10790 }
10791 }
10792 None
10793 }
10794
10795 fn load_autoload_function(&mut self, name: &str) -> Option<ShellCommand> {
10797 if !self.zsh_compat {
10800 if let Some(ref cache) = self.compsys_cache {
10801 if let Ok(Some(bc_blob)) = cache.get_autoload_bytecode(name) {
10803 if let Ok(chunk) = bincode::deserialize::<fusevm::Chunk>(&bc_blob) {
10805 if !chunk.ops.is_empty() {
10806 tracing::trace!(
10807 name,
10808 bytes = bc_blob.len(),
10809 ops = chunk.ops.len(),
10810 "autoload: bytecode cache hit → VM"
10811 );
10812 let mut vm = fusevm::VM::new(chunk);
10814 let _ = vm.run();
10815 self.last_status = vm.last_status;
10816 return Some(ShellCommand::Simple(crate::parser::SimpleCommand {
10818 assignments: Vec::new(),
10819 words: Vec::new(),
10820 redirects: Vec::new(),
10821 }));
10822 }
10823 }
10824 if let Ok(commands) = bincode::deserialize::<Vec<ShellCommand>>(&bc_blob) {
10826 if !commands.is_empty() {
10827 tracing::trace!(
10828 name,
10829 bytes = bc_blob.len(),
10830 "autoload: legacy AST cache hit"
10831 );
10832 return Some(self.wrap_autoload_commands(name, commands));
10833 }
10834 }
10835 }
10836
10837 if let Ok(Some(body)) = cache.get_autoload_body(name) {
10839 let mut parser = ShellParser::new(&body);
10840 if let Ok(commands) = parser.parse_script() {
10841 if !commands.is_empty() {
10842 let compiler = crate::shell_compiler::ShellCompiler::new();
10844 let chunk = compiler.compile(&commands);
10845 if let Ok(blob) = bincode::serialize(&chunk) {
10846 let _ = cache.set_autoload_bytecode(name, &blob);
10847 tracing::trace!(
10848 name,
10849 bytes = blob.len(),
10850 "autoload: bytecodes compiled and cached"
10851 );
10852 }
10853 return Some(self.wrap_autoload_commands(name, commands));
10854 }
10855 }
10856 }
10857 }
10858 }
10859
10860 if !self.functions.contains_key(name) {
10862 for dir in &self.fpath.clone() {
10864 let zwc_path = dir.with_extension("zwc");
10866 if zwc_path.exists() {
10867 let prefixed_name = format!(
10869 "{}/{}",
10870 dir.file_name().and_then(|n| n.to_str()).unwrap_or(""),
10871 name
10872 );
10873 if let Some(func) = self.load_function_from_zwc(&zwc_path, &prefixed_name) {
10874 return Some(func);
10875 }
10876 if let Some(func) = self.load_function_from_zwc(&zwc_path, name) {
10878 return Some(func);
10879 }
10880 }
10881 let func_zwc = dir.join(format!("{}.zwc", name));
10883 if func_zwc.exists() {
10884 if let Some(func) = self.load_function_from_zwc(&func_zwc, name) {
10885 return Some(func);
10886 }
10887 }
10888 }
10889 }
10890
10891 let path = self.find_function_file(name)?;
10893
10894 let content = std::fs::read_to_string(&path).ok()?;
10896
10897 let mut parser = ShellParser::new(&content);
10899
10900 if let Ok(commands) = parser.parse_script() {
10901 if commands.is_empty() {
10902 return None;
10903 }
10904
10905 if commands.len() == 1 {
10907 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
10908 if fn_name == name {
10909 return Some(commands[0].clone());
10910 }
10911 }
10912 }
10913
10914 let body = if commands.len() == 1 {
10917 commands.into_iter().next().unwrap()
10918 } else {
10919 let list_cmds: Vec<(ShellCommand, ListOp)> =
10921 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
10922 ShellCommand::List(list_cmds)
10923 };
10924
10925 return Some(ShellCommand::FunctionDef(name.to_string(), Box::new(body)));
10926 }
10927
10928 None
10929 }
10930
10931 fn wrap_autoload_commands(&self, name: &str, commands: Vec<ShellCommand>) -> ShellCommand {
10933 if commands.len() == 1 {
10935 if let ShellCommand::FunctionDef(ref fn_name, _) = commands[0] {
10936 if fn_name == name {
10937 return commands.into_iter().next().unwrap();
10938 }
10939 }
10940 }
10941 let body = if commands.len() == 1 {
10943 commands.into_iter().next().unwrap()
10944 } else {
10945 let list_cmds: Vec<(ShellCommand, ListOp)> =
10946 commands.into_iter().map(|c| (c, ListOp::Semi)).collect();
10947 ShellCommand::List(list_cmds)
10948 };
10949 ShellCommand::FunctionDef(name.to_string(), Box::new(body))
10950 }
10951
10952 pub fn maybe_autoload(&mut self, name: &str) -> bool {
10954 if self.autoload_pending.contains_key(name) {
10955 if let Some(func) = self.load_autoload_function(name) {
10956 let to_store = match func {
10958 ShellCommand::FunctionDef(_, body) => (*body).clone(),
10959 other => other,
10960 };
10961 self.functions.insert(name.to_string(), to_store);
10962 self.autoload_pending.remove(name);
10963 return true;
10964 }
10965 }
10966 false
10967 }
10968
10969 fn builtin_jobs(&mut self, args: &[String]) -> i32 {
10970 let mut long_format = false;
10979 let mut pids_only = false;
10980 let mut show_dir = false;
10981 let mut running_only = false;
10982 let mut stopped_only = false;
10983 let mut job_ids: Vec<usize> = Vec::new();
10984
10985 for arg in args {
10986 if arg.starts_with('-') {
10987 for c in arg[1..].chars() {
10988 match c {
10989 'l' => long_format = true,
10990 'p' => pids_only = true,
10991 'd' => show_dir = true,
10992 'r' => running_only = true,
10993 's' => stopped_only = true,
10994 'Z' => {} _ => {}
10996 }
10997 }
10998 } else if arg.starts_with('%') {
10999 if let Ok(id) = arg[1..].parse::<usize>() {
11000 job_ids.push(id);
11001 }
11002 } else if let Ok(id) = arg.parse::<usize>() {
11003 job_ids.push(id);
11004 }
11005 }
11006
11007 for job in self.jobs.reap_finished() {
11009 if !running_only && !stopped_only {
11010 if pids_only {
11011 println!("{}", job.pid);
11012 } else {
11013 println!("[{}] Done {}", job.id, job.command);
11014 }
11015 }
11016 }
11017
11018 for job in self.jobs.list() {
11020 if !job_ids.is_empty() && !job_ids.contains(&job.id) {
11022 continue;
11023 }
11024
11025 if running_only && job.state != JobState::Running {
11027 continue;
11028 }
11029 if stopped_only && job.state != JobState::Stopped {
11030 continue;
11031 }
11032
11033 if pids_only {
11034 println!("{}", job.pid);
11035 continue;
11036 }
11037
11038 let marker = if job.is_current { "+" } else { "-" };
11039 let state = match job.state {
11040 JobState::Running => "running",
11041 JobState::Stopped => "suspended",
11042 JobState::Done => "done",
11043 };
11044
11045 if long_format {
11046 println!(
11047 "[{}]{} {:6} {} {}",
11048 job.id, marker, job.pid, state, job.command
11049 );
11050 } else {
11051 println!("[{}]{} {} {}", job.id, marker, state, job.command);
11052 }
11053
11054 if show_dir {
11055 if let Ok(cwd) = env::current_dir() {
11056 println!(" (pwd: {})", cwd.display());
11057 }
11058 }
11059 }
11060 0
11061 }
11062
11063 fn builtin_fg(&mut self, args: &[String]) -> i32 {
11064 let job_id = if let Some(arg) = args.first() {
11065 let s = arg.trim_start_matches('%');
11067 match s.parse::<usize>() {
11068 Ok(id) => Some(id),
11069 Err(_) => {
11070 eprintln!("fg: {}: no such job", arg);
11071 return 1;
11072 }
11073 }
11074 } else {
11075 self.jobs.current().map(|j| j.id)
11076 };
11077
11078 let Some(id) = job_id else {
11079 eprintln!("fg: no current job");
11080 return 1;
11081 };
11082
11083 let Some(job) = self.jobs.get(id) else {
11084 eprintln!("fg: %{}: no such job", id);
11085 return 1;
11086 };
11087
11088 let pid = job.pid;
11089 let cmd = job.command.clone();
11090 println!("{}", cmd);
11091
11092 if let Err(e) = continue_job(pid) {
11094 eprintln!("fg: {}", e);
11095 return 1;
11096 }
11097
11098 match wait_for_job(pid) {
11100 Ok(status) => {
11101 self.jobs.remove(id);
11102 status
11103 }
11104 Err(e) => {
11105 eprintln!("fg: {}", e);
11106 1
11107 }
11108 }
11109 }
11110
11111 fn builtin_bg(&mut self, args: &[String]) -> i32 {
11112 let job_id = if let Some(arg) = args.first() {
11113 let s = arg.trim_start_matches('%');
11114 match s.parse::<usize>() {
11115 Ok(id) => Some(id),
11116 Err(_) => {
11117 eprintln!("bg: {}: no such job", arg);
11118 return 1;
11119 }
11120 }
11121 } else {
11122 self.jobs.current().map(|j| j.id)
11123 };
11124
11125 let Some(id) = job_id else {
11126 eprintln!("bg: no current job");
11127 return 1;
11128 };
11129
11130 let Some(job) = self.jobs.get_mut(id) else {
11131 eprintln!("bg: %{}: no such job", id);
11132 return 1;
11133 };
11134
11135 let pid = job.pid;
11136 let cmd = job.command.clone();
11137
11138 if let Err(e) = continue_job(pid) {
11139 eprintln!("bg: {}", e);
11140 return 1;
11141 }
11142
11143 job.state = JobState::Running;
11144 println!("[{}] {} &", id, cmd);
11145 0
11146 }
11147
11148 fn builtin_kill(&mut self, args: &[String]) -> i32 {
11149 use crate::jobs::send_signal;
11152 use nix::sys::signal::Signal;
11153
11154 if args.is_empty() {
11155 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
11156 eprintln!(" kill -l [sig ...]");
11157 return 1;
11158 }
11159
11160 let signal_map: &[(&str, i32, Signal)] = &[
11162 ("HUP", 1, Signal::SIGHUP),
11163 ("INT", 2, Signal::SIGINT),
11164 ("QUIT", 3, Signal::SIGQUIT),
11165 ("ILL", 4, Signal::SIGILL),
11166 ("TRAP", 5, Signal::SIGTRAP),
11167 ("ABRT", 6, Signal::SIGABRT),
11168 ("BUS", 7, Signal::SIGBUS),
11169 ("FPE", 8, Signal::SIGFPE),
11170 ("KILL", 9, Signal::SIGKILL),
11171 ("USR1", 10, Signal::SIGUSR1),
11172 ("SEGV", 11, Signal::SIGSEGV),
11173 ("USR2", 12, Signal::SIGUSR2),
11174 ("PIPE", 13, Signal::SIGPIPE),
11175 ("ALRM", 14, Signal::SIGALRM),
11176 ("TERM", 15, Signal::SIGTERM),
11177 ("CHLD", 17, Signal::SIGCHLD),
11178 ("CONT", 18, Signal::SIGCONT),
11179 ("STOP", 19, Signal::SIGSTOP),
11180 ("TSTP", 20, Signal::SIGTSTP),
11181 ("TTIN", 21, Signal::SIGTTIN),
11182 ("TTOU", 22, Signal::SIGTTOU),
11183 ("URG", 23, Signal::SIGURG),
11184 ("XCPU", 24, Signal::SIGXCPU),
11185 ("XFSZ", 25, Signal::SIGXFSZ),
11186 ("VTALRM", 26, Signal::SIGVTALRM),
11187 ("PROF", 27, Signal::SIGPROF),
11188 ("WINCH", 28, Signal::SIGWINCH),
11189 ("IO", 29, Signal::SIGIO),
11190 ("SYS", 31, Signal::SIGSYS),
11191 ];
11192
11193 let mut sig = Signal::SIGTERM;
11194 let mut pids: Vec<String> = Vec::new();
11195 let mut list_mode = false;
11196 let mut list_args: Vec<String> = Vec::new();
11197
11198 let mut i = 0;
11199 while i < args.len() {
11200 let arg = &args[i];
11201
11202 if arg == "-l" || arg == "-L" {
11203 list_mode = true;
11204 list_args = args[i + 1..].to_vec();
11206 break;
11207 } else if arg == "-s" {
11208 i += 1;
11210 if i >= args.len() {
11211 eprintln!("kill: -s requires an argument");
11212 return 1;
11213 }
11214 let sig_name = args[i].to_uppercase();
11215 let sig_name = sig_name.strip_prefix("SIG").unwrap_or(&sig_name);
11216 if let Some((_, _, s)) = signal_map.iter().find(|(name, _, _)| *name == sig_name) {
11217 sig = *s;
11218 } else {
11219 eprintln!("kill: invalid signal: {}", args[i]);
11220 return 1;
11221 }
11222 } else if arg == "-n" {
11223 i += 1;
11225 if i >= args.len() {
11226 eprintln!("kill: -n requires an argument");
11227 return 1;
11228 }
11229 let num: i32 = match args[i].parse() {
11230 Ok(n) => n,
11231 Err(_) => {
11232 eprintln!("kill: invalid signal number: {}", args[i]);
11233 return 1;
11234 }
11235 };
11236 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11237 sig = *s;
11238 } else {
11239 eprintln!("kill: invalid signal number: {}", num);
11240 return 1;
11241 }
11242 } else if arg.starts_with('-') && arg.len() > 1 {
11243 let sig_str = &arg[1..];
11245 let sig_upper = sig_str.to_uppercase();
11246 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
11247
11248 if let Ok(num) = sig_str.parse::<i32>() {
11250 if let Some((_, _, s)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11251 sig = *s;
11252 } else {
11253 eprintln!("kill: invalid signal: {}", arg);
11254 return 1;
11255 }
11256 } else if let Some((_, _, s)) =
11257 signal_map.iter().find(|(name, _, _)| *name == sig_name)
11258 {
11259 sig = *s;
11260 } else {
11261 eprintln!("kill: invalid signal: {}", arg);
11262 return 1;
11263 }
11264 } else {
11265 pids.push(arg.clone());
11266 }
11267 i += 1;
11268 }
11269
11270 if list_mode {
11272 if list_args.is_empty() {
11273 for (name, num, _) in signal_map {
11275 println!("{:2}) SIG{}", num, name);
11276 }
11277 } else {
11278 for arg in &list_args {
11280 if let Ok(num) = arg.parse::<i32>() {
11281 if let Some((name, _, _)) = signal_map.iter().find(|(_, n, _)| *n == num) {
11283 println!("{}", name);
11284 } else {
11285 eprintln!("kill: unknown signal: {}", num);
11286 }
11287 } else {
11288 let sig_upper = arg.to_uppercase();
11290 let sig_name = sig_upper.strip_prefix("SIG").unwrap_or(&sig_upper);
11291 if let Some((_, num, _)) =
11292 signal_map.iter().find(|(name, _, _)| *name == sig_name)
11293 {
11294 println!("{}", num);
11295 } else {
11296 eprintln!("kill: unknown signal: {}", arg);
11297 }
11298 }
11299 }
11300 }
11301 return 0;
11302 }
11303
11304 if pids.is_empty() {
11305 eprintln!("kill: usage: kill [-s signal | -n num | -sig] pid ...");
11306 return 1;
11307 }
11308
11309 let mut status = 0;
11310 for arg in &pids {
11311 if arg.starts_with('%') {
11313 let id: usize = match arg[1..].parse() {
11314 Ok(id) => id,
11315 Err(_) => {
11316 eprintln!("kill: {}: no such job", arg);
11317 status = 1;
11318 continue;
11319 }
11320 };
11321 if let Some(job) = self.jobs.get(id) {
11322 if let Err(e) = send_signal(job.pid, sig) {
11323 eprintln!("kill: {}", e);
11324 status = 1;
11325 }
11326 } else {
11327 eprintln!("kill: {}: no such job", arg);
11328 status = 1;
11329 }
11330 } else {
11331 let pid: u32 = match arg.parse() {
11333 Ok(p) => p,
11334 Err(_) => {
11335 eprintln!("kill: {}: invalid pid", arg);
11336 status = 1;
11337 continue;
11338 }
11339 };
11340 if let Err(e) = send_signal(pid as i32, sig) {
11341 eprintln!("kill: {}", e);
11342 status = 1;
11343 }
11344 }
11345 }
11346 status
11347 }
11348
11349 fn builtin_disown(&mut self, args: &[String]) -> i32 {
11350 if args.is_empty() {
11351 if let Some(job) = self.jobs.current() {
11353 let id = job.id;
11354 self.jobs.remove(id);
11355 }
11356 return 0;
11357 }
11358
11359 for arg in args {
11360 let s = arg.trim_start_matches('%');
11361 if let Ok(id) = s.parse::<usize>() {
11362 self.jobs.remove(id);
11363 } else {
11364 eprintln!("disown: {}: no such job", arg);
11365 }
11366 }
11367 0
11368 }
11369
11370 fn builtin_wait(&mut self, args: &[String]) -> i32 {
11371 if args.is_empty() {
11372 let ids: Vec<usize> = self.jobs.list().iter().map(|j| j.id).collect();
11374 for id in ids {
11375 if let Some(mut job) = self.jobs.remove(id) {
11376 if let Some(ref mut child) = job.child {
11377 let _ = wait_for_child(child);
11378 }
11379 }
11380 }
11381 return 0;
11382 }
11383
11384 let mut status = 0;
11385 for arg in args {
11386 if arg.starts_with('%') {
11387 let id: usize = match arg[1..].parse() {
11388 Ok(id) => id,
11389 Err(_) => {
11390 eprintln!("wait: {}: no such job", arg);
11391 status = 127;
11392 continue;
11393 }
11394 };
11395 if let Some(mut job) = self.jobs.remove(id) {
11396 if let Some(ref mut child) = job.child {
11397 match wait_for_child(child) {
11398 Ok(s) => status = s,
11399 Err(e) => {
11400 eprintln!("wait: {}", e);
11401 status = 127;
11402 }
11403 }
11404 }
11405 } else {
11406 eprintln!("wait: {}: no such job", arg);
11407 status = 127;
11408 }
11409 } else {
11410 let pid: u32 = match arg.parse() {
11411 Ok(p) => p,
11412 Err(_) => {
11413 eprintln!("wait: {}: invalid pid", arg);
11414 status = 127;
11415 continue;
11416 }
11417 };
11418 match wait_for_job(pid as i32) {
11419 Ok(s) => status = s,
11420 Err(e) => {
11421 eprintln!("wait: {}", e);
11422 status = 127;
11423 }
11424 }
11425 }
11426 }
11427 status
11428 }
11429
11430 fn builtin_suspend(&self, args: &[String]) -> i32 {
11431 let mut force = false;
11432 for arg in args {
11433 if arg == "-f" {
11434 force = true;
11435 }
11436 }
11437
11438 #[cfg(unix)]
11439 {
11440 use nix::sys::signal::{kill, Signal};
11441 use nix::unistd::getppid;
11442
11443 let ppid = getppid();
11445 if !force && ppid == nix::unistd::Pid::from_raw(1) {
11446 eprintln!("suspend: cannot suspend a login shell");
11447 return 1;
11448 }
11449
11450 let pid = nix::unistd::getpid();
11452 if let Err(e) = kill(pid, Signal::SIGTSTP) {
11453 eprintln!("suspend: {}", e);
11454 return 1;
11455 }
11456 0
11457 }
11458
11459 #[cfg(not(unix))]
11460 {
11461 eprintln!("suspend: not supported on this platform");
11462 1
11463 }
11464 }
11465}
11466
11467impl Default for ShellExecutor {
11468 fn default() -> Self {
11469 Self::new()
11470 }
11471}
11472
11473#[cfg(test)]
11474mod tests {
11475 use super::*;
11476
11477 #[test]
11478 fn test_simple_echo() {
11479 let mut exec = ShellExecutor::new();
11480 let status = exec.execute_script("true").unwrap();
11481 assert_eq!(status, 0);
11482 }
11483
11484 #[test]
11485 fn test_if_true() {
11486 let mut exec = ShellExecutor::new();
11487 let status = exec.execute_script("if true; then true; fi").unwrap();
11488 assert_eq!(status, 0);
11489 }
11490
11491 #[test]
11492 fn test_if_false() {
11493 let mut exec = ShellExecutor::new();
11494 let status = exec
11495 .execute_script("if false; then true; else false; fi")
11496 .unwrap();
11497 assert_eq!(status, 1);
11498 }
11499
11500 #[test]
11501 fn test_for_loop() {
11502 let mut exec = ShellExecutor::new();
11503 exec.execute_script("for i in a b c; do true; done")
11504 .unwrap();
11505 assert_eq!(exec.last_status, 0);
11506 }
11507
11508 #[test]
11509 fn test_and_list() {
11510 let mut exec = ShellExecutor::new();
11511 let status = exec.execute_script("true && true").unwrap();
11512 assert_eq!(status, 0);
11513
11514 let status = exec.execute_script("true && false").unwrap();
11515 assert_eq!(status, 1);
11516 }
11517
11518 #[test]
11519 fn test_or_list() {
11520 let mut exec = ShellExecutor::new();
11521 let status = exec.execute_script("false || true").unwrap();
11522 assert_eq!(status, 0);
11523 }
11524}
11525
11526impl ShellExecutor {
11527 fn builtin_history(&self, args: &[String]) -> i32 {
11528 let Some(ref engine) = self.history else {
11529 eprintln!("history: history engine not available");
11530 return 1;
11531 };
11532
11533 let mut count = 20usize;
11535 let mut show_all = false;
11536 let mut search_query = None;
11537
11538 let mut i = 0;
11539 while i < args.len() {
11540 match args[i].as_str() {
11541 "-c" | "--clear" => {
11542 eprintln!("history: clear not supported in this mode");
11544 return 1;
11545 }
11546 "-a" | "--all" => show_all = true,
11547 "-n" => {
11548 if i + 1 < args.len() {
11549 i += 1;
11550 count = args[i].parse().unwrap_or(20);
11551 }
11552 }
11553 s if s.starts_with('-') && s[1..].chars().all(|c| c.is_ascii_digit()) => {
11554 count = s[1..].parse().unwrap_or(20);
11555 }
11556 s if s.chars().all(|c| c.is_ascii_digit()) => {
11557 count = s.parse().unwrap_or(20);
11558 }
11559 s => {
11560 search_query = Some(s.to_string());
11561 }
11562 }
11563 i += 1;
11564 }
11565
11566 if show_all {
11567 count = 10000;
11568 }
11569
11570 let entries = if let Some(ref q) = search_query {
11571 engine.search(q, count)
11572 } else {
11573 engine.recent(count)
11574 };
11575
11576 match entries {
11577 Ok(entries) => {
11578 for entry in entries.into_iter().rev() {
11580 println!("{:>6} {}", entry.id, entry.command);
11581 }
11582 0
11583 }
11584 Err(e) => {
11585 eprintln!("history: {}", e);
11586 1
11587 }
11588 }
11589 }
11590
11591 fn builtin_fc(&mut self, args: &[String]) -> i32 {
11597 let Some(ref engine) = self.history else {
11598 eprintln!("fc: history engine not available");
11599 return 1;
11600 };
11601
11602 let mut list_mode = false;
11604 let mut no_numbers = false;
11605 let mut reverse = false;
11606 let mut show_time = false;
11607 let mut show_duration = false;
11608 let mut editor: Option<String> = None;
11609 let mut read_file = false;
11610 let mut write_file = false;
11611 let mut append_file = false;
11612 let mut substitute_mode = false;
11613 let mut positional: Vec<&str> = Vec::new();
11614 let mut substitutions: Vec<(String, String)> = Vec::new();
11615
11616 let mut i = 0;
11617 while i < args.len() {
11618 let arg = &args[i];
11619 if arg == "--" {
11620 i += 1;
11621 while i < args.len() {
11622 positional.push(&args[i]);
11623 i += 1;
11624 }
11625 break;
11626 }
11627 if arg.starts_with('-') && arg.len() > 1 {
11628 let chars: Vec<char> = arg[1..].chars().collect();
11629 let mut j = 0;
11630 while j < chars.len() {
11631 match chars[j] {
11632 'l' => list_mode = true,
11633 'n' => no_numbers = true,
11634 'r' => reverse = true,
11635 'd' | 'f' | 'E' | 'i' => show_time = true,
11636 'D' => show_duration = true,
11637 'R' => read_file = true,
11638 'W' => write_file = true,
11639 'A' => append_file = true,
11640 's' => substitute_mode = true,
11641 'e' => {
11642 if j + 1 < chars.len() {
11643 editor = Some(chars[j + 1..].iter().collect());
11644 break;
11645 } else {
11646 i += 1;
11647 if i < args.len() {
11648 editor = Some(args[i].clone());
11649 }
11650 }
11651 }
11652 't' => {
11653 show_time = true;
11654 if j + 1 < chars.len() {
11655 break;
11656 } else {
11657 i += 1;
11658 }
11659 }
11660 'p' | 'P' | 'a' | 'I' | 'L' | 'm' => {} _ => {
11662 if chars[j].is_ascii_digit() {
11663 positional.push(arg);
11664 break;
11665 }
11666 }
11667 }
11668 j += 1;
11669 }
11670 } else if arg.contains('=') && !list_mode {
11671 if let Some((old, new)) = arg.split_once('=') {
11672 substitutions.push((old.to_string(), new.to_string()));
11673 }
11674 } else {
11675 positional.push(arg);
11676 }
11677 i += 1;
11678 }
11679
11680 if read_file || write_file || append_file {
11683 let filename = positional.first().map(|s| *s).unwrap_or("~/.zsh_history");
11684 let path = if filename.starts_with("~/") {
11685 dirs::home_dir()
11686 .map(|h| h.join(&filename[2..]))
11687 .unwrap_or_else(|| std::path::PathBuf::from(filename))
11688 } else {
11689 std::path::PathBuf::from(filename)
11690 };
11691
11692 if read_file {
11693 if let Ok(contents) = std::fs::read_to_string(&path) {
11695 for line in contents.lines() {
11696 if !line.is_empty() && !line.starts_with('#') && !line.starts_with(':') {
11697 let _ = engine.add(line, None);
11698 }
11699 }
11700 } else {
11701 eprintln!("fc: cannot read {}", path.display());
11702 return 1;
11703 }
11704 } else if write_file || append_file {
11705 let mode = if append_file {
11707 std::fs::OpenOptions::new()
11708 .create(true)
11709 .append(true)
11710 .open(&path)
11711 } else {
11712 std::fs::File::create(&path)
11713 };
11714 match mode {
11715 Ok(mut file) => {
11716 use std::io::Write;
11717 if let Ok(entries) = engine.recent(10000) {
11718 for entry in entries.iter().rev() {
11719 let _ = writeln!(file, ": {}:0;{}", entry.timestamp, entry.command);
11720 }
11721 }
11722 }
11723 Err(e) => {
11724 eprintln!("fc: cannot write {}: {}", path.display(), e);
11725 return 1;
11726 }
11727 }
11728 }
11729 return 0;
11730 }
11731
11732 if list_mode || args.is_empty() {
11734 let (first, last) = match positional.len() {
11735 0 => (-16i64, -1i64),
11736 1 => {
11737 let n = positional[0].parse::<i64>().unwrap_or(-16);
11738 (n, -1)
11739 }
11740 _ => {
11741 let f = positional[0].parse::<i64>().unwrap_or(-16);
11742 let l = positional[1].parse::<i64>().unwrap_or(-1);
11743 (f, l)
11744 }
11745 };
11746
11747 let count = if first < 0 { (-first) as usize } else { 16 };
11748 match engine.recent(count.max(100)) {
11749 Ok(mut entries) => {
11750 if reverse {
11751 entries.reverse();
11752 }
11753 for entry in entries.iter().rev().take(count) {
11754 if no_numbers {
11755 println!("{}", entry.command);
11756 } else if show_time {
11757 println!(
11758 "{:>6} {:>10} {}",
11759 entry.id, entry.timestamp, entry.command
11760 );
11761 } else if show_duration {
11762 println!(
11763 "{:>6} {:>5} {}",
11764 entry.id,
11765 entry.duration_ms.unwrap_or(0),
11766 entry.command
11767 );
11768 } else {
11769 println!("{:>6} {}", entry.id, entry.command);
11770 }
11771 }
11772 0
11773 }
11774 Err(e) => {
11775 eprintln!("fc: {}", e);
11776 1
11777 }
11778 }
11779 } else if substitute_mode || !substitutions.is_empty() {
11780 match engine.get_by_offset(0) {
11782 Ok(Some(entry)) => {
11783 let mut cmd = entry.command.clone();
11784 for (old, new) in &substitutions {
11785 cmd = cmd.replace(old, new);
11786 }
11787 println!("{}", cmd);
11788 self.execute_script(&cmd).unwrap_or(1)
11789 }
11790 Ok(None) => {
11791 eprintln!("fc: no command to re-execute");
11792 1
11793 }
11794 Err(e) => {
11795 eprintln!("fc: {}", e);
11796 1
11797 }
11798 }
11799 } else if editor.as_deref() == Some("-") {
11800 match engine.get_by_offset(0) {
11802 Ok(Some(entry)) => {
11803 println!("{}", entry.command);
11804 self.execute_script(&entry.command).unwrap_or(1)
11805 }
11806 Ok(None) => {
11807 eprintln!("fc: no command to re-execute");
11808 1
11809 }
11810 Err(e) => {
11811 eprintln!("fc: {}", e);
11812 1
11813 }
11814 }
11815 } else if let Some(arg) = positional.first() {
11816 if arg.starts_with('-') || arg.starts_with('+') {
11817 let n: usize = arg[1..].parse().unwrap_or(1);
11819 let offset = if arg.starts_with('-') { n - 1 } else { n };
11820 match engine.get_by_offset(offset) {
11821 Ok(Some(entry)) => {
11822 println!("{}", entry.command);
11823 self.execute_script(&entry.command).unwrap_or(1)
11824 }
11825 Ok(None) => {
11826 eprintln!("fc: event not found");
11827 1
11828 }
11829 Err(e) => {
11830 eprintln!("fc: {}", e);
11831 1
11832 }
11833 }
11834 } else {
11835 match engine.search_prefix(arg, 1) {
11837 Ok(entries) if !entries.is_empty() => {
11838 println!("{}", entries[0].command);
11839 self.execute_script(&entries[0].command).unwrap_or(1)
11840 }
11841 Ok(_) => {
11842 eprintln!("fc: event not found: {}", arg);
11843 1
11844 }
11845 Err(e) => {
11846 eprintln!("fc: {}", e);
11847 1
11848 }
11849 }
11850 }
11851 } else {
11852 match engine.get_by_offset(0) {
11854 Ok(Some(entry)) => {
11855 println!("{}", entry.command);
11856 self.execute_script(&entry.command).unwrap_or(1)
11857 }
11858 Ok(None) => {
11859 eprintln!("fc: no command to re-execute");
11860 1
11861 }
11862 Err(e) => {
11863 eprintln!("fc: {}", e);
11864 1
11865 }
11866 }
11867 }
11868 }
11869
11870 fn builtin_trap(&mut self, args: &[String]) -> i32 {
11871 if args.is_empty() {
11872 for (sig, action) in &self.traps {
11874 println!("trap -- '{}' {}", action, sig);
11875 }
11876 return 0;
11877 }
11878
11879 if args.len() == 1 && args[0] == "-l" {
11881 let signals = [
11882 "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
11883 "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN",
11884 "TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS",
11885 ];
11886 for (i, sig) in signals.iter().enumerate() {
11887 print!("{:2}) SIG{:<8}", i + 1, sig);
11888 if (i + 1) % 5 == 0 {
11889 println!();
11890 }
11891 }
11892 println!();
11893 return 0;
11894 }
11895
11896 if args.len() >= 1 && args[0] == "-p" {
11898 let signals = if args.len() > 1 {
11899 &args[1..]
11900 } else {
11901 &[] as &[String]
11902 };
11903 if signals.is_empty() {
11904 for (sig, action) in &self.traps {
11905 println!("trap -- '{}' {}", action, sig);
11906 }
11907 } else {
11908 for sig in signals {
11909 if let Some(action) = self.traps.get(sig) {
11910 println!("trap -- '{}' {}", action, sig);
11911 }
11912 }
11913 }
11914 return 0;
11915 }
11916
11917 if args.len() == 1 {
11921 let sig = &args[0];
11923 if let Some(action) = self.traps.get(sig) {
11924 println!("trap -- '{}' {}", action, sig);
11925 }
11926 return 0;
11927 }
11928
11929 let action = &args[0];
11930 let signals = &args[1..];
11931
11932 for sig in signals {
11933 let sig_upper = sig.to_uppercase();
11934 let sig_name = if sig_upper.starts_with("SIG") {
11935 sig_upper[3..].to_string()
11936 } else {
11937 sig_upper.clone()
11938 };
11939
11940 if action.is_empty() || action == "-" {
11941 self.traps.remove(&sig_name);
11943 } else {
11944 self.traps.insert(sig_name, action.clone());
11945 }
11946 }
11947
11948 0
11949 }
11950
11951 pub fn run_trap(&mut self, signal: &str) {
11953 if let Some(action) = self.traps.get(signal).cloned() {
11954 let _ = self.execute_script(&action);
11955 }
11956 }
11957
11958 fn builtin_alias(&mut self, args: &[String]) -> i32 {
11959 let mut is_global = false;
11968 let mut is_suffix = false;
11969 let mut list_form = false;
11970 let mut pattern_match = false;
11971 let mut print_global = false;
11972 let mut print_suffix = false;
11973 let mut print_regular = false;
11974 let mut positional_args = Vec::new();
11975
11976 let mut i = 0;
11977 while i < args.len() {
11978 let arg = &args[i];
11979 if arg.starts_with('+') && arg.len() > 1 {
11980 for ch in arg[1..].chars() {
11982 match ch {
11983 'g' => print_global = true,
11984 's' => print_suffix = true,
11985 'r' => print_regular = true,
11986 'L' => list_form = true,
11987 'm' => pattern_match = true,
11988 _ => {}
11989 }
11990 }
11991 } else if arg.starts_with('-') && arg != "-" {
11992 for ch in arg[1..].chars() {
11993 match ch {
11994 'g' => is_global = true,
11995 's' => is_suffix = true,
11996 'L' => list_form = true,
11997 'm' => pattern_match = true,
11998 'r' => {} _ => {
12000 eprintln!("zshrs: alias: bad option: -{}", ch);
12001 return 1;
12002 }
12003 }
12004 }
12005 } else {
12006 positional_args.push(arg.clone());
12007 }
12008 i += 1;
12009 }
12010
12011 if print_global || print_suffix || print_regular {
12013 if print_regular {
12014 for (name, value) in &self.aliases {
12015 if list_form {
12016 println!("alias {}='{}'", name, value);
12017 } else {
12018 println!("{}='{}'", name, value);
12019 }
12020 }
12021 }
12022 if print_global {
12023 for (name, value) in &self.global_aliases {
12024 if list_form {
12025 println!("alias -g {}='{}'", name, value);
12026 } else {
12027 println!("{}='{}'", name, value);
12028 }
12029 }
12030 }
12031 if print_suffix {
12032 for (name, value) in &self.suffix_aliases {
12033 if list_form {
12034 println!("alias -s {}='{}'", name, value);
12035 } else {
12036 println!("{}='{}'", name, value);
12037 }
12038 }
12039 }
12040 return 0;
12041 }
12042
12043 if positional_args.is_empty() {
12044 let prefix = if is_suffix {
12046 "alias -s "
12047 } else if is_global {
12048 "alias -g "
12049 } else {
12050 "alias "
12051 };
12052 let alias_map: Vec<(String, String)> = if is_suffix {
12053 self.suffix_aliases
12054 .iter()
12055 .map(|(k, v)| (k.clone(), v.clone()))
12056 .collect()
12057 } else if is_global {
12058 self.global_aliases
12059 .iter()
12060 .map(|(k, v)| (k.clone(), v.clone()))
12061 .collect()
12062 } else {
12063 self.aliases
12064 .iter()
12065 .map(|(k, v)| (k.clone(), v.clone()))
12066 .collect()
12067 };
12068 for (name, value) in alias_map {
12069 if list_form {
12070 println!("{}{}='{}'", prefix, name, value);
12071 } else {
12072 println!("{}='{}'", name, value);
12073 }
12074 }
12075 return 0;
12076 }
12077
12078 for arg in &positional_args {
12079 if let Some(eq_pos) = arg.find('=') {
12080 let name = &arg[..eq_pos];
12082 let value = &arg[eq_pos + 1..];
12083 if is_suffix {
12084 self.suffix_aliases
12085 .insert(name.to_string(), value.to_string());
12086 } else if is_global {
12087 self.global_aliases
12088 .insert(name.to_string(), value.to_string());
12089 } else {
12090 self.aliases.insert(name.to_string(), value.to_string());
12091 }
12092 } else if pattern_match {
12093 let pattern = arg.replace("*", ".*").replace("?", ".");
12095 let re = regex::Regex::new(&format!("^{}$", pattern));
12096
12097 let alias_map: &HashMap<String, String> = if is_suffix {
12098 &self.suffix_aliases
12099 } else if is_global {
12100 &self.global_aliases
12101 } else {
12102 &self.aliases
12103 };
12104
12105 let prefix = if is_suffix {
12106 "alias -s "
12107 } else if is_global {
12108 "alias -g "
12109 } else {
12110 "alias "
12111 };
12112
12113 for (name, value) in alias_map {
12114 let matches = if let Ok(ref r) = re {
12115 r.is_match(name)
12116 } else {
12117 name.contains(arg.as_str())
12118 };
12119 if matches {
12120 if list_form {
12121 println!("{}{}='{}'", prefix, name, value);
12122 } else {
12123 println!("{}='{}'", name, value);
12124 }
12125 }
12126 }
12127 } else {
12128 let value = if is_suffix {
12130 self.suffix_aliases.get(arg.as_str()).cloned()
12131 } else if is_global {
12132 self.global_aliases.get(arg.as_str()).cloned()
12133 } else {
12134 self.aliases.get(arg.as_str()).cloned()
12135 };
12136 if let Some(v) = value {
12137 println!("{}='{}'", arg, v);
12138 } else {
12139 eprintln!("zshrs: alias: {}: not found", arg);
12140 return 1;
12141 }
12142 }
12143 }
12144 0
12145 }
12146
12147 fn builtin_unalias(&mut self, args: &[String]) -> i32 {
12148 if args.is_empty() {
12149 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
12150 return 1;
12151 }
12152
12153 let mut is_global = false;
12154 let mut is_suffix = false;
12155 let mut remove_all = false;
12156 let mut positional_args = Vec::new();
12157
12158 for arg in args {
12159 if arg.starts_with('-') && arg != "-" {
12160 for ch in arg[1..].chars() {
12161 match ch {
12162 'a' => remove_all = true,
12163 'g' => is_global = true,
12164 's' => is_suffix = true,
12165 'm' => {} _ => {
12167 eprintln!("zshrs: unalias: bad option: -{}", ch);
12168 return 1;
12169 }
12170 }
12171 }
12172 } else {
12173 positional_args.push(arg.clone());
12174 }
12175 }
12176
12177 if remove_all {
12178 if is_suffix {
12179 self.suffix_aliases.clear();
12180 } else if is_global {
12181 self.global_aliases.clear();
12182 } else {
12183 self.aliases.clear();
12185 self.global_aliases.clear();
12186 self.suffix_aliases.clear();
12187 }
12188 return 0;
12189 }
12190
12191 if positional_args.is_empty() {
12192 eprintln!("zshrs: unalias: usage: unalias [-agsm] name [name ...]");
12193 return 1;
12194 }
12195
12196 for name in positional_args {
12197 let removed = if is_suffix {
12198 self.suffix_aliases.remove(&name).is_some()
12199 } else if is_global {
12200 self.global_aliases.remove(&name).is_some()
12201 } else {
12202 self.aliases.remove(&name).is_some()
12203 };
12204 if !removed {
12205 eprintln!("zshrs: unalias: {}: not found", name);
12206 return 1;
12207 }
12208 }
12209 0
12210 }
12211
12212 fn builtin_set(&mut self, args: &[String]) -> i32 {
12213 if args.is_empty() {
12214 let mut vars: Vec<_> = self.variables.iter().collect();
12216 vars.sort_by_key(|(k, _)| *k);
12217 for (k, v) in vars {
12218 println!("{}={}", k, shell_quote(v));
12219 }
12220 let mut arrs: Vec<_> = self.arrays.iter().collect();
12222 arrs.sort_by_key(|(k, _)| *k);
12223 for (k, v) in arrs {
12224 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
12225 println!("{}=( {} )", k, quoted.join(" "));
12226 }
12227 return 0;
12228 }
12229
12230 if args.len() == 1 && args[0] == "+" {
12232 let mut names: Vec<_> = self.variables.keys().collect();
12233 names.extend(self.arrays.keys());
12234 names.sort();
12235 names.dedup();
12236 for name in names {
12237 println!("{}", name);
12238 }
12239 return 0;
12240 }
12241
12242 let mut iter = args.iter().peekable();
12243 let mut set_array: Option<bool> = None; let mut array_name: Option<String> = None;
12245 let mut sort_asc = false;
12246 let mut sort_desc = false;
12247
12248 while let Some(arg) = iter.next() {
12249 match arg.as_str() {
12250 "-o" => {
12251 if iter.peek().is_none()
12253 || iter
12254 .peek()
12255 .map(|s| s.starts_with('-') || s.starts_with('+'))
12256 .unwrap_or(false)
12257 {
12258 self.print_options_table();
12259 continue;
12260 }
12261 if let Some(opt) = iter.next() {
12262 let (name, enable) = Self::normalize_option_name(opt);
12263 self.options.insert(name, enable);
12264 }
12265 }
12266 "+o" => {
12267 if iter.peek().is_none()
12269 || iter
12270 .peek()
12271 .map(|s| s.starts_with('-') || s.starts_with('+'))
12272 .unwrap_or(false)
12273 {
12274 self.print_options_reentrant();
12275 continue;
12276 }
12277 if let Some(opt) = iter.next() {
12278 let (name, enable) = Self::normalize_option_name(opt);
12279 self.options.insert(name, !enable);
12280 }
12281 }
12282 "-A" => {
12283 set_array = Some(true);
12284 if let Some(name) = iter.next() {
12285 if !name.starts_with('-') && !name.starts_with('+') {
12286 array_name = Some(name.clone());
12287 }
12288 }
12289 if array_name.is_none() {
12290 let mut arrs: Vec<_> = self.arrays.iter().collect();
12292 arrs.sort_by_key(|(k, _)| *k);
12293 for (k, v) in arrs {
12294 let quoted: Vec<String> = v.iter().map(|s| shell_quote(s)).collect();
12295 println!("{}=( {} )", k, quoted.join(" "));
12296 }
12297 return 0;
12298 }
12299 }
12300 "+A" => {
12301 set_array = Some(false);
12302 if let Some(name) = iter.next() {
12303 if !name.starts_with('-') && !name.starts_with('+') {
12304 array_name = Some(name.clone());
12305 }
12306 }
12307 if array_name.is_none() {
12308 let mut names: Vec<_> = self.arrays.keys().collect();
12310 names.sort();
12311 for name in names {
12312 println!("{}", name);
12313 }
12314 return 0;
12315 }
12316 }
12317 "-s" => sort_asc = true,
12318 "+s" => sort_desc = true,
12319 "-e" => {
12320 self.options.insert("errexit".to_string(), true);
12321 }
12322 "+e" => {
12323 self.options.insert("errexit".to_string(), false);
12324 }
12325 "-x" => {
12326 self.options.insert("xtrace".to_string(), true);
12327 }
12328 "+x" => {
12329 self.options.insert("xtrace".to_string(), false);
12330 }
12331 "-u" => {
12332 self.options.insert("nounset".to_string(), true);
12333 }
12334 "+u" => {
12335 self.options.insert("nounset".to_string(), false);
12336 }
12337 "-v" => {
12338 self.options.insert("verbose".to_string(), true);
12339 }
12340 "+v" => {
12341 self.options.insert("verbose".to_string(), false);
12342 }
12343 "-n" => {
12344 self.options.insert("exec".to_string(), false);
12345 }
12346 "+n" => {
12347 self.options.insert("exec".to_string(), true);
12348 }
12349 "-f" => {
12350 self.options.insert("glob".to_string(), false);
12351 }
12352 "+f" => {
12353 self.options.insert("glob".to_string(), true);
12354 }
12355 "-m" => {
12356 self.options.insert("monitor".to_string(), true);
12357 }
12358 "+m" => {
12359 self.options.insert("monitor".to_string(), false);
12360 }
12361 "-C" => {
12362 self.options.insert("clobber".to_string(), false);
12363 }
12364 "+C" => {
12365 self.options.insert("clobber".to_string(), true);
12366 }
12367 "-b" => {
12368 self.options.insert("notify".to_string(), true);
12369 }
12370 "+b" => {
12371 self.options.insert("notify".to_string(), false);
12372 }
12373 "--" => {
12374 let remaining: Vec<String> = iter.cloned().collect();
12375 if let Some(ref name) = array_name {
12376 let mut values = remaining;
12377 if sort_asc {
12378 values.sort();
12379 } else if sort_desc {
12380 values.sort();
12381 values.reverse();
12382 }
12383 if set_array == Some(true) {
12384 self.arrays.insert(name.clone(), values);
12385 } else {
12386 let arr = self.arrays.entry(name.clone()).or_default();
12388 for (i, v) in values.into_iter().enumerate() {
12389 if i < arr.len() {
12390 arr[i] = v;
12391 } else {
12392 arr.push(v);
12393 }
12394 }
12395 }
12396 } else if remaining.is_empty() {
12397 self.positional_params.clear();
12399 } else {
12400 let mut values = remaining;
12401 if sort_asc {
12402 values.sort();
12403 } else if sort_desc {
12404 values.sort();
12405 values.reverse();
12406 }
12407 self.positional_params = values;
12408 }
12409 return 0;
12410 }
12411 _ => {
12412 if arg.starts_with('-') && arg.len() > 1 {
12414 for c in arg[1..].chars() {
12415 match c {
12416 'e' => {
12417 self.options.insert("errexit".to_string(), true);
12418 }
12419 'x' => {
12420 self.options.insert("xtrace".to_string(), true);
12421 }
12422 'u' => {
12423 self.options.insert("nounset".to_string(), true);
12424 }
12425 'v' => {
12426 self.options.insert("verbose".to_string(), true);
12427 }
12428 'n' => {
12429 self.options.insert("exec".to_string(), false);
12430 }
12431 'f' => {
12432 self.options.insert("glob".to_string(), false);
12433 }
12434 'm' => {
12435 self.options.insert("monitor".to_string(), true);
12436 }
12437 'C' => {
12438 self.options.insert("clobber".to_string(), false);
12439 }
12440 'b' => {
12441 self.options.insert("notify".to_string(), true);
12442 }
12443 _ => {
12444 eprintln!("zshrs: set: -{}: invalid option", c);
12445 return 1;
12446 }
12447 }
12448 }
12449 continue;
12450 }
12451 if arg.starts_with('+') && arg.len() > 1 {
12452 for c in arg[1..].chars() {
12453 match c {
12454 'e' => {
12455 self.options.insert("errexit".to_string(), false);
12456 }
12457 'x' => {
12458 self.options.insert("xtrace".to_string(), false);
12459 }
12460 'u' => {
12461 self.options.insert("nounset".to_string(), false);
12462 }
12463 'v' => {
12464 self.options.insert("verbose".to_string(), false);
12465 }
12466 'n' => {
12467 self.options.insert("exec".to_string(), true);
12468 }
12469 'f' => {
12470 self.options.insert("glob".to_string(), true);
12471 }
12472 'm' => {
12473 self.options.insert("monitor".to_string(), false);
12474 }
12475 'C' => {
12476 self.options.insert("clobber".to_string(), true);
12477 }
12478 'b' => {
12479 self.options.insert("notify".to_string(), false);
12480 }
12481 _ => {
12482 eprintln!("zshrs: set: +{}: invalid option", c);
12483 return 1;
12484 }
12485 }
12486 }
12487 continue;
12488 }
12489 let mut values: Vec<String> =
12491 std::iter::once(arg.clone()).chain(iter.cloned()).collect();
12492 if sort_asc {
12493 values.sort();
12494 } else if sort_desc {
12495 values.sort();
12496 values.reverse();
12497 }
12498 if let Some(ref name) = array_name {
12499 if set_array == Some(true) {
12500 self.arrays.insert(name.clone(), values);
12501 } else {
12502 let arr = self.arrays.entry(name.clone()).or_default();
12503 for (i, v) in values.into_iter().enumerate() {
12504 if i < arr.len() {
12505 arr[i] = v;
12506 } else {
12507 arr.push(v);
12508 }
12509 }
12510 }
12511 } else {
12512 self.positional_params = values;
12513 }
12514 return 0;
12515 }
12516 }
12517 }
12518 0
12519 }
12520
12521 fn default_on_options() -> &'static [&'static str] {
12522 &[
12523 "aliases",
12524 "alwayslastprompt",
12525 "appendhistory",
12526 "autolist",
12527 "automenu",
12528 "autoparamkeys",
12529 "autoparamslash",
12530 "autoremoveslash",
12531 "badpattern",
12532 "banghist",
12533 "bareglobqual",
12534 "beep",
12535 "bgnice",
12536 "caseglob",
12537 "casematch",
12538 "checkjobs",
12539 "checkrunningjobs",
12540 "clobber",
12541 "debugbeforecmd",
12542 "equals",
12543 "evallineno",
12544 "exec",
12545 "flowcontrol",
12546 "functionargzero",
12547 "glob",
12548 "globalexport",
12549 "globalrcs",
12550 "hashcmds",
12551 "hashdirs",
12552 "hashlistall",
12553 "histbeep",
12554 "histsavebycopy",
12555 "hup",
12556 "interactive",
12557 "listambiguous",
12558 "listbeep",
12559 "listtypes",
12560 "monitor",
12561 "multibyte",
12562 "multifuncdef",
12563 "multios",
12564 "nomatch",
12565 "notify",
12566 "promptcr",
12567 "promptpercent",
12568 "promptsp",
12569 "rcs",
12570 "shinstdin",
12571 "shortloops",
12572 "unset",
12573 "zle",
12574 ]
12575 }
12576
12577 fn print_options_table(&self) {
12578 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
12579 opts.sort();
12580 let defaults_on = Self::default_on_options();
12581 for &opt in &opts {
12582 let enabled = self.options.get(opt).copied().unwrap_or(false);
12583 let is_default_on = defaults_on.contains(&opt);
12584 let (display_name, display_state) = if is_default_on {
12587 (format!("no{}", opt), if enabled { "off" } else { "on" })
12588 } else {
12589 (opt.to_string(), if enabled { "on" } else { "off" })
12590 };
12591 println!("{:<22}{}", display_name, display_state);
12592 }
12593 }
12594
12595 fn print_options_reentrant(&self) {
12596 let mut opts: Vec<_> = Self::all_zsh_options().to_vec();
12597 opts.sort();
12598 let defaults_on = Self::default_on_options();
12599 for &opt in &opts {
12600 let enabled = self.options.get(opt).copied().unwrap_or(false);
12601 let is_default_on = defaults_on.contains(&opt);
12602 let (display_name, use_minus) = if is_default_on {
12604 (format!("no{}", opt), !enabled)
12605 } else {
12606 (opt.to_string(), enabled)
12607 };
12608 if use_minus {
12609 println!("set -o {}", display_name);
12610 } else {
12611 println!("set +o {}", display_name);
12612 }
12613 }
12614 }
12615
12616 fn builtin_caller(&self, args: &[String]) -> i32 {
12618 let depth: usize = args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
12619 if depth == 0 {
12622 println!("1 main");
12623 } else {
12624 println!("{} main", depth);
12625 }
12626 0
12627 }
12628
12629 fn builtin_doctor(&self, _args: &[String]) -> i32 {
12631 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
12632 let red = |s: &str| format!("\x1b[31m{}\x1b[0m", s);
12633 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
12634 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12635 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
12636
12637 println!("{}", bold("zshrs doctor"));
12638 println!("{}", dim(&"=".repeat(60)));
12639 println!();
12640
12641 println!("{}", bold("Environment"));
12643 println!(" version: zshrs {}", env!("CARGO_PKG_VERSION"));
12644 println!(" pid: {}", std::process::id());
12645 let cwd = env::current_dir()
12646 .map(|p| p.to_string_lossy().to_string())
12647 .unwrap_or_else(|_| "?".to_string());
12648 println!(" cwd: {}", cwd);
12649 println!(
12650 " shell: {}",
12651 env::var("SHELL").unwrap_or_else(|_| "?".to_string())
12652 );
12653 println!(" pool size: {}", self.worker_pool.size());
12654 println!(
12655 " pool done: {} tasks completed",
12656 self.worker_pool.completed()
12657 );
12658 println!(" pool queue: {} pending", self.worker_pool.queue_depth());
12659 println!();
12660
12661 println!("{}", bold("Config"));
12663 let config_path = crate::config::config_path();
12664 if config_path.exists() {
12665 println!(" {} {}", green("*"), config_path.display());
12666 } else {
12667 println!(
12668 " {} {} {}",
12669 dim("-"),
12670 config_path.display(),
12671 dim("(using defaults)")
12672 );
12673 }
12674 println!();
12675
12676 println!("{}", bold("PATH"));
12678 let path_var = env::var("PATH").unwrap_or_default();
12679 let path_dirs: Vec<&str> = path_var.split(':').filter(|s| !s.is_empty()).collect();
12680 let path_ok = path_dirs
12681 .iter()
12682 .filter(|d| std::path::Path::new(d).is_dir())
12683 .count();
12684 let path_missing = path_dirs.len() - path_ok;
12685 println!(
12686 " directories: {} total, {} {}, {} {}",
12687 path_dirs.len(),
12688 path_ok,
12689 green("valid"),
12690 path_missing,
12691 if path_missing > 0 {
12692 red("missing")
12693 } else {
12694 green("missing")
12695 },
12696 );
12697 println!(" hash table: {} entries", self.command_hash.len());
12698 println!();
12699
12700 println!("{}", bold("FPATH"));
12702 println!(" directories: {}", self.fpath.len());
12703 let fpath_ok = self.fpath.iter().filter(|d| d.is_dir()).count();
12704 let fpath_missing = self.fpath.len() - fpath_ok;
12705 if fpath_missing > 0 {
12706 println!(" {} {} missing fpath directories", red("!"), fpath_missing);
12707 }
12708 println!(" functions: {} loaded", self.functions.len());
12709 println!(" autoload: {} pending", self.autoload_pending.len());
12710 println!();
12711
12712 println!("{}", bold("SQLite Caches"));
12714 if let Some(ref engine) = self.history {
12715 let count = engine.count().unwrap_or(0);
12716 println!(" history: {} entries {}", count, green("OK"));
12717 } else {
12718 println!(" history: {}", yellow("not initialized"));
12719 }
12720
12721 if let Some(ref cache) = self.compsys_cache {
12722 let count = compsys::cache_entry_count(cache);
12723 println!(" compsys: {} completions {}", count, green("OK"));
12724
12725 if let Ok(missing) = cache.get_autoloads_missing_bytecode() {
12727 if missing.is_empty() {
12728 println!(
12729 " bytecode cache: {}",
12730 green("all functions compiled to bytecode")
12731 );
12732 } else {
12733 println!(
12734 " bytecode cache: {} functions {}",
12735 missing.len(),
12736 yellow("missing bytecode blobs")
12737 );
12738 }
12739 }
12740 } else {
12741 println!(" compsys: {}", yellow("no cache"));
12742 }
12743
12744 if let Some(ref cache) = self.plugin_cache {
12745 let (plugins, functions) = cache.stats();
12746 println!(
12747 " plugins: {} plugins, {} cached functions {}",
12748 plugins,
12749 functions,
12750 green("OK")
12751 );
12752 } else {
12753 println!(" plugins: {}", yellow("no cache"));
12754 }
12755 println!();
12756
12757 println!("{}", bold("Shell State"));
12759 println!(" aliases: {}", self.aliases.len());
12760 println!(" global: {} aliases", self.global_aliases.len());
12761 println!(" suffix: {} aliases", self.suffix_aliases.len());
12762 println!(" variables: {}", self.variables.len());
12763 println!(" arrays: {}", self.arrays.len());
12764 println!(" assoc: {}", self.assoc_arrays.len());
12765 println!(
12766 " options: {} set",
12767 self.options.iter().filter(|(_, v)| **v).count()
12768 );
12769 println!(" traps: {} active", self.traps.len());
12770 println!(
12771 " hooks: {} registered",
12772 self.hook_functions.values().map(|v| v.len()).sum::<usize>()
12773 );
12774 println!();
12775
12776 println!("{}", bold("Log"));
12778 let log_path = crate::log::log_path();
12779 if log_path.exists() {
12780 let size = std::fs::metadata(&log_path).map(|m| m.len()).unwrap_or(0);
12781 println!(" {} {} bytes", log_path.display(), size);
12782 } else {
12783 println!(" {}", dim("no log file yet"));
12784 }
12785 println!();
12786
12787 println!("{}", bold("Profiling"));
12789 println!(
12790 " chrome tracing: {}",
12791 if crate::log::profiling_enabled() {
12792 green("enabled")
12793 } else {
12794 dim("disabled")
12795 }
12796 );
12797 println!(
12798 " flamegraph: {}",
12799 if crate::log::flamegraph_enabled() {
12800 green("enabled")
12801 } else {
12802 dim("disabled")
12803 }
12804 );
12805 println!(
12806 " prometheus: {}",
12807 if crate::log::prometheus_enabled() {
12808 green("enabled")
12809 } else {
12810 dim("disabled")
12811 }
12812 );
12813 println!();
12814
12815 0
12816 }
12817
12818 fn builtin_dbview(&self, args: &[String]) -> i32 {
12831 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
12832 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
12833 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
12834 let green = |s: &str| format!("\x1b[32m{}\x1b[0m", s);
12835 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
12836
12837 if args.is_empty() {
12838 println!("{}", bold("zshrs SQLite caches"));
12840 println!();
12841
12842 if let Some(ref cache) = self.compsys_cache {
12843 println!(" {} {}", bold("compsys.db"), dim("(completion cache)"));
12844 if let Ok(n) = cache.count_table("autoloads") {
12845 let bc_count = cache
12846 .count_table_where("autoloads", "bytecode IS NOT NULL")
12847 .unwrap_or(0);
12848 println!(" autoloads: {:>6} rows ({} compiled)", n, bc_count);
12849 }
12850 if let Ok(n) = cache.count_table("comps") {
12851 println!(" comps: {:>6} rows", n);
12852 }
12853 if let Ok(n) = cache.count_table("services") {
12854 println!(" services: {:>6} rows", n);
12855 }
12856 if let Ok(n) = cache.count_table("patcomps") {
12857 println!(" patcomps: {:>6} rows", n);
12858 }
12859 if let Ok(n) = cache.count_table("executables") {
12860 println!(" executables: {:>6} rows", n);
12861 }
12862 if let Ok(n) = cache.count_table("zstyles") {
12863 println!(" zstyles: {:>6} rows", n);
12864 }
12865 println!();
12866 }
12867
12868 if let Some(ref engine) = self.history {
12869 println!(" {} {}", bold("history.db"), dim("(command history)"));
12870 if let Ok(n) = engine.count() {
12871 println!(" entries: {:>6} rows", n);
12872 }
12873 println!();
12874 }
12875
12876 if let Some(ref cache) = self.plugin_cache {
12877 let (plugins, functions) = cache.stats();
12878 println!(" {} {}", bold("plugins.db"), dim("(plugin source cache)"));
12879 println!(" plugins: {:>6} rows", plugins);
12880 println!(" functions: {:>6} rows", functions);
12881 println!();
12882 }
12883
12884 println!(" Usage: {} <table> [name] [--count]", cyan("dbview"));
12885 return 0;
12886 }
12887
12888 let table = args[0].as_str();
12889 let filter = args.get(1).map(|s| s.as_str());
12890 let count_only = args.iter().any(|a| a == "--count" || a == "-c");
12891
12892 match table {
12893 "autoloads" => {
12894 let Some(ref cache) = self.compsys_cache else {
12895 eprintln!("dbview: no compsys cache");
12896 return 1;
12897 };
12898
12899 if count_only {
12900 let n = cache.count_table("autoloads").unwrap_or(0);
12901 println!("{}", n);
12902 return 0;
12903 }
12904
12905 if let Some(name) = filter {
12906 match cache.get_autoload(name) {
12908 Ok(Some(stub)) => {
12909 println!("{}", bold(&format!("autoload: {}", name)));
12910 println!(" source: {}", stub.source);
12911 println!(
12912 " body: {} bytes",
12913 stub.body.as_ref().map(|b| b.len()).unwrap_or(0)
12914 );
12915 match cache.get_autoload_bytecode(name) {
12916 Ok(Some(blob)) => {
12917 println!(" bytecode: {} {} bytes", green("YES"), blob.len())
12918 }
12919 _ => println!(" bytecode: {}", yellow("NULL")),
12920 }
12921 if let Some(ref body) = stub.body {
12923 println!(" preview:");
12924 for (i, line) in body.lines().take(10).enumerate() {
12925 println!(" {:>3}: {}", i + 1, dim(line));
12926 }
12927 let total = body.lines().count();
12928 if total > 10 {
12929 println!(" {} ({} more lines)", dim("..."), total - 10);
12930 }
12931 }
12932 }
12933 _ => {
12934 eprintln!("dbview: autoload '{}' not found", name);
12935 return 1;
12936 }
12937 }
12938 return 0;
12939 }
12940
12941 let conn = &cache.conn();
12943 match conn.prepare("SELECT name, source, length(body), length(bytecode) FROM autoloads ORDER BY name LIMIT 200") {
12944 Ok(mut stmt) => {
12945 let rows = stmt.query_map([], |row| {
12946 Ok((
12947 row.get::<_, String>(0)?,
12948 row.get::<_, String>(1)?,
12949 row.get::<_, Option<i64>>(2)?,
12950 row.get::<_, Option<i64>>(3)?,
12951 ))
12952 });
12953 if let Ok(rows) = rows {
12954 println!("{:<40} {:>8} {:>8} {}", bold("NAME"), bold("BODY"), bold("BYTECODE"), bold("SOURCE"));
12955 let mut count = 0;
12956 for row in rows.flatten() {
12957 let (name, source, body_len, ast_len) = row;
12958 let ast_str = match ast_len {
12959 Some(n) => green(&format!("{:>8}", n)),
12960 None => yellow(&format!("{:>8}", "NULL")),
12961 };
12962 let body_str = match body_len {
12963 Some(n) => format!("{:>8}", n),
12964 None => dim("NULL").to_string(),
12965 };
12966 let src_short = if source.len() > 50 {
12968 format!("...{}", &source[source.len() - 47..])
12969 } else {
12970 source
12971 };
12972 println!("{:<40} {} {} {}", name, body_str, ast_str, dim(&src_short));
12973 count += 1;
12974 }
12975 println!("\n{} rows shown (LIMIT 200)", count);
12976 }
12977 }
12978 Err(e) => {
12979 eprintln!("dbview: query failed: {}", e);
12980 return 1;
12981 }
12982 }
12983 }
12984
12985 "comps" => {
12986 let Some(ref cache) = self.compsys_cache else {
12987 eprintln!("dbview: no compsys cache");
12988 return 1;
12989 };
12990 if count_only {
12991 println!("{}", cache.count_table("comps").unwrap_or(0));
12992 return 0;
12993 }
12994 let conn = cache.conn();
12995 let query = if let Some(pat) = filter {
12996 format!("SELECT command, function FROM comps WHERE command LIKE '%{}%' ORDER BY command LIMIT 100", pat)
12997 } else {
12998 "SELECT command, function FROM comps ORDER BY command LIMIT 100".to_string()
12999 };
13000 match conn.prepare(&query) {
13001 Ok(mut stmt) => {
13002 println!("{:<40} {}", bold("COMMAND"), bold("FUNCTION"));
13003 let rows = stmt.query_map([], |row| {
13004 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
13005 });
13006 if let Ok(rows) = rows {
13007 for row in rows.flatten() {
13008 println!("{:<40} {}", row.0, cyan(&row.1));
13009 }
13010 }
13011 }
13012 Err(e) => {
13013 eprintln!("dbview: {}", e);
13014 return 1;
13015 }
13016 }
13017 }
13018
13019 "executables" => {
13020 let Some(ref cache) = self.compsys_cache else {
13021 eprintln!("dbview: no compsys cache");
13022 return 1;
13023 };
13024 if count_only {
13025 println!("{}", cache.count_table("executables").unwrap_or(0));
13026 return 0;
13027 }
13028 let conn = cache.conn();
13029 let query = if let Some(pat) = filter {
13030 format!("SELECT name, path FROM executables WHERE name LIKE '%{}%' ORDER BY name LIMIT 100", pat)
13031 } else {
13032 "SELECT name, path FROM executables ORDER BY name LIMIT 100".to_string()
13033 };
13034 match conn.prepare(&query) {
13035 Ok(mut stmt) => {
13036 println!("{:<30} {}", bold("NAME"), bold("PATH"));
13037 let rows = stmt.query_map([], |row| {
13038 Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
13039 });
13040 if let Ok(rows) = rows {
13041 for row in rows.flatten() {
13042 println!("{:<30} {}", row.0, dim(&row.1));
13043 }
13044 }
13045 }
13046 Err(e) => {
13047 eprintln!("dbview: {}", e);
13048 return 1;
13049 }
13050 }
13051 }
13052
13053 "history" => {
13054 let Some(ref engine) = self.history else {
13055 eprintln!("dbview: no history engine");
13056 return 1;
13057 };
13058 if count_only {
13059 println!("{}", engine.count().unwrap_or(0));
13060 return 0;
13061 }
13062 if let Some(pat) = filter {
13063 if let Ok(entries) = engine.search(pat, 20) {
13064 for e in entries {
13065 println!(
13066 " {} {} {}",
13067 dim(&e.timestamp.to_string()),
13068 cyan(&e.command),
13069 dim(&format!("[{}]", e.exit_code.unwrap_or(0)))
13070 );
13071 }
13072 }
13073 } else if let Ok(entries) = engine.recent(20) {
13074 for e in entries {
13075 println!(
13076 " {} {} {}",
13077 dim(&e.timestamp.to_string()),
13078 cyan(&e.command),
13079 dim(&format!("[{}]", e.exit_code.unwrap_or(0)))
13080 );
13081 }
13082 }
13083 }
13084
13085 "plugins" => {
13086 let Some(ref cache) = self.plugin_cache else {
13087 eprintln!("dbview: no plugin cache");
13088 return 1;
13089 };
13090 let (plugins, functions) = cache.stats();
13091 println!("{} plugins, {} cached functions", plugins, functions);
13092 }
13093
13094 _ => {
13095 eprintln!("dbview: unknown table '{}'. Available: autoloads, comps, executables, history, plugins", table);
13096 return 1;
13097 }
13098 }
13099
13100 0
13101 }
13102
13103 fn builtin_profile(&mut self, args: &[String]) -> i32 {
13116 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
13117 let dim = |s: &str| format!("\x1b[2m{}\x1b[0m", s);
13118 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
13119 let yellow = |s: &str| format!("\x1b[33m{}\x1b[0m", s);
13120
13121 if args.is_empty() {
13122 println!("Usage: profile {{ commands }}");
13123 println!(" profile -s 'script string'");
13124 println!(" profile -f function_name [args...]");
13125 println!(" profile --clear");
13126 println!(" profile --dump");
13127 return 0;
13128 }
13129
13130 if args[0] == "--clear" {
13131 self.profiler = crate::zprof::Profiler::new();
13132 println!("profile data cleared");
13133 return 0;
13134 }
13135
13136 if args[0] == "--dump" {
13137 let (_, output) = crate::zprof::builtin_zprof(
13138 &mut self.profiler,
13139 &crate::zprof::ZprofOptions { clear: false },
13140 );
13141 if !output.is_empty() {
13142 print!("{}", output);
13143 } else {
13144 println!("{}", dim("no profile data"));
13145 }
13146 return 0;
13147 }
13148
13149 let code = if args[0] == "-s" {
13151 if args.len() < 2 {
13153 eprintln!("profile: -s requires a script string");
13154 return 1;
13155 }
13156 args[1..].join(" ")
13157 } else if args[0] == "-f" {
13158 if args.len() < 2 {
13160 eprintln!("profile: -f requires a function name");
13161 return 1;
13162 }
13163 args[1..].join(" ")
13164 } else {
13165 args.join(" ")
13167 };
13168
13169 let was_enabled = self.profiling_enabled;
13171 self.profiling_enabled = true;
13172 self.profiler = crate::zprof::Profiler::new(); let t0 = std::time::Instant::now();
13175 let result = self.execute_script(&code);
13176 let elapsed = t0.elapsed();
13177 let status = match result {
13178 Ok(s) => s,
13179 Err(e) => {
13180 eprintln!("profile: {}", e);
13181 1
13182 }
13183 };
13184
13185 println!();
13187 println!("{}", bold("profile results"));
13188 println!("{}", dim(&"─".repeat(60)));
13189 let dur_str = if elapsed.as_secs() > 0 {
13190 format!("{:.3}s", elapsed.as_secs_f64())
13191 } else if elapsed.as_millis() > 0 {
13192 format!("{:.3}ms", elapsed.as_secs_f64() * 1000.0)
13193 } else {
13194 format!("{:.1}µs", elapsed.as_secs_f64() * 1_000_000.0)
13195 };
13196 println!(" total: {}", cyan(&dur_str));
13197 println!(" status: {}", status);
13198 println!();
13199
13200 let (_, output) = crate::zprof::builtin_zprof(
13202 &mut self.profiler,
13203 &crate::zprof::ZprofOptions { clear: false },
13204 );
13205 if !output.is_empty() {
13206 println!("{}", bold("function breakdown"));
13207 print!("{}", output);
13208 }
13209
13210 println!();
13212 println!(
13213 " {} set ZSHRS_LOG=trace for per-command tracing",
13214 yellow("tip:")
13215 );
13216 println!(
13217 " {} output: {}",
13218 dim("log"),
13219 dim(&crate::log::log_path().display().to_string())
13220 );
13221
13222 self.profiling_enabled = was_enabled;
13223 status
13224 }
13225
13226 fn run_intercepts(
13233 &mut self,
13234 cmd_name: &str,
13235 full_cmd: &str,
13236 args: &[String],
13237 ) -> Option<Result<i32, String>> {
13238 let matching: Vec<Intercept> = self
13240 .intercepts
13241 .iter()
13242 .filter(|i| intercept_matches(&i.pattern, cmd_name, full_cmd))
13243 .cloned()
13244 .collect();
13245
13246 if matching.is_empty() {
13247 return None;
13248 }
13249
13250 self.variables
13252 .insert("INTERCEPT_NAME".to_string(), cmd_name.to_string());
13253 self.variables
13254 .insert("INTERCEPT_ARGS".to_string(), args.join(" "));
13255 self.variables
13256 .insert("INTERCEPT_CMD".to_string(), full_cmd.to_string());
13257
13258 for advice in matching
13260 .iter()
13261 .filter(|i| matches!(i.kind, AdviceKind::Before))
13262 {
13263 let _ = self.execute_advice(&advice.code);
13264 }
13265
13266 let around = matching
13268 .iter()
13269 .find(|i| matches!(i.kind, AdviceKind::Around));
13270
13271 let t0 = std::time::Instant::now();
13272
13273 let result = if let Some(advice) = around {
13274 self.variables
13277 .insert("__intercept_proceed".to_string(), "0".to_string());
13278 let advice_result = self.execute_advice(&advice.code);
13279
13280 let proceeded = self
13282 .variables
13283 .get("__intercept_proceed")
13284 .map(|v| v == "1")
13285 .unwrap_or(false);
13286
13287 if proceeded {
13288 advice_result
13290 } else {
13291 advice_result
13293 }
13294 } else {
13295 let has_after = matching.iter().any(|i| matches!(i.kind, AdviceKind::After));
13300 if !has_after {
13301 return None;
13303 }
13304
13305 self.run_original_command(cmd_name, args)
13307 };
13308
13309 let elapsed = t0.elapsed();
13310
13311 let ms = elapsed.as_secs_f64() * 1000.0;
13313 self.variables
13314 .insert("INTERCEPT_MS".to_string(), format!("{:.3}", ms));
13315 self.variables
13316 .insert("INTERCEPT_US".to_string(), format!("{:.0}", ms * 1000.0));
13317
13318 for advice in matching
13320 .iter()
13321 .filter(|i| matches!(i.kind, AdviceKind::After))
13322 {
13323 let _ = self.execute_advice(&advice.code);
13324 }
13325
13326 self.variables.remove("INTERCEPT_NAME");
13328 self.variables.remove("INTERCEPT_ARGS");
13329 self.variables.remove("INTERCEPT_CMD");
13330 self.variables.remove("INTERCEPT_MS");
13331 self.variables.remove("INTERCEPT_US");
13332 self.variables.remove("__intercept_proceed");
13333
13334 Some(result)
13335 }
13336
13337 fn execute_advice(&mut self, code: &str) -> Result<i32, String> {
13341 let code = code.trim();
13342 if code.starts_with('@') {
13343 let stryke_code = code.trim_start_matches('@').trim();
13344 if let Some(status) = crate::try_stryke_dispatch(stryke_code) {
13345 self.last_status = status;
13346 return Ok(status);
13347 }
13348 }
13350 self.execute_script(code)
13351 }
13352
13353 fn run_original_command(&mut self, cmd_name: &str, args: &[String]) -> Result<i32, String> {
13354 if let Some(func) = self.functions.get(cmd_name).cloned() {
13356 return self.call_function(&func, args);
13357 }
13358 if self.maybe_autoload(cmd_name) {
13359 if let Some(func) = self.functions.get(cmd_name).cloned() {
13360 return self.call_function(&func, args);
13361 }
13362 }
13363 self.execute_external(cmd_name, &args.to_vec(), &[])
13365 }
13366
13367 fn builtin_intercept(&mut self, args: &[String]) -> i32 {
13377 if args.is_empty() {
13378 println!("Usage: intercept <before|after|around> <pattern> {{ code }}");
13379 println!(" intercept list | remove <id> | clear");
13380 return 0;
13381 }
13382
13383 match args[0].as_str() {
13384 "list" => {
13385 if self.intercepts.is_empty() {
13386 println!("no intercepts registered");
13387 } else {
13388 let bold = |s: &str| format!("\x1b[1m{}\x1b[0m", s);
13389 let cyan = |s: &str| format!("\x1b[36m{}\x1b[0m", s);
13390 println!(
13391 "{:>4} {:<8} {:<20} {}",
13392 bold("ID"),
13393 bold("KIND"),
13394 bold("PATTERN"),
13395 bold("CODE")
13396 );
13397 for i in &self.intercepts {
13398 let kind = match i.kind {
13399 AdviceKind::Before => "before",
13400 AdviceKind::After => "after",
13401 AdviceKind::Around => "around",
13402 };
13403 let code_preview = if i.code.len() > 40 {
13404 format!("{}...", &i.code[..37])
13405 } else {
13406 i.code.clone()
13407 };
13408 println!(
13409 "{:>4} {:<8} {:<20} {}",
13410 cyan(&i.id.to_string()),
13411 kind,
13412 i.pattern,
13413 code_preview
13414 );
13415 }
13416 }
13417 0
13418 }
13419 "clear" => {
13420 let count = self.intercepts.len();
13421 self.intercepts.clear();
13422 println!("cleared {} intercepts", count);
13423 0
13424 }
13425 "remove" => {
13426 if args.len() < 2 {
13427 eprintln!("intercept remove: requires ID");
13428 return 1;
13429 }
13430 if let Ok(id) = args[1].parse::<u32>() {
13431 let before = self.intercepts.len();
13432 self.intercepts.retain(|i| i.id != id);
13433 if self.intercepts.len() < before {
13434 println!("removed intercept {}", id);
13435 0
13436 } else {
13437 eprintln!("intercept: no intercept with ID {}", id);
13438 1
13439 }
13440 } else {
13441 eprintln!("intercept remove: invalid ID");
13442 1
13443 }
13444 }
13445 "before" | "after" | "around" => {
13446 let kind = match args[0].as_str() {
13447 "before" => AdviceKind::Before,
13448 "after" => AdviceKind::After,
13449 "around" => AdviceKind::Around,
13450 _ => unreachable!(),
13451 };
13452
13453 if args.len() < 3 {
13454 eprintln!("intercept {}: requires <pattern> {{ code }}", args[0]);
13455 return 1;
13456 }
13457
13458 let pattern = args[1].clone();
13459 let code = args[2..].join(" ");
13461 let code = code.trim().to_string();
13463 let code = if code.starts_with('{') && code.ends_with('}') {
13464 code[1..code.len() - 1].trim().to_string()
13465 } else {
13466 code
13467 };
13468
13469 let id = self.intercepts.iter().map(|i| i.id).max().unwrap_or(0) + 1;
13470 self.intercepts.push(Intercept {
13471 pattern,
13472 kind: kind.clone(),
13473 code: code.clone(),
13474 id,
13475 });
13476
13477 let kind_str = match kind {
13478 AdviceKind::Before => "before",
13479 AdviceKind::After => "after",
13480 AdviceKind::Around => "around",
13481 };
13482 println!(
13483 "intercept #{}: {} {} → {}",
13484 id,
13485 kind_str,
13486 self.intercepts.last().unwrap().pattern,
13487 if code.len() > 50 {
13488 format!("{}...", &code[..47])
13489 } else {
13490 code
13491 }
13492 );
13493 0
13494 }
13495 _ => {
13496 eprintln!(
13497 "intercept: unknown subcommand '{}'. Use before|after|around|list|remove|clear",
13498 args[0]
13499 );
13500 1
13501 }
13502 }
13503 }
13504
13505 fn builtin_intercept_proceed(&mut self, _args: &[String]) -> i32 {
13507 self.variables
13508 .insert("__intercept_proceed".to_string(), "1".to_string());
13509 let cmd_name = self
13511 .variables
13512 .get("INTERCEPT_NAME")
13513 .cloned()
13514 .unwrap_or_default();
13515 let args_str = self
13516 .variables
13517 .get("INTERCEPT_ARGS")
13518 .cloned()
13519 .unwrap_or_default();
13520 let args: Vec<String> = if args_str.is_empty() {
13521 Vec::new()
13522 } else {
13523 args_str.split_whitespace().map(|s| s.to_string()).collect()
13524 };
13525 match self.run_original_command(&cmd_name, &args) {
13526 Ok(status) => status,
13527 Err(e) => {
13528 eprintln!("intercept_proceed: {}", e);
13529 1
13530 }
13531 }
13532 }
13533
13534 fn builtin_async(&mut self, args: &[String]) -> i32 {
13547 if args.is_empty() {
13548 eprintln!("async: requires a command string");
13549 return 1;
13550 }
13551
13552 let code = args.join(" ");
13553 let id = self.next_async_id;
13554 self.next_async_id += 1;
13555
13556 let (tx, rx) = crossbeam_channel::bounded::<(i32, String)>(1);
13557 let pool = std::sync::Arc::clone(&self.worker_pool);
13558
13559 pool.submit(move || {
13560 use std::process::{Command, Stdio};
13562 let output = Command::new("sh")
13563 .args(["-c", &code])
13564 .stdout(Stdio::piped())
13565 .stderr(Stdio::inherit())
13566 .output();
13567 match output {
13568 Ok(out) => {
13569 let stdout = String::from_utf8_lossy(&out.stdout).to_string();
13570 let status = out.status.code().unwrap_or(1);
13571 let _ = tx.send((status, stdout));
13572 }
13573 Err(_) => {
13574 let _ = tx.send((127, String::new()));
13575 }
13576 }
13577 });
13578
13579 self.async_jobs.insert(id, rx);
13580 println!("{}", id);
13582 0
13583 }
13584
13585 fn builtin_await(&mut self, args: &[String]) -> i32 {
13592 if args.is_empty() {
13593 eprintln!("await: requires a job ID");
13594 return 1;
13595 }
13596
13597 let id: u32 = match args[0].parse() {
13598 Ok(n) => n,
13599 Err(_) => {
13600 eprintln!("await: invalid job ID '{}'", args[0]);
13601 return 1;
13602 }
13603 };
13604
13605 let rx = match self.async_jobs.remove(&id) {
13606 Some(rx) => rx,
13607 None => {
13608 eprintln!("await: no async job with ID {}", id);
13609 return 1;
13610 }
13611 };
13612
13613 match rx.recv() {
13615 Ok((status, stdout)) => {
13616 if !stdout.is_empty() {
13617 print!("{}", stdout);
13618 }
13619 self.last_status = status;
13620 status
13621 }
13622 Err(_) => {
13623 eprintln!("await: job {} died without result", id);
13624 1
13625 }
13626 }
13627 }
13628
13629 fn builtin_pmap(&mut self, args: &[String]) -> i32 {
13638 if args.len() < 2 {
13639 eprintln!("pmap: requires 'command {{}}' followed by arguments");
13640 return 1;
13641 }
13642
13643 let template = &args[0];
13644 let items = &args[1..];
13645
13646 let mut results: Vec<(i32, String)> = Vec::with_capacity(items.len());
13648
13649 for item in items {
13650 let cmd = template.replace("{}", item);
13651 let mut parser = crate::parser::ShellParser::new(&cmd);
13652 match parser.parse_script() {
13653 Ok(commands) => {
13654 let compiler = crate::shell_compiler::ShellCompiler::new();
13655 let chunk = compiler.compile(&commands);
13656
13657 let mut output = Vec::new();
13659 let status = {
13660 let mut vm = fusevm::VM::new(chunk);
13661 register_builtins(&mut vm);
13662 let _ctx = ExecutorContext::enter(self);
13663 match vm.run() {
13664 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => vm.last_status,
13665 fusevm::VMResult::Error(_) => 1,
13666 }
13667 };
13668 results.push((status, String::from_utf8_lossy(&output).to_string()));
13669 }
13670 Err(e) => {
13671 eprintln!("pmap: parse error: {}", e);
13672 results.push((1, String::new()));
13673 }
13674 }
13675 }
13676
13677 let mut any_fail = false;
13678 for (status, stdout) in results {
13679 if !stdout.is_empty() {
13680 print!("{}", stdout);
13681 }
13682 if status != 0 {
13683 any_fail = true;
13684 }
13685 }
13686
13687 if any_fail { 1 } else { 0 }
13688 }
13689
13690 fn builtin_pgrep(&mut self, args: &[String]) -> i32 {
13697 if args.len() < 2 {
13698 eprintln!("pgrep: requires 'test_command {{}}' followed by arguments");
13699 return 1;
13700 }
13701
13702 let template = &args[0];
13703 let items = &args[1..];
13704
13705 for item in items {
13707 let cmd = template.replace("{}", item);
13708 let mut parser = crate::parser::ShellParser::new(&cmd);
13709 if let Ok(commands) = parser.parse_script() {
13710 let compiler = crate::shell_compiler::ShellCompiler::new();
13711 let chunk = compiler.compile(&commands);
13712
13713 let mut vm = fusevm::VM::new(chunk);
13714 register_builtins(&mut vm);
13715 let _ctx = ExecutorContext::enter(self);
13716 let status = match vm.run() {
13717 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => vm.last_status,
13718 fusevm::VMResult::Error(_) => 1,
13719 };
13720
13721 if status == 0 {
13722 println!("{}", item);
13723 }
13724 }
13725 }
13726
13727 0
13728 }
13729
13730 fn builtin_peach(&mut self, args: &[String]) -> i32 {
13737 if args.len() < 2 {
13738 eprintln!("peach: requires 'command {{}}' followed by arguments");
13739 return 1;
13740 }
13741
13742 let template = &args[0];
13743 let items = &args[1..];
13744
13745 let mut any_fail = false;
13747
13748 for item in items {
13749 let cmd = template.replace("{}", item);
13750 let mut parser = crate::parser::ShellParser::new(&cmd);
13751 if let Ok(commands) = parser.parse_script() {
13752 let compiler = crate::shell_compiler::ShellCompiler::new();
13753 let chunk = compiler.compile(&commands);
13754
13755 let mut vm = fusevm::VM::new(chunk);
13756 register_builtins(&mut vm);
13757 let _ctx = ExecutorContext::enter(self);
13758 let status = match vm.run() {
13759 fusevm::VMResult::Ok(_) | fusevm::VMResult::Halted => vm.last_status,
13760 fusevm::VMResult::Error(_) => 1,
13761 };
13762
13763 if status != 0 {
13764 any_fail = true;
13765 }
13766 } else {
13767 any_fail = true;
13768 }
13769 }
13770
13771 if any_fail { 1 } else { 0 }
13772 }
13773
13774 fn builtin_barrier(&mut self, args: &[String]) -> i32 {
13781 if args.is_empty() {
13782 eprintln!("barrier: requires commands separated by :::");
13783 return 1;
13784 }
13785
13786 let mut commands: Vec<String> = Vec::new();
13788 let mut current = String::new();
13789 for arg in args {
13790 if arg == ":::" {
13791 if !current.is_empty() {
13792 commands.push(current.trim().to_string());
13793 current.clear();
13794 }
13795 } else {
13796 if !current.is_empty() {
13797 current.push(' ');
13798 }
13799 current.push_str(arg);
13800 }
13801 }
13802 if !current.is_empty() {
13803 commands.push(current.trim().to_string());
13804 }
13805
13806 if commands.is_empty() {
13807 return 0;
13808 }
13809
13810 let mut receivers = Vec::with_capacity(commands.len());
13812 for cmd in &commands {
13813 let cmd = cmd.clone();
13814 let rx = self.worker_pool.submit_with_result(move || {
13815 use std::process::{Command, Stdio};
13816 Command::new("sh")
13817 .args(["-c", &cmd])
13818 .stdout(Stdio::inherit())
13819 .stderr(Stdio::inherit())
13820 .status()
13821 .map(|s| s.code().unwrap_or(1))
13822 .unwrap_or(127)
13823 });
13824 receivers.push(rx);
13825 }
13826
13827 let mut worst = 0i32;
13829 for rx in receivers {
13830 if let Ok(status) = rx.recv() {
13831 if status > worst {
13832 worst = status;
13833 }
13834 }
13835 }
13836
13837 self.last_status = worst;
13838 worst
13839 }
13840
13841 fn builtin_help(&self, args: &[String]) -> i32 {
13843 if args.is_empty() {
13844 println!("zshrs shell builtins:");
13845 println!("");
13846 println!(" alias, bg, bind, break, builtin, cd, command, continue,");
13847 println!(" declare, dirs, disown, echo, enable, eval, exec, exit,");
13848 println!(" export, false, fc, fg, getopts, hash, help, history,");
13849 println!(" jobs, kill, let, local, logout, popd, printf, pushd,");
13850 println!(" pwd, read, readonly, return, set, shift, shopt, source,");
13851 println!(" suspend, test, times, trap, true, type, typeset, ulimit,");
13852 println!(" umask, unalias, unset, wait, whence, where, which");
13853 println!("");
13854 println!("Type 'help name' for more information about 'name'.");
13855 return 0;
13856 }
13857
13858 let cmd = &args[0];
13859 match cmd.as_str() {
13860 "cd" => println!("cd: cd [-L|-P] [dir]\n Change the shell working directory."),
13861 "echo" => println!("echo: echo [-neE] [arg ...]\n Write arguments to standard output."),
13862 "export" => println!("export: export [-fn] [name[=value] ...]\n Set export attribute for shell variables."),
13863 "alias" => println!("alias: alias [-p] [name[=value] ...]\n Define or display aliases."),
13864 "history" => println!("history: history [-c] [-d offset] [n]\n Display or manipulate the history list."),
13865 "jobs" => println!("jobs: jobs [-lnprs] [jobspec ...]\n Display status of jobs."),
13866 "kill" => println!("kill: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ...\n Send a signal to a job."),
13867 "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."),
13868 "set" => println!("set: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]\n Set or unset values of shell options and positional parameters."),
13869 "test" | "[" => println!("test: test [expr]\n Evaluate conditional expression."),
13870 "type" => println!("type: type [-afptP] name [name ...]\n Display information about command type."),
13871 _ => println!("{}: no help available", cmd),
13872 }
13873 0
13874 }
13875
13876 fn builtin_readarray(&mut self, args: &[String]) -> i32 {
13878 use std::io::{BufRead, BufReader};
13879
13880 let mut array_name = "MAPFILE".to_string();
13881 let mut delimiter = '\n';
13882 let mut count = 0usize; let mut skip = 0usize;
13884 let mut strip_trailing = false;
13885 let mut callback: Option<String> = None;
13886 let mut callback_quantum = 0usize;
13887
13888 let mut i = 0;
13889 while i < args.len() {
13890 match args[i].as_str() {
13891 "-d" => {
13892 i += 1;
13893 if i < args.len() && !args[i].is_empty() {
13894 delimiter = args[i].chars().next().unwrap_or('\n');
13895 }
13896 }
13897 "-n" => {
13898 i += 1;
13899 if i < args.len() {
13900 count = args[i].parse().unwrap_or(0);
13901 }
13902 }
13903 "-O" => {
13904 i += 1;
13905 }
13907 "-s" => {
13908 i += 1;
13909 if i < args.len() {
13910 skip = args[i].parse().unwrap_or(0);
13911 }
13912 }
13913 "-t" => strip_trailing = true,
13914 "-C" => {
13915 i += 1;
13916 if i < args.len() {
13917 callback = Some(args[i].clone());
13918 }
13919 }
13920 "-c" => {
13921 i += 1;
13922 if i < args.len() {
13923 callback_quantum = args[i].parse().unwrap_or(5000);
13924 }
13925 }
13926 "-u" => {
13927 i += 1;
13928 }
13930 s if !s.starts_with('-') => {
13931 array_name = s.to_string();
13932 }
13933 _ => {}
13934 }
13935 i += 1;
13936 }
13937
13938 let stdin = std::io::stdin();
13939 let reader = BufReader::new(stdin.lock());
13940 let mut lines = Vec::new();
13941 let mut line_count = 0usize;
13942
13943 for line_result in reader.lines() {
13944 if let Ok(mut line) = line_result {
13945 line_count += 1;
13946
13947 if line_count <= skip {
13948 continue;
13949 }
13950
13951 if strip_trailing {
13952 while line.ends_with('\n') || line.ends_with('\r') {
13953 line.pop();
13954 }
13955 }
13956
13957 lines.push(line);
13958
13959 if count > 0 && lines.len() >= count {
13960 break;
13961 }
13962 }
13963 }
13964
13965 self.arrays.insert(array_name, lines);
13966 let _ = (callback, callback_quantum);
13967 0
13968 }
13969
13970 fn builtin_shopt(&mut self, args: &[String]) -> i32 {
13971 if args.is_empty() {
13972 for (opt, val) in &self.options {
13974 println!("shopt {} {}", if *val { "-s" } else { "-u" }, opt);
13975 }
13976 return 0;
13977 }
13978
13979 let mut set = None;
13980 let mut opts = Vec::new();
13981
13982 for arg in args {
13983 match arg.as_str() {
13984 "-s" => set = Some(true),
13985 "-u" => set = Some(false),
13986 "-p" => {
13987 for opt in &opts {
13989 let val = self.options.get(opt).copied().unwrap_or(false);
13990 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
13991 }
13992 return 0;
13993 }
13994 _ => opts.push(arg.clone()),
13995 }
13996 }
13997
13998 if let Some(enable) = set {
13999 for opt in &opts {
14000 self.options.insert(opt.clone(), enable);
14001 }
14002 } else {
14003 for opt in &opts {
14005 let val = self.options.get(opt).copied().unwrap_or(false);
14006 println!("shopt {} {}", if val { "-s" } else { "-u" }, opt);
14007 }
14008 }
14009 0
14010 }
14011
14012 fn builtin_setopt(&mut self, args: &[String]) -> i32 {
14014 if args.is_empty() {
14015 let defaults_on = Self::default_on_options();
14019 let mut diff_opts: Vec<String> = Vec::new();
14020
14021 for &opt in Self::all_zsh_options() {
14022 let enabled = self.options.get(opt).copied().unwrap_or(false);
14023 let is_default_on = defaults_on.contains(&opt);
14024
14025 if is_default_on && !enabled {
14026 diff_opts.push(format!("no{}", opt));
14028 } else if !is_default_on && enabled {
14029 diff_opts.push(opt.to_string());
14031 }
14032 }
14033 diff_opts.sort();
14034 for opt in diff_opts {
14035 println!("{}", opt);
14036 }
14037 return 0;
14038 }
14039
14040 let mut use_pattern = false;
14041 let mut iter = args.iter().peekable();
14042
14043 while let Some(arg) = iter.next() {
14044 match arg.as_str() {
14045 "-m" => use_pattern = true,
14046 "-o" => {
14047 if let Some(opt) = iter.next() {
14049 let (name, enable) = Self::normalize_option_name(opt);
14050 self.options.insert(name, enable);
14051 }
14052 }
14053 "+o" => {
14054 if let Some(opt) = iter.next() {
14056 let (name, enable) = Self::normalize_option_name(opt);
14057 self.options.insert(name, !enable);
14058 }
14059 }
14060 _ => {
14061 if use_pattern {
14062 for opt in Self::all_zsh_options() {
14064 if Self::option_matches_pattern(opt, arg) {
14065 self.options.insert(opt.to_string(), true);
14066 }
14067 }
14068 } else {
14069 let (name, enable) = Self::normalize_option_name(arg);
14070 self.options.insert(name, enable);
14072 }
14073 }
14074 }
14075 }
14076 0
14077 }
14078
14079 fn builtin_unsetopt(&mut self, args: &[String]) -> i32 {
14081 if args.is_empty() {
14082 let defaults_on = Self::default_on_options();
14086 let mut all_opts: Vec<String> = Vec::new();
14087
14088 for &opt in Self::all_zsh_options() {
14089 let is_default_on = defaults_on.contains(&opt);
14090 if is_default_on {
14091 all_opts.push(format!("no{}", opt));
14092 } else {
14093 all_opts.push(opt.to_string());
14094 }
14095 }
14096 all_opts.sort();
14097 for opt in all_opts {
14098 println!("{}", opt);
14099 }
14100 return 0;
14101 }
14102
14103 let mut use_pattern = false;
14104 let mut iter = args.iter().peekable();
14105
14106 while let Some(arg) = iter.next() {
14107 match arg.as_str() {
14108 "-m" => use_pattern = true,
14109 "-o" => {
14110 if let Some(opt) = iter.next() {
14112 let (name, enable) = Self::normalize_option_name(opt);
14113 self.options.insert(name, !enable);
14114 }
14115 }
14116 "+o" => {
14117 if let Some(opt) = iter.next() {
14119 let (name, enable) = Self::normalize_option_name(opt);
14120 self.options.insert(name, enable);
14121 }
14122 }
14123 _ => {
14124 if use_pattern {
14125 for opt in Self::all_zsh_options() {
14126 if Self::option_matches_pattern(opt, arg) {
14127 self.options.insert(opt.to_string(), false);
14128 }
14129 }
14130 } else {
14131 let (name, enable) = Self::normalize_option_name(arg);
14132 self.options.insert(name, !enable);
14134 }
14135 }
14136 }
14137 }
14138 0
14139 }
14140
14141 fn builtin_getopts(&mut self, args: &[String]) -> i32 {
14142 if args.len() < 2 {
14143 eprintln!("zshrs: getopts: usage: getopts optstring name [arg ...]");
14144 return 1;
14145 }
14146
14147 let optstring = &args[0];
14148 let varname = &args[1];
14149 let opt_args: Vec<&str> = if args.len() > 2 {
14150 args[2..].iter().map(|s| s.as_str()).collect()
14151 } else {
14152 self.positional_params.iter().map(|s| s.as_str()).collect()
14153 };
14154
14155 let optind: usize = self
14157 .variables
14158 .get("OPTIND")
14159 .and_then(|s| s.parse().ok())
14160 .unwrap_or(1);
14161
14162 if optind > opt_args.len() {
14163 self.variables.insert(varname.to_string(), "?".to_string());
14164 return 1;
14165 }
14166
14167 let current_arg = opt_args[optind - 1];
14168
14169 if !current_arg.starts_with('-') || current_arg == "-" {
14170 self.variables.insert(varname.to_string(), "?".to_string());
14171 return 1;
14172 }
14173
14174 if current_arg == "--" {
14175 self.variables
14176 .insert("OPTIND".to_string(), (optind + 1).to_string());
14177 self.variables.insert(varname.to_string(), "?".to_string());
14178 return 1;
14179 }
14180
14181 let optpos: usize = self
14183 .variables
14184 .get("_OPTPOS")
14185 .and_then(|s| s.parse().ok())
14186 .unwrap_or(1);
14187
14188 let opt_char = current_arg.chars().nth(optpos);
14189
14190 if let Some(c) = opt_char {
14191 let opt_idx = optstring.find(c);
14193
14194 match opt_idx {
14195 Some(idx) => {
14196 let takes_arg = optstring.chars().nth(idx + 1) == Some(':');
14198
14199 if takes_arg {
14200 let arg = if optpos + 1 < current_arg.len() {
14202 current_arg[optpos + 1..].to_string()
14204 } else if optind < opt_args.len() {
14205 self.variables
14207 .insert("OPTIND".to_string(), (optind + 2).to_string());
14208 self.variables.remove("_OPTPOS");
14209 opt_args[optind].to_string()
14210 } else {
14211 self.variables.insert(varname.to_string(), "?".to_string());
14213 if !optstring.starts_with(':') {
14214 eprintln!("zshrs: getopts: option requires an argument -- {}", c);
14215 }
14216 self.variables.insert("OPTARG".to_string(), c.to_string());
14217 return 1;
14218 };
14219
14220 self.variables.insert("OPTARG".to_string(), arg);
14221 self.variables
14222 .insert("OPTIND".to_string(), (optind + 1).to_string());
14223 self.variables.remove("_OPTPOS");
14224 } else {
14225 if optpos + 1 < current_arg.len() {
14227 self.variables
14229 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
14230 } else {
14231 self.variables
14233 .insert("OPTIND".to_string(), (optind + 1).to_string());
14234 self.variables.remove("_OPTPOS");
14235 }
14236 }
14237
14238 self.variables.insert(varname.to_string(), c.to_string());
14239 0
14240 }
14241 None => {
14242 if !optstring.starts_with(':') {
14244 eprintln!("zshrs: getopts: illegal option -- {}", c);
14245 }
14246 self.variables.insert(varname.to_string(), "?".to_string());
14247 self.variables.insert("OPTARG".to_string(), c.to_string());
14248
14249 if optpos + 1 < current_arg.len() {
14251 self.variables
14252 .insert("_OPTPOS".to_string(), (optpos + 1).to_string());
14253 } else {
14254 self.variables
14255 .insert("OPTIND".to_string(), (optind + 1).to_string());
14256 self.variables.remove("_OPTPOS");
14257 }
14258 0
14259 }
14260 }
14261 } else {
14262 self.variables
14264 .insert("OPTIND".to_string(), (optind + 1).to_string());
14265 self.variables.remove("_OPTPOS");
14266 self.variables.insert(varname.to_string(), "?".to_string());
14267 1
14268 }
14269 }
14270
14271 fn builtin_type(&mut self, args: &[String]) -> i32 {
14272 if args.is_empty() {
14273 return 0;
14274 }
14275
14276 let mut show_all = false;
14277 let mut path_only = false;
14278 let mut silent = false;
14279 let mut show_type = false;
14280 let mut names = Vec::new();
14281
14282 let mut iter = args.iter();
14283 while let Some(arg) = iter.next() {
14284 if arg.starts_with('-') && arg.len() > 1 {
14285 for c in arg[1..].chars() {
14286 match c {
14287 'a' => show_all = true,
14288 'p' => path_only = true,
14289 'P' => path_only = true,
14290 's' => silent = true,
14291 't' => show_type = true,
14292 'f' => {} 'w' => {} _ => {}
14295 }
14296 }
14297 } else {
14298 names.push(arg.clone());
14299 }
14300 }
14301
14302 if names.is_empty() {
14303 return 0;
14304 }
14305
14306 let mut status = 0;
14307 for name in &names {
14308 let mut found_any = false;
14309
14310 if !path_only && self.aliases.contains_key(name) {
14312 found_any = true;
14313 if !silent {
14314 if show_type {
14315 println!("alias");
14316 } else {
14317 println!(
14318 "{} is aliased to `{}'",
14319 name,
14320 self.aliases.get(name).unwrap()
14321 );
14322 }
14323 }
14324 if !show_all {
14325 continue;
14326 }
14327 }
14328
14329 if !path_only && self.functions.contains_key(name) {
14331 found_any = true;
14332 if !silent {
14333 if show_type {
14334 println!("function");
14335 } else {
14336 println!("{} is a shell function", name);
14337 }
14338 }
14339 if !show_all {
14340 continue;
14341 }
14342 }
14343
14344 if !path_only && (self.is_builtin(name) || name == ":" || name == "[") {
14346 found_any = true;
14347 if !silent {
14348 if show_type {
14349 println!("builtin");
14350 } else {
14351 println!("{} is a shell builtin", name);
14352 }
14353 }
14354 if !show_all {
14355 continue;
14356 }
14357 }
14358
14359 if let Ok(path_env) = std::env::var("PATH") {
14361 for dir in path_env.split(':') {
14362 let full_path = format!("{}/{}", dir, name);
14363 if std::path::Path::new(&full_path).exists() {
14364 found_any = true;
14365 if !silent {
14366 if show_type {
14367 println!("file");
14368 } else {
14369 println!("{} is {}", name, full_path);
14370 }
14371 }
14372 if !show_all {
14373 break;
14374 }
14375 }
14376 }
14377 }
14378
14379 if !found_any {
14380 if !silent {
14381 eprintln!("zshrs: type: {}: not found", name);
14382 }
14383 status = 1;
14384 }
14385 }
14386 status
14387 }
14388
14389 fn builtin_hash(&mut self, args: &[String]) -> i32 {
14390 let mut dir_mode = false;
14399 let mut rehash = false;
14400 let mut fill_all = false;
14401 let mut pattern_match = false;
14402 let mut verbose = false;
14403 let mut list_form = false;
14404 let mut names = Vec::new();
14405
14406 let mut i = 0;
14407 while i < args.len() {
14408 let arg = &args[i];
14409 if arg.starts_with('-') && arg.len() > 1 {
14410 for ch in arg[1..].chars() {
14411 match ch {
14412 'd' => dir_mode = true,
14413 'r' => rehash = true,
14414 'f' => fill_all = true,
14415 'm' => pattern_match = true,
14416 'v' => verbose = true,
14417 'L' => list_form = true,
14418 _ => {}
14419 }
14420 }
14421 } else {
14422 names.push(arg.clone());
14423 }
14424 i += 1;
14425 }
14426
14427 if rehash && !dir_mode && names.is_empty() {
14429 self.command_hash.clear();
14430 return 0;
14431 }
14432
14433 if fill_all {
14435 if let Ok(path_var) = env::var("PATH") {
14436 for dir in path_var.split(':') {
14437 if let Ok(entries) = std::fs::read_dir(dir) {
14438 for entry in entries.flatten() {
14439 if let Ok(ft) = entry.file_type() {
14440 if ft.is_file() || ft.is_symlink() {
14441 if let Some(name) = entry.file_name().to_str() {
14442 let path = entry.path().to_string_lossy().to_string();
14443 self.command_hash.insert(name.to_string(), path);
14444 }
14445 }
14446 }
14447 }
14448 }
14449 }
14450 }
14451 return 0;
14452 }
14453
14454 if dir_mode {
14455 if names.is_empty() {
14457 for (name, path) in &self.named_dirs {
14459 if list_form {
14460 println!("hash -d {}={}", name, path.display());
14461 } else if verbose {
14462 println!("{}={}", name, path.display());
14463 } else {
14464 println!("{}={}", name, path.display());
14465 }
14466 }
14467 return 0;
14468 }
14469
14470 if rehash {
14471 if pattern_match {
14473 let to_remove: Vec<String> = self
14475 .named_dirs
14476 .keys()
14477 .filter(|k| {
14478 names.iter().any(|pat| {
14479 let pattern = pat.replace("*", ".*").replace("?", ".");
14480 regex::Regex::new(&format!("^{}$", pattern))
14481 .map(|r| r.is_match(k))
14482 .unwrap_or(false)
14483 })
14484 })
14485 .cloned()
14486 .collect();
14487 for name in to_remove {
14488 self.named_dirs.remove(&name);
14489 }
14490 } else {
14491 for name in &names {
14492 self.named_dirs.remove(name);
14493 }
14494 }
14495 return 0;
14496 }
14497
14498 for name in &names {
14500 if let Some((n, p)) = name.split_once('=') {
14501 self.add_named_dir(n, p);
14502 } else {
14503 eprintln!("hash: -d: {} not in name=value format", name);
14504 return 1;
14505 }
14506 }
14507 return 0;
14508 }
14509
14510 if names.is_empty() {
14512 for (name, path) in &self.command_hash {
14514 if list_form {
14515 println!("hash {}={}", name, path);
14516 } else {
14517 println!("{}={}", name, path);
14518 }
14519 }
14520 return 0;
14521 }
14522
14523 for name in &names {
14524 if let Some((cmd, path)) = name.split_once('=') {
14525 self.command_hash.insert(cmd.to_string(), path.to_string());
14527 if verbose {
14528 println!("{}={}", cmd, path);
14529 }
14530 } else if let Some(path) = self.find_in_path(name) {
14531 self.command_hash.insert(name.clone(), path.clone());
14533 if verbose {
14534 println!("{}={}", name, path);
14535 }
14536 } else {
14537 eprintln!("zshrs: hash: {}: not found", name);
14538 return 1;
14539 }
14540 }
14541 0
14542 }
14543
14544 fn builtin_add_zsh_hook(&mut self, args: &[String]) -> i32 {
14546 if args.len() < 2 {
14548 eprintln!("usage: add-zsh-hook [-d] hook function");
14549 return 1;
14550 }
14551
14552 let (delete, hook, func) = if args[0] == "-d" {
14553 if args.len() < 3 {
14554 eprintln!("usage: add-zsh-hook -d hook function");
14555 return 1;
14556 }
14557 (true, &args[1], &args[2])
14558 } else {
14559 (false, &args[0], &args[1])
14560 };
14561
14562 if delete {
14563 if let Some(funcs) = self.hook_functions.get_mut(hook.as_str()) {
14565 funcs.retain(|f| f != func);
14566 }
14567 } else {
14568 self.add_hook(hook, func);
14570 }
14571 0
14572 }
14573
14574 fn builtin_command(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
14575 let mut use_default_path = false;
14580 let mut print_path = false;
14581 let mut verbose = false;
14582 let mut positional_args: Vec<&str> = Vec::new();
14583
14584 let mut i = 0;
14585 while i < args.len() {
14586 let arg = &args[i];
14587 if arg.starts_with('-') && arg.len() > 1 && positional_args.is_empty() {
14588 for ch in arg[1..].chars() {
14589 match ch {
14590 'p' => use_default_path = true,
14591 'v' => print_path = true,
14592 'V' => verbose = true,
14593 '-' => {
14594 i += 1;
14596 break;
14597 }
14598 _ => {
14599 eprintln!("command: bad option: -{}", ch);
14600 return 1;
14601 }
14602 }
14603 }
14604 } else {
14605 positional_args.push(arg);
14606 }
14607 i += 1;
14608 }
14609
14610 while i < args.len() {
14612 positional_args.push(&args[i]);
14613 i += 1;
14614 }
14615
14616 if positional_args.is_empty() {
14617 return 0;
14618 }
14619
14620 let cmd = positional_args[0];
14621
14622 if print_path || verbose {
14624 let path_var = if use_default_path {
14626 "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin".to_string()
14627 } else {
14628 env::var("PATH").unwrap_or_default()
14629 };
14630
14631 for dir in path_var.split(':') {
14632 let full_path = PathBuf::from(dir).join(cmd);
14633 if full_path.exists() && full_path.is_file() {
14634 if verbose {
14635 println!("{} is {}", cmd, full_path.display());
14636 } else {
14637 println!("{}", full_path.display());
14638 }
14639 return 0;
14640 }
14641 }
14642
14643 if verbose {
14644 eprintln!("{} not found", cmd);
14645 }
14646 return 1;
14647 }
14648
14649 let cmd_args: Vec<String> = positional_args[1..].iter().map(|s| s.to_string()).collect();
14651
14652 if use_default_path {
14653 let old_path = env::var("PATH").ok();
14655 env::set_var("PATH", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin");
14656 let result = self
14657 .execute_external(
14658 cmd,
14659 &cmd_args
14660 .iter()
14661 .map(|s| s.as_str())
14662 .collect::<Vec<_>>()
14663 .join(" ")
14664 .split_whitespace()
14665 .map(String::from)
14666 .collect::<Vec<_>>(),
14667 redirects,
14668 )
14669 .unwrap_or(127);
14670 if let Some(p) = old_path {
14671 env::set_var("PATH", p);
14672 }
14673 result
14674 } else {
14675 self.execute_external(cmd, &cmd_args, redirects)
14676 .unwrap_or(127)
14677 }
14678 }
14679
14680 fn builtin_builtin(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
14681 if args.is_empty() {
14683 return 0;
14684 }
14685
14686 let cmd = &args[0];
14687 let cmd_args = &args[1..];
14688
14689 match cmd.as_str() {
14690 "cd" => self.builtin_cd(cmd_args),
14691 "pwd" => self.builtin_pwd(redirects),
14692 "echo" => self.builtin_echo(cmd_args, redirects),
14693 "export" => self.builtin_export(cmd_args),
14694 "unset" => self.builtin_unset(cmd_args),
14695 "exit" => self.builtin_exit(cmd_args),
14696 "return" => self.builtin_return(cmd_args),
14697 "true" => 0,
14698 "false" => 1,
14699 ":" => 0,
14700 "test" | "[" => self.builtin_test(cmd_args),
14701 "local" => self.builtin_local(cmd_args),
14702 "declare" | "typeset" => self.builtin_declare(cmd_args),
14703 "read" => self.builtin_read(cmd_args),
14704 "shift" => self.builtin_shift(cmd_args),
14705 "eval" => self.builtin_eval(cmd_args),
14706 "alias" => self.builtin_alias(cmd_args),
14707 "unalias" => self.builtin_unalias(cmd_args),
14708 "set" => self.builtin_set(cmd_args),
14709 "shopt" => self.builtin_shopt(cmd_args),
14710 "getopts" => self.builtin_getopts(cmd_args),
14711 "type" => self.builtin_type(cmd_args),
14712 "hash" => self.builtin_hash(cmd_args),
14713 "add-zsh-hook" => self.builtin_add_zsh_hook(cmd_args),
14714 "autoload" => self.builtin_autoload(cmd_args),
14715 "source" | "." => self.builtin_source(cmd_args),
14716 "functions" => self.builtin_functions(cmd_args),
14717 "zle" => self.builtin_zle(cmd_args),
14718 "bindkey" => self.builtin_bindkey(cmd_args),
14719 "setopt" => self.builtin_setopt(cmd_args),
14720 "unsetopt" => self.builtin_unsetopt(cmd_args),
14721 "emulate" => self.builtin_emulate(cmd_args),
14722 "zstyle" => self.builtin_zstyle(cmd_args),
14723 "compadd" => self.builtin_compadd(cmd_args),
14724 "compset" => self.builtin_compset(cmd_args),
14725 "compdef" => self.builtin_compdef(cmd_args),
14726 "compinit" => self.builtin_compinit(cmd_args),
14727 "cdreplay" => self.builtin_cdreplay(cmd_args),
14728 "zmodload" => self.builtin_zmodload(cmd_args),
14729 "zcompile" => self.builtin_zcompile(cmd_args),
14730 "zformat" => self.builtin_zformat(cmd_args),
14731 "zprof" => self.builtin_zprof(cmd_args),
14732 "print" => self.builtin_print(cmd_args),
14733 "printf" => self.builtin_printf(cmd_args),
14734 "command" => self.builtin_command(cmd_args, redirects),
14735 "whence" => self.builtin_whence(cmd_args),
14736 "which" => self.builtin_which(cmd_args),
14737 "where" => self.builtin_where(cmd_args),
14738 "fc" => self.builtin_fc(cmd_args),
14739 "history" => self.builtin_history(cmd_args),
14740 "dirs" => self.builtin_dirs(cmd_args),
14741 "pushd" => self.builtin_pushd(cmd_args),
14742 "popd" => self.builtin_popd(cmd_args),
14743 "bg" => self.builtin_bg(cmd_args),
14744 "fg" => self.builtin_fg(cmd_args),
14745 "jobs" => self.builtin_jobs(cmd_args),
14746 "kill" => self.builtin_kill(cmd_args),
14747 "wait" => self.builtin_wait(cmd_args),
14748 "trap" => self.builtin_trap(cmd_args),
14749 "umask" => self.builtin_umask(cmd_args),
14750 "ulimit" => self.builtin_ulimit(cmd_args),
14751 "times" => self.builtin_times(cmd_args),
14752 "let" => self.builtin_let(cmd_args),
14753 "integer" => self.builtin_integer(cmd_args),
14754 "float" => self.builtin_float(cmd_args),
14755 "readonly" => self.builtin_readonly(cmd_args),
14756 _ => {
14757 eprintln!("zshrs: builtin: {}: not a shell builtin", cmd);
14758 1
14759 }
14760 }
14761 }
14762
14763 fn builtin_let(&mut self, args: &[String]) -> i32 {
14764 if args.is_empty() {
14765 return 1;
14766 }
14767
14768 let mut result = 0i64;
14769 for expr in args {
14770 result = self.evaluate_arithmetic_expr(expr);
14771 }
14772
14773 if result == 0 {
14775 1
14776 } else {
14777 0
14778 }
14779 }
14780
14781 fn builtin_compgen(&self, args: &[String]) -> i32 {
14783 let mut i = 0;
14784 let mut prefix = String::new();
14785 let mut actions = Vec::new();
14786 let mut wordlist = None;
14787 let mut globpat = None;
14788
14789 while i < args.len() {
14790 match args[i].as_str() {
14791 "-W" => {
14792 i += 1;
14793 if i < args.len() {
14794 wordlist = Some(args[i].clone());
14795 }
14796 }
14797 "-G" => {
14798 i += 1;
14799 if i < args.len() {
14800 globpat = Some(args[i].clone());
14801 }
14802 }
14803 "-a" => actions.push("alias"),
14804 "-b" => actions.push("builtin"),
14805 "-c" => actions.push("command"),
14806 "-d" => actions.push("directory"),
14807 "-e" => actions.push("export"),
14808 "-f" => actions.push("file"),
14809 "-j" => actions.push("job"),
14810 "-k" => actions.push("keyword"),
14811 "-u" => actions.push("user"),
14812 "-v" => actions.push("variable"),
14813 s if !s.starts_with('-') => prefix = s.to_string(),
14814 _ => {}
14815 }
14816 i += 1;
14817 }
14818
14819 let mut results = Vec::new();
14820
14821 for action in actions {
14823 match action {
14824 "alias" => {
14825 for name in self.aliases.keys() {
14826 if name.starts_with(&prefix) {
14827 results.push(name.clone());
14828 }
14829 }
14830 }
14831 "builtin" => {
14832 for name in [
14833 "cd", "pwd", "echo", "export", "unset", "source", "exit", "return", "true",
14834 "false", ":", "test", "[", "local", "declare", "jobs", "fg", "bg", "kill",
14835 "disown", "wait", "alias", "unalias", "set", "shopt",
14836 ] {
14837 if name.starts_with(&prefix) {
14838 results.push(name.to_string());
14839 }
14840 }
14841 }
14842 "directory" => {
14843 if let Ok(entries) = std::fs::read_dir(".") {
14844 for entry in entries.flatten() {
14845 if let Ok(ft) = entry.file_type() {
14846 if ft.is_dir() {
14847 let name = entry.file_name().to_string_lossy().to_string();
14848 if name.starts_with(&prefix) {
14849 results.push(name);
14850 }
14851 }
14852 }
14853 }
14854 }
14855 }
14856 "file" => {
14857 if let Ok(entries) = std::fs::read_dir(".") {
14858 for entry in entries.flatten() {
14859 let name = entry.file_name().to_string_lossy().to_string();
14860 if name.starts_with(&prefix) {
14861 results.push(name);
14862 }
14863 }
14864 }
14865 }
14866 "variable" => {
14867 for name in self.variables.keys() {
14868 if name.starts_with(&prefix) {
14869 results.push(name.clone());
14870 }
14871 }
14872 for name in std::env::vars().map(|(k, _)| k) {
14873 if name.starts_with(&prefix) && !results.contains(&name) {
14874 results.push(name);
14875 }
14876 }
14877 }
14878 _ => {}
14879 }
14880 }
14881
14882 if let Some(words) = wordlist {
14884 for word in words.split_whitespace() {
14885 if word.starts_with(&prefix) {
14886 results.push(word.to_string());
14887 }
14888 }
14889 }
14890
14891 if let Some(_pattern) = globpat {
14893 let full_pattern = format!("{}*", prefix);
14894 if let Ok(paths) = glob::glob(&full_pattern) {
14895 for path in paths.flatten() {
14896 results.push(path.to_string_lossy().to_string());
14897 }
14898 }
14899 }
14900
14901 results.sort();
14902 results.dedup();
14903 for r in results {
14904 println!("{}", r);
14905 }
14906 0
14907 }
14908
14909 fn builtin_complete(&mut self, args: &[String]) -> i32 {
14911 if args.is_empty() {
14912 for (cmd, spec) in &self.completions {
14914 let mut parts = vec!["complete".to_string()];
14915 for action in &spec.actions {
14916 parts.push(format!("-{}", action));
14917 }
14918 if let Some(ref w) = spec.wordlist {
14919 parts.push("-W".to_string());
14920 parts.push(format!("'{}'", w));
14921 }
14922 if let Some(ref f) = spec.function {
14923 parts.push("-F".to_string());
14924 parts.push(f.clone());
14925 }
14926 if let Some(ref c) = spec.command {
14927 parts.push("-C".to_string());
14928 parts.push(c.clone());
14929 }
14930 parts.push(cmd.clone());
14931 println!("{}", parts.join(" "));
14932 }
14933 return 0;
14934 }
14935
14936 let mut spec = CompSpec::default();
14937 let mut commands = Vec::new();
14938 let mut i = 0;
14939
14940 while i < args.len() {
14941 match args[i].as_str() {
14942 "-W" => {
14943 i += 1;
14944 if i < args.len() {
14945 spec.wordlist = Some(args[i].clone());
14946 }
14947 }
14948 "-F" => {
14949 i += 1;
14950 if i < args.len() {
14951 spec.function = Some(args[i].clone());
14952 }
14953 }
14954 "-C" => {
14955 i += 1;
14956 if i < args.len() {
14957 spec.command = Some(args[i].clone());
14958 }
14959 }
14960 "-G" => {
14961 i += 1;
14962 if i < args.len() {
14963 spec.globpat = Some(args[i].clone());
14964 }
14965 }
14966 "-P" => {
14967 i += 1;
14968 if i < args.len() {
14969 spec.prefix = Some(args[i].clone());
14970 }
14971 }
14972 "-S" => {
14973 i += 1;
14974 if i < args.len() {
14975 spec.suffix = Some(args[i].clone());
14976 }
14977 }
14978 "-a" => spec.actions.push("a".to_string()),
14979 "-b" => spec.actions.push("b".to_string()),
14980 "-c" => spec.actions.push("c".to_string()),
14981 "-d" => spec.actions.push("d".to_string()),
14982 "-e" => spec.actions.push("e".to_string()),
14983 "-f" => spec.actions.push("f".to_string()),
14984 "-j" => spec.actions.push("j".to_string()),
14985 "-r" => {
14986 i += 1;
14988 while i < args.len() {
14989 self.completions.remove(&args[i]);
14990 i += 1;
14991 }
14992 return 0;
14993 }
14994 s if !s.starts_with('-') => commands.push(s.to_string()),
14995 _ => {}
14996 }
14997 i += 1;
14998 }
14999
15000 for cmd in commands {
15001 self.completions.insert(cmd, spec.clone());
15002 }
15003 0
15004 }
15005
15006 fn builtin_compopt(&mut self, args: &[String]) -> i32 {
15008 let _ = args;
15010 0
15011 }
15012
15013 fn builtin_compadd(&mut self, args: &[String]) -> i32 {
15015 let _ = args;
15018 0
15019 }
15020
15021 fn builtin_compset(&mut self, args: &[String]) -> i32 {
15023 let _ = args;
15025 0
15026 }
15027
15028 fn builtin_compdef(&mut self, args: &[String]) -> i32 {
15033 if let Some(cache) = &mut self.compsys_cache {
15034 compsys::compdef::compdef_execute(cache, args)
15035 } else {
15036 self.deferred_compdefs.push(args.to_vec());
15038 0
15039 }
15040 }
15041
15042 #[tracing::instrument(level = "info", skip(self))]
15045 fn builtin_compinit(&mut self, args: &[String]) -> i32 {
15046 let mut quiet = false;
15053 let mut no_dump = false;
15054 let mut dump_file: Option<String> = None;
15055 let mut use_cache = false;
15056 let mut ignore_insecure = false;
15057 let mut use_insecure = false;
15058
15059 let mut i = 0;
15060 while i < args.len() {
15061 match args[i].as_str() {
15062 "-q" => quiet = true,
15063 "-C" => use_cache = true,
15064 "-D" => no_dump = true,
15065 "-d" => {
15066 i += 1;
15067 if i < args.len() {
15068 dump_file = Some(args[i].clone());
15069 }
15070 }
15071 "-u" => use_insecure = true,
15072 "-i" => ignore_insecure = true,
15073 _ => {}
15074 }
15075 i += 1;
15076 }
15077
15078 if !use_insecure && !self.posix_mode {
15080 if let Some(ref cache) = self.plugin_cache {
15081 let insecure = cache.compaudit_cached(&self.fpath);
15082 if !insecure.is_empty() && !ignore_insecure {
15083 if !quiet {
15084 eprintln!("compinit: insecure directories:");
15085 for d in &insecure {
15086 eprintln!(" {}", d);
15087 }
15088 eprintln!("compinit: run with -i to ignore or -u to use anyway");
15089 }
15090 return 1;
15091 }
15092 }
15093 }
15094
15095 if self.zsh_compat {
15097 return self.compinit_compat(quiet, no_dump, dump_file, use_cache);
15098 }
15099
15100 if use_cache {
15104 if let Some(cache) = &self.compsys_cache {
15105 if compsys::cache_is_valid(cache) {
15106 if let Ok(result) = compsys::load_from_cache(cache) {
15108 if !quiet {
15109 tracing::info!(
15110 comps = result.comps.len(),
15111 "compinit: using cached completions"
15112 );
15113 }
15114 self.assoc_arrays.insert("_comps".to_string(), result.comps);
15115 self.assoc_arrays
15116 .insert("_services".to_string(), result.services);
15117 self.assoc_arrays
15118 .insert("_patcomps".to_string(), result.patcomps);
15119
15120 if let Some(ref cache) = self.compsys_cache {
15123 if let Ok(missing) = cache.count_autoloads_missing_bytecode() {
15124 if missing > 0 {
15125 tracing::info!(
15126 count = missing,
15127 "compinit: backfilling bytecode blobs on worker pool"
15128 );
15129 let cache_path = compsys::cache::default_cache_path();
15130 let total_missing = missing;
15131 self.worker_pool.submit(move || {
15132 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
15133 Ok(c) => c,
15134 Err(_) => return,
15135 };
15136 let mut total_cached = 0usize;
15140 loop {
15141 let stubs = match cache.get_autoloads_missing_bytecode_batch(100) {
15142 Ok(s) if !s.is_empty() => s,
15143 _ => break,
15144 };
15145 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(stubs.len());
15146 for (name, body) in &stubs {
15147 let mut parser = crate::parser::ShellParser::new(body);
15148 if let Ok(commands) = parser.parse_script() {
15149 if !commands.is_empty() {
15150 let compiler = crate::shell_compiler::ShellCompiler::new();
15151 let chunk = compiler.compile(&commands);
15152 if let Ok(blob) = bincode::serialize(&chunk) {
15153 batch.push((name.clone(), blob));
15154 }
15155 }
15156 }
15157 }
15158 total_cached += batch.len();
15159 if let Err(e) = cache.set_autoload_bytecodes_bulk(&batch) {
15160 tracing::warn!(error = %e, "compinit: bytecode backfill batch failed");
15161 break;
15162 }
15163 if stubs.len() < 100 {
15165 break;
15166 }
15167 }
15168 tracing::info!(
15169 cached = total_cached,
15170 total = total_missing,
15171 "compinit: bytecode backfill complete"
15172 );
15173 });
15174 }
15175 }
15176 }
15177
15178 return 0;
15179 }
15180 }
15181 }
15182 }
15183
15184 let fpath = self.fpath.clone();
15188 let fpath_count = fpath.len();
15189 let pool_size = self.worker_pool.size();
15190 let (tx, rx) = std::sync::mpsc::channel();
15191 let bg_start = std::time::Instant::now();
15192 tracing::info!(
15193 fpath_dirs = fpath_count,
15194 worker_pool = pool_size,
15195 "compinit: shipping to worker pool"
15196 );
15197 self.worker_pool.submit(move || {
15198 tracing::debug!("compinit-bg: thread started");
15199 let cache_path = compsys::cache::default_cache_path();
15200 if let Some(parent) = cache_path.parent() {
15201 let _ = std::fs::create_dir_all(parent);
15202 }
15203 let _ = std::fs::remove_file(&cache_path);
15205 let _ = std::fs::remove_file(format!("{}-shm", cache_path.display()));
15206 let _ = std::fs::remove_file(format!("{}-wal", cache_path.display()));
15207
15208 let mut cache = match compsys::cache::CompsysCache::open(&cache_path) {
15209 Ok(c) => c,
15210 Err(e) => {
15211 tracing::error!("compinit: failed to create cache: {}", e);
15212 return;
15213 }
15214 };
15215
15216 let result = match compsys::build_cache_from_fpath(&fpath, &mut cache) {
15217 Ok(r) => r,
15218 Err(e) => {
15219 tracing::error!("compinit: scan failed: {}", e);
15220 return;
15221 }
15222 };
15223
15224 tracing::info!(
15225 functions = result.files_scanned,
15226 comps = result.comps.len(),
15227 dirs = result.dirs_scanned,
15228 ms = result.scan_time_ms,
15229 "compinit: background scan complete"
15230 );
15231
15232 let parse_start = std::time::Instant::now();
15236 let mut parse_ok = 0usize;
15237 let mut parse_fail = 0usize;
15238 let mut no_body = 0usize;
15239 let batch_size = 100;
15240 let mut batch: Vec<(String, Vec<u8>)> = Vec::with_capacity(batch_size);
15241
15242 for file in &result.files {
15243 if let Some(ref body) = file.body {
15244 let mut parser = crate::parser::ShellParser::new(body);
15245 match parser.parse_script() {
15246 Ok(commands) if !commands.is_empty() => {
15247 let compiler = crate::shell_compiler::ShellCompiler::new();
15249 let chunk = compiler.compile(&commands);
15250 if let Ok(blob) = bincode::serialize(&chunk) {
15251 batch.push((file.name.clone(), blob));
15252 parse_ok += 1;
15253 if batch.len() >= batch_size {
15254 let _ = cache.set_autoload_bytecodes_bulk(&batch);
15255 batch.clear();
15256 }
15257 }
15258 }
15259 Ok(_) => {
15260 parse_fail += 1;
15261 }
15262 Err(_) => {
15263 parse_fail += 1;
15264 }
15265 }
15266 } else {
15267 no_body += 1;
15268 }
15269 }
15270 if !batch.is_empty() {
15272 let _ = cache.set_autoload_bytecodes_bulk(&batch);
15273 batch.clear();
15274 }
15275
15276 tracing::info!(
15277 cached = parse_ok,
15278 failed = parse_fail,
15279 no_body = no_body,
15280 total = result.files.len(),
15281 ms = parse_start.elapsed().as_millis() as u64,
15282 "compinit: bytecode blobs cached"
15283 );
15284
15285 let _ = tx.send(CompInitBgResult { result, cache });
15286 });
15287
15288 self.compinit_pending = Some((rx, bg_start));
15289 0
15290 }
15291
15292 pub fn drain_compinit_bg(&mut self) {
15296 if let Some((rx, start)) = self.compinit_pending.take() {
15297 match rx.try_recv() {
15298 Ok(bg) => {
15299 let comps = bg.result.comps.len();
15300 self.assoc_arrays
15301 .insert("_comps".to_string(), bg.result.comps);
15302 self.assoc_arrays
15303 .insert("_services".to_string(), bg.result.services);
15304 self.assoc_arrays
15305 .insert("_patcomps".to_string(), bg.result.patcomps);
15306 self.compsys_cache = Some(bg.cache);
15307 tracing::info!(
15308 wall_ms = start.elapsed().as_millis() as u64,
15309 comps,
15310 "compinit: background results merged"
15311 );
15312 }
15313 Err(std::sync::mpsc::TryRecvError::Empty) => {
15314 self.compinit_pending = Some((rx, start));
15316 }
15317 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
15318 tracing::warn!("compinit: background thread died without sending results");
15319 }
15320 }
15321 }
15322 }
15323
15324 fn compinit_compat(
15327 &mut self,
15328 quiet: bool,
15329 no_dump: bool,
15330 dump_file: Option<String>,
15331 use_cache: bool,
15332 ) -> i32 {
15333 let zdotdir = self
15334 .variables
15335 .get("ZDOTDIR")
15336 .cloned()
15337 .or_else(|| std::env::var("ZDOTDIR").ok())
15338 .unwrap_or_else(|| std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()));
15339
15340 let dump_path = dump_file
15341 .map(PathBuf::from)
15342 .unwrap_or_else(|| PathBuf::from(&zdotdir).join(".zcompdump"));
15343
15344 if use_cache && dump_path.exists() {
15346 if compsys::check_dump(&dump_path, &self.fpath, "zshrs-0.1.0") {
15347 if !quiet {
15350 tracing::info!("compinit: .zcompdump valid, rescanning for compat");
15351 }
15352 }
15353 }
15354
15355 let result = compsys::compinit(&self.fpath);
15357
15358 if !quiet {
15359 tracing::info!(
15360 functions = result.files_scanned,
15361 comps = result.comps.len(),
15362 dirs = result.dirs_scanned,
15363 ms = result.scan_time_ms,
15364 "compinit: fpath scan complete"
15365 );
15366 }
15367
15368 if !no_dump {
15370 let _ = compsys::compdump(&result, &dump_path, "zshrs-0.1.0");
15371 }
15372
15373 self.assoc_arrays
15375 .insert("_comps".to_string(), result.comps.clone());
15376 self.assoc_arrays
15377 .insert("_services".to_string(), result.services.clone());
15378 self.assoc_arrays
15379 .insert("_patcomps".to_string(), result.patcomps.clone());
15380
15381 self.compsys_cache = None;
15383
15384 0
15385 }
15386
15387 fn builtin_cdreplay(&mut self, args: &[String]) -> i32 {
15390 let quiet = args.contains(&"-q".to_string());
15391
15392 if self.deferred_compdefs.is_empty() {
15393 return 0;
15394 }
15395
15396 let deferred = std::mem::take(&mut self.deferred_compdefs);
15397 let count = deferred.len();
15398
15399 if let Some(cache) = &mut self.compsys_cache {
15400 for compdef_args in deferred {
15401 compsys::compdef::compdef_execute(cache, &compdef_args);
15402 }
15403 }
15404
15405 if !quiet {
15406 eprintln!("cdreplay: replayed {} compdef calls", count);
15407 }
15408
15409 0
15410 }
15411
15412 fn builtin_zstyle(&mut self, args: &[String]) -> i32 {
15414 if args.is_empty() {
15415 for (pattern, style, values) in self.style_table.list(None) {
15417 println!("zstyle '{}' {} {}", pattern, style, values.join(" "));
15418 }
15419 return 0;
15420 }
15421
15422 if args[0].starts_with('-') {
15424 match args[0].as_str() {
15425 "-d" => {
15426 let pattern = args.get(1).map(|s| s.as_str());
15428 let style = args.get(2).map(|s| s.as_str());
15429 self.style_table.delete(pattern, style);
15430 return 0;
15431 }
15432 "-g" => {
15433 if args.len() >= 4 {
15435 let array_name = &args[1];
15436 let context = &args[2];
15437 let style = &args[3];
15438 if let Some(values) = self.style_table.get(context, style) {
15439 self.arrays.insert(array_name.clone(), values.to_vec());
15440 return 0;
15441 }
15442 }
15443 return 1;
15444 }
15445 "-s" => {
15446 if args.len() >= 4 {
15448 let var_name = &args[1];
15449 let context = &args[2];
15450 let style = &args[3];
15451 let sep = args.get(4).map(|s| s.as_str()).unwrap_or(" ");
15452 if let Some(values) = self.style_table.get(context, style) {
15453 self.variables.insert(var_name.clone(), values.join(sep));
15454 return 0;
15455 }
15456 }
15457 return 1;
15458 }
15459 "-t" => {
15460 if args.len() >= 3 {
15462 let context = &args[1];
15463 let style = &args[2];
15464 return if self.style_table.test_bool(context, style).unwrap_or(false) {
15465 0
15466 } else {
15467 1
15468 };
15469 }
15470 return 1;
15471 }
15472 "-L" => {
15473 for (pattern, style, values) in self.style_table.list(None) {
15475 let values_str = values
15476 .iter()
15477 .map(|v| format!("'{}'", v.replace('\'', "'\\''")))
15478 .collect::<Vec<_>>()
15479 .join(" ");
15480 println!("zstyle '{}' {} {}", pattern, style, values_str);
15481 }
15482 return 0;
15483 }
15484 _ => {}
15485 }
15486 }
15487
15488 if args.len() >= 2 {
15490 let pattern = &args[0];
15491 let style = &args[1];
15492 let values: Vec<String> = args[2..].to_vec();
15493 self.style_table.set(pattern, style, values.clone(), false);
15494
15495 if let Some(cache) = &self.compsys_cache {
15497 let _ = cache.set_zstyle(pattern, style, &values, false);
15498 }
15499
15500 let existing = self
15502 .zstyles
15503 .iter_mut()
15504 .find(|s| s.pattern == *pattern && s.style == *style);
15505 if let Some(s) = existing {
15506 s.values = args[2..].to_vec();
15507 } else {
15508 self.zstyles.push(ZStyle {
15509 pattern: pattern.clone(),
15510 style: style.clone(),
15511 values: args[2..].to_vec(),
15512 });
15513 }
15514 }
15515 0
15516 }
15517
15518 fn builtin_ztie(&mut self, args: &[String]) -> i32 {
15521 use crate::db_gdbm;
15522
15523 let mut db_type: Option<String> = None;
15524 let mut file_path: Option<String> = None;
15525 let mut readonly = false;
15526 let mut param_args: Vec<String> = Vec::new();
15527
15528 let mut i = 0;
15529 while i < args.len() {
15530 match args[i].as_str() {
15531 "-d" => {
15532 if i + 1 < args.len() {
15533 db_type = Some(args[i + 1].clone());
15534 i += 2;
15535 } else {
15536 eprintln!("ztie: -d requires an argument");
15537 return 1;
15538 }
15539 }
15540 "-f" => {
15541 if i + 1 < args.len() {
15542 file_path = Some(args[i + 1].clone());
15543 i += 2;
15544 } else {
15545 eprintln!("ztie: -f requires an argument");
15546 return 1;
15547 }
15548 }
15549 "-r" => {
15550 readonly = true;
15551 i += 1;
15552 }
15553 arg if arg.starts_with('-') => {
15554 eprintln!("ztie: bad option: {}", arg);
15555 return 1;
15556 }
15557 _ => {
15558 param_args.push(args[i].clone());
15559 i += 1;
15560 }
15561 }
15562 }
15563
15564 match db_gdbm::ztie(
15565 ¶m_args,
15566 readonly,
15567 db_type.as_deref(),
15568 file_path.as_deref(),
15569 ) {
15570 Ok(()) => 0,
15571 Err(e) => {
15572 eprintln!("ztie: {}", e);
15573 1
15574 }
15575 }
15576 }
15577
15578 fn builtin_zuntie(&mut self, args: &[String]) -> i32 {
15581 use crate::db_gdbm;
15582
15583 let mut force_unset = false;
15584 let mut param_args: Vec<String> = Vec::new();
15585
15586 for arg in args {
15587 match arg.as_str() {
15588 "-u" => force_unset = true,
15589 a if a.starts_with('-') => {
15590 eprintln!("zuntie: bad option: {}", a);
15591 return 1;
15592 }
15593 _ => param_args.push(arg.clone()),
15594 }
15595 }
15596
15597 if param_args.is_empty() {
15598 eprintln!("zuntie: not enough arguments");
15599 return 1;
15600 }
15601
15602 match db_gdbm::zuntie(¶m_args, force_unset) {
15603 Ok(()) => 0,
15604 Err(e) => {
15605 eprintln!("zuntie: {}", e);
15606 1
15607 }
15608 }
15609 }
15610
15611 fn builtin_zgdbmpath(&mut self, args: &[String]) -> i32 {
15615 use crate::db_gdbm;
15616
15617 if args.is_empty() {
15618 eprintln!(
15619 "zgdbmpath: parameter name (whose path is to be written to $REPLY) is required"
15620 );
15621 return 1;
15622 }
15623
15624 match db_gdbm::zgdbmpath(&args[0]) {
15625 Ok(path) => {
15626 self.variables.insert("REPLY".to_string(), path.clone());
15627 std::env::set_var("REPLY", &path);
15628 0
15629 }
15630 Err(e) => {
15631 eprintln!("zgdbmpath: {}", e);
15632 1
15633 }
15634 }
15635 }
15636
15637 fn builtin_pushd(&mut self, args: &[String]) -> i32 {
15639 let mut quiet = false;
15648 let mut physical = false;
15649 let mut positional_args: Vec<String> = Vec::new();
15650
15651 for arg in args {
15652 if arg.starts_with('-') && arg.len() > 1 {
15653 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15655 positional_args.push(arg.clone());
15656 continue;
15657 }
15658 for ch in arg[1..].chars() {
15659 match ch {
15660 'q' => quiet = true,
15661 's' => physical = false,
15662 'L' => physical = false,
15663 'P' => physical = true,
15664 _ => {}
15665 }
15666 }
15667 } else if arg.starts_with('+') {
15668 positional_args.push(arg.clone());
15669 } else {
15670 positional_args.push(arg.clone());
15671 }
15672 }
15673
15674 let current = match std::env::current_dir() {
15675 Ok(p) => p,
15676 Err(e) => {
15677 eprintln!("pushd: {}", e);
15678 return 1;
15679 }
15680 };
15681
15682 if positional_args.is_empty() {
15683 if self.dir_stack.is_empty() {
15685 eprintln!("pushd: no other directory");
15686 return 1;
15687 }
15688 let target = self.dir_stack.pop().unwrap();
15689 self.dir_stack.push(current.clone());
15690
15691 let resolved = if physical {
15692 target.canonicalize().unwrap_or(target.clone())
15693 } else {
15694 target.clone()
15695 };
15696
15697 if let Err(e) = std::env::set_current_dir(&resolved) {
15698 eprintln!("pushd: {}: {}", target.display(), e);
15699 self.dir_stack.pop();
15700 self.dir_stack.push(target);
15701 return 1;
15702 }
15703 if !quiet {
15704 self.print_dir_stack();
15705 }
15706 return 0;
15707 }
15708
15709 let arg = &positional_args[0];
15710
15711 if arg.starts_with('+') || arg.starts_with('-') {
15713 if let Ok(n) = arg[1..].parse::<usize>() {
15714 let total = self.dir_stack.len() + 1;
15715 if n >= total {
15716 eprintln!("pushd: {}: directory stack index out of range", arg);
15717 return 1;
15718 }
15719 let rotate_pos = if arg.starts_with('+') { n } else { total - n };
15721 let mut full_stack = vec![current.clone()];
15722 full_stack.extend(self.dir_stack.iter().cloned());
15723 full_stack.rotate_left(rotate_pos);
15724
15725 let target = full_stack.remove(0);
15726 self.dir_stack = full_stack;
15727
15728 let resolved = if physical {
15729 target.canonicalize().unwrap_or(target.clone())
15730 } else {
15731 target.clone()
15732 };
15733
15734 if let Err(e) = std::env::set_current_dir(&resolved) {
15735 eprintln!("pushd: {}: {}", target.display(), e);
15736 return 1;
15737 }
15738 if !quiet {
15739 self.print_dir_stack();
15740 }
15741 return 0;
15742 }
15743 }
15744
15745 let target = PathBuf::from(arg);
15747 let resolved = if physical {
15748 target.canonicalize().unwrap_or(target.clone())
15749 } else {
15750 target.clone()
15751 };
15752
15753 self.dir_stack.push(current);
15754 if let Err(e) = std::env::set_current_dir(&resolved) {
15755 eprintln!("pushd: {}: {}", arg, e);
15756 self.dir_stack.pop();
15757 return 1;
15758 }
15759 if !quiet {
15760 self.print_dir_stack();
15761 }
15762 0
15763 }
15764
15765 fn builtin_popd(&mut self, args: &[String]) -> i32 {
15767 let mut quiet = false;
15774 let mut physical = false;
15775 let mut stack_index: Option<String> = None;
15776
15777 for arg in args {
15778 if arg.starts_with('-') && arg.len() > 1 {
15779 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15781 stack_index = Some(arg.clone());
15782 continue;
15783 }
15784 for ch in arg[1..].chars() {
15785 match ch {
15786 'q' => quiet = true,
15787 's' => physical = false,
15788 'L' => physical = false,
15789 'P' => physical = true,
15790 _ => {}
15791 }
15792 }
15793 } else if arg.starts_with('+') {
15794 stack_index = Some(arg.clone());
15795 }
15796 }
15797
15798 if self.dir_stack.is_empty() {
15799 eprintln!("popd: directory stack empty");
15800 return 1;
15801 }
15802
15803 if let Some(arg) = stack_index {
15805 if arg.starts_with('+') || arg.starts_with('-') {
15806 if let Ok(n) = arg[1..].parse::<usize>() {
15807 let total = self.dir_stack.len() + 1;
15808 if n >= total {
15809 eprintln!("popd: {}: directory stack index out of range", arg);
15810 return 1;
15811 }
15812 let remove_pos = if arg.starts_with('+') {
15813 n
15814 } else {
15815 total - 1 - n
15816 };
15817 if remove_pos == 0 {
15818 let target = self.dir_stack.remove(0);
15820 let resolved = if physical {
15821 target.canonicalize().unwrap_or(target.clone())
15822 } else {
15823 target.clone()
15824 };
15825 if let Err(e) = std::env::set_current_dir(&resolved) {
15826 eprintln!("popd: {}: {}", target.display(), e);
15827 return 1;
15828 }
15829 } else {
15830 self.dir_stack.remove(remove_pos - 1);
15831 }
15832 if !quiet {
15833 self.print_dir_stack();
15834 }
15835 return 0;
15836 }
15837 }
15838 }
15839
15840 let target = self.dir_stack.pop().unwrap();
15841 let resolved = if physical {
15842 target.canonicalize().unwrap_or(target.clone())
15843 } else {
15844 target.clone()
15845 };
15846 if let Err(e) = std::env::set_current_dir(&resolved) {
15847 eprintln!("popd: {}: {}", target.display(), e);
15848 self.dir_stack.push(target);
15849 return 1;
15850 }
15851 if !quiet {
15852 self.print_dir_stack();
15853 }
15854 0
15855 }
15856
15857 fn builtin_dirs(&mut self, args: &[String]) -> i32 {
15859 let mut clear = false;
15866 let mut full_paths = false;
15867 let mut per_line = false;
15868 let mut verbose = false;
15869 let mut indices: Vec<i32> = Vec::new();
15870
15871 for arg in args {
15872 if arg.starts_with('-') && arg.len() > 1 {
15873 if arg[1..].chars().all(|c| c.is_ascii_digit()) {
15875 if let Ok(n) = arg.parse::<i32>() {
15876 indices.push(n);
15877 continue;
15878 }
15879 }
15880 for ch in arg[1..].chars() {
15881 match ch {
15882 'c' => clear = true,
15883 'l' => full_paths = true,
15884 'p' => per_line = true,
15885 'v' => verbose = true,
15886 _ => {}
15887 }
15888 }
15889 } else if arg.starts_with('+') && arg.len() > 1 {
15890 if let Ok(n) = arg[1..].parse::<i32>() {
15891 indices.push(n);
15892 }
15893 } else {
15894 if let Ok(n) = arg.parse::<i32>() {
15896 indices.push(n);
15897 }
15898 }
15899 }
15900
15901 if clear {
15902 self.dir_stack.clear();
15903 return 0;
15904 }
15905
15906 let current = std::env::current_dir().unwrap_or_default();
15907 let home = dirs::home_dir().unwrap_or_default();
15908
15909 let format_path = |p: &std::path::Path| -> String {
15910 let path_str = p.to_string_lossy().to_string();
15911 if !full_paths {
15912 let home_str = home.to_string_lossy();
15913 if path_str.starts_with(home_str.as_ref()) {
15914 return format!("~{}", &path_str[home_str.len()..]);
15915 }
15916 }
15917 path_str
15918 };
15919
15920 if !indices.is_empty() {
15922 let stack_len = self.dir_stack.len() + 1; for idx in indices {
15924 let actual_idx = if idx >= 0 {
15925 idx as usize
15926 } else {
15927 stack_len.saturating_sub((-idx) as usize)
15928 };
15929
15930 if actual_idx == 0 {
15931 println!("{}", format_path(¤t));
15932 } else if actual_idx <= self.dir_stack.len() {
15933 let stack_idx = self.dir_stack.len() - actual_idx;
15935 if let Some(dir) = self.dir_stack.get(stack_idx) {
15936 println!("{}", format_path(dir));
15937 }
15938 }
15939 }
15940 return 0;
15941 }
15942
15943 if verbose {
15944 println!(" 0 {}", format_path(¤t));
15945 for (i, dir) in self.dir_stack.iter().rev().enumerate() {
15946 println!("{:2} {}", i + 1, format_path(dir));
15947 }
15948 } else if per_line {
15949 println!("{}", format_path(¤t));
15950 for dir in self.dir_stack.iter().rev() {
15951 println!("{}", format_path(dir));
15952 }
15953 } else {
15954 let mut parts = vec![format_path(¤t)];
15955 for dir in self.dir_stack.iter().rev() {
15956 parts.push(format_path(dir));
15957 }
15958 println!("{}", parts.join(" "));
15959 }
15960 0
15961 }
15962
15963 fn print_dir_stack(&self) {
15964 let current = std::env::current_dir().unwrap_or_default();
15965 let mut parts = vec![current.to_string_lossy().to_string()];
15966 for dir in self.dir_stack.iter().rev() {
15967 parts.push(dir.to_string_lossy().to_string());
15968 }
15969 println!("{}", parts.join(" "));
15970 }
15971
15972 fn builtin_printf(&self, args: &[String]) -> i32 {
15974 if args.is_empty() {
15975 eprintln!("printf: usage: printf format [arguments]");
15976 return 1;
15977 }
15978
15979 let format = &args[0];
15980 let format_args = &args[1..];
15981 let mut arg_idx = 0;
15982 let mut output = String::new();
15983 let mut chars = format.chars().peekable();
15984
15985 while let Some(c) = chars.next() {
15986 if c == '\\' {
15987 match chars.next() {
15988 Some('n') => output.push('\n'),
15989 Some('t') => output.push('\t'),
15990 Some('r') => output.push('\r'),
15991 Some('\\') => output.push('\\'),
15992 Some('a') => output.push('\x07'),
15993 Some('b') => output.push('\x08'),
15994 Some('e') | Some('E') => output.push('\x1b'),
15995 Some('f') => output.push('\x0c'),
15996 Some('v') => output.push('\x0b'),
15997 Some('"') => output.push('"'),
15998 Some('\'') => output.push('\''),
15999 Some('0') => {
16000 let mut octal = String::new();
16001 while octal.len() < 3 {
16002 if let Some(&d) = chars.peek() {
16003 if d >= '0' && d <= '7' {
16004 octal.push(d);
16005 chars.next();
16006 } else {
16007 break;
16008 }
16009 } else {
16010 break;
16011 }
16012 }
16013 if octal.is_empty() {
16014 output.push('\0');
16015 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
16016 output.push(val as char);
16017 }
16018 }
16019 Some('x') => {
16020 let mut hex = String::new();
16021 while hex.len() < 2 {
16022 if let Some(&d) = chars.peek() {
16023 if d.is_ascii_hexdigit() {
16024 hex.push(d);
16025 chars.next();
16026 } else {
16027 break;
16028 }
16029 } else {
16030 break;
16031 }
16032 }
16033 if !hex.is_empty() {
16034 if let Ok(val) = u8::from_str_radix(&hex, 16) {
16035 output.push(val as char);
16036 }
16037 }
16038 }
16039 Some('u') => {
16040 let mut hex = String::new();
16041 while hex.len() < 4 {
16042 if let Some(&d) = chars.peek() {
16043 if d.is_ascii_hexdigit() {
16044 hex.push(d);
16045 chars.next();
16046 } else {
16047 break;
16048 }
16049 } else {
16050 break;
16051 }
16052 }
16053 if !hex.is_empty() {
16054 if let Ok(val) = u32::from_str_radix(&hex, 16) {
16055 if let Some(c) = char::from_u32(val) {
16056 output.push(c);
16057 }
16058 }
16059 }
16060 }
16061 Some('U') => {
16062 let mut hex = String::new();
16063 while hex.len() < 8 {
16064 if let Some(&d) = chars.peek() {
16065 if d.is_ascii_hexdigit() {
16066 hex.push(d);
16067 chars.next();
16068 } else {
16069 break;
16070 }
16071 } else {
16072 break;
16073 }
16074 }
16075 if !hex.is_empty() {
16076 if let Ok(val) = u32::from_str_radix(&hex, 16) {
16077 if let Some(c) = char::from_u32(val) {
16078 output.push(c);
16079 }
16080 }
16081 }
16082 }
16083 Some('c') => {
16084 print!("{}", output);
16085 return 0;
16086 }
16087 Some(other) => {
16088 output.push('\\');
16089 output.push(other);
16090 }
16091 None => output.push('\\'),
16092 }
16093 } else if c == '%' {
16094 if chars.peek() == Some(&'%') {
16095 chars.next();
16096 output.push('%');
16097 continue;
16098 }
16099
16100 let mut flags = String::new();
16101 while let Some(&f) = chars.peek() {
16102 if f == '-' || f == '+' || f == ' ' || f == '#' || f == '0' {
16103 flags.push(f);
16104 chars.next();
16105 } else {
16106 break;
16107 }
16108 }
16109
16110 let mut width = String::new();
16111 if chars.peek() == Some(&'*') {
16112 chars.next();
16113 if arg_idx < format_args.len() {
16114 width = format_args[arg_idx].clone();
16115 arg_idx += 1;
16116 }
16117 } else {
16118 while let Some(&d) = chars.peek() {
16119 if d.is_ascii_digit() {
16120 width.push(d);
16121 chars.next();
16122 } else {
16123 break;
16124 }
16125 }
16126 }
16127
16128 let mut precision = String::new();
16129 if chars.peek() == Some(&'.') {
16130 chars.next();
16131 if chars.peek() == Some(&'*') {
16132 chars.next();
16133 if arg_idx < format_args.len() {
16134 precision = format_args[arg_idx].clone();
16135 arg_idx += 1;
16136 }
16137 } else {
16138 while let Some(&d) = chars.peek() {
16139 if d.is_ascii_digit() {
16140 precision.push(d);
16141 chars.next();
16142 } else {
16143 break;
16144 }
16145 }
16146 }
16147 }
16148
16149 let specifier = chars.next().unwrap_or('s');
16150 let arg = if arg_idx < format_args.len() {
16151 let a = &format_args[arg_idx];
16152 arg_idx += 1;
16153 a.clone()
16154 } else {
16155 String::new()
16156 };
16157
16158 let width_val: usize = width.parse().unwrap_or(0);
16159 let prec_val: Option<usize> = if precision.is_empty() {
16160 None
16161 } else {
16162 precision.parse().ok()
16163 };
16164 let left_align = flags.contains('-');
16165 let zero_pad = flags.contains('0') && !left_align;
16166 let plus_sign = flags.contains('+');
16167 let space_sign = flags.contains(' ') && !plus_sign;
16168 let alt_form = flags.contains('#');
16169
16170 match specifier {
16171 's' => {
16172 let mut s = arg;
16173 if let Some(p) = prec_val {
16174 s = s.chars().take(p).collect();
16175 }
16176 if width_val > s.len() {
16177 if left_align {
16178 output.push_str(&s);
16179 output.push_str(&" ".repeat(width_val - s.len()));
16180 } else {
16181 output.push_str(&" ".repeat(width_val - s.len()));
16182 output.push_str(&s);
16183 }
16184 } else {
16185 output.push_str(&s);
16186 }
16187 }
16188 'b' => {
16189 let expanded = self.expand_printf_escapes(&arg);
16190 if let Some(p) = prec_val {
16191 let s: String = expanded.chars().take(p).collect();
16192 output.push_str(&s);
16193 } else {
16194 output.push_str(&expanded);
16195 }
16196 }
16197 'c' => {
16198 if let Some(ch) = arg.chars().next() {
16199 output.push(ch);
16200 }
16201 }
16202 'q' => {
16203 output.push('\'');
16204 for ch in arg.chars() {
16205 if ch == '\'' {
16206 output.push_str("'\\''");
16207 } else {
16208 output.push(ch);
16209 }
16210 }
16211 output.push('\'');
16212 }
16213 'd' | 'i' => {
16214 let val: i64 = if arg.starts_with("0x") || arg.starts_with("0X") {
16215 i64::from_str_radix(&arg[2..], 16).unwrap_or(0)
16216 } else if arg.starts_with("0") && arg.len() > 1 && !arg.contains('.') {
16217 i64::from_str_radix(&arg[1..], 8).unwrap_or(0)
16218 } else if arg.starts_with('\'') || arg.starts_with('"') {
16219 arg.chars().nth(1).map(|c| c as i64).unwrap_or(0)
16220 } else {
16221 arg.parse().unwrap_or(0)
16222 };
16223
16224 let sign = if val < 0 {
16225 "-"
16226 } else if plus_sign {
16227 "+"
16228 } else if space_sign {
16229 " "
16230 } else {
16231 ""
16232 };
16233 let abs_val = val.abs();
16234 let num_str = abs_val.to_string();
16235 let total_len = sign.len() + num_str.len();
16236
16237 if width_val > total_len {
16238 if left_align {
16239 output.push_str(sign);
16240 output.push_str(&num_str);
16241 output.push_str(&" ".repeat(width_val - total_len));
16242 } else if zero_pad {
16243 output.push_str(sign);
16244 output.push_str(&"0".repeat(width_val - total_len));
16245 output.push_str(&num_str);
16246 } else {
16247 output.push_str(&" ".repeat(width_val - total_len));
16248 output.push_str(sign);
16249 output.push_str(&num_str);
16250 }
16251 } else {
16252 output.push_str(sign);
16253 output.push_str(&num_str);
16254 }
16255 }
16256 'u' => {
16257 let val: u64 = if arg.starts_with("0x") || arg.starts_with("0X") {
16258 u64::from_str_radix(&arg[2..], 16).unwrap_or(0)
16259 } else if arg.starts_with("0") && arg.len() > 1 {
16260 u64::from_str_radix(&arg[1..], 8).unwrap_or(0)
16261 } else {
16262 arg.parse().unwrap_or(0)
16263 };
16264 let num_str = val.to_string();
16265 if width_val > num_str.len() {
16266 if left_align {
16267 output.push_str(&num_str);
16268 output.push_str(&" ".repeat(width_val - num_str.len()));
16269 } else if zero_pad {
16270 output.push_str(&"0".repeat(width_val - num_str.len()));
16271 output.push_str(&num_str);
16272 } else {
16273 output.push_str(&" ".repeat(width_val - num_str.len()));
16274 output.push_str(&num_str);
16275 }
16276 } else {
16277 output.push_str(&num_str);
16278 }
16279 }
16280 'o' => {
16281 let val: u64 = arg.parse().unwrap_or(0);
16282 let num_str = format!("{:o}", val);
16283 let prefix = if alt_form && val != 0 { "0" } else { "" };
16284 let total_len = prefix.len() + num_str.len();
16285 if width_val > total_len {
16286 if left_align {
16287 output.push_str(prefix);
16288 output.push_str(&num_str);
16289 output.push_str(&" ".repeat(width_val - total_len));
16290 } else {
16291 output.push_str(&" ".repeat(width_val - total_len));
16292 output.push_str(prefix);
16293 output.push_str(&num_str);
16294 }
16295 } else {
16296 output.push_str(prefix);
16297 output.push_str(&num_str);
16298 }
16299 }
16300 'x' => {
16301 let val: u64 = arg.parse().unwrap_or(0);
16302 let num_str = format!("{:x}", val);
16303 let prefix = if alt_form && val != 0 { "0x" } else { "" };
16304 let total_len = prefix.len() + num_str.len();
16305 if width_val > total_len {
16306 if left_align {
16307 output.push_str(prefix);
16308 output.push_str(&num_str);
16309 output.push_str(&" ".repeat(width_val - total_len));
16310 } else {
16311 output.push_str(&" ".repeat(width_val - total_len));
16312 output.push_str(prefix);
16313 output.push_str(&num_str);
16314 }
16315 } else {
16316 output.push_str(prefix);
16317 output.push_str(&num_str);
16318 }
16319 }
16320 'X' => {
16321 let val: u64 = arg.parse().unwrap_or(0);
16322 let num_str = format!("{:X}", val);
16323 let prefix = if alt_form && val != 0 { "0X" } else { "" };
16324 let total_len = prefix.len() + num_str.len();
16325 if width_val > total_len {
16326 if left_align {
16327 output.push_str(prefix);
16328 output.push_str(&num_str);
16329 output.push_str(&" ".repeat(width_val - total_len));
16330 } else {
16331 output.push_str(&" ".repeat(width_val - total_len));
16332 output.push_str(prefix);
16333 output.push_str(&num_str);
16334 }
16335 } else {
16336 output.push_str(prefix);
16337 output.push_str(&num_str);
16338 }
16339 }
16340 'e' | 'E' => {
16341 let val: f64 = arg.parse().unwrap_or(0.0);
16342 let prec = prec_val.unwrap_or(6);
16343 let formatted = if specifier == 'e' {
16344 format!("{:.prec$e}", val, prec = prec)
16345 } else {
16346 format!("{:.prec$E}", val, prec = prec)
16347 };
16348 if width_val > formatted.len() {
16349 if left_align {
16350 output.push_str(&formatted);
16351 output.push_str(&" ".repeat(width_val - formatted.len()));
16352 } else {
16353 output.push_str(&" ".repeat(width_val - formatted.len()));
16354 output.push_str(&formatted);
16355 }
16356 } else {
16357 output.push_str(&formatted);
16358 }
16359 }
16360 'f' | 'F' => {
16361 let val: f64 = arg.parse().unwrap_or(0.0);
16362 let prec = prec_val.unwrap_or(6);
16363 let sign = if val < 0.0 {
16364 "-"
16365 } else if plus_sign {
16366 "+"
16367 } else if space_sign {
16368 " "
16369 } else {
16370 ""
16371 };
16372 let formatted = format!("{:.prec$}", val.abs(), prec = prec);
16373 let total = sign.len() + formatted.len();
16374 if width_val > total {
16375 if left_align {
16376 output.push_str(sign);
16377 output.push_str(&formatted);
16378 output.push_str(&" ".repeat(width_val - total));
16379 } else if zero_pad {
16380 output.push_str(sign);
16381 output.push_str(&"0".repeat(width_val - total));
16382 output.push_str(&formatted);
16383 } else {
16384 output.push_str(&" ".repeat(width_val - total));
16385 output.push_str(sign);
16386 output.push_str(&formatted);
16387 }
16388 } else {
16389 output.push_str(sign);
16390 output.push_str(&formatted);
16391 }
16392 }
16393 'g' | 'G' => {
16394 let val: f64 = arg.parse().unwrap_or(0.0);
16395 let prec = prec_val.unwrap_or(6).max(1);
16396 let formatted = if specifier == 'g' {
16397 format!("{:.prec$}", val, prec = prec)
16398 } else {
16399 format!("{:.prec$}", val, prec = prec).to_uppercase()
16400 };
16401 output.push_str(&formatted);
16402 }
16403 'a' | 'A' => {
16404 let val: f64 = arg.parse().unwrap_or(0.0);
16405 let formatted = float_to_hex(val, specifier == 'A');
16406 output.push_str(&formatted);
16407 }
16408 _ => {
16409 output.push('%');
16410 output.push(specifier);
16411 }
16412 }
16413 } else {
16414 output.push(c);
16415 }
16416 }
16417
16418 print!("{}", output);
16419 0
16420 }
16421
16422 fn expand_printf_escapes(&self, s: &str) -> String {
16423 let mut result = String::new();
16424 let mut chars = s.chars().peekable();
16425 while let Some(c) = chars.next() {
16426 if c == '\\' {
16427 match chars.next() {
16428 Some('n') => result.push('\n'),
16429 Some('t') => result.push('\t'),
16430 Some('r') => result.push('\r'),
16431 Some('\\') => result.push('\\'),
16432 Some('a') => result.push('\x07'),
16433 Some('b') => result.push('\x08'),
16434 Some('e') | Some('E') => result.push('\x1b'),
16435 Some('f') => result.push('\x0c'),
16436 Some('v') => result.push('\x0b'),
16437 Some('0') => {
16438 let mut octal = String::new();
16439 while octal.len() < 3 {
16440 if let Some(&d) = chars.peek() {
16441 if d >= '0' && d <= '7' {
16442 octal.push(d);
16443 chars.next();
16444 } else {
16445 break;
16446 }
16447 } else {
16448 break;
16449 }
16450 }
16451 if octal.is_empty() {
16452 result.push('\0');
16453 } else if let Ok(val) = u8::from_str_radix(&octal, 8) {
16454 result.push(val as char);
16455 }
16456 }
16457 Some('c') => break,
16458 Some(other) => {
16459 result.push('\\');
16460 result.push(other);
16461 }
16462 None => result.push('\\'),
16463 }
16464 } else {
16465 result.push(c);
16466 }
16467 }
16468 result
16469 }
16470
16471 fn evaluate_arithmetic_expr(&mut self, expr: &str) -> i64 {
16472 self.eval_arith_expr(expr)
16473 }
16474
16475 fn builtin_break(&mut self, args: &[String]) -> i32 {
16481 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
16482 self.breaking = levels.max(1);
16483 0
16484 }
16485
16486 fn builtin_continue(&mut self, args: &[String]) -> i32 {
16488 let levels: i32 = args.first().and_then(|s| s.parse().ok()).unwrap_or(1);
16489 self.continuing = levels.max(1);
16490 0
16491 }
16492
16493 fn builtin_disable(&mut self, args: &[String]) -> i32 {
16495 let mut disable_aliases = false;
16496 let mut disable_builtins = false;
16497 let mut disable_functions = false;
16498 let mut names = Vec::new();
16499
16500 let mut iter = args.iter();
16501 while let Some(arg) = iter.next() {
16502 match arg.as_str() {
16503 "-a" => disable_aliases = true,
16504 "-f" => disable_functions = true,
16505 "-r" => disable_builtins = true,
16506 _ if arg.starts_with('-') => {}
16507 _ => names.push(arg.clone()),
16508 }
16509 }
16510
16511 if !disable_aliases && !disable_functions {
16513 disable_builtins = true;
16514 }
16515
16516 for name in names {
16517 if disable_aliases {
16518 self.aliases.remove(&name);
16519 }
16520 if disable_functions {
16521 self.functions.remove(&name);
16522 }
16523 if disable_builtins {
16524 self.options.insert(format!("_disabled_{}", name), true);
16526 }
16527 }
16528 0
16529 }
16530
16531 fn builtin_enable(&mut self, args: &[String]) -> i32 {
16533 for arg in args {
16534 if !arg.starts_with('-') {
16535 self.options.remove(&format!("_disabled_{}", arg));
16536 }
16537 }
16538 0
16539 }
16540
16541 fn builtin_emulate(&mut self, args: &[String]) -> i32 {
16543 let mut local_mode = false;
16546 let mut reset_mode = false;
16547 let mut list_mode = false;
16548 let mut mode: Option<String> = None;
16549 let mut command_arg: Option<String> = None;
16550 let mut extra_set_opts: Vec<String> = Vec::new();
16551 let mut extra_unset_opts: Vec<String> = Vec::new();
16552
16553 let mut i = 0;
16554 while i < args.len() {
16555 let arg = &args[i];
16556
16557 if arg == "-c" {
16558 i += 1;
16560 if i < args.len() {
16561 command_arg = Some(args[i].clone());
16562 } else {
16563 eprintln!("emulate: -c requires an argument");
16564 return 1;
16565 }
16566 } else if arg == "-o" {
16567 i += 1;
16569 if i < args.len() {
16570 extra_set_opts.push(args[i].clone());
16571 } else {
16572 eprintln!("emulate: -o requires an argument");
16573 return 1;
16574 }
16575 } else if arg == "+o" {
16576 i += 1;
16578 if i < args.len() {
16579 extra_unset_opts.push(args[i].clone());
16580 } else {
16581 eprintln!("emulate: +o requires an argument");
16582 return 1;
16583 }
16584 } else if arg.starts_with('-') && arg.len() > 1 && !arg.starts_with("--") {
16585 for ch in arg[1..].chars() {
16587 match ch {
16588 'L' => local_mode = true,
16589 'R' => reset_mode = true,
16590 'l' => list_mode = true,
16591 _ => {
16592 eprintln!("emulate: bad option: -{}", ch);
16593 return 1;
16594 }
16595 }
16596 }
16597 } else if arg.starts_with('+') && arg.len() > 1 {
16598 for ch in arg[1..].chars() {
16600 extra_unset_opts.push(ch.to_string());
16602 }
16603 } else if mode.is_none() {
16604 mode = Some(arg.clone());
16605 }
16606 i += 1;
16607 }
16608
16609 if local_mode && command_arg.is_some() {
16611 eprintln!("emulate: -L and -c are mutually exclusive");
16612 return 1;
16613 }
16614
16615 if mode.is_none() && !list_mode {
16617 let current = self
16618 .variables
16619 .get("EMULATE")
16620 .cloned()
16621 .unwrap_or_else(|| "zsh".to_string());
16622 println!("{}", current);
16623 return 0;
16624 }
16625
16626 let mode = mode.unwrap_or_else(|| "zsh".to_string());
16627
16628 let (set_opts, unset_opts) = Self::emulate_mode_options(&mode, reset_mode);
16630
16631 if list_mode {
16633 for opt in &set_opts {
16634 println!("{}", opt);
16635 }
16636 for opt in &unset_opts {
16637 println!("no{}", opt);
16638 }
16639 if local_mode {
16640 println!("localoptions");
16641 println!("localpatterns");
16642 println!("localtraps");
16643 }
16644 return 0;
16645 }
16646
16647 let saved_options = if command_arg.is_some() {
16649 Some(self.options.clone())
16650 } else {
16651 None
16652 };
16653 let saved_emulate = if command_arg.is_some() {
16654 self.variables.get("EMULATE").cloned()
16655 } else {
16656 None
16657 };
16658
16659 self.variables.insert("EMULATE".to_string(), mode.clone());
16661
16662 for opt in &set_opts {
16664 let opt_name = opt.to_lowercase().replace('_', "");
16665 self.options.insert(opt_name, true);
16666 }
16667 for opt in &unset_opts {
16668 let opt_name = opt.to_lowercase().replace('_', "");
16669 self.options.insert(opt_name, false);
16670 }
16671
16672 for opt in &extra_set_opts {
16674 let opt_name = opt.to_lowercase().replace('_', "");
16675 self.options.insert(opt_name, true);
16676 }
16677 for opt in &extra_unset_opts {
16678 let opt_name = opt.to_lowercase().replace('_', "");
16679 self.options.insert(opt_name, false);
16680 }
16681
16682 if local_mode {
16684 self.options.insert("localoptions".to_string(), true);
16685 self.options.insert("localpatterns".to_string(), true);
16686 self.options.insert("localtraps".to_string(), true);
16687 }
16688
16689 let result = if let Some(cmd) = command_arg {
16691 let status = self.execute_script(&cmd).unwrap_or(1);
16692
16693 if let Some(opts) = saved_options {
16695 self.options = opts;
16696 }
16697 if let Some(emu) = saved_emulate {
16698 self.variables.insert("EMULATE".to_string(), emu);
16699 } else {
16700 self.variables.remove("EMULATE");
16701 }
16702
16703 status
16704 } else {
16705 0
16706 };
16707
16708 result
16709 }
16710
16711 fn emulate_mode_options(mode: &str, reset: bool) -> (Vec<&'static str>, Vec<&'static str>) {
16713 match mode {
16714 "zsh" => {
16715 if reset {
16716 (
16718 vec![
16719 "aliases",
16720 "alwayslastprompt",
16721 "autolist",
16722 "automenu",
16723 "autoparamslash",
16724 "autoremoveslash",
16725 "banghist",
16726 "bareglobqual",
16727 "completeinword",
16728 "extendedhistory",
16729 "functionargzero",
16730 "glob",
16731 "hashcmds",
16732 "hashdirs",
16733 "histexpand",
16734 "histignoredups",
16735 "interactivecomments",
16736 "listambiguous",
16737 "listtypes",
16738 "multios",
16739 "nomatch",
16740 "notify",
16741 "promptpercent",
16742 "promptsubst",
16743 ],
16744 vec![
16745 "ksharrays",
16746 "kshglob",
16747 "shwordsplit",
16748 "shglob",
16749 "posixbuiltins",
16750 "posixidentifiers",
16751 "posixstrings",
16752 "bsdecho",
16753 "ignorebraces",
16754 ],
16755 )
16756 } else {
16757 (vec!["functionargzero"], vec!["ksharrays", "shwordsplit"])
16759 }
16760 }
16761 "sh" => {
16762 let set = vec![
16763 "ksharrays",
16764 "shwordsplit",
16765 "posixbuiltins",
16766 "shglob",
16767 "shfileexpansion",
16768 "globsubst",
16769 "interactivecomments",
16770 "rmstarsilent",
16771 "bsdecho",
16772 "ignorebraces",
16773 ];
16774 let unset = vec![
16775 "badpattern",
16776 "banghist",
16777 "bgnice",
16778 "equals",
16779 "functionargzero",
16780 "globalexport",
16781 "multios",
16782 "nomatch",
16783 "notify",
16784 "promptpercent",
16785 ];
16786 (set, unset)
16787 }
16788 "ksh" => {
16789 let set = vec![
16790 "ksharrays",
16791 "kshglob",
16792 "shwordsplit",
16793 "posixbuiltins",
16794 "kshoptionprint",
16795 "localoptions",
16796 "promptbang",
16797 "promptsubst",
16798 "singlelinezle",
16799 "interactivecomments",
16800 ];
16801 let unset = vec![
16802 "badpattern",
16803 "banghist",
16804 "bgnice",
16805 "equals",
16806 "functionargzero",
16807 "globalexport",
16808 "multios",
16809 "nomatch",
16810 "notify",
16811 "promptpercent",
16812 ];
16813 (set, unset)
16814 }
16815 "csh" => {
16816 (vec!["cshnullglob", "cshjunkiequotes"], vec!["nomatch"])
16818 }
16819 "bash" => {
16820 let set = vec![
16821 "ksharrays",
16822 "shwordsplit",
16823 "interactivecomments",
16824 "shfileexpansion",
16825 "globsubst",
16826 ];
16827 let unset = vec![
16828 "badpattern",
16829 "banghist",
16830 "functionargzero",
16831 "multios",
16832 "nomatch",
16833 "notify",
16834 "promptpercent",
16835 ];
16836 (set, unset)
16837 }
16838 _ => (vec![], vec![]),
16839 }
16840 }
16841
16842 fn builtin_exec(&mut self, args: &[String]) -> i32 {
16844 let mut clear_env = false;
16850 let mut login_shell = false;
16851 let mut argv0: Option<String> = None;
16852 let mut cmd_args: Vec<String> = Vec::new();
16853
16854 let mut i = 0;
16855 while i < args.len() {
16856 let arg = &args[i];
16857
16858 if arg == "-c" && cmd_args.is_empty() {
16859 clear_env = true;
16860 } else if arg == "-l" && cmd_args.is_empty() {
16861 login_shell = true;
16862 } else if arg == "-a" && cmd_args.is_empty() {
16863 i += 1;
16864 if i < args.len() {
16865 argv0 = Some(args[i].clone());
16866 }
16867 } else if arg.starts_with('-') && cmd_args.is_empty() {
16868 for ch in arg[1..].chars() {
16870 match ch {
16871 'c' => clear_env = true,
16872 'l' => login_shell = true,
16873 'a' => {
16874 i += 1;
16875 if i < args.len() {
16876 argv0 = Some(args[i].clone());
16877 }
16878 }
16879 _ => {}
16880 }
16881 }
16882 } else {
16883 cmd_args.push(arg.clone());
16884 }
16885 i += 1;
16886 }
16887
16888 if cmd_args.is_empty() {
16889 if clear_env {
16891 for (key, _) in env::vars() {
16892 env::remove_var(&key);
16893 }
16894 }
16895 return 0;
16896 }
16897
16898 let cmd = &cmd_args[0];
16899 let rest_args: Vec<&str> = cmd_args[1..].iter().map(|s| s.as_str()).collect();
16900
16901 let effective_argv0 = if let Some(a0) = argv0 {
16903 a0
16904 } else if login_shell {
16905 format!("-{}", cmd)
16906 } else {
16907 cmd.clone()
16908 };
16909
16910 use std::os::unix::process::CommandExt;
16911 let mut command = std::process::Command::new(cmd);
16912 command.arg0(&effective_argv0);
16913 command.args(&rest_args);
16914
16915 if clear_env {
16916 command.env_clear();
16917 }
16918
16919 let err = command.exec();
16920 eprintln!("exec: {}: {}", cmd, err);
16921 1
16922 }
16923
16924 fn builtin_float(&mut self, args: &[String]) -> i32 {
16926 for arg in args {
16927 if arg.starts_with('-') {
16928 continue;
16929 }
16930 if let Some(eq_pos) = arg.find('=') {
16931 let name = &arg[..eq_pos];
16932 let value = &arg[eq_pos + 1..];
16933 let float_val: f64 = value.parse().unwrap_or(0.0);
16934 self.variables
16935 .insert(name.to_string(), float_val.to_string());
16936 self.options.insert(format!("_float_{}", name), true);
16937 } else {
16938 self.variables.insert(arg.clone(), "0.0".to_string());
16939 self.options.insert(format!("_float_{}", arg), true);
16940 }
16941 }
16942 0
16943 }
16944
16945 fn builtin_integer(&mut self, args: &[String]) -> i32 {
16947 for arg in args {
16948 if arg.starts_with('-') {
16949 continue;
16950 }
16951 if let Some(eq_pos) = arg.find('=') {
16952 let name = &arg[..eq_pos];
16953 let value = &arg[eq_pos + 1..];
16954 let int_val: i64 = value.parse().unwrap_or(0);
16955 self.variables.insert(name.to_string(), int_val.to_string());
16956 self.options.insert(format!("_integer_{}", name), true);
16957 } else {
16958 self.variables.insert(arg.clone(), "0".to_string());
16959 self.options.insert(format!("_integer_{}", arg), true);
16960 }
16961 }
16962 0
16963 }
16964
16965 fn builtin_functions(&self, args: &[String]) -> i32 {
16967 let mut list_only = false;
16968 let mut show_trace = false;
16969 let mut names: Vec<&str> = Vec::new();
16970
16971 for arg in args {
16972 match arg.as_str() {
16973 "-l" => list_only = true,
16974 "-t" => show_trace = true,
16975 _ if arg.starts_with('-') => {}
16976 _ => names.push(arg),
16977 }
16978 }
16979
16980 if names.is_empty() {
16981 let mut func_names: Vec<_> = self.functions.keys().collect();
16983 func_names.sort();
16984 for name in func_names {
16985 if list_only {
16986 println!("{}", name);
16987 } else if let Some(func) = self.functions.get(name) {
16988 let body = crate::text::getpermtext(func);
16989 println!("{} () {{\n\t{}\n}}", name, body.trim());
16990 }
16991 }
16992 } else {
16993 for name in names {
16995 if let Some(func) = self.functions.get(name) {
16996 if show_trace {
16997 println!("functions -t {}", name);
16998 } else {
16999 let body = crate::text::getpermtext(func);
17000 println!("{} () {{\n\t{}\n}}", name, body.trim());
17001 }
17002 } else {
17003 eprintln!("functions: no such function: {}", name);
17004 return 1;
17005 }
17006 }
17007 }
17008 0
17009 }
17010
17011 fn builtin_print(&mut self, args: &[String]) -> i32 {
17013 let mut no_newline = false;
17016 let mut one_per_line = false;
17017 let mut interpret_escapes = true; let mut raw_mode = false;
17019 let mut prompt_expand = false;
17020 let mut fd: i32 = 1; let mut columns = 0usize;
17022 let mut null_terminate = false;
17023 let mut push_to_stack = false;
17024 let mut add_to_history = false;
17025 let mut sort_asc = false;
17026 let mut sort_desc = false;
17027 let mut named_dir_subst = false;
17028 let mut store_var: Option<String> = None;
17029 let mut format_string: Option<String> = None;
17030 let mut output_args: Vec<String> = Vec::new();
17031
17032 let mut i = 0;
17033 while i < args.len() {
17034 let arg = &args[i];
17035
17036 if arg == "--" {
17037 i += 1;
17038 while i < args.len() {
17039 output_args.push(args[i].clone());
17040 i += 1;
17041 }
17042 break;
17043 }
17044
17045 if arg.starts_with('-')
17046 && arg.len() > 1
17047 && !arg
17048 .chars()
17049 .nth(1)
17050 .map(|c| c.is_ascii_digit())
17051 .unwrap_or(false)
17052 {
17053 let mut chars = arg[1..].chars().peekable();
17054 while let Some(ch) = chars.next() {
17055 match ch {
17056 'n' => no_newline = true,
17057 'l' => one_per_line = true,
17058 'r' => {
17059 raw_mode = true;
17060 interpret_escapes = false;
17061 }
17062 'R' => {
17063 raw_mode = true;
17064 interpret_escapes = false;
17065 }
17066 'e' => interpret_escapes = true,
17067 'E' => interpret_escapes = false,
17068 'P' => prompt_expand = true,
17069 'N' => null_terminate = true,
17070 'z' => push_to_stack = true,
17071 's' => add_to_history = true,
17072 'o' => sort_asc = true,
17073 'O' => sort_desc = true,
17074 'D' => named_dir_subst = true,
17075 'c' => columns = 1,
17076 'a' | 'b' | 'i' | 'm' | 'p' | 'S' | 'x' | 'X' => {} 'u' => {
17078 let rest: String = chars.collect();
17080 if !rest.is_empty() {
17081 fd = rest.parse().unwrap_or(1);
17082 } else {
17083 i += 1;
17084 if i < args.len() {
17085 fd = args[i].parse().unwrap_or(1);
17086 }
17087 }
17088 break;
17089 }
17090 'C' => {
17091 let rest: String = chars.collect();
17093 if !rest.is_empty() {
17094 columns = rest.parse().unwrap_or(0);
17095 } else {
17096 i += 1;
17097 if i < args.len() {
17098 columns = args[i].parse().unwrap_or(0);
17099 }
17100 }
17101 break;
17102 }
17103 'v' => {
17104 let rest: String = chars.collect();
17106 if !rest.is_empty() {
17107 store_var = Some(rest);
17108 } else {
17109 i += 1;
17110 if i < args.len() {
17111 store_var = Some(args[i].clone());
17112 }
17113 }
17114 break;
17115 }
17116 'f' => {
17117 let rest: String = chars.collect();
17119 if !rest.is_empty() {
17120 format_string = Some(rest);
17121 } else {
17122 i += 1;
17123 if i < args.len() {
17124 format_string = Some(args[i].clone());
17125 }
17126 }
17127 break;
17128 }
17129 _ => {}
17130 }
17131 }
17132 } else {
17133 output_args.push(arg.clone());
17134 }
17135 i += 1;
17136 }
17137
17138 let _ = push_to_stack; let _ = fd; if sort_asc {
17143 output_args.sort();
17144 } else if sort_desc {
17145 output_args.sort_by(|a, b| b.cmp(a));
17146 }
17147
17148 if let Some(fmt) = format_string {
17150 let output = self.printf_format(&fmt, &output_args);
17151 if let Some(var) = store_var {
17152 self.variables.insert(var, output);
17153 } else {
17154 print!("{}", output);
17155 }
17156 return 0;
17157 }
17158
17159 let processed: Vec<String> = output_args
17161 .iter()
17162 .map(|s| {
17163 let mut result = s.clone();
17164 if prompt_expand {
17165 result = self.expand_prompt_string(&result);
17166 }
17167 if interpret_escapes && !raw_mode {
17168 result = self.expand_printf_escapes(&result);
17169 }
17170 if named_dir_subst {
17171 if let Ok(home) = env::var("HOME") {
17173 if result.starts_with(&home) {
17174 result = format!("~{}", &result[home.len()..]);
17175 }
17176 }
17177 for (name, path) in &self.named_dirs {
17179 let path_str = path.to_string_lossy();
17180 if result.starts_with(path_str.as_ref()) {
17181 result = format!("~{}{}", name, &result[path_str.len()..]);
17182 break;
17183 }
17184 }
17185 }
17186 result
17187 })
17188 .collect();
17189
17190 let separator = if one_per_line { "\n" } else { " " };
17192 let terminator = if null_terminate {
17193 "\0"
17194 } else if no_newline {
17195 ""
17196 } else {
17197 "\n"
17198 };
17199
17200 let output = if one_per_line {
17202 processed.join("\n")
17203 } else if columns > 0 {
17204 let mut result = String::new();
17206 let num_items = processed.len();
17207 let rows = (num_items + columns - 1) / columns;
17208 for row in 0..rows {
17209 let mut row_items = Vec::new();
17210 for col in 0..columns {
17211 let idx = row + col * rows;
17212 if idx < num_items {
17213 row_items.push(processed[idx].as_str());
17214 }
17215 }
17216 result.push_str(&row_items.join("\t"));
17217 if row < rows - 1 {
17218 result.push('\n');
17219 }
17220 }
17221 result
17222 } else {
17223 processed.join(separator)
17224 };
17225
17226 if add_to_history {
17228 if let Some(ref mut engine) = self.history {
17229 let _ = engine.add(&output, None);
17230 }
17231 }
17232
17233 if let Some(var) = store_var {
17235 self.variables.insert(var, output);
17236 } else {
17237 print!("{}{}", output, terminator);
17238 }
17239
17240 0
17241 }
17242
17243 fn printf_format(&self, format: &str, args: &[String]) -> String {
17244 let mut result = String::new();
17245 let mut arg_idx = 0;
17246 let mut chars = format.chars().peekable();
17247
17248 while let Some(ch) = chars.next() {
17249 if ch == '%' {
17250 if chars.peek() == Some(&'%') {
17251 chars.next();
17252 result.push('%');
17253 continue;
17254 }
17255
17256 let mut spec = String::from("%");
17258
17259 while let Some(&c) = chars.peek() {
17261 if c == '-' || c == '+' || c == ' ' || c == '#' || c == '0' {
17262 spec.push(c);
17263 chars.next();
17264 } else {
17265 break;
17266 }
17267 }
17268
17269 while let Some(&c) = chars.peek() {
17271 if c.is_ascii_digit() {
17272 spec.push(c);
17273 chars.next();
17274 } else {
17275 break;
17276 }
17277 }
17278
17279 if chars.peek() == Some(&'.') {
17281 spec.push('.');
17282 chars.next();
17283 while let Some(&c) = chars.peek() {
17284 if c.is_ascii_digit() {
17285 spec.push(c);
17286 chars.next();
17287 } else {
17288 break;
17289 }
17290 }
17291 }
17292
17293 if let Some(conv) = chars.next() {
17295 let arg = args.get(arg_idx).map(|s| s.as_str()).unwrap_or("");
17296 arg_idx += 1;
17297
17298 match conv {
17299 's' => result.push_str(arg),
17300 'd' | 'i' => {
17301 let n: i64 = arg.parse().unwrap_or(0);
17302 result.push_str(&n.to_string());
17303 }
17304 'u' => {
17305 let n: u64 = arg.parse().unwrap_or(0);
17306 result.push_str(&n.to_string());
17307 }
17308 'x' => {
17309 let n: i64 = arg.parse().unwrap_or(0);
17310 result.push_str(&format!("{:x}", n));
17311 }
17312 'X' => {
17313 let n: i64 = arg.parse().unwrap_or(0);
17314 result.push_str(&format!("{:X}", n));
17315 }
17316 'o' => {
17317 let n: i64 = arg.parse().unwrap_or(0);
17318 result.push_str(&format!("{:o}", n));
17319 }
17320 'f' | 'F' | 'e' | 'E' | 'g' | 'G' => {
17321 let n: f64 = arg.parse().unwrap_or(0.0);
17322 result.push_str(&format!("{}", n));
17323 }
17324 'c' => {
17325 if let Some(c) = arg.chars().next() {
17326 result.push(c);
17327 }
17328 }
17329 'b' => {
17330 result.push_str(&self.expand_printf_escapes(arg));
17331 }
17332 'n' => result.push('\n'),
17333 _ => {
17334 result.push('%');
17335 result.push(conv);
17336 }
17337 }
17338 }
17339 } else {
17340 result.push(ch);
17341 }
17342 }
17343
17344 result
17345 }
17346
17347 fn builtin_whence(&self, args: &[String]) -> i32 {
17349 let mut verbose = false;
17362 let mut csh_style = false;
17363 let mut word_type = false;
17364 let mut skip_functions = false;
17365 let mut path_only = false;
17366 let mut show_all = false;
17367 let mut pattern_mode = false;
17368 let mut show_symlink = false;
17369 let mut show_symlink_steps = false;
17370 let mut tab_expand: Option<usize> = None;
17371 let mut names: Vec<&str> = Vec::new();
17372
17373 let mut i = 0;
17374 while i < args.len() {
17375 let arg = &args[i];
17376
17377 if arg == "--" {
17378 i += 1;
17379 while i < args.len() {
17380 names.push(&args[i]);
17381 i += 1;
17382 }
17383 break;
17384 }
17385
17386 if arg.starts_with('-') && arg.len() > 1 {
17387 let mut chars = arg[1..].chars().peekable();
17388 while let Some(ch) = chars.next() {
17389 match ch {
17390 'v' => verbose = true,
17391 'c' => csh_style = true,
17392 'w' => word_type = true,
17393 'f' => skip_functions = true,
17394 'p' => path_only = true,
17395 'a' => show_all = true,
17396 'm' => pattern_mode = true,
17397 's' => show_symlink = true,
17398 'S' => show_symlink_steps = true,
17399 'x' => {
17400 let rest: String = chars.collect();
17402 if !rest.is_empty() {
17403 tab_expand = rest.parse().ok();
17404 } else {
17405 i += 1;
17406 if i < args.len() {
17407 tab_expand = args[i].parse().ok();
17408 }
17409 }
17410 break;
17411 }
17412 _ => {}
17413 }
17414 }
17415 } else {
17416 names.push(arg);
17417 }
17418 i += 1;
17419 }
17420
17421 let _ = csh_style; let _ = pattern_mode; let _ = tab_expand;
17424
17425 let mut status = 0;
17426 for name in names {
17427 let mut found = false;
17428 let mut word = "none";
17429
17430 if !path_only {
17431 if self.is_reserved_word(name) {
17433 found = true;
17434 word = "reserved";
17435 if word_type {
17436 println!("{}: {}", name, word);
17437 } else if verbose {
17438 println!("{} is a reserved word", name);
17439 } else {
17440 println!("{}", name);
17441 }
17442 if !show_all {
17443 continue;
17444 }
17445 }
17446
17447 if let Some(alias_val) = self.aliases.get(name) {
17449 found = true;
17450 word = "alias";
17451 if word_type {
17452 println!("{}: {}", name, word);
17453 } else if verbose {
17454 println!("{} is an alias for {}", name, alias_val);
17455 } else {
17456 println!("{}", alias_val);
17457 }
17458 if !show_all {
17459 continue;
17460 }
17461 }
17462
17463 if !skip_functions && self.functions.contains_key(name) {
17465 found = true;
17466 word = "function";
17467 if word_type {
17468 println!("{}: {}", name, word);
17469 } else if verbose {
17470 println!("{} is a shell function", name);
17471 } else {
17472 println!("{}", name);
17473 }
17474 if !show_all {
17475 continue;
17476 }
17477 }
17478
17479 if self.is_builtin(name) {
17481 found = true;
17482 word = "builtin";
17483 if word_type {
17484 println!("{}: {}", name, word);
17485 } else if verbose {
17486 println!("{} is a shell builtin", name);
17487 } else {
17488 println!("{}", name);
17489 }
17490 if !show_all {
17491 continue;
17492 }
17493 }
17494
17495 if let Some(path) = self.named_dirs.get(name) {
17498 found = true;
17499 word = "hashed";
17500 if word_type {
17501 println!("{}: {}", name, word);
17502 } else if verbose {
17503 println!("{} is hashed ({})", name, path.display());
17504 } else {
17505 println!("{}", path.display());
17506 }
17507 if !show_all {
17508 continue;
17509 }
17510 }
17511 }
17512
17513 if let Some(path) = self.find_in_path(name) {
17515 found = true;
17516 word = "command";
17517
17518 let display_path = if show_symlink || show_symlink_steps {
17520 let p = std::path::Path::new(&path);
17521 if show_symlink_steps {
17522 let mut current = p.to_path_buf();
17523 let mut steps = vec![path.clone()];
17524 while let Ok(target) = std::fs::read_link(¤t) {
17525 let resolved = if target.is_absolute() {
17526 target.clone()
17527 } else {
17528 current
17529 .parent()
17530 .unwrap_or(std::path::Path::new("/"))
17531 .join(&target)
17532 };
17533 steps.push(resolved.to_string_lossy().to_string());
17534 current = resolved;
17535 }
17536 steps.join(" -> ")
17537 } else {
17538 match p.canonicalize() {
17539 Ok(resolved) => format!("{} -> {}", path, resolved.display()),
17540 Err(_) => path.clone(),
17541 }
17542 }
17543 } else {
17544 path.clone()
17545 };
17546
17547 if word_type {
17548 println!("{}: {}", name, word);
17549 } else if verbose {
17550 println!("{} is {}", name, display_path);
17551 } else {
17552 println!("{}", display_path);
17553 }
17554 }
17555
17556 if !found {
17557 if word_type {
17558 println!("{}: none", name);
17559 } else if verbose {
17560 println!("{} not found", name);
17561 }
17562 status = 1;
17563 }
17564 }
17565 status
17566 }
17567
17568 fn is_reserved_word(&self, name: &str) -> bool {
17569 matches!(
17570 name,
17571 "if" | "then"
17572 | "else"
17573 | "elif"
17574 | "fi"
17575 | "case"
17576 | "esac"
17577 | "for"
17578 | "select"
17579 | "while"
17580 | "until"
17581 | "do"
17582 | "done"
17583 | "in"
17584 | "function"
17585 | "time"
17586 | "coproc"
17587 | "{"
17588 | "}"
17589 | "!"
17590 | "[["
17591 | "]]"
17592 | "(("
17593 | "))"
17594 )
17595 }
17596
17597 fn builtin_where(&self, args: &[String]) -> i32 {
17599 let mut new_args = vec!["-a".to_string(), "-v".to_string()];
17601 new_args.extend(args.iter().cloned());
17602 self.builtin_whence(&new_args)
17603 }
17604
17605 fn builtin_which(&self, args: &[String]) -> i32 {
17607 let mut new_args = vec!["-c".to_string()];
17609 new_args.extend(args.iter().cloned());
17610 self.builtin_whence(&new_args)
17611 }
17612
17613 fn is_builtin(&self, name: &str) -> bool {
17616 BUILTIN_SET.contains(name) || name.starts_with('_')
17617 }
17618
17619 fn find_in_path(&self, name: &str) -> Option<String> {
17621 if let Some(path) = self.command_hash.get(name) {
17623 return Some(path.clone());
17624 }
17625 let path_var = env::var("PATH").unwrap_or_default();
17627 for dir in path_var.split(':') {
17628 let full_path = format!("{}/{}", dir, name);
17629 if std::path::Path::new(&full_path).exists() {
17630 return Some(full_path);
17631 }
17632 }
17633 None
17634 }
17635
17636 fn builtin_ulimit(&self, args: &[String]) -> i32 {
17638 use libc::{getrlimit, rlimit, setrlimit};
17639 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
17640 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
17641
17642 let mut resource = RLIMIT_FSIZE; let mut hard = false;
17644 let mut soft = true;
17645 let mut value: Option<u64> = None;
17646
17647 let mut iter = args.iter();
17648 while let Some(arg) = iter.next() {
17649 match arg.as_str() {
17650 "-H" => {
17651 hard = true;
17652 soft = false;
17653 }
17654 "-S" => {
17655 soft = true;
17656 hard = false;
17657 }
17658 "-a" => {
17659 self.print_all_limits(soft);
17661 return 0;
17662 }
17663 "-c" => resource = RLIMIT_CORE,
17664 "-d" => resource = RLIMIT_DATA,
17665 "-f" => resource = RLIMIT_FSIZE,
17666 "-n" => resource = RLIMIT_NOFILE,
17667 "-s" => resource = RLIMIT_STACK,
17668 "-t" => resource = RLIMIT_CPU,
17669 "-u" => resource = RLIMIT_NPROC,
17670 "-v" => resource = RLIMIT_AS,
17671 "-m" => resource = RLIMIT_RSS,
17672 "unlimited" => value = Some(libc::RLIM_INFINITY as u64),
17673 _ if !arg.starts_with('-') => {
17674 value = arg.parse().ok();
17675 }
17676 _ => {}
17677 }
17678 }
17679
17680 let mut rlim = rlimit {
17681 rlim_cur: 0,
17682 rlim_max: 0,
17683 };
17684 unsafe {
17685 if getrlimit(resource, &mut rlim) != 0 {
17686 eprintln!("ulimit: cannot get limit");
17687 return 1;
17688 }
17689 }
17690
17691 if let Some(v) = value {
17692 if soft {
17694 rlim.rlim_cur = v as libc::rlim_t;
17695 }
17696 if hard {
17697 rlim.rlim_max = v as libc::rlim_t;
17698 }
17699 unsafe {
17700 if setrlimit(resource, &rlim) != 0 {
17701 eprintln!("ulimit: cannot set limit");
17702 return 1;
17703 }
17704 }
17705 } else {
17706 let limit = if hard { rlim.rlim_max } else { rlim.rlim_cur };
17708 if limit == libc::RLIM_INFINITY as libc::rlim_t {
17709 println!("unlimited");
17710 } else {
17711 println!("{}", limit);
17712 }
17713 }
17714 0
17715 }
17716
17717 fn print_all_limits(&self, soft: bool) {
17718 use libc::{getrlimit, rlimit};
17719 use libc::{RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE};
17720 use libc::{RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_STACK};
17721
17722 let limits = [
17723 (RLIMIT_CORE, "core file size", "blocks", 512),
17724 (RLIMIT_DATA, "data seg size", "kbytes", 1024),
17725 (RLIMIT_FSIZE, "file size", "blocks", 512),
17726 (RLIMIT_NOFILE, "open files", "", 1),
17727 (RLIMIT_STACK, "stack size", "kbytes", 1024),
17728 (RLIMIT_CPU, "cpu time", "seconds", 1),
17729 (RLIMIT_NPROC, "max user processes", "", 1),
17730 (RLIMIT_AS, "virtual memory", "kbytes", 1024),
17731 (RLIMIT_RSS, "max memory size", "kbytes", 1024),
17732 ];
17733
17734 for (resource, name, unit, divisor) in limits {
17735 let mut rlim = rlimit {
17736 rlim_cur: 0,
17737 rlim_max: 0,
17738 };
17739 unsafe {
17740 if getrlimit(resource, &mut rlim) == 0 {
17741 let limit = if soft { rlim.rlim_cur } else { rlim.rlim_max };
17742 let unit_str = if unit.is_empty() {
17743 ""
17744 } else {
17745 &format!("({})", unit)
17746 };
17747 if limit == libc::RLIM_INFINITY as libc::rlim_t {
17748 println!("{:25} {} unlimited", name, unit_str);
17749 } else {
17750 println!("{:25} {} {}", name, unit_str, limit / divisor);
17751 }
17752 }
17753 }
17754 }
17755 }
17756
17757 fn builtin_limit(&self, args: &[String]) -> i32 {
17759 if args.is_empty() {
17761 use libc::{getrlimit, rlimit, RLIM_INFINITY};
17763 let resources = [
17764 (libc::RLIMIT_CPU, "cputime", 1, "seconds"),
17765 (libc::RLIMIT_FSIZE, "filesize", 1024, "kB"),
17766 (libc::RLIMIT_DATA, "datasize", 1024, "kB"),
17767 (libc::RLIMIT_STACK, "stacksize", 1024, "kB"),
17768 (libc::RLIMIT_CORE, "coredumpsize", 1024, "kB"),
17769 (libc::RLIMIT_RSS, "memoryuse", 1024, "kB"),
17770 #[cfg(target_os = "linux")]
17771 (libc::RLIMIT_NPROC, "maxproc", 1, ""),
17772 (libc::RLIMIT_NOFILE, "descriptors", 1, ""),
17773 ];
17774 for (res, name, divisor, unit) in resources {
17775 let mut rl: rlimit = unsafe { std::mem::zeroed() };
17776 unsafe {
17777 getrlimit(res, &mut rl);
17778 }
17779 let val = if rl.rlim_cur == RLIM_INFINITY as u64 {
17780 "unlimited".to_string()
17781 } else {
17782 let v = rl.rlim_cur as u64 / divisor;
17783 if unit.is_empty() {
17784 format!("{}", v)
17785 } else {
17786 format!("{}{}", v, unit)
17787 }
17788 };
17789 println!("{:<16}{}", name, val);
17790 }
17791 return 0;
17792 }
17793 self.builtin_ulimit(args)
17794 }
17795
17796 fn builtin_unlimit(&self, args: &[String]) -> i32 {
17798 let mut new_args = args.to_vec();
17799 new_args.push("unlimited".to_string());
17800 self.builtin_ulimit(&new_args)
17801 }
17802
17803 fn builtin_umask(&self, args: &[String]) -> i32 {
17805 use libc::umask;
17806
17807 let mut symbolic = false;
17808 let mut value: Option<&str> = None;
17809
17810 for arg in args {
17811 match arg.as_str() {
17812 "-S" => symbolic = true,
17813 _ if !arg.starts_with('-') => value = Some(arg),
17814 _ => {}
17815 }
17816 }
17817
17818 if let Some(v) = value {
17819 if let Ok(mask) = u32::from_str_radix(v, 8) {
17821 unsafe {
17822 umask(mask as libc::mode_t);
17823 }
17824 } else {
17825 eprintln!("umask: invalid mask: {}", v);
17826 return 1;
17827 }
17828 } else {
17829 let mask = unsafe {
17831 let m = umask(0);
17832 umask(m);
17833 m
17834 };
17835 if symbolic {
17836 let u = 7 - ((mask >> 6) & 7);
17837 let g = 7 - ((mask >> 3) & 7);
17838 let o = 7 - (mask & 7);
17839 println!(
17840 "u={}{}{}g={}{}{}o={}{}{}",
17841 if u & 4 != 0 { "r" } else { "" },
17842 if u & 2 != 0 { "w" } else { "" },
17843 if u & 1 != 0 { "x" } else { "" },
17844 if g & 4 != 0 { "r" } else { "" },
17845 if g & 2 != 0 { "w" } else { "" },
17846 if g & 1 != 0 { "x" } else { "" },
17847 if o & 4 != 0 { "r" } else { "" },
17848 if o & 2 != 0 { "w" } else { "" },
17849 if o & 1 != 0 { "x" } else { "" },
17850 );
17851 } else {
17852 println!("{:04o}", mask);
17853 }
17854 }
17855 0
17856 }
17857
17858 fn builtin_rehash(&mut self, args: &[String]) -> i32 {
17860 let mut rehash_dirs = false;
17866 let mut force = false;
17867 let mut verbose = false;
17868
17869 for arg in args {
17870 if arg.starts_with('-') {
17871 for ch in arg[1..].chars() {
17872 match ch {
17873 'd' => rehash_dirs = true,
17874 'f' => force = true,
17875 'v' => verbose = true,
17876 _ => {}
17877 }
17878 }
17879 }
17880 }
17881
17882 if rehash_dirs {
17883 self.named_dirs.clear();
17886 if let Ok(home) = env::var("HOME") {
17887 self.named_dirs.insert(String::new(), PathBuf::from(&home)); }
17889 return 0;
17890 }
17891
17892 self.command_hash.clear();
17894
17895 if force {
17896 if let Ok(path_var) = env::var("PATH") {
17899 let dirs: Vec<String> = path_var
17900 .split(':')
17901 .filter(|s| !s.is_empty())
17902 .map(|s| s.to_string())
17903 .collect();
17904
17905 let (tx, rx) = std::sync::mpsc::channel::<Vec<(String, String)>>();
17906
17907 for dir in dirs {
17908 let tx = tx.clone();
17909 self.worker_pool.submit(move || {
17910 let mut batch = Vec::new();
17911 if let Ok(entries) = std::fs::read_dir(&dir) {
17912 for entry in entries.flatten() {
17913 if let Ok(ft) = entry.file_type() {
17914 if ft.is_file() || ft.is_symlink() {
17915 if let Some(name) = entry.file_name().to_str() {
17916 let path = entry.path().to_string_lossy().to_string();
17917 batch.push((name.to_string(), path));
17918 }
17919 }
17920 }
17921 }
17922 }
17923 let _ = tx.send(batch);
17924 });
17925 }
17926 drop(tx);
17927
17928 for batch in rx {
17929 for (name, path) in batch {
17930 if verbose {
17931 println!("{}={}", name, path);
17932 }
17933 self.command_hash.insert(name, path);
17934 }
17935 }
17936 }
17937 }
17938
17939 0
17940 }
17941
17942 fn builtin_unhash(&mut self, args: &[String]) -> i32 {
17944 let mut remove_aliases = false;
17945 let mut remove_functions = false;
17946 let mut remove_dirs = false;
17947 let mut names: Vec<&str> = Vec::new();
17948
17949 for arg in args {
17950 match arg.as_str() {
17951 "-a" => remove_aliases = true,
17952 "-f" => remove_functions = true,
17953 "-d" => remove_dirs = true,
17954 "-m" => {} _ if arg.starts_with('-') => {}
17956 _ => names.push(arg),
17957 }
17958 }
17959
17960 for name in names {
17961 if remove_aliases {
17962 self.aliases.remove(name);
17963 }
17964 if remove_functions {
17965 self.functions.remove(name);
17966 }
17967 if remove_dirs {
17968 }
17970 }
17971 0
17972 }
17973
17974 fn builtin_times(&self, _args: &[String]) -> i32 {
17976 use libc::{getrusage, rusage, RUSAGE_CHILDREN, RUSAGE_SELF};
17977
17978 let mut self_usage: rusage = unsafe { std::mem::zeroed() };
17979 let mut child_usage: rusage = unsafe { std::mem::zeroed() };
17980
17981 unsafe {
17982 getrusage(RUSAGE_SELF, &mut self_usage);
17983 getrusage(RUSAGE_CHILDREN, &mut child_usage);
17984 }
17985
17986 let self_user =
17987 self_usage.ru_utime.tv_sec as f64 + self_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
17988 let self_sys =
17989 self_usage.ru_stime.tv_sec as f64 + self_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
17990 let child_user =
17991 child_usage.ru_utime.tv_sec as f64 + child_usage.ru_utime.tv_usec as f64 / 1_000_000.0;
17992 let child_sys =
17993 child_usage.ru_stime.tv_sec as f64 + child_usage.ru_stime.tv_usec as f64 / 1_000_000.0;
17994
17995 println!("{:.3}s {:.3}s", self_user, self_sys);
17996 println!("{:.3}s {:.3}s", child_user, child_sys);
17997 0
17998 }
17999
18000 fn builtin_zmodload(&mut self, args: &[String]) -> i32 {
18002 let mut list_loaded = false;
18003 let mut unload = false;
18004 let mut modules: Vec<&str> = Vec::new();
18005
18006 for arg in args {
18007 match arg.as_str() {
18008 "-l" | "-L" => list_loaded = true,
18009 "-u" => unload = true,
18010 "-a" | "-b" | "-c" | "-d" | "-e" | "-f" | "-i" | "-p" | "-s" => {}
18011 _ if arg.starts_with('-') => {}
18012 _ => modules.push(arg),
18013 }
18014 }
18015
18016 if list_loaded || modules.is_empty() {
18017 println!("zsh/complete");
18019 println!("zsh/complist");
18020 println!("zsh/parameter");
18021 println!("zsh/zutil");
18022 return 0;
18023 }
18024
18025 for module in modules {
18026 if unload {
18027 self.options.remove(&format!("_module_{}", module));
18029 } else {
18030 self.options.insert(format!("_module_{}", module), true);
18032 }
18033 }
18034 0
18035 }
18036
18037 fn builtin_r(&mut self, args: &[String]) -> i32 {
18039 let mut fc_args = vec!["-e".to_string(), "-".to_string()];
18040 fc_args.extend(args.iter().cloned());
18041 self.builtin_fc(&fc_args)
18042 }
18043
18044 fn builtin_ttyctl(&self, args: &[String]) -> i32 {
18046 for arg in args {
18047 match arg.as_str() {
18048 "-f" => {
18049 }
18052 "-u" => {
18053 }
18055 _ => {}
18056 }
18057 }
18058 0
18059 }
18060
18061 fn builtin_noglob(&mut self, args: &[String], redirects: &[Redirect]) -> i32 {
18063 if args.is_empty() {
18064 return 0;
18065 }
18066
18067 let saved = self.options.get("noglob").cloned();
18069 self.options.insert("noglob".to_string(), true);
18070
18071 let status = self.builtin_command(args, redirects);
18073
18074 if let Some(v) = saved {
18076 self.options.insert("noglob".to_string(), v);
18077 } else {
18078 self.options.remove("noglob");
18079 }
18080
18081 status
18082 }
18083
18084 fn builtin_zstat(&self, args: &[String]) -> i32 {
18090 use std::os::unix::fs::MetadataExt;
18091 use std::os::unix::fs::PermissionsExt;
18092
18093 let mut show_all = true;
18094 let mut symbolic_mode = false;
18095 let mut show_link = false;
18096 let mut _as_array = false;
18097 let mut _array_name = String::new();
18098 let mut format_time = String::new();
18099 let mut elements: Vec<String> = Vec::new();
18100 let mut files: Vec<&str> = Vec::new();
18101
18102 let mut iter = args.iter().peekable();
18103 while let Some(arg) = iter.next() {
18104 match arg.as_str() {
18105 "-s" => symbolic_mode = true,
18106 "-L" => show_link = true,
18107 "-N" => {} "-n" => {} "-o" => show_all = false,
18110 "-A" => {
18111 _as_array = true;
18112 if let Some(name) = iter.next() {
18113 _array_name = name.clone();
18114 }
18115 }
18116 "-F" => {
18117 if let Some(fmt) = iter.next() {
18118 format_time = fmt.clone();
18119 }
18120 }
18121 s if s.starts_with('+') => {
18122 elements.push(s[1..].to_string());
18123 show_all = false;
18124 }
18125 s if !s.starts_with('-') => files.push(s),
18126 _ => {}
18127 }
18128 }
18129
18130 if files.is_empty() {
18131 eprintln!("zstat: no files specified");
18132 return 1;
18133 }
18134
18135 for file in files {
18136 let meta = if show_link {
18137 std::fs::symlink_metadata(file)
18138 } else {
18139 std::fs::metadata(file)
18140 };
18141
18142 let meta = match meta {
18143 Ok(m) => m,
18144 Err(e) => {
18145 eprintln!("zstat: {}: {}", file, e);
18146 return 1;
18147 }
18148 };
18149
18150 let output_element = |name: &str, value: &str| {
18151 if _as_array {
18152 println!("{}={}", name, value);
18154 } else if show_all || elements.contains(&name.to_string()) {
18155 println!("{}: {}", name, value);
18156 }
18157 };
18158
18159 output_element("device", &meta.dev().to_string());
18160 output_element("inode", &meta.ino().to_string());
18161
18162 if symbolic_mode {
18163 let mode = meta.permissions().mode();
18164 let mode_str = format!(
18165 "{}{}{}{}{}{}{}{}{}{}",
18166 match mode & 0o170000 {
18167 0o040000 => 'd',
18168 0o120000 => 'l',
18169 0o100000 => '-',
18170 0o060000 => 'b',
18171 0o020000 => 'c',
18172 0o010000 => 'p',
18173 0o140000 => 's',
18174 _ => '?',
18175 },
18176 if mode & 0o400 != 0 { 'r' } else { '-' },
18177 if mode & 0o200 != 0 { 'w' } else { '-' },
18178 if mode & 0o4000 != 0 {
18179 's'
18180 } else if mode & 0o100 != 0 {
18181 'x'
18182 } else {
18183 '-'
18184 },
18185 if mode & 0o040 != 0 { 'r' } else { '-' },
18186 if mode & 0o020 != 0 { 'w' } else { '-' },
18187 if mode & 0o2000 != 0 {
18188 's'
18189 } else if mode & 0o010 != 0 {
18190 'x'
18191 } else {
18192 '-'
18193 },
18194 if mode & 0o004 != 0 { 'r' } else { '-' },
18195 if mode & 0o002 != 0 { 'w' } else { '-' },
18196 if mode & 0o1000 != 0 {
18197 't'
18198 } else if mode & 0o001 != 0 {
18199 'x'
18200 } else {
18201 '-'
18202 },
18203 );
18204 output_element("mode", &mode_str);
18205 } else {
18206 output_element("mode", &format!("{:o}", meta.permissions().mode()));
18207 }
18208
18209 output_element("nlink", &meta.nlink().to_string());
18210 output_element("uid", &meta.uid().to_string());
18211 output_element("gid", &meta.gid().to_string());
18212 output_element("rdev", &meta.rdev().to_string());
18213 output_element("size", &meta.len().to_string());
18214
18215 let format_timestamp = |secs: i64| -> String {
18216 if format_time.is_empty() {
18217 secs.to_string()
18218 } else {
18219 chrono::DateTime::from_timestamp(secs, 0)
18220 .map(|dt| dt.format(&format_time).to_string())
18221 .unwrap_or_else(|| secs.to_string())
18222 }
18223 };
18224
18225 output_element("atime", &format_timestamp(meta.atime()));
18226 output_element("mtime", &format_timestamp(meta.mtime()));
18227 output_element("ctime", &format_timestamp(meta.ctime()));
18228 output_element("blksize", &meta.blksize().to_string());
18229 output_element("blocks", &meta.blocks().to_string());
18230
18231 if show_link && meta.file_type().is_symlink() {
18232 if let Ok(target) = std::fs::read_link(file) {
18233 output_element("link", &target.to_string_lossy());
18234 }
18235 }
18236 }
18237
18238 0
18239 }
18240
18241 fn builtin_strftime(&self, args: &[String]) -> i32 {
18243 let mut format = "%c".to_string();
18244 let mut timestamp: Option<i64> = None;
18245 let mut to_var = false;
18246 let mut var_name = String::new();
18247
18248 let mut iter = args.iter();
18249 while let Some(arg) = iter.next() {
18250 match arg.as_str() {
18251 "-s" => {
18252 to_var = true;
18253 if let Some(name) = iter.next() {
18254 var_name = name.clone();
18255 }
18256 }
18257 "-r" => {
18258 if let Some(ts_str) = iter.next() {
18260 timestamp = ts_str.parse().ok();
18261 }
18262 }
18263 s if !s.starts_with('-') => {
18264 if format == "%c" {
18265 format = s.to_string();
18266 } else if timestamp.is_none() {
18267 timestamp = s.parse().ok();
18268 }
18269 }
18270 _ => {}
18271 }
18272 }
18273
18274 let ts = timestamp.unwrap_or_else(|| chrono::Local::now().timestamp());
18275
18276 let result = chrono::DateTime::from_timestamp(ts, 0)
18277 .map(|dt: chrono::DateTime<chrono::Utc>| {
18278 dt.with_timezone(&chrono::Local).format(&format).to_string()
18279 })
18280 .unwrap_or_else(|| "invalid timestamp".to_string());
18281
18282 if to_var && !var_name.is_empty() {
18283 println!("{}={}", var_name, result);
18285 } else {
18286 println!("{}", result);
18287 }
18288
18289 0
18290 }
18291
18292 fn builtin_zsleep(&self, args: &[String]) -> i32 {
18294 if args.is_empty() {
18295 eprintln!("zsleep: missing argument");
18296 return 1;
18297 }
18298
18299 let secs: f64 = match args[0].parse() {
18300 Ok(s) => s,
18301 Err(_) => {
18302 eprintln!("zsleep: invalid number: {}", args[0]);
18303 return 1;
18304 }
18305 };
18306
18307 std::thread::sleep(std::time::Duration::from_secs_f64(secs));
18308 0
18309 }
18310
18311 fn builtin_zsystem(&mut self, args: &[String]) -> i32 {
18314 if args.is_empty() {
18315 eprintln!("zsystem: subcommand expected");
18316 return 1;
18317 }
18318 match args[0].as_str() {
18319 "flock" => self.builtin_zsystem_flock(&args[1..]),
18320 "supports" => self.builtin_zsystem_supports(&args[1..]),
18321 _ => {
18322 eprintln!("zsystem: unknown subcommand: {}", args[0]);
18323 1
18324 }
18325 }
18326 }
18327
18328 fn builtin_zsystem_supports(&self, args: &[String]) -> i32 {
18330 if args.is_empty() {
18331 eprintln!("zsystem: supports: not enough arguments");
18332 return 255;
18333 }
18334 if args.len() > 1 {
18335 eprintln!("zsystem: supports: too many arguments");
18336 return 255;
18337 }
18338 match args[0].as_str() {
18339 "supports" | "flock" => 0,
18340 _ => 1,
18341 }
18342 }
18343
18344 fn builtin_zsystem_flock(&mut self, args: &[String]) -> i32 {
18346 #[cfg(unix)]
18347 {
18348 use std::os::unix::io::AsRawFd;
18349
18350 let mut cloexec = true;
18351 let mut readlock = false;
18352 let mut timeout: Option<f64> = None;
18353 let mut fdvar: Option<String> = None;
18354 let mut file: Option<&str> = None;
18355
18356 let mut i = 0;
18357 while i < args.len() {
18358 let arg = &args[i];
18359 if arg == "--" {
18360 i += 1;
18361 if i < args.len() {
18362 file = Some(&args[i]);
18363 }
18364 break;
18365 }
18366 if !arg.starts_with('-') {
18367 file = Some(arg);
18368 break;
18369 }
18370 let mut chars = arg[1..].chars().peekable();
18371 while let Some(c) = chars.next() {
18372 match c {
18373 'e' => cloexec = false,
18374 'r' => readlock = true,
18375 'u' => return 0,
18376 'f' => {
18377 let rest: String = chars.collect();
18378 if !rest.is_empty() {
18379 fdvar = Some(rest);
18380 } else {
18381 i += 1;
18382 if i < args.len() {
18383 fdvar = Some(args[i].clone());
18384 } else {
18385 eprintln!("zsystem: flock: option f requires a variable name");
18386 return 1;
18387 }
18388 }
18389 break;
18390 }
18391 't' => {
18392 let rest: String = chars.collect();
18393 let val = if !rest.is_empty() {
18394 rest
18395 } else {
18396 i += 1;
18397 if i < args.len() {
18398 args[i].clone()
18399 } else {
18400 eprintln!(
18401 "zsystem: flock: option t requires a numeric timeout"
18402 );
18403 return 1;
18404 }
18405 };
18406 match val.parse::<f64>() {
18407 Ok(t) => timeout = Some(t),
18408 Err(_) => {
18409 eprintln!("zsystem: flock: invalid timeout value: '{}'", val);
18410 return 1;
18411 }
18412 }
18413 break;
18414 }
18415 'i' => {
18416 let rest: String = chars.collect();
18417 if rest.is_empty() {
18418 i += 1;
18419 if i >= args.len() {
18420 eprintln!("zsystem: flock: option i requires a numeric retry interval");
18421 return 1;
18422 }
18423 }
18424 break;
18425 }
18426 _ => {
18427 eprintln!("zsystem: flock: unknown option: -{}", c);
18428 return 1;
18429 }
18430 }
18431 }
18432 i += 1;
18433 }
18434
18435 let filepath = match file {
18436 Some(f) => f,
18437 None => {
18438 eprintln!("zsystem: flock: not enough arguments");
18439 return 1;
18440 }
18441 };
18442
18443 use std::fs::OpenOptions;
18444 let file_handle = match OpenOptions::new()
18445 .read(true)
18446 .write(!readlock)
18447 .create(true)
18448 .truncate(false)
18449 .open(filepath)
18450 {
18451 Ok(f) => f,
18452 Err(e) => {
18453 eprintln!("zsystem: flock: {}: {}", filepath, e);
18454 return 1;
18455 }
18456 };
18457
18458 let lock_type = if readlock {
18459 libc::F_RDLCK as i16
18460 } else {
18461 libc::F_WRLCK as i16
18462 };
18463
18464 let mut flock = libc::flock {
18465 l_type: lock_type,
18466 l_whence: libc::SEEK_SET as i16,
18467 l_start: 0,
18468 l_len: 0,
18469 l_pid: 0,
18470 };
18471
18472 let cmd = if timeout.is_some() {
18473 libc::F_SETLK
18474 } else {
18475 libc::F_SETLKW
18476 };
18477 let start = std::time::Instant::now();
18478 let timeout_duration = timeout.map(|t| std::time::Duration::from_secs_f64(t));
18479
18480 loop {
18481 let ret = unsafe { libc::fcntl(file_handle.as_raw_fd(), cmd, &mut flock) };
18482 if ret == 0 {
18483 if let Some(ref var) = fdvar {
18484 let fd = file_handle.as_raw_fd();
18485 std::mem::forget(file_handle);
18486 self.variables.insert(var.clone(), fd.to_string());
18487 } else {
18488 std::mem::forget(file_handle);
18489 }
18490 let _ = cloexec;
18491 return 0;
18492 }
18493 let errno = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
18494 if errno != libc::EACCES && errno != libc::EAGAIN {
18495 eprintln!(
18496 "zsystem: flock: {}: {}",
18497 filepath,
18498 std::io::Error::last_os_error()
18499 );
18500 return 1;
18501 }
18502 if let Some(td) = timeout_duration {
18503 if start.elapsed() >= td {
18504 return 2;
18505 }
18506 std::thread::sleep(std::time::Duration::from_millis(100));
18507 } else {
18508 eprintln!(
18509 "zsystem: flock: {}: {}",
18510 filepath,
18511 std::io::Error::last_os_error()
18512 );
18513 return 1;
18514 }
18515 }
18516 }
18517 #[cfg(not(unix))]
18518 {
18519 eprintln!("zsystem: flock: not supported on this platform");
18520 1
18521 }
18522 }
18523
18524 fn builtin_sync(&self, _args: &[String]) -> i32 {
18527 #[cfg(unix)]
18528 unsafe {
18529 libc::sync();
18530 }
18531 0
18532 }
18533
18534 fn builtin_mkdir(&self, args: &[String]) -> i32 {
18537 let mut mode: u32 = 0o777;
18538 let mut parents = false;
18539 let mut dirs: Vec<&str> = Vec::new();
18540
18541 let mut i = 0;
18542 while i < args.len() {
18543 let arg = &args[i];
18544 if arg == "-p" {
18545 parents = true;
18546 } else if arg == "-m" && i + 1 < args.len() {
18547 i += 1;
18548 mode = u32::from_str_radix(&args[i], 8).unwrap_or(0o777);
18549 } else if arg.starts_with("-m") {
18550 mode = u32::from_str_radix(&arg[2..], 8).unwrap_or(0o777);
18551 } else if !arg.starts_with('-') || arg == "-" || arg == "--" {
18552 if arg == "--" {
18553 dirs.extend(args[i + 1..].iter().map(|s| s.as_str()));
18554 break;
18555 }
18556 dirs.push(arg);
18557 }
18558 i += 1;
18559 }
18560
18561 let mut err = 0;
18562 for dir in dirs {
18563 let path = std::path::Path::new(dir);
18564 let result = if parents {
18565 std::fs::create_dir_all(path)
18566 } else {
18567 std::fs::create_dir(path)
18568 };
18569 if let Err(e) = result {
18570 eprintln!("mkdir: cannot create directory '{}': {}", dir, e);
18571 err = 1;
18572 } else {
18573 #[cfg(unix)]
18574 {
18575 use std::os::unix::fs::PermissionsExt;
18576 let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode));
18577 }
18578 }
18579 }
18580 err
18581 }
18582
18583 fn builtin_rmdir(&self, args: &[String]) -> i32 {
18586 let mut err = 0;
18587 for arg in args {
18588 if arg.starts_with('-') {
18589 continue;
18590 }
18591 if let Err(e) = std::fs::remove_dir(arg) {
18592 eprintln!("rmdir: cannot remove '{}': {}", arg, e);
18593 err = 1;
18594 }
18595 }
18596 err
18597 }
18598
18599 fn builtin_ln(&self, args: &[String]) -> i32 {
18602 let mut symbolic = false;
18603 let mut force = false;
18604 let mut no_deref = false;
18605 let mut files: Vec<&str> = Vec::new();
18606
18607 for arg in args {
18608 match arg.as_str() {
18609 "-s" => symbolic = true,
18610 "-f" => force = true,
18611 "-n" | "-h" => no_deref = true,
18612 s if !s.starts_with('-') => files.push(s),
18613 _ => {}
18614 }
18615 }
18616
18617 if files.len() < 2 {
18618 if files.len() == 1 {
18619 let src = files[0];
18620 let target = std::path::Path::new(src)
18621 .file_name()
18622 .map(|n| n.to_string_lossy().to_string())
18623 .unwrap_or_else(|| src.to_string());
18624 files.push(Box::leak(target.into_boxed_str()));
18625 } else {
18626 eprintln!("ln: missing file operand");
18627 return 1;
18628 }
18629 }
18630
18631 let target = files.pop().unwrap();
18632 let target_path = std::path::Path::new(target);
18633 let is_dir = !no_deref && target_path.is_dir();
18634
18635 for src in files {
18636 let dest = if is_dir {
18637 format!(
18638 "{}/{}",
18639 target,
18640 std::path::Path::new(src)
18641 .file_name()
18642 .map(|n| n.to_string_lossy().to_string())
18643 .unwrap_or_else(|| src.to_string())
18644 )
18645 } else {
18646 target.to_string()
18647 };
18648
18649 let dest_path = std::path::Path::new(&dest);
18650 if force && dest_path.exists() {
18651 let _ = std::fs::remove_file(&dest);
18652 }
18653
18654 let result = if symbolic {
18655 #[cfg(unix)]
18656 {
18657 std::os::unix::fs::symlink(src, &dest)
18658 }
18659 #[cfg(not(unix))]
18660 {
18661 Err(std::io::Error::new(
18662 std::io::ErrorKind::Unsupported,
18663 "symlinks not supported",
18664 ))
18665 }
18666 } else {
18667 std::fs::hard_link(src, &dest)
18668 };
18669
18670 if let Err(e) = result {
18671 eprintln!("ln: cannot create link '{}' -> '{}': {}", dest, src, e);
18672 return 1;
18673 }
18674 }
18675 0
18676 }
18677
18678 fn builtin_mv(&self, args: &[String]) -> i32 {
18681 let mut force = false;
18682 let mut interactive = false;
18683 let mut verbose = false;
18684 let mut files: Vec<&str> = Vec::new();
18685
18686 for arg in args {
18687 match arg.as_str() {
18688 "-f" => force = true,
18689 "-i" => interactive = true,
18690 "-v" => verbose = true,
18691 s if !s.starts_with('-') => files.push(s),
18692 _ => {}
18693 }
18694 }
18695
18696 if files.len() < 2 {
18697 eprintln!("mv: missing file operand");
18698 return 1;
18699 }
18700
18701 let target = files.pop().unwrap();
18702 let target_path = std::path::Path::new(target);
18703 let is_dir = target_path.is_dir();
18704
18705 for src in files {
18706 let dest = if is_dir {
18707 format!(
18708 "{}/{}",
18709 target,
18710 std::path::Path::new(src)
18711 .file_name()
18712 .map(|n| n.to_string_lossy().to_string())
18713 .unwrap_or_else(|| src.to_string())
18714 )
18715 } else {
18716 target.to_string()
18717 };
18718
18719 let dest_path = std::path::Path::new(&dest);
18720 if dest_path.exists() && !force {
18721 if interactive {
18722 eprint!("mv: overwrite '{}'? ", dest);
18723 let mut response = String::new();
18724 if std::io::stdin().read_line(&mut response).is_err()
18725 || !response.trim().eq_ignore_ascii_case("y")
18726 {
18727 continue;
18728 }
18729 } else {
18730 eprintln!("mv: cannot overwrite '{}': File exists", dest);
18731 return 1;
18732 }
18733 }
18734
18735 if let Err(e) = std::fs::rename(src, &dest) {
18736 eprintln!("mv: cannot move '{}' to '{}': {}", src, dest, e);
18737 return 1;
18738 }
18739
18740 if verbose {
18741 println!("'{}' -> '{}'", src, dest);
18742 }
18743 }
18744 0
18745 }
18746
18747 fn builtin_cp(&self, args: &[String]) -> i32 {
18750 let mut recursive = false;
18751 let mut force = false;
18752 let mut interactive = false;
18753 let mut preserve = false;
18754 let mut verbose = false;
18755 let mut files: Vec<&str> = Vec::new();
18756
18757 for arg in args {
18758 match arg.as_str() {
18759 "-r" | "-R" => recursive = true,
18760 "-f" => force = true,
18761 "-i" => interactive = true,
18762 "-p" => preserve = true,
18763 "-v" => verbose = true,
18764 s if !s.starts_with('-') => files.push(s),
18765 _ => {}
18766 }
18767 }
18768
18769 let _ = preserve; if files.len() < 2 {
18772 eprintln!("cp: missing file operand");
18773 return 1;
18774 }
18775
18776 let target = files.pop().unwrap();
18777 let target_path = std::path::Path::new(target);
18778 let is_dir = target_path.is_dir();
18779
18780 for src in files {
18781 let src_path = std::path::Path::new(src);
18782 let dest = if is_dir {
18783 format!(
18784 "{}/{}",
18785 target,
18786 src_path
18787 .file_name()
18788 .map(|n| n.to_string_lossy().to_string())
18789 .unwrap_or_else(|| src.to_string())
18790 )
18791 } else {
18792 target.to_string()
18793 };
18794
18795 let dest_path = std::path::Path::new(&dest);
18796 if dest_path.exists() && !force {
18797 if interactive {
18798 eprint!("cp: overwrite '{}'? ", dest);
18799 let mut response = String::new();
18800 if std::io::stdin().read_line(&mut response).is_err()
18801 || !response.trim().eq_ignore_ascii_case("y")
18802 {
18803 continue;
18804 }
18805 }
18806 }
18807
18808 let result = if src_path.is_dir() {
18809 if recursive {
18810 Self::copy_dir_recursive(src_path, dest_path)
18811 } else {
18812 eprintln!("cp: -r not specified; omitting directory '{}'", src);
18813 continue;
18814 }
18815 } else {
18816 std::fs::copy(src, &dest).map(|_| ())
18817 };
18818
18819 if let Err(e) = result {
18820 eprintln!("cp: cannot copy '{}' to '{}': {}", src, dest, e);
18821 return 1;
18822 }
18823
18824 if verbose {
18825 println!("'{}' -> '{}'", src, dest);
18826 }
18827 }
18828 0
18829 }
18830
18831 fn copy_dir_recursive(src: &std::path::Path, dest: &std::path::Path) -> std::io::Result<()> {
18832 if !dest.exists() {
18833 std::fs::create_dir_all(dest)?;
18834 }
18835 for entry in std::fs::read_dir(src)? {
18836 let entry = entry?;
18837 let file_type = entry.file_type()?;
18838 let src_path = entry.path();
18839 let dest_path = dest.join(entry.file_name());
18840
18841 if file_type.is_dir() {
18842 Self::copy_dir_recursive(&src_path, &dest_path)?;
18843 } else {
18844 std::fs::copy(&src_path, &dest_path)?;
18845 }
18846 }
18847 Ok(())
18848 }
18849
18850 fn builtin_rm(&self, args: &[String]) -> i32 {
18852 let mut recursive = false;
18853 let mut force = false;
18854 let mut interactive = false;
18855 let mut verbose = false;
18856 let mut files: Vec<&str> = Vec::new();
18857
18858 for arg in args {
18859 match arg.as_str() {
18860 "-r" | "-R" => recursive = true,
18861 "-f" => force = true,
18862 "-i" => interactive = true,
18863 "-v" => verbose = true,
18864 "-rf" | "-fr" => {
18865 recursive = true;
18866 force = true;
18867 }
18868 s if !s.starts_with('-') => files.push(s),
18869 _ => {}
18870 }
18871 }
18872
18873 for file in files {
18874 let path = std::path::Path::new(file);
18875
18876 if !path.exists() {
18877 if !force {
18878 eprintln!("rm: cannot remove '{}': No such file or directory", file);
18879 return 1;
18880 }
18881 continue;
18882 }
18883
18884 if interactive {
18885 let file_type = if path.is_dir() { "directory" } else { "file" };
18886 eprint!("rm: remove {} '{}'? ", file_type, file);
18887 let mut response = String::new();
18888 if std::io::stdin().read_line(&mut response).is_err()
18889 || !response.trim().eq_ignore_ascii_case("y")
18890 {
18891 continue;
18892 }
18893 }
18894
18895 let result = if path.is_dir() {
18896 if recursive {
18897 std::fs::remove_dir_all(path)
18898 } else {
18899 eprintln!("rm: cannot remove '{}': Is a directory", file);
18900 return 1;
18901 }
18902 } else {
18903 std::fs::remove_file(path)
18904 };
18905
18906 if let Err(e) = result {
18907 if !force {
18908 eprintln!("rm: cannot remove '{}': {}", file, e);
18909 return 1;
18910 }
18911 } else if verbose {
18912 println!("removed '{}'", file);
18913 }
18914 }
18915 0
18916 }
18917
18918 #[cfg(unix)]
18920 fn builtin_chown(&self, args: &[String]) -> i32 {
18921 use std::os::unix::fs::MetadataExt;
18922
18923 let mut recursive = false;
18924 let mut positional: Vec<&str> = Vec::new();
18925
18926 for arg in args {
18927 match arg.as_str() {
18928 "-R" => recursive = true,
18929 "-h" => {} s if !s.starts_with('-') => positional.push(s),
18931 _ => {}
18932 }
18933 }
18934
18935 if positional.len() < 2 {
18936 eprintln!("chown: missing operand");
18937 return 1;
18938 }
18939
18940 let owner_spec = positional[0];
18941 let files = &positional[1..];
18942
18943 let (user, group) = if let Some(colon_pos) = owner_spec.find(':') {
18945 (&owner_spec[..colon_pos], Some(&owner_spec[colon_pos + 1..]))
18946 } else {
18947 (owner_spec, None)
18948 };
18949
18950 let uid: u32 = if user.is_empty() {
18951 u32::MAX
18952 } else if let Ok(id) = user.parse() {
18953 id
18954 } else {
18955 unsafe {
18957 let c_user = std::ffi::CString::new(user).unwrap();
18958 let pw = libc::getpwnam(c_user.as_ptr());
18959 if pw.is_null() {
18960 eprintln!("chown: invalid user: '{}'", user);
18961 return 1;
18962 }
18963 (*pw).pw_uid
18964 }
18965 };
18966
18967 let gid: u32 = match group {
18968 Some(g) if !g.is_empty() => {
18969 if let Ok(id) = g.parse() {
18970 id
18971 } else {
18972 unsafe {
18973 let c_group = std::ffi::CString::new(g).unwrap();
18974 let gr = libc::getgrnam(c_group.as_ptr());
18975 if gr.is_null() {
18976 eprintln!("chown: invalid group: '{}'", g);
18977 return 1;
18978 }
18979 (*gr).gr_gid
18980 }
18981 }
18982 }
18983 _ => u32::MAX,
18984 };
18985
18986 fn do_chown(path: &std::path::Path, uid: u32, gid: u32, recursive: bool) -> i32 {
18987 let c_path = match std::ffi::CString::new(path.to_string_lossy().as_bytes()) {
18988 Ok(p) => p,
18989 Err(_) => return 1,
18990 };
18991
18992 let ret = unsafe { libc::chown(c_path.as_ptr(), uid, gid) };
18993 if ret != 0 {
18994 eprintln!(
18995 "chown: changing ownership of '{}': {}",
18996 path.display(),
18997 std::io::Error::last_os_error()
18998 );
18999 return 1;
19000 }
19001
19002 if recursive && path.is_dir() {
19003 if let Ok(entries) = std::fs::read_dir(path) {
19004 for entry in entries.flatten() {
19005 if do_chown(&entry.path(), uid, gid, true) != 0 {
19006 return 1;
19007 }
19008 }
19009 }
19010 }
19011 0
19012 }
19013
19014 for file in files {
19015 if do_chown(std::path::Path::new(file), uid, gid, recursive) != 0 {
19016 return 1;
19017 }
19018 }
19019 0
19020 }
19021
19022 #[cfg(not(unix))]
19023 fn builtin_chown(&self, _args: &[String]) -> i32 {
19024 eprintln!("chown: not supported on this platform");
19025 1
19026 }
19027
19028 fn builtin_chmod(&self, args: &[String]) -> i32 {
19030 let mut recursive = false;
19031 let mut positional: Vec<&str> = Vec::new();
19032
19033 for arg in args {
19034 match arg.as_str() {
19035 "-R" => recursive = true,
19036 s if !s.starts_with('-') => positional.push(s),
19037 _ => {}
19038 }
19039 }
19040
19041 if positional.len() < 2 {
19042 eprintln!("chmod: missing operand");
19043 return 1;
19044 }
19045
19046 let mode_spec = positional[0];
19047 let files = &positional[1..];
19048
19049 let mode: Option<u32> = u32::from_str_radix(mode_spec, 8).ok();
19051
19052 if mode.is_none() {
19053 eprintln!("chmod: symbolic mode not implemented, use octal");
19055 return 1;
19056 }
19057
19058 let mode = mode.unwrap();
19059
19060 fn do_chmod(path: &std::path::Path, mode: u32, recursive: bool) -> i32 {
19061 #[cfg(unix)]
19062 {
19063 use std::os::unix::fs::PermissionsExt;
19064 if let Err(e) =
19065 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
19066 {
19067 eprintln!("chmod: changing permissions of '{}': {}", path.display(), e);
19068 return 1;
19069 }
19070
19071 if recursive && path.is_dir() {
19072 if let Ok(entries) = std::fs::read_dir(path) {
19073 for entry in entries.flatten() {
19074 if do_chmod(&entry.path(), mode, true) != 0 {
19075 return 1;
19076 }
19077 }
19078 }
19079 }
19080 }
19081 #[cfg(not(unix))]
19082 {
19083 let _ = (path, mode, recursive);
19084 }
19085 0
19086 }
19087
19088 for file in files {
19089 if do_chmod(std::path::Path::new(file), mode, recursive) != 0 {
19090 return 1;
19091 }
19092 }
19093 0
19094 }
19095
19096 fn builtin_zfiles(&self, cmd: &str, args: &[String]) -> i32 {
19098 let mut force = false;
19099 let mut verbose = false;
19100 let mut files: Vec<&str> = Vec::new();
19101
19102 for arg in args {
19103 match arg.as_str() {
19104 "-f" => force = true,
19105 "-v" => verbose = true,
19106 "-i" => {} s if !s.starts_with('-') => files.push(s),
19108 _ => {}
19109 }
19110 }
19111
19112 if files.len() < 2 {
19113 eprintln!("{}: missing operand", cmd);
19114 return 1;
19115 }
19116
19117 let target = files.pop().unwrap();
19118 let target_is_dir = std::path::Path::new(target).is_dir();
19119
19120 for src in files {
19121 let dest = if target_is_dir {
19122 format!(
19123 "{}/{}",
19124 target,
19125 std::path::Path::new(src)
19126 .file_name()
19127 .map(|n| n.to_string_lossy().to_string())
19128 .unwrap_or_else(|| src.to_string())
19129 )
19130 } else {
19131 target.to_string()
19132 };
19133
19134 if !force && std::path::Path::new(&dest).exists() {
19135 eprintln!("{}: '{}' already exists", cmd, dest);
19136 continue;
19137 }
19138
19139 let result = match cmd {
19140 "zln" => {
19141 #[cfg(unix)]
19142 {
19143 std::os::unix::fs::symlink(src, &dest)
19144 }
19145 #[cfg(not(unix))]
19146 {
19147 Err(std::io::Error::new(
19148 std::io::ErrorKind::Unsupported,
19149 "symlinks not supported",
19150 ))
19151 }
19152 }
19153 "zcp" => std::fs::copy(src, &dest).map(|_| ()),
19154 "zmv" => std::fs::rename(src, &dest),
19155 _ => Ok(()),
19156 };
19157
19158 match result {
19159 Ok(()) => {
19160 if verbose {
19161 println!("{} -> {}", src, dest);
19162 }
19163 }
19164 Err(e) => {
19165 eprintln!("{}: {}: {}", cmd, src, e);
19166 return 1;
19167 }
19168 }
19169 }
19170
19171 0
19172 }
19173
19174 fn builtin_coproc(&mut self, args: &[String]) -> i32 {
19176 if args.is_empty() {
19178 println!("(no coprocesses)");
19180 return 0;
19181 }
19182
19183 let cmd = args.join(" ");
19185 match std::process::Command::new("sh")
19186 .arg("-c")
19187 .arg(&cmd)
19188 .stdin(std::process::Stdio::piped())
19189 .stdout(std::process::Stdio::piped())
19190 .spawn()
19191 {
19192 Ok(child) => {
19193 println!("[coproc] {}", child.id());
19194 0
19195 }
19196 Err(e) => {
19197 eprintln!("coproc: {}", e);
19198 1
19199 }
19200 }
19201 }
19202
19203 fn builtin_zparseopts(&mut self, args: &[String]) -> i32 {
19205 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();
19213
19214 let mut iter = args.iter().peekable();
19215
19216 while let Some(arg) = iter.next() {
19218 match arg.as_str() {
19219 "-D" => remove_parsed = true,
19220 "-E" => keep_going = true,
19221 "-F" => fail_on_error = true,
19222 "-K" => keep_values = true,
19223 "-M" => _map_names = true,
19224 "-a" => {
19225 if let Some(name) = iter.next() {
19226 array_name = Some(name.clone());
19227 }
19228 }
19229 "-A" => {
19230 if let Some(name) = iter.next() {
19231 assoc_name = Some(name.clone());
19232 }
19233 }
19234 "-" | "--" => break,
19235 s if !s.starts_with('-') || s.contains('=') || s.contains(':') => {
19236 specs.push(s.to_string());
19237 }
19238 _ => specs.push(arg.clone()),
19239 }
19240 }
19241
19242 for arg in iter {
19244 specs.push(arg.clone());
19245 }
19246
19247 #[derive(Clone)]
19249 struct OptSpec {
19250 name: String,
19251 takes_arg: bool,
19252 optional_arg: bool,
19253 #[allow(dead_code)]
19254 append: bool,
19255 target_array: Option<String>,
19256 }
19257
19258 let mut opt_specs: Vec<OptSpec> = Vec::new();
19259 for spec in &specs {
19260 let mut s = spec.as_str();
19261 let mut target = None;
19262
19263 if let Some(eq_pos) = s.rfind('=') {
19265 if !s[eq_pos + 1..].contains(':') {
19266 target = Some(s[eq_pos + 1..].to_string());
19267 s = &s[..eq_pos];
19268 }
19269 }
19270
19271 let append = s.ends_with('+') || s.contains("+:");
19272 let s = s.trim_end_matches('+');
19273
19274 let (name, takes_arg, optional_arg) = if s.ends_with("::") {
19275 (s.trim_end_matches(':').trim_end_matches(':'), true, true)
19276 } else if s.ends_with(':') {
19277 (s.trim_end_matches(':'), true, false)
19278 } else {
19279 (s, false, false)
19280 };
19281
19282 opt_specs.push(OptSpec {
19283 name: name.to_string(),
19284 takes_arg,
19285 optional_arg,
19286 append,
19287 target_array: target,
19288 });
19289 }
19290
19291 let positionals: Vec<String> = (1..=99)
19293 .map(|i| self.get_variable(&i.to_string()))
19294 .take_while(|v| !v.is_empty())
19295 .collect();
19296
19297 let mut results: Vec<(String, Option<String>)> = Vec::new();
19299 let mut i = 0;
19300 let mut parsed_count = 0;
19301
19302 while i < positionals.len() {
19303 let arg = &positionals[i];
19304
19305 if arg == "-" || arg == "--" {
19306 parsed_count = i + 1;
19307 break;
19308 }
19309
19310 if !arg.starts_with('-') {
19311 if !keep_going {
19312 break;
19313 }
19314 i += 1;
19315 continue;
19316 }
19317
19318 let opt_name = arg.trim_start_matches('-');
19320 let mut matched = false;
19321
19322 for spec in &opt_specs {
19323 if opt_name == spec.name || opt_name.starts_with(&format!("{}=", spec.name)) {
19324 matched = true;
19325
19326 if spec.takes_arg {
19327 let arg_value = if opt_name.contains('=') {
19328 Some(opt_name.splitn(2, '=').nth(1).unwrap_or("").to_string())
19329 } else if i + 1 < positionals.len()
19330 && (!positionals[i + 1].starts_with('-') || spec.optional_arg)
19331 {
19332 i += 1;
19333 Some(positionals[i].clone())
19334 } else if spec.optional_arg {
19335 None
19336 } else if fail_on_error {
19337 eprintln!("zparseopts: missing argument for option: {}", spec.name);
19338 return 1;
19339 } else {
19340 None
19341 };
19342 results.push((format!("-{}", spec.name), arg_value));
19343 } else {
19344 results.push((format!("-{}", spec.name), None));
19345 }
19346 break;
19347 }
19348 }
19349
19350 if !matched && !keep_going {
19351 break;
19352 }
19353
19354 i += 1;
19355 parsed_count = i;
19356 }
19357
19358 if let Some(arr_name) = &array_name {
19360 let mut arr_values: Vec<String> = Vec::new();
19361 for (opt, val) in &results {
19362 arr_values.push(opt.clone());
19363 if let Some(v) = val {
19364 arr_values.push(v.clone());
19365 }
19366 }
19367 self.arrays.insert(arr_name.clone(), arr_values);
19368 }
19369
19370 if let Some(assoc) = &assoc_name {
19372 let mut map: HashMap<String, String> = HashMap::new();
19373 for (opt, val) in &results {
19374 map.insert(opt.clone(), val.clone().unwrap_or_default());
19375 }
19376 self.assoc_arrays.insert(assoc.clone(), map);
19377 }
19378
19379 for spec in &opt_specs {
19381 if let Some(target) = &spec.target_array {
19382 let values: Vec<String> = results
19383 .iter()
19384 .filter(|(opt, _)| opt.trim_start_matches('-') == spec.name)
19385 .flat_map(|(opt, val)| {
19386 let mut v = vec![opt.clone()];
19387 if let Some(arg) = val {
19388 v.push(arg.clone());
19389 }
19390 v
19391 })
19392 .collect();
19393 if !values.is_empty() || !keep_values {
19394 self.arrays.insert(target.clone(), values);
19395 }
19396 }
19397 }
19398
19399 if remove_parsed && parsed_count > 0 {
19401 for i in 1..=parsed_count {
19402 self.variables.remove(&i.to_string());
19403 std::env::remove_var(i.to_string());
19404 }
19405 let remaining: Vec<String> = ((parsed_count + 1)..=99)
19407 .map(|i| self.get_variable(&i.to_string()))
19408 .take_while(|v| !v.is_empty())
19409 .collect();
19410 for (i, val) in remaining.iter().enumerate() {
19411 self.variables.insert((i + 1).to_string(), val.clone());
19412 }
19413 }
19414
19415 0
19416 }
19417
19418 fn builtin_readonly(&mut self, args: &[String]) -> i32 {
19420 if args.is_empty() {
19421 for name in &self.readonly_vars {
19423 if let Some(val) = self.variables.get(name) {
19424 println!("readonly {}={}", name, val);
19425 }
19426 }
19427 return 0;
19428 }
19429
19430 for arg in args {
19431 if arg == "-p" {
19432 for name in &self.readonly_vars {
19433 if let Some(val) = self.variables.get(name) {
19434 println!("declare -r {}=\"{}\"", name, val);
19435 }
19436 }
19437 } else if let Some(eq_pos) = arg.find('=') {
19438 let name = &arg[..eq_pos];
19439 let value = &arg[eq_pos + 1..];
19440 self.variables.insert(name.to_string(), value.to_string());
19441 self.readonly_vars.insert(name.to_string());
19442 } else {
19443 self.readonly_vars.insert(arg.clone());
19444 }
19445 }
19446 0
19447 }
19448
19449 fn builtin_unfunction(&mut self, args: &[String]) -> i32 {
19451 for name in args {
19452 if self.functions.remove(name).is_none() {
19453 eprintln!("unfunction: no such function: {}", name);
19454 }
19455 }
19456 0
19457 }
19458
19459 fn builtin_getln(&mut self, args: &[String]) -> i32 {
19461 if args.is_empty() {
19462 eprintln!("getln: missing variable name");
19463 return 1;
19464 }
19465 let mut line = String::new();
19467 if std::io::stdin().read_line(&mut line).is_ok() {
19468 let line = line.trim_end_matches('\n');
19469 self.variables.insert(args[0].clone(), line.to_string());
19470 0
19471 } else {
19472 1
19473 }
19474 }
19475
19476 fn builtin_pushln(&mut self, args: &[String]) -> i32 {
19478 for arg in args {
19479 println!("{}", arg);
19480 }
19481 0
19482 }
19483
19484 fn builtin_bindkey(&mut self, args: &[String]) -> i32 {
19486 use crate::zle::{zle, KeymapName};
19487
19488 if args.is_empty() {
19489 let zle = zle();
19491 for (keys, widget) in zle
19492 .keymaps
19493 .get(&KeymapName::Main)
19494 .map(|km| km.list_bindings().collect::<Vec<_>>())
19495 .unwrap_or_default()
19496 {
19497 println!("\"{}\" {}", keys, widget);
19498 }
19499 return 0;
19500 }
19501
19502 let mut iter = args.iter().peekable();
19503 let mut keymap = KeymapName::Main;
19504 let mut list_mode = false;
19505 let mut list_all = false;
19506 let mut remove = false;
19507
19508 while let Some(arg) = iter.next() {
19509 match arg.as_str() {
19510 "-l" => {
19511 list_mode = true;
19512 }
19513 "-L" => {
19514 list_mode = true;
19515 list_all = true;
19516 }
19517 "-la" | "-lL" => {
19518 list_mode = true;
19519 list_all = true;
19520 }
19521 "-M" => {
19522 if let Some(name) = iter.next() {
19523 if let Some(km) = KeymapName::from_str(name) {
19524 keymap = km;
19525 }
19526 }
19527 }
19528 "-r" => {
19529 remove = true;
19530 }
19531 "-A" => {
19532 return 0;
19534 }
19535 "-N" => {
19536 return 0;
19538 }
19539 "-e" => {
19540 keymap = KeymapName::Emacs;
19541 }
19542 "-v" => {
19543 keymap = KeymapName::ViInsert;
19544 }
19545 "-a" => {
19546 keymap = KeymapName::ViCommand;
19547 }
19548 key if !key.starts_with('-') => {
19549 if let Some(widget) = iter.next() {
19551 let mut zle = zle();
19552 if remove {
19553 zle.unbind_key(keymap, key);
19554 } else {
19555 zle.bind_key(keymap, key, widget);
19556 }
19557 }
19558 return 0;
19559 }
19560 _ => {}
19561 }
19562 }
19563
19564 if list_mode {
19565 let zle = zle();
19566 if list_all {
19567 for km_name in &[
19568 KeymapName::Emacs,
19569 KeymapName::ViInsert,
19570 KeymapName::ViCommand,
19571 ] {
19572 println!("{}", km_name.as_str());
19573 }
19574 } else {
19575 if let Some(km) = zle.keymaps.get(&keymap) {
19576 for (keys, widget) in km.list_bindings() {
19577 println!("bindkey \"{}\" {}", keys, widget);
19578 }
19579 }
19580 }
19581 }
19582
19583 0
19584 }
19585
19586 fn builtin_zle(&mut self, args: &[String]) -> i32 {
19588 use crate::zle::zle;
19589
19590 if args.is_empty() {
19591 return 0;
19592 }
19593
19594 let mut iter = args.iter().peekable();
19595
19596 while let Some(arg) = iter.next() {
19597 match arg.as_str() {
19598 "-l" => {
19599 let zle = zle();
19601 let mut widgets: Vec<&str> = zle.list_widgets();
19602 widgets.sort();
19603 for w in widgets {
19604 println!("{}", w);
19605 }
19606 return 0;
19607 }
19608 "-la" | "-lL" => {
19609 let zle = zle();
19611 let mut widgets: Vec<&str> = zle.list_widgets();
19612 widgets.sort();
19613 for w in widgets {
19614 println!("{}", w);
19615 }
19616 return 0;
19617 }
19618 "-N" => {
19619 if let Some(widget_name) = iter.next() {
19621 let func_name = iter
19622 .next()
19623 .map(|s| s.as_str())
19624 .unwrap_or(widget_name.as_str());
19625 let mut zle = zle();
19626 zle.define_widget(widget_name, func_name);
19627 }
19628 return 0;
19629 }
19630 "-D" => {
19631 return 0;
19633 }
19634 "-A" => {
19635 return 0;
19637 }
19638 "-R" => {
19639 return 0;
19641 }
19642 "-U" => {
19643 return 0;
19645 }
19646 "-K" => {
19647 return 0;
19649 }
19650 "-F" => {
19651 return 0;
19653 }
19654 "-M" => {
19655 return 0;
19657 }
19658 "-I" => {
19659 return 0;
19661 }
19662 "-f" => {
19663 if let Some(name) = iter.next() {
19665 let zle = zle();
19666 return if zle.get_widget(name).is_some() { 0 } else { 1 };
19667 }
19668 return 1;
19669 }
19670 widget_name if !widget_name.starts_with('-') => {
19671 let mut zle = zle();
19673 match zle.execute_widget(widget_name, None) {
19674 crate::zle::WidgetResult::Ok => return 0,
19675 crate::zle::WidgetResult::Error(e) => {
19676 eprintln!("zle: {}", e);
19677 return 1;
19678 }
19679 crate::zle::WidgetResult::CallFunction(func) => {
19680 drop(zle);
19682 if let Some(f) = self.functions.get(&func).cloned() {
19683 return self.call_function(&f, &[]).unwrap_or(1);
19684 }
19685 return 1;
19686 }
19687 _ => return 0,
19688 }
19689 }
19690 _ => {}
19691 }
19692 }
19693
19694 0
19695 }
19696
19697 fn builtin_sched(&mut self, args: &[String]) -> i32 {
19699 use std::time::{Duration, SystemTime};
19700
19701 if args.is_empty() {
19702 if self.scheduled_commands.is_empty() {
19704 return 0;
19705 }
19706 let now = SystemTime::now();
19707 for cmd in &self.scheduled_commands {
19708 let remaining = cmd.run_at.duration_since(now).unwrap_or(Duration::ZERO);
19709 println!("{:3} +{:5} {}", cmd.id, remaining.as_secs(), cmd.command);
19710 }
19711 return 0;
19712 }
19713
19714 let mut i = 0;
19715 while i < args.len() {
19716 match args[i].as_str() {
19717 "-" => {
19718 i += 1;
19720 if i >= args.len() {
19721 eprintln!("sched: -: need item number");
19722 return 1;
19723 }
19724 if let Ok(id) = args[i].parse::<u32>() {
19725 self.scheduled_commands.retain(|c| c.id != id);
19726 return 0;
19727 } else {
19728 eprintln!("sched: invalid item number");
19729 return 1;
19730 }
19731 }
19732 "+" => {
19733 i += 1;
19735 if i >= args.len() {
19736 eprintln!("sched: +: need time");
19737 return 1;
19738 }
19739 let secs: u64 = args[i].parse().unwrap_or(0);
19740 i += 1;
19741 let command = args[i..].join(" ");
19742
19743 let id = self.scheduled_commands.len() as u32 + 1;
19744 self.scheduled_commands.push(ScheduledCommand {
19745 id,
19746 run_at: SystemTime::now() + Duration::from_secs(secs),
19747 command,
19748 });
19749 return 0;
19750 }
19751 time_str => {
19752 let parts: Vec<&str> = time_str.split(':').collect();
19754 if parts.len() >= 2 {
19755 let hour: u32 = parts[0].parse().unwrap_or(0);
19756 let min: u32 = parts[1].parse().unwrap_or(0);
19757 let sec: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(0);
19758
19759 let now = SystemTime::now();
19761 let target_secs = (hour * 3600 + min * 60 + sec) as u64;
19762 let _day_secs = 86400u64;
19763
19764 let run_at = now + Duration::from_secs(target_secs);
19766
19767 i += 1;
19768 let command = args[i..].join(" ");
19769
19770 let id = self.scheduled_commands.len() as u32 + 1;
19771 self.scheduled_commands.push(ScheduledCommand {
19772 id,
19773 run_at,
19774 command,
19775 });
19776 return 0;
19777 } else {
19778 eprintln!("sched: invalid time format");
19779 return 1;
19780 }
19781 }
19782 }
19783 }
19784 0
19785 }
19786
19787 fn builtin_zcompile(&mut self, args: &[String]) -> i32 {
19789 use crate::zwc::{ZwcBuilder, ZwcFile};
19790
19791 let mut list_mode = false; let mut compile_current = false; let mut compile_auto = false; let mut files: Vec<String> = Vec::new();
19795
19796 let mut i = 0;
19797 while i < args.len() {
19798 let arg = &args[i];
19799 if arg.starts_with('-') && arg.len() > 1 {
19800 for c in arg[1..].chars() {
19801 match c {
19802 't' => list_mode = true,
19803 'c' => compile_current = true,
19804 'a' => compile_auto = true,
19805 'U' | 'M' | 'R' | 'm' | 'z' | 'k' => {} _ => {
19807 eprintln!("zcompile: unknown option: -{}", c);
19808 return 1;
19809 }
19810 }
19811 }
19812 } else {
19813 files.push(arg.clone());
19814 }
19815 i += 1;
19816 }
19817
19818 if files.is_empty() {
19819 eprintln!("zcompile: not enough arguments");
19820 return 1;
19821 }
19822
19823 if list_mode {
19825 let zwc_path = if files[0].ends_with(".zwc") {
19826 files[0].clone()
19827 } else {
19828 format!("{}.zwc", files[0])
19829 };
19830
19831 match ZwcFile::load(&zwc_path) {
19832 Ok(zwc) => {
19833 println!("zwc file for zshrs-{}", env!("CARGO_PKG_VERSION"));
19834 if files.len() > 1 {
19835 for name in &files[1..] {
19837 if zwc.get_function(name).is_some() {
19838 println!("{}", name);
19839 } else {
19840 eprintln!("zcompile: function not found: {}", name);
19841 return 1;
19842 }
19843 }
19844 } else {
19845 for name in zwc.list_functions() {
19847 println!("{}", name);
19848 }
19849 }
19850 return 0;
19851 }
19852 Err(e) => {
19853 eprintln!("zcompile: can't read zwc file: {}: {}", zwc_path, e);
19854 return 1;
19855 }
19856 }
19857 }
19858
19859 if compile_current || compile_auto {
19861 let zwc_path = if files[0].ends_with(".zwc") {
19862 files[0].clone()
19863 } else {
19864 format!("{}.zwc", files[0])
19865 };
19866
19867 let mut builder = ZwcBuilder::new();
19868
19869 if files.len() > 1 {
19870 for name in &files[1..] {
19872 if let Some(func) = self.functions.get(name) {
19873 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
19875 builder.add_source(name, &source);
19876 } else if compile_auto && self.autoload_pending.contains_key(name) {
19877 if let Some(path) = self.find_function_file(name) {
19879 if let Err(e) = builder.add_file(&path) {
19880 eprintln!("zcompile: can't read {}: {}", name, e);
19881 return 1;
19882 }
19883 }
19884 } else {
19885 eprintln!("zcompile: no such function: {}", name);
19886 return 1;
19887 }
19888 }
19889 } else {
19890 for (name, func) in &self.functions {
19892 let source = format!("# Compiled function: {}\n# Body: {:?}", name, func);
19893 builder.add_source(name, &source);
19894 }
19895 }
19896
19897 if let Err(e) = builder.write(&zwc_path) {
19898 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
19899 return 1;
19900 }
19901 return 0;
19902 }
19903
19904 let zwc_path = if files[0].ends_with(".zwc") {
19906 files[0].clone()
19907 } else {
19908 format!("{}.zwc", files[0])
19909 };
19910
19911 let mut builder = ZwcBuilder::new();
19912
19913 let source_files = if files.len() == 1 {
19915 let path = std::path::Path::new(&files[0]);
19917 if path.is_dir() {
19918 match std::fs::read_dir(path) {
19920 Ok(entries) => {
19921 for entry in entries.flatten() {
19922 let p = entry.path();
19923 if p.is_file() && !p.extension().map_or(false, |e| e == "zwc") {
19924 if let Err(e) = builder.add_file(&p) {
19925 eprintln!("zcompile: can't read {:?}: {}", p, e);
19926 }
19927 }
19928 }
19929 }
19930 Err(e) => {
19931 eprintln!("zcompile: can't read directory: {}", e);
19932 return 1;
19933 }
19934 }
19935 vec![]
19936 } else {
19937 vec![files[0].clone()]
19938 }
19939 } else {
19940 files[1..].to_vec()
19941 };
19942
19943 for file in &source_files {
19944 let path = std::path::Path::new(file);
19945 if let Err(e) = builder.add_file(path) {
19946 eprintln!("zcompile: can't read {}: {}", file, e);
19947 return 1;
19948 }
19949 }
19950
19951 if let Err(e) = builder.write(&zwc_path) {
19952 eprintln!("zcompile: can't write {}: {}", zwc_path, e);
19953 return 1;
19954 }
19955
19956 0
19957 }
19958
19959 fn builtin_zformat(&self, args: &[String]) -> i32 {
19961 if args.len() < 2 {
19962 eprintln!("zformat: not enough arguments");
19963 return 1;
19964 }
19965
19966 match args[0].as_str() {
19967 "-f" => {
19968 if args.len() < 3 {
19970 return 1;
19971 }
19972 let _var_name = &args[1];
19973 let format = &args[2];
19974 let specs: HashMap<char, &str> = args[3..]
19975 .iter()
19976 .filter_map(|s| {
19977 let mut chars = s.chars();
19978 let key = chars.next()?;
19979 if chars.next() == Some(':') {
19980 Some((key, &s[2..]))
19981 } else {
19982 None
19983 }
19984 })
19985 .collect();
19986
19987 let mut result = String::new();
19988 let mut chars = format.chars().peekable();
19989 while let Some(c) = chars.next() {
19990 if c == '%' {
19991 if let Some(&spec_char) = chars.peek() {
19992 if let Some(replacement) = specs.get(&spec_char) {
19993 result.push_str(replacement);
19994 chars.next();
19995 continue;
19996 }
19997 }
19998 }
19999 result.push(c);
20000 }
20001 println!("{}", result);
20002 }
20003 "-a" => {
20004 if args.len() < 4 {
20007 eprintln!("zformat -a: need array, separator, and specs");
20008 return 1;
20009 }
20010 let _array_name = &args[1];
20011 let sep = &args[2];
20012
20013 let mut results = Vec::new();
20014 for spec in &args[3..] {
20015 let parts: Vec<&str> = spec.splitn(3, ':').collect();
20016 if parts.len() >= 2 {
20017 let text = parts[0];
20018 let value = parts[1];
20019 let cond = parts.get(2).copied();
20020
20021 if let Some(c) = cond {
20023 if c.is_empty() || c == "0" {
20024 continue;
20025 }
20026 }
20027
20028 if !value.is_empty() {
20029 results.push(format!("{}{}{}", text, sep, value));
20030 }
20031 }
20032 }
20033
20034 for r in results {
20035 println!("{}", r);
20036 }
20037 }
20038 _ => {
20039 eprintln!("zformat: unknown option: {}", args[0]);
20040 return 1;
20041 }
20042 }
20043 0
20044 }
20045
20046 fn builtin_vared(&mut self, args: &[String]) -> i32 {
20048 if args.is_empty() {
20049 eprintln!("vared: not enough arguments");
20050 return 1;
20051 }
20052
20053 let mut var_name = String::new();
20054 let mut prompt = String::new();
20055 let mut rprompt = String::new();
20056 let mut _history = false; let mut i = 0;
20058
20059 while i < args.len() {
20060 match args[i].as_str() {
20061 "-p" if i + 1 < args.len() => {
20062 i += 1;
20063 prompt = args[i].clone();
20064 }
20065 "-r" if i + 1 < args.len() => {
20066 i += 1;
20067 rprompt = args[i].clone();
20068 }
20069 "-h" => _history = true,
20070 "-c" => {} "-e" => {} "-M" | "-m" => {
20073 i += 1;
20074 } "-a" | "-A" => {
20076 i += 1;
20077 } s if !s.starts_with('-') => {
20079 var_name = s.to_string();
20080 }
20081 _ => {}
20082 }
20083 i += 1;
20084 }
20085
20086 if var_name.is_empty() {
20087 eprintln!("vared: not enough arguments");
20088 return 1;
20089 }
20090
20091 let current = self.get_variable(&var_name);
20093
20094 if !prompt.is_empty() {
20096 eprint!("{}", prompt);
20097 }
20098 print!("{}", current);
20099 if !rprompt.is_empty() {
20100 eprint!("{}", rprompt);
20101 }
20102
20103 let mut input = String::new();
20104 if std::io::stdin().read_line(&mut input).is_ok() {
20105 let value = input.trim_end_matches('\n').to_string();
20106 self.variables.insert(var_name, value);
20107 return 0;
20108 }
20109 1
20110 }
20111
20112 fn builtin_echotc(&self, args: &[String]) -> i32 {
20114 if args.is_empty() {
20115 eprintln!("echotc: not enough arguments");
20116 return 1;
20117 }
20118
20119 match args[0].as_str() {
20121 "cl" => print!("\x1b[H\x1b[2J"), "cd" => print!("\x1b[J"), "ce" => print!("\x1b[K"), "cm" => {
20125 if args.len() >= 3 {
20127 if let (Ok(row), Ok(col)) = (args[1].parse::<u32>(), args[2].parse::<u32>()) {
20128 print!("\x1b[{};{}H", row + 1, col + 1);
20129 }
20130 }
20131 }
20132 "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" => {
20147 if args.len() >= 2 {
20149 if let Ok(color) = args[1].parse::<u32>() {
20150 print!("\x1b[38;5;{}m", color);
20151 }
20152 }
20153 }
20154 "AB" | "setab" => {
20155 if args.len() >= 2 {
20157 if let Ok(color) = args[1].parse::<u32>() {
20158 print!("\x1b[48;5;{}m", color);
20159 }
20160 }
20161 }
20162 "Co" | "colors" => {
20163 println!("256");
20165 }
20166 "co" | "cols" => {
20167 println!(
20169 "{}",
20170 std::env::var("COLUMNS")
20171 .ok()
20172 .and_then(|s| s.parse().ok())
20173 .unwrap_or(80u16)
20174 );
20175 }
20176 "li" | "lines" => {
20177 println!(
20179 "{}",
20180 std::env::var("LINES")
20181 .ok()
20182 .and_then(|s| s.parse().ok())
20183 .unwrap_or(24u16)
20184 );
20185 }
20186 cap => {
20187 eprintln!("echotc: unknown capability: {}", cap);
20188 return 1;
20189 }
20190 }
20191 use std::io::Write;
20192 let _ = std::io::stdout().flush();
20193 0
20194 }
20195
20196 fn builtin_echoti(&self, args: &[String]) -> i32 {
20198 self.builtin_echotc(args)
20201 }
20202
20203 fn builtin_zpty(&mut self, args: &[String]) -> i32 {
20205 use std::io::{Read, Write};
20206 use std::process::{Command, Stdio};
20207
20208 if args.is_empty() {
20209 if self.zptys.is_empty() {
20211 return 0;
20212 }
20213 for (name, state) in &self.zptys {
20214 println!("{}: {} (pid {})", name, state.cmd, state.pid);
20215 }
20216 return 0;
20217 }
20218
20219 let mut i = 0;
20220 while i < args.len() {
20221 match args[i].as_str() {
20222 "-d" => {
20223 i += 1;
20225 if i >= args.len() {
20226 eprintln!("zpty: -d requires pty name");
20227 return 1;
20228 }
20229 let name = &args[i];
20230 if let Some(mut state) = self.zptys.remove(name) {
20231 if let Some(ref mut child) = state.child {
20232 let _ = child.kill();
20233 }
20234 return 0;
20235 } else {
20236 eprintln!("zpty: no such pty: {}", name);
20237 return 1;
20238 }
20239 }
20240 "-w" => {
20241 i += 1;
20243 if i >= args.len() {
20244 eprintln!("zpty: -w requires pty name");
20245 return 1;
20246 }
20247 let name = args[i].clone();
20248 i += 1;
20249 let data = args[i..].join(" ") + "\n";
20250
20251 if let Some(state) = self.zptys.get_mut(&name) {
20252 if let Some(ref mut stdin) = state.stdin {
20253 if stdin.write_all(data.as_bytes()).is_ok() {
20254 let _ = stdin.flush();
20255 return 0;
20256 }
20257 }
20258 eprintln!("zpty: write failed");
20259 return 1;
20260 } else {
20261 eprintln!("zpty: no such pty: {}", name);
20262 return 1;
20263 }
20264 }
20265 "-r" => {
20266 i += 1;
20268 if i >= args.len() {
20269 eprintln!("zpty: -r requires pty name");
20270 return 1;
20271 }
20272 let name = args[i].clone();
20273 i += 1;
20274 let var_name = if i < args.len() {
20275 args[i].clone()
20276 } else {
20277 "REPLY".to_string()
20278 };
20279
20280 if let Some(state) = self.zptys.get_mut(&name) {
20281 if let Some(ref mut stdout) = state.stdout {
20282 let mut buf = vec![0u8; 4096];
20283 match stdout.read(&mut buf) {
20284 Ok(n) => {
20285 let data = String::from_utf8_lossy(&buf[..n]).to_string();
20286 self.variables.insert(var_name, data);
20287 return 0;
20288 }
20289 Err(_) => return 1,
20290 }
20291 }
20292 return 1;
20293 } else {
20294 eprintln!("zpty: no such pty: {}", name);
20295 return 1;
20296 }
20297 }
20298 "-t" => {
20299 i += 1;
20301 if i >= args.len() {
20302 return 1;
20303 }
20304 let name = &args[i];
20305 if self.zptys.contains_key(name) {
20306 return 0; }
20308 return 1;
20309 }
20310 "-L" => {
20311 for (name, state) in &self.zptys {
20313 println!("zpty {} {}", name, state.cmd);
20314 }
20315 return 0;
20316 }
20317 "-b" | "-e" => {
20318 i += 1;
20320 continue;
20321 }
20322 name if !name.starts_with('-') => {
20323 i += 1;
20325 if i >= args.len() {
20326 eprintln!("zpty: command required");
20327 return 1;
20328 }
20329 let cmd_str = args[i..].join(" ");
20330
20331 match Command::new("sh")
20332 .arg("-c")
20333 .arg(&cmd_str)
20334 .stdin(Stdio::piped())
20335 .stdout(Stdio::piped())
20336 .stderr(Stdio::piped())
20337 .spawn()
20338 {
20339 Ok(mut child) => {
20340 let pid = child.id();
20341 let stdin = child.stdin.take();
20342 let stdout = child.stdout.take();
20343
20344 self.zptys.insert(
20345 name.to_string(),
20346 ZptyState {
20347 pid,
20348 cmd: cmd_str,
20349 stdin,
20350 stdout,
20351 child: Some(child),
20352 },
20353 );
20354 return 0;
20355 }
20356 Err(e) => {
20357 eprintln!("zpty: failed to start: {}", e);
20358 return 1;
20359 }
20360 }
20361 }
20362 _ => {
20363 i += 1;
20364 }
20365 }
20366 i += 1;
20367 }
20368 0
20369 }
20370
20371 fn builtin_zprof(&mut self, args: &[String]) -> i32 {
20373 use crate::zprof::ZprofOptions;
20374
20375 let options = ZprofOptions {
20376 clear: args.iter().any(|a| a == "-c"),
20377 };
20378
20379 let (status, output) = crate::zprof::builtin_zprof(&mut self.profiler, &options);
20380 if !output.is_empty() {
20381 print!("{}", output);
20382 }
20383 status
20384 }
20385
20386 fn builtin_zsocket(&mut self, args: &[String]) -> i32 {
20388 use std::os::unix::net::{UnixListener, UnixStream};
20389
20390 if args.is_empty() {
20391 if self.unix_sockets.is_empty() {
20393 return 0;
20394 }
20395 for (fd, state) in &self.unix_sockets {
20396 let path = state
20397 .path
20398 .as_ref()
20399 .map(|p| p.display().to_string())
20400 .unwrap_or_default();
20401 let status = if state.listening {
20402 "listening"
20403 } else {
20404 "connected"
20405 };
20406 println!("{}: {} ({})", fd, path, status);
20407 }
20408 return 0;
20409 }
20410
20411 let mut i = 0;
20412 let mut verbose = false;
20413 let mut var_name = "REPLY".to_string();
20414
20415 while i < args.len() {
20416 match args[i].as_str() {
20417 "-v" => {
20418 verbose = true;
20419 i += 1;
20420 if i < args.len() && !args[i].starts_with('-') {
20421 var_name = args[i].clone();
20422 }
20423 }
20424 "-l" => {
20425 i += 1;
20427 if i >= args.len() {
20428 eprintln!("zsocket: -l requires path");
20429 return 1;
20430 }
20431 let path = PathBuf::from(&args[i]);
20432
20433 let _ = std::fs::remove_file(&path);
20435
20436 match UnixListener::bind(&path) {
20437 Ok(listener) => {
20438 let fd = self.next_fd;
20439 self.next_fd += 1;
20440
20441 self.unix_sockets.insert(
20442 fd,
20443 UnixSocketState {
20444 path: Some(path),
20445 listening: true,
20446 stream: None,
20447 listener: Some(listener),
20448 },
20449 );
20450
20451 if verbose {
20452 self.variables.insert(var_name.clone(), fd.to_string());
20453 }
20454 println!("{}", fd);
20455 return 0;
20456 }
20457 Err(e) => {
20458 eprintln!("zsocket: bind failed: {}", e);
20459 return 1;
20460 }
20461 }
20462 }
20463 "-a" => {
20464 i += 1;
20466 if i >= args.len() {
20467 eprintln!("zsocket: -a requires fd");
20468 return 1;
20469 }
20470 let listen_fd: i32 = args[i].parse().unwrap_or(-1);
20471
20472 if let Some(state) = self.unix_sockets.get(&listen_fd) {
20473 if let Some(ref listener) = state.listener {
20474 match listener.accept() {
20475 Ok((stream, _addr)) => {
20476 let new_fd = self.next_fd;
20477 self.next_fd += 1;
20478
20479 self.unix_sockets.insert(
20480 new_fd,
20481 UnixSocketState {
20482 path: None,
20483 listening: false,
20484 stream: Some(stream),
20485 listener: None,
20486 },
20487 );
20488
20489 if verbose {
20490 self.variables.insert(var_name.clone(), new_fd.to_string());
20491 }
20492 println!("{}", new_fd);
20493 return 0;
20494 }
20495 Err(e) => {
20496 eprintln!("zsocket: accept failed: {}", e);
20497 return 1;
20498 }
20499 }
20500 }
20501 }
20502 eprintln!("zsocket: invalid fd");
20503 return 1;
20504 }
20505 "-d" => {
20506 i += 1;
20508 if i >= args.len() {
20509 eprintln!("zsocket: -d requires fd");
20510 return 1;
20511 }
20512 let fd: i32 = args[i].parse().unwrap_or(-1);
20513
20514 if let Some(state) = self.unix_sockets.remove(&fd) {
20515 if let Some(path) = state.path {
20516 let _ = std::fs::remove_file(path);
20517 }
20518 return 0;
20519 }
20520 eprintln!("zsocket: no such fd");
20521 return 1;
20522 }
20523 path if !path.starts_with('-') => {
20524 match UnixStream::connect(path) {
20526 Ok(stream) => {
20527 let fd = self.next_fd;
20528 self.next_fd += 1;
20529
20530 self.unix_sockets.insert(
20531 fd,
20532 UnixSocketState {
20533 path: Some(PathBuf::from(path)),
20534 listening: false,
20535 stream: Some(stream),
20536 listener: None,
20537 },
20538 );
20539
20540 if verbose {
20541 self.variables.insert(var_name.clone(), fd.to_string());
20542 }
20543 println!("{}", fd);
20544 return 0;
20545 }
20546 Err(e) => {
20547 eprintln!("zsocket: connect failed: {}", e);
20548 return 1;
20549 }
20550 }
20551 }
20552 _ => {}
20553 }
20554 i += 1;
20555 }
20556 0
20557 }
20558
20559 fn builtin_ztcp(&mut self, args: &[String]) -> i32 {
20561 self.builtin_zsocket(args)
20563 }
20564
20565 fn builtin_zregexparse(&mut self, args: &[String]) -> i32 {
20567 if args.len() < 2 {
20568 eprintln!("zregexparse: usage: zregexparse var pattern [string]");
20569 return 1;
20570 }
20571
20572 let var_name = &args[0];
20573 let pattern = &args[1];
20574 let string = if args.len() > 2 {
20575 args[2].clone()
20576 } else {
20577 self.variables.get("REPLY").cloned().unwrap_or_default()
20578 };
20579
20580 match regex::Regex::new(pattern) {
20581 Ok(re) => {
20582 if let Some(captures) = re.captures(&string) {
20583 if let Some(m) = captures.get(0) {
20585 self.variables
20586 .insert(var_name.clone(), m.as_str().to_string());
20587 }
20588
20589 let mut match_array = Vec::new();
20591 let mut mbegin_array = Vec::new();
20592 let mut mend_array = Vec::new();
20593
20594 for (i, cap) in captures.iter().enumerate() {
20595 if let Some(c) = cap {
20596 match_array.push(c.as_str().to_string());
20597 mbegin_array.push((c.start() + 1).to_string());
20598 mend_array.push(c.end().to_string());
20599 self.variables
20600 .insert(format!("match[{}]", i), c.as_str().to_string());
20601 }
20602 }
20603 self.arrays.insert("match".to_string(), match_array);
20604 self.arrays.insert("mbegin".to_string(), mbegin_array);
20605 self.arrays.insert("mend".to_string(), mend_array);
20606
20607 if let Some(m) = captures.get(0) {
20609 self.variables
20610 .insert("MBEGIN".to_string(), (m.start() + 1).to_string());
20611 self.variables
20612 .insert("MEND".to_string(), m.end().to_string());
20613 }
20614
20615 0
20616 } else {
20617 1
20618 }
20619 }
20620 Err(e) => {
20621 eprintln!("zregexparse: invalid regex: {}", e);
20622 2
20623 }
20624 }
20625 }
20626
20627 fn builtin_clone(&mut self, args: &[String]) -> i32 {
20629 use std::process::Command;
20630
20631 let mut cmd =
20634 Command::new(std::env::current_exe().unwrap_or_else(|_| PathBuf::from("zshrs")));
20635
20636 if !args.is_empty() {
20637 cmd.arg("-c").arg(args.join(" "));
20639 }
20640
20641 for (k, v) in &self.variables {
20643 cmd.env(k, v);
20644 }
20645
20646 match cmd.spawn() {
20647 Ok(mut child) => match child.wait() {
20648 Ok(status) => status.code().unwrap_or(0),
20649 Err(_) => 1,
20650 },
20651 Err(e) => {
20652 eprintln!("clone: failed to spawn subshell: {}", e);
20653 1
20654 }
20655 }
20656 }
20657
20658 fn builtin_log(&mut self, args: &[String]) -> i32 {
20660 self.builtin_exit(args)
20661 }
20662
20663 fn builtin_comparguments(&mut self, _args: &[String]) -> i32 {
20667 0
20669 }
20670
20671 fn builtin_compcall(&mut self, _args: &[String]) -> i32 {
20673 0
20675 }
20676
20677 fn builtin_compctl(&mut self, args: &[String]) -> i32 {
20679 if args.is_empty() {
20680 println!("compctl: old-style completion system");
20681 println!("Use the new completion system (compsys) instead");
20682 return 0;
20683 }
20684 0
20686 }
20687
20688 fn builtin_compdescribe(&mut self, _args: &[String]) -> i32 {
20690 0
20691 }
20692
20693 fn builtin_compfiles(&mut self, _args: &[String]) -> i32 {
20695 0
20696 }
20697
20698 fn builtin_compgroups(&mut self, _args: &[String]) -> i32 {
20700 0
20701 }
20702
20703 fn builtin_compquote(&mut self, _args: &[String]) -> i32 {
20705 0
20706 }
20707
20708 fn builtin_comptags(&mut self, args: &[String]) -> i32 {
20710 if args.is_empty() {
20711 return 1;
20712 }
20713 match args[0].as_str() {
20714 "-i" => {
20715 0
20717 }
20718 "-S" => {
20719 0
20721 }
20722 _ => 1,
20723 }
20724 }
20725
20726 fn builtin_comptry(&mut self, _args: &[String]) -> i32 {
20728 1 }
20730
20731 fn builtin_compvalues(&mut self, _args: &[String]) -> i32 {
20733 0
20734 }
20735
20736 fn builtin_cap(&self, args: &[String]) -> i32 {
20738 if args.is_empty() {
20741 println!("cap: display/set capabilities");
20742 println!(" getcap file... - display capabilities");
20743 println!(" setcap caps file - set capabilities");
20744 return 0;
20745 }
20746
20747 #[cfg(target_os = "linux")]
20748 {
20749 let status = std::process::Command::new(&args[0])
20752 .args(&args[1..])
20753 .status();
20754 return status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
20755 }
20756
20757 #[cfg(not(target_os = "linux"))]
20758 {
20759 eprintln!("cap: capabilities not supported on this platform");
20760 1
20761 }
20762 }
20763
20764 fn builtin_zcurses(&mut self, args: &[String]) -> i32 {
20766 if args.is_empty() {
20767 eprintln!("zcurses: requires subcommand");
20768 return 1;
20769 }
20770
20771 match args[0].as_str() {
20772 "init" => {
20773 println!("zcurses: would initialize curses");
20775 0
20776 }
20777 "end" => {
20778 println!("zcurses: would end curses");
20780 0
20781 }
20782 "addwin" => {
20783 0
20785 }
20786 "delwin" => {
20787 0
20789 }
20790 "refresh" => {
20791 0
20793 }
20794 "move" => {
20795 0
20797 }
20798 "clear" => {
20799 0
20801 }
20802 "char" | "string" => {
20803 0
20805 }
20806 "border" => {
20807 0
20809 }
20810 "attr" => {
20811 0
20813 }
20814 "color" => {
20815 0
20817 }
20818 "scroll" => {
20819 0
20821 }
20822 "input" => {
20823 0
20825 }
20826 "mouse" => {
20827 0
20829 }
20830 "querychar" => {
20831 0
20833 }
20834 "resize" => {
20835 0
20837 }
20838 cmd => {
20839 eprintln!("zcurses: unknown subcommand: {}", cmd);
20840 1
20841 }
20842 }
20843 }
20844
20845 fn builtin_sysread(&mut self, args: &[String]) -> i32 {
20847 use std::io::Read;
20848
20849 let mut fd = 0i32; let mut count: Option<usize> = None;
20851 let mut var_name = "REPLY".to_string();
20852 let mut i = 0;
20853
20854 while i < args.len() {
20855 match args[i].as_str() {
20856 "-c" if i + 1 < args.len() => {
20857 i += 1;
20858 count = args[i].parse().ok();
20859 }
20860 "-i" if i + 1 < args.len() => {
20861 i += 1;
20862 fd = args[i].parse().unwrap_or(0);
20863 }
20864 "-o" if i + 1 < args.len() => {
20865 i += 1;
20866 var_name = args[i].clone();
20867 }
20868 "-t" if i + 1 < args.len() => {
20869 i += 1;
20870 }
20872 _ => {
20873 var_name = args[i].clone();
20874 }
20875 }
20876 i += 1;
20877 }
20878
20879 let mut buffer = vec![0u8; count.unwrap_or(8192)];
20880
20881 if fd == 0 {
20883 match std::io::stdin().read(&mut buffer) {
20884 Ok(n) => {
20885 buffer.truncate(n);
20886 let s = String::from_utf8_lossy(&buffer).to_string();
20887 self.variables.insert(var_name, s);
20888 0
20889 }
20890 Err(_) => 1,
20891 }
20892 } else {
20893 eprintln!("sysread: only fd 0 (stdin) supported");
20894 1
20895 }
20896 }
20897
20898 fn builtin_syswrite(&mut self, args: &[String]) -> i32 {
20900 use std::io::Write;
20901
20902 let mut fd = 1i32; let mut data = String::new();
20904 let mut i = 0;
20905
20906 while i < args.len() {
20907 match args[i].as_str() {
20908 "-o" if i + 1 < args.len() => {
20909 i += 1;
20910 fd = args[i].parse().unwrap_or(1);
20911 }
20912 "-c" if i + 1 < args.len() => {
20913 i += 1;
20914 }
20916 _ => {
20917 data = args[i].clone();
20918 }
20919 }
20920 i += 1;
20921 }
20922
20923 match fd {
20924 1 => {
20925 let _ = std::io::stdout().write_all(data.as_bytes());
20926 let _ = std::io::stdout().flush();
20927 0
20928 }
20929 2 => {
20930 let _ = std::io::stderr().write_all(data.as_bytes());
20931 let _ = std::io::stderr().flush();
20932 0
20933 }
20934 _ => {
20935 eprintln!("syswrite: only fd 1 (stdout) and 2 (stderr) supported");
20936 1
20937 }
20938 }
20939 }
20940
20941 fn builtin_syserror(&self, args: &[String]) -> i32 {
20943 let errno = if args.is_empty() {
20944 std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
20946 } else {
20947 args[0].parse().unwrap_or(0)
20948 };
20949
20950 let err = std::io::Error::from_raw_os_error(errno);
20951 println!("{}", err);
20952 0
20953 }
20954
20955 fn builtin_sysopen(&mut self, args: &[String]) -> i32 {
20957 use std::fs::OpenOptions;
20958
20959 let mut filename = String::new();
20960 let mut var_name = "REPLY".to_string();
20961 let mut read = false;
20962 let mut write = false;
20963 let mut append = false;
20964 let mut create = false;
20965 let mut truncate = false;
20966
20967 let mut i = 0;
20968 while i < args.len() {
20969 match args[i].as_str() {
20970 "-r" => read = true,
20971 "-w" => write = true,
20972 "-a" => append = true,
20973 "-c" => create = true,
20974 "-t" => truncate = true,
20975 "-u" => {
20976 i += 1;
20977 if i < args.len() {
20978 var_name = args[i].clone();
20979 }
20980 }
20981 "-o" => {
20982 i += 1;
20983 }
20985 s if !s.starts_with('-') => {
20986 filename = s.to_string();
20987 }
20988 _ => {}
20989 }
20990 i += 1;
20991 }
20992
20993 if filename.is_empty() {
20994 eprintln!("sysopen: need filename");
20995 return 1;
20996 }
20997
20998 if !read && !write && !append {
21000 read = true;
21001 }
21002
21003 let file = OpenOptions::new()
21004 .read(read)
21005 .write(write || append || truncate)
21006 .append(append)
21007 .create(create || write)
21008 .truncate(truncate)
21009 .open(&filename);
21010
21011 match file {
21012 Ok(f) => {
21013 let fd = self.next_fd;
21014 self.next_fd += 1;
21015 self.open_fds.insert(fd, f);
21016 self.variables.insert(var_name, fd.to_string());
21017 0
21018 }
21019 Err(e) => {
21020 eprintln!("sysopen: {}: {}", filename, e);
21021 1
21022 }
21023 }
21024 }
21025
21026 fn builtin_sysseek(&mut self, args: &[String]) -> i32 {
21028 use std::io::{Seek, SeekFrom};
21029
21030 let mut fd = -1i32;
21031 let mut offset = 0i64;
21032 let mut whence = SeekFrom::Start(0);
21033
21034 let mut i = 0;
21035 while i < args.len() {
21036 match args[i].as_str() {
21037 "-u" => {
21038 i += 1;
21039 if i < args.len() {
21040 fd = args[i].parse().unwrap_or(-1);
21041 }
21042 }
21043 "-w" => {
21044 i += 1;
21045 if i < args.len() {
21046 whence = match args[i].as_str() {
21047 "start" | "set" | "0" => SeekFrom::Start(offset as u64),
21048 "current" | "cur" | "1" => SeekFrom::Current(offset),
21049 "end" | "2" => SeekFrom::End(offset),
21050 _ => SeekFrom::Start(offset as u64),
21051 };
21052 }
21053 }
21054 s if !s.starts_with('-') => {
21055 offset = s.parse().unwrap_or(0);
21056 }
21057 _ => {}
21058 }
21059 i += 1;
21060 }
21061
21062 if fd < 0 {
21063 eprintln!("sysseek: need fd (-u)");
21064 return 1;
21065 }
21066
21067 whence = match whence {
21069 SeekFrom::Start(_) => SeekFrom::Start(offset as u64),
21070 SeekFrom::Current(_) => SeekFrom::Current(offset),
21071 SeekFrom::End(_) => SeekFrom::End(offset),
21072 };
21073
21074 if let Some(file) = self.open_fds.get_mut(&fd) {
21075 match file.seek(whence) {
21076 Ok(pos) => {
21077 self.variables.insert("REPLY".to_string(), pos.to_string());
21078 0
21079 }
21080 Err(e) => {
21081 eprintln!("sysseek: {}", e);
21082 1
21083 }
21084 }
21085 } else {
21086 eprintln!("sysseek: bad fd: {}", fd);
21087 1
21088 }
21089 }
21090
21091 fn builtin_private(&mut self, args: &[String]) -> i32 {
21093 self.builtin_local(args)
21095 }
21096
21097 fn builtin_zattr(&self, cmd: &str, args: &[String]) -> i32 {
21100 match cmd {
21101 "zgetattr" => {
21102 if args.len() < 2 {
21103 eprintln!("zgetattr: need file and attribute name");
21104 return 1;
21105 }
21106 let path = std::ffi::CString::new(args[0].as_str()).unwrap_or_default();
21107 let name = std::ffi::CString::new(args[1].as_str()).unwrap_or_default();
21108 let mut buf = vec![0u8; 4096];
21109
21110 #[cfg(target_os = "macos")]
21111 let len = unsafe {
21112 libc::getxattr(
21113 path.as_ptr(),
21114 name.as_ptr(),
21115 buf.as_mut_ptr() as *mut libc::c_void,
21116 buf.len(),
21117 0,
21118 0,
21119 )
21120 };
21121
21122 #[cfg(target_os = "linux")]
21123 let len = unsafe {
21124 libc::getxattr(
21125 path.as_ptr(),
21126 name.as_ptr(),
21127 buf.as_mut_ptr() as *mut libc::c_void,
21128 buf.len(),
21129 )
21130 };
21131
21132 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
21133 let len: isize = -1;
21134
21135 if len >= 0 {
21136 buf.truncate(len as usize);
21137 println!("{}", String::from_utf8_lossy(&buf));
21138 0
21139 } else {
21140 eprintln!("zgetattr: {}: {}", args[0], std::io::Error::last_os_error());
21141 1
21142 }
21143 }
21144 "zsetattr" => {
21145 if args.len() < 3 {
21146 eprintln!("zsetattr: need file, attribute name, and value");
21147 return 1;
21148 }
21149 let path = std::ffi::CString::new(args[0].as_str()).unwrap_or_default();
21150 let name = std::ffi::CString::new(args[1].as_str()).unwrap_or_default();
21151 let value = args[2].as_bytes();
21152
21153 #[cfg(target_os = "macos")]
21154 let ret = unsafe {
21155 libc::setxattr(
21156 path.as_ptr(),
21157 name.as_ptr(),
21158 value.as_ptr() as *const libc::c_void,
21159 value.len(),
21160 0,
21161 0,
21162 )
21163 };
21164
21165 #[cfg(target_os = "linux")]
21166 let ret = unsafe {
21167 libc::setxattr(
21168 path.as_ptr(),
21169 name.as_ptr(),
21170 value.as_ptr() as *const libc::c_void,
21171 value.len(),
21172 0,
21173 )
21174 };
21175
21176 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
21177 let ret: i32 = -1;
21178
21179 if ret == 0 { 0 } else {
21180 eprintln!("zsetattr: {}: {}", args[0], std::io::Error::last_os_error());
21181 1
21182 }
21183 }
21184 "zdelattr" => {
21185 if args.len() < 2 {
21186 eprintln!("zdelattr: need file and attribute name");
21187 return 1;
21188 }
21189 let path = std::ffi::CString::new(args[0].as_str()).unwrap_or_default();
21190 let name = std::ffi::CString::new(args[1].as_str()).unwrap_or_default();
21191
21192 #[cfg(target_os = "macos")]
21193 let ret = unsafe { libc::removexattr(path.as_ptr(), name.as_ptr(), 0) };
21194
21195 #[cfg(target_os = "linux")]
21196 let ret = unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) };
21197
21198 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
21199 let ret: i32 = -1;
21200
21201 if ret == 0 { 0 } else {
21202 eprintln!("zdelattr: {}: {}", args[0], std::io::Error::last_os_error());
21203 1
21204 }
21205 }
21206 "zlistattr" => {
21207 if args.is_empty() {
21208 eprintln!("zlistattr: need file");
21209 return 1;
21210 }
21211 let path = std::ffi::CString::new(args[0].as_str()).unwrap_or_default();
21212 let mut buf = vec![0u8; 4096];
21213
21214 #[cfg(target_os = "macos")]
21215 let len = unsafe {
21216 libc::listxattr(path.as_ptr(), buf.as_mut_ptr() as *mut i8, buf.len(), 0)
21217 };
21218
21219 #[cfg(target_os = "linux")]
21220 let len = unsafe {
21221 libc::listxattr(path.as_ptr(), buf.as_mut_ptr() as *mut i8, buf.len())
21222 };
21223
21224 #[cfg(not(any(target_os = "macos", target_os = "linux")))]
21225 let len: isize = -1;
21226
21227 if len >= 0 {
21228 buf.truncate(len as usize);
21229 for name in buf.split(|&b| b == 0).filter(|s| !s.is_empty()) {
21230 println!("{}", String::from_utf8_lossy(name));
21231 }
21232 0
21233 } else {
21234 eprintln!("zlistattr: {}: {}", args[0], std::io::Error::last_os_error());
21235 1
21236 }
21237 }
21238 _ => 1,
21239 }
21240 }
21241
21242 fn builtin_zftp(&mut self, args: &[String]) -> i32 {
21244 if args.is_empty() {
21245 println!("zftp: FTP client");
21246 println!(" zftp open host [port]");
21247 println!(" zftp login [user [password]]");
21248 println!(" zftp cd dir");
21249 println!(" zftp get file [localfile]");
21250 println!(" zftp put file [remotefile]");
21251 println!(" zftp ls [dir]");
21252 println!(" zftp close");
21253 return 0;
21254 }
21255
21256 match args[0].as_str() {
21257 "open" => {
21258 if args.len() < 2 {
21259 eprintln!("zftp open: need hostname");
21260 return 1;
21261 }
21262 println!("zftp: would connect to {}", args[1]);
21264 0
21265 }
21266 "login" => {
21267 println!("zftp: would login");
21269 0
21270 }
21271 "cd" => {
21272 if args.len() < 2 {
21273 eprintln!("zftp cd: need directory");
21274 return 1;
21275 }
21276 println!("zftp: would cd to {}", args[1]);
21277 0
21278 }
21279 "get" => {
21280 if args.len() < 2 {
21281 eprintln!("zftp get: need filename");
21282 return 1;
21283 }
21284 println!("zftp: would download {}", args[1]);
21285 0
21286 }
21287 "put" => {
21288 if args.len() < 2 {
21289 eprintln!("zftp put: need filename");
21290 return 1;
21291 }
21292 println!("zftp: would upload {}", args[1]);
21293 0
21294 }
21295 "ls" => {
21296 println!("zftp: would list directory");
21297 0
21298 }
21299 "close" | "quit" => {
21300 println!("zftp: would close connection");
21301 0
21302 }
21303 "params" => {
21304 println!("ZFTP_HOST=");
21306 println!("ZFTP_PORT=21");
21307 println!("ZFTP_USER=");
21308 println!("ZFTP_PWD=");
21309 println!("ZFTP_TYPE=A");
21310 0
21311 }
21312 cmd => {
21313 eprintln!("zftp: unknown command: {}", cmd);
21314 1
21315 }
21316 }
21317 }
21318
21319 fn builtin_promptinit(&mut self, _args: &[String]) -> i32 {
21321 self.arrays.insert(
21322 "prompt_themes".to_string(),
21323 vec![
21324 "adam1".to_string(),
21325 "adam2".to_string(),
21326 "bart".to_string(),
21327 "bigfade".to_string(),
21328 "clint".to_string(),
21329 "default".to_string(),
21330 "elite".to_string(),
21331 "elite2".to_string(),
21332 "fade".to_string(),
21333 "fire".to_string(),
21334 "minimal".to_string(),
21335 "off".to_string(),
21336 "oliver".to_string(),
21337 "pws".to_string(),
21338 "redhat".to_string(),
21339 "restore".to_string(),
21340 "suse".to_string(),
21341 "walters".to_string(),
21342 "zefram".to_string(),
21343 ],
21344 );
21345 self.variables
21346 .insert("prompt_theme".to_string(), "default".to_string());
21347 0
21348 }
21349
21350 fn builtin_prompt(&mut self, args: &[String]) -> i32 {
21352 if args.is_empty() {
21353 let theme = self
21354 .variables
21355 .get("prompt_theme")
21356 .cloned()
21357 .unwrap_or_else(|| "default".to_string());
21358 println!("Current prompt theme: {}", theme);
21359 return 0;
21360 }
21361 match args[0].as_str() {
21362 "-l" | "--list" => {
21363 println!("Available prompt themes:");
21364 if let Some(themes) = self.arrays.get("prompt_themes") {
21365 for theme in themes {
21366 println!(" {}", theme);
21367 }
21368 }
21369 }
21370 "-p" | "--preview" => {
21371 let theme = args.get(1).map(|s| s.as_str()).unwrap_or("default");
21372 self.apply_prompt_theme(theme, true);
21373 }
21374 "-h" | "--help" => {
21375 println!("prompt [options] [theme]");
21376 println!(" -l, --list List available themes");
21377 println!(" -p, --preview Preview a theme");
21378 println!(" -s, --setup Set up a theme");
21379 }
21380 _ => {
21381 let theme = if args[0].starts_with('-') {
21382 args.get(1).map(|s| s.as_str()).unwrap_or("default")
21383 } else {
21384 args[0].as_str()
21385 };
21386 self.apply_prompt_theme(theme, false);
21387 }
21388 }
21389 0
21390 }
21391
21392 fn apply_prompt_theme(&mut self, theme: &str, preview: bool) {
21393 let (ps1, rps1) = match theme {
21394 "minimal" => ("%# ", ""),
21395 "off" => ("$ ", ""),
21396 "adam1" => (
21397 "%B%F{cyan}%n@%m %F{blue}%~%f%b %# ",
21398 "%F{yellow}%D{%H:%M}%f",
21399 ),
21400 "redhat" => ("[%n@%m %~]$ ", ""),
21401 _ => ("%n@%m %~ %# ", ""),
21402 };
21403 if preview {
21404 println!("PS1={:?}", ps1);
21405 println!("RPS1={:?}", rps1);
21406 } else {
21407 self.variables.insert("PS1".to_string(), ps1.to_string());
21408 self.variables.insert("RPS1".to_string(), rps1.to_string());
21409 self.variables
21410 .insert("prompt_theme".to_string(), theme.to_string());
21411 }
21412 }
21413
21414 fn builtin_pcre_compile(&mut self, args: &[String]) -> i32 {
21416 use crate::pcre::{pcre_compile, PcreCompileOptions};
21417
21418 let mut pattern = String::new();
21419 let mut options = PcreCompileOptions::default();
21420
21421 for arg in args {
21422 match arg.as_str() {
21423 "-a" => options.anchored = true,
21424 "-i" => options.caseless = true,
21425 "-m" => options.multiline = true,
21426 "-s" => options.dotall = true,
21427 "-x" => options.extended = true,
21428 s if !s.starts_with('-') => pattern = s.to_string(),
21429 _ => {}
21430 }
21431 }
21432
21433 if pattern.is_empty() {
21434 eprintln!("pcre_compile: no pattern specified");
21435 return 1;
21436 }
21437
21438 match pcre_compile(&pattern, &options, &mut self.pcre_state) {
21439 Ok(()) => 0,
21440 Err(e) => {
21441 eprintln!("pcre_compile: {}", e);
21442 1
21443 }
21444 }
21445 }
21446
21447 fn builtin_pcre_match(&mut self, args: &[String]) -> i32 {
21449 use crate::pcre::{pcre_match, PcreMatchOptions};
21450
21451 let mut var_name = "MATCH".to_string();
21452 let mut array_name = "match".to_string();
21453 let mut string = String::new();
21454 let mut i = 0;
21455
21456 while i < args.len() {
21457 match args[i].as_str() {
21458 "-v" => {
21459 i += 1;
21460 if i < args.len() {
21461 var_name = args[i].clone();
21462 }
21463 }
21464 "-a" => {
21465 i += 1;
21466 if i < args.len() {
21467 array_name = args[i].clone();
21468 }
21469 }
21470 s if !s.starts_with('-') => string = s.to_string(),
21471 _ => {}
21472 }
21473 i += 1;
21474 }
21475
21476 let options = PcreMatchOptions {
21477 match_var: Some(var_name.clone()),
21478 array_var: Some(array_name.clone()),
21479 ..Default::default()
21480 };
21481
21482 match pcre_match(&string, &options, &self.pcre_state) {
21483 Ok(result) => {
21484 if result.matched {
21485 if let Some(m) = result.full_match {
21486 self.variables.insert(var_name, m);
21487 }
21488 let matches: Vec<String> =
21489 result.captures.into_iter().filter_map(|c| c).collect();
21490 self.arrays.insert(array_name, matches);
21491 0
21492 } else {
21493 1
21494 }
21495 }
21496 Err(e) => {
21497 eprintln!("pcre_match: {}", e);
21498 1
21499 }
21500 }
21501 }
21502
21503 fn builtin_pcre_study(&mut self, _args: &[String]) -> i32 {
21505 use crate::pcre::pcre_study;
21506
21507 match pcre_study(&self.pcre_state) {
21508 Ok(()) => 0,
21509 Err(e) => {
21510 eprintln!("pcre_study: {}", e);
21511 1
21512 }
21513 }
21514 }
21515
21516 pub fn zfork(&mut self, flags: ForkFlags) -> std::io::Result<ForkResult> {
21523 let can_background = self.options.get("monitor").copied().unwrap_or(false);
21525
21526 unsafe {
21527 match libc::fork() {
21528 -1 => Err(std::io::Error::last_os_error()),
21529 0 => {
21530 if !flags.contains(ForkFlags::NOJOB) && can_background {
21532 let pid = libc::getpid();
21534 if flags.contains(ForkFlags::NEWGRP) {
21535 libc::setpgid(0, 0);
21536 }
21537 if flags.contains(ForkFlags::FGTTY) {
21538 libc::tcsetpgrp(0, pid);
21539 }
21540 }
21541
21542 if !flags.contains(ForkFlags::KEEPSIGS) {
21544 self.reset_signals();
21545 }
21546
21547 Ok(ForkResult::Child)
21548 }
21549 pid => {
21550 if !flags.contains(ForkFlags::NOJOB) {
21552 self.add_child_process(pid);
21554 }
21555 Ok(ForkResult::Parent(pid))
21556 }
21557 }
21558 }
21559 }
21560
21561 fn add_child_process(&mut self, pid: i32) {
21563 self.variables.insert("!".to_string(), pid.to_string());
21565 }
21566
21567 fn reset_signals(&self) {
21569 unsafe {
21570 libc::signal(libc::SIGINT, libc::SIG_DFL);
21571 libc::signal(libc::SIGQUIT, libc::SIG_DFL);
21572 libc::signal(libc::SIGTERM, libc::SIG_DFL);
21573 libc::signal(libc::SIGTSTP, libc::SIG_DFL);
21574 libc::signal(libc::SIGTTIN, libc::SIG_DFL);
21575 libc::signal(libc::SIGTTOU, libc::SIG_DFL);
21576 libc::signal(libc::SIGCHLD, libc::SIG_DFL);
21577 }
21578 }
21579
21580 pub fn zexecve(&self, cmd: &str, args: &[String]) -> ! {
21583 use std::ffi::CString;
21584 use std::os::unix::ffi::OsStrExt;
21585
21586 let c_cmd = CString::new(cmd).expect("CString::new failed");
21587
21588 let c_args: Vec<CString> = std::iter::once(c_cmd.clone())
21590 .chain(args.iter().map(|s| CString::new(s.as_str()).unwrap()))
21591 .collect();
21592
21593 let c_argv: Vec<*const libc::c_char> = c_args
21594 .iter()
21595 .map(|s| s.as_ptr())
21596 .chain(std::iter::once(std::ptr::null()))
21597 .collect();
21598
21599 let env_vars: Vec<CString> = std::env::vars()
21601 .map(|(k, v)| CString::new(format!("{}={}", k, v)).unwrap())
21602 .collect();
21603
21604 let c_envp: Vec<*const libc::c_char> = env_vars
21605 .iter()
21606 .map(|s| s.as_ptr())
21607 .chain(std::iter::once(std::ptr::null()))
21608 .collect();
21609
21610 unsafe {
21611 libc::execve(c_cmd.as_ptr(), c_argv.as_ptr(), c_envp.as_ptr());
21612 eprintln!(
21614 "zshrs: exec failed: {}: {}",
21615 cmd,
21616 std::io::Error::last_os_error()
21617 );
21618 std::process::exit(127);
21619 }
21620 }
21621
21622 pub fn entersubsh(&mut self, flags: SubshellFlags) {
21625 let level = self
21627 .get_variable("ZSH_SUBSHELL")
21628 .parse::<i32>()
21629 .unwrap_or(0);
21630 self.variables
21631 .insert("ZSH_SUBSHELL".to_string(), (level + 1).to_string());
21632
21633 if flags.contains(SubshellFlags::NOMONITOR) {
21635 self.options.insert("monitor".to_string(), false);
21636 }
21637
21638 if !flags.contains(SubshellFlags::KEEPFDS) {
21640 self.close_extra_fds();
21641 }
21642
21643 if !flags.contains(SubshellFlags::KEEPTRAPS) {
21645 self.reset_traps();
21646 }
21647 }
21648
21649 fn close_extra_fds(&self) {
21651 for fd in 10..256 {
21653 unsafe {
21654 libc::close(fd);
21655 }
21656 }
21657 }
21658
21659 fn reset_traps(&mut self) {
21661 self.traps.clear();
21662 }
21663
21664 pub fn doshfunc(
21667 &mut self,
21668 name: &str,
21669 func: &ShellCommand,
21670 args: &[String],
21671 ) -> Result<i32, String> {
21672 let old_argv = self.positional_params.clone();
21674 let old_funcstack = self.arrays.get("funcstack").cloned();
21675 let old_funcsourcetrace = self.arrays.get("funcsourcetrace").cloned();
21676
21677 self.positional_params = args.to_vec();
21679
21680 let mut funcstack = old_funcstack.clone().unwrap_or_default();
21682 funcstack.insert(0, name.to_string());
21683 self.arrays.insert("funcstack".to_string(), funcstack);
21684
21685 let result = self.execute_command(func);
21687
21688 self.positional_params = old_argv;
21690 if let Some(fs) = old_funcstack {
21691 self.arrays.insert("funcstack".to_string(), fs);
21692 } else {
21693 self.arrays.remove("funcstack");
21694 }
21695 if let Some(fst) = old_funcsourcetrace {
21696 self.arrays.insert("funcsourcetrace".to_string(), fst);
21697 }
21698
21699 result
21700 }
21701
21702 pub fn execarith(&mut self, expr: &str) -> i32 {
21705 let result = self.eval_arith_expr(expr);
21706 if result == 0 {
21707 1
21708 } else {
21709 0
21710 }
21711 }
21712
21713 pub fn execcond(&mut self, cond: &CondExpr) -> i32 {
21716 if self.eval_cond_expr(cond) {
21717 0
21718 } else {
21719 1
21720 }
21721 }
21722
21723 pub fn exectime(&mut self, cmd: &ShellCommand) -> Result<i32, String> {
21726 use std::time::Instant;
21727
21728 let start = Instant::now();
21729 let result = self.execute_command(cmd);
21730 let elapsed = start.elapsed();
21731
21732 let user_time = elapsed.as_secs_f64() * 0.7; let sys_time = elapsed.as_secs_f64() * 0.1;
21735 let real_time = elapsed.as_secs_f64();
21736
21737 eprintln!(
21738 "{:.2}s user {:.2}s system {:.0}% cpu {:.3} total",
21739 user_time,
21740 sys_time,
21741 ((user_time + sys_time) / real_time * 100.0).min(100.0),
21742 real_time
21743 );
21744
21745 result
21746 }
21747
21748 pub fn findcmd(&self, name: &str, do_hash: bool) -> Option<String> {
21751 if do_hash {
21753 if let Some(path) = self.command_hash.get(name) {
21754 if std::path::Path::new(path).exists() {
21755 return Some(path.clone());
21756 }
21757 }
21758 }
21759
21760 if let Ok(path_var) = std::env::var("PATH") {
21762 for dir in path_var.split(':') {
21763 let full_path = format!("{}/{}", dir, name);
21764 if std::path::Path::new(&full_path).is_file() {
21765 return Some(full_path);
21766 }
21767 }
21768 }
21769
21770 None
21771 }
21772
21773 pub fn hashcmd(&mut self, name: &str, path: &str) {
21776 self.command_hash.insert(name.to_string(), path.to_string());
21777 }
21778
21779 pub fn iscom(&self, name: &str) -> bool {
21782 if self.is_builtin_cmd(name) {
21784 return true;
21785 }
21786
21787 if self.functions.contains_key(name) {
21789 return true;
21790 }
21791
21792 if self.aliases.contains_key(name) {
21794 return true;
21795 }
21796
21797 self.findcmd(name, true).is_some()
21799 }
21800
21801 fn is_builtin_cmd(&self, name: &str) -> bool {
21803 BUILTIN_SET.contains(name)
21804 }
21805
21806 pub fn closem(&self, exceptions: &[i32]) {
21809 for fd in 3..256 {
21810 if !exceptions.contains(&fd) {
21811 unsafe {
21812 libc::close(fd);
21813 }
21814 }
21815 }
21816 }
21817
21818 pub fn mpipe(&self) -> std::io::Result<(i32, i32)> {
21821 let mut fds = [0i32; 2];
21822 let result = unsafe { libc::pipe(fds.as_mut_ptr()) };
21823 if result == -1 {
21824 Err(std::io::Error::last_os_error())
21825 } else {
21826 Ok((fds[0], fds[1]))
21827 }
21828 }
21829
21830 pub fn addfd(&self, fd: i32, target_fd: i32, mode: RedirMode) -> std::io::Result<()> {
21833 match mode {
21834 RedirMode::Dup => {
21835 if fd != target_fd {
21836 unsafe {
21837 if libc::dup2(fd, target_fd) == -1 {
21838 return Err(std::io::Error::last_os_error());
21839 }
21840 }
21841 }
21842 }
21843 RedirMode::Close => unsafe {
21844 libc::close(target_fd);
21845 },
21846 }
21847 Ok(())
21848 }
21849
21850 pub fn gethere(&mut self, terminator: &str, strip_tabs: bool) -> String {
21853 let mut content = String::new();
21854
21855 if strip_tabs {
21859 content = content
21860 .lines()
21861 .map(|line| line.trim_start_matches('\t'))
21862 .collect::<Vec<_>>()
21863 .join("\n");
21864 }
21865
21866 content
21867 }
21868
21869 pub fn getherestr(&mut self, word: &str) -> String {
21872 let expanded = self.expand_string(word);
21873 format!("{}\n", expanded)
21874 }
21875
21876 pub fn resolvebuiltin(&self, name: &str) -> Option<BuiltinType> {
21879 if self.is_builtin_cmd(name) {
21880 Some(BuiltinType::Normal)
21881 } else {
21882 None
21884 }
21885 }
21886
21887 pub fn cancd(&self, path_str: &str) -> bool {
21890 use std::os::unix::fs::PermissionsExt;
21891
21892 let path = std::path::Path::new(path_str);
21893 if !path.is_dir() {
21894 return false;
21895 }
21896
21897 if let Ok(meta) = path.metadata() {
21898 let mode = meta.permissions().mode();
21899 let uid = unsafe { libc::getuid() };
21901 let gid = unsafe { libc::getgid() };
21902 let file_uid = meta.uid();
21903 let file_gid = meta.gid();
21904
21905 if uid == file_uid {
21906 return (mode & 0o100) != 0;
21907 } else if gid == file_gid {
21908 return (mode & 0o010) != 0;
21909 } else {
21910 return (mode & 0o001) != 0;
21911 }
21912 }
21913
21914 false
21915 }
21916
21917 pub fn commandnotfound(&mut self, name: &str, args: &[String]) -> i32 {
21920 if self.functions.contains_key("command_not_found_handler") {
21922 let mut handler_args = vec![name.to_string()];
21923 handler_args.extend(args.iter().cloned());
21924
21925 if let Some(func) = self.functions.get("command_not_found_handler").cloned() {
21926 if let Ok(code) = self.doshfunc("command_not_found_handler", &func, &handler_args) {
21927 return code;
21928 }
21929 }
21930 }
21931
21932 eprintln!("zshrs: command not found: {}", name);
21933 127
21934 }
21935
21936 fn builtin_cat(&self, args: &[String]) -> i32 {
21941 use std::io::{self, Read, Write};
21942
21943 let mut show_line_numbers = false;
21944 let mut files: Vec<&str> = Vec::new();
21945
21946 for arg in args {
21947 match arg.as_str() {
21948 "-n" => show_line_numbers = true,
21949 "-" => files.push("-"),
21950 a if a.starts_with('-') => {} _ => files.push(arg),
21952 }
21953 }
21954
21955 if files.is_empty() {
21956 files.push("-");
21957 }
21958
21959 let mut stdout = io::stdout().lock();
21960 let mut line_num = 1usize;
21961
21962 for file in files {
21963 let result: io::Result<()> = (|| {
21964 if file == "-" {
21965 let stdin = io::stdin();
21966 let mut handle = stdin.lock();
21967 if show_line_numbers {
21968 let mut buf = String::new();
21969 handle.read_to_string(&mut buf)?;
21970 for line in buf.lines() {
21971 writeln!(stdout, "{:6}\t{}", line_num, line)?;
21972 line_num += 1;
21973 }
21974 } else {
21975 io::copy(&mut handle, &mut stdout)?;
21976 }
21977 } else {
21978 let mut f = match std::fs::File::open(file) {
21979 Ok(f) => f,
21980 Err(e) => {
21981 eprintln!("cat: {}: {}", file, e);
21982 return Err(e);
21983 }
21984 };
21985 if show_line_numbers {
21986 let mut buf = String::new();
21987 f.read_to_string(&mut buf)?;
21988 for line in buf.lines() {
21989 writeln!(stdout, "{:6}\t{}", line_num, line)?;
21990 line_num += 1;
21991 }
21992 } else {
21993 io::copy(&mut f, &mut stdout)?;
21994 }
21995 }
21996 Ok(())
21997 })();
21998
21999 if result.is_err() {
22000 return 1;
22001 }
22002 }
22003 0
22004 }
22005
22006 fn builtin_head(&self, args: &[String]) -> i32 {
22007 use std::io::{BufRead, BufReader};
22008
22009 let mut lines = 10usize;
22010 let mut files: Vec<&str> = Vec::new();
22011 let mut i = 0;
22012
22013 while i < args.len() {
22014 let arg = &args[i];
22015 if arg == "-n" && i + 1 < args.len() {
22016 i += 1;
22017 lines = args[i].parse().unwrap_or(10);
22018 } else if arg.starts_with("-n") {
22019 lines = arg[2..].parse().unwrap_or(10);
22020 } else if arg.starts_with('-') && arg.len() > 1 && arg[1..].chars().all(|c| c.is_ascii_digit()) {
22021 lines = arg[1..].parse().unwrap_or(10);
22022 } else if !arg.starts_with('-') {
22023 files.push(arg);
22024 }
22025 i += 1;
22026 }
22027
22028 if files.is_empty() {
22029 files.push("-");
22030 }
22031
22032 let show_headers = files.len() > 1;
22033
22034 for (idx, file) in files.iter().enumerate() {
22035 if show_headers {
22036 if idx > 0 {
22037 println!();
22038 }
22039 println!("==> {} <==", file);
22040 }
22041
22042 let reader: Box<dyn BufRead> = if *file == "-" {
22043 Box::new(BufReader::new(std::io::stdin()))
22044 } else {
22045 match std::fs::File::open(file) {
22046 Ok(f) => Box::new(BufReader::new(f)),
22047 Err(e) => {
22048 eprintln!("head: {}: {}", file, e);
22049 return 1;
22050 }
22051 }
22052 };
22053
22054 for line in reader.lines().take(lines) {
22055 match line {
22056 Ok(l) => println!("{}", l),
22057 Err(_) => break,
22058 }
22059 }
22060 }
22061 0
22062 }
22063
22064 fn builtin_tail(&self, args: &[String]) -> i32 {
22065 use std::collections::VecDeque;
22066 use std::io::{BufRead, BufReader};
22067
22068 let mut lines = 10usize;
22069 let mut files: Vec<&str> = Vec::new();
22070 let mut i = 0;
22071
22072 while i < args.len() {
22073 let arg = &args[i];
22074 if arg == "-n" && i + 1 < args.len() {
22075 i += 1;
22076 lines = args[i].parse().unwrap_or(10);
22077 } else if arg.starts_with("-n") {
22078 lines = arg[2..].parse().unwrap_or(10);
22079 } else if arg.starts_with('-') && arg.len() > 1 && arg[1..].chars().all(|c| c.is_ascii_digit()) {
22080 lines = arg[1..].parse().unwrap_or(10);
22081 } else if !arg.starts_with('-') || arg == "-" {
22082 files.push(arg);
22083 }
22084 i += 1;
22085 }
22086
22087 if files.is_empty() {
22088 files.push("-");
22089 }
22090
22091 let show_headers = files.len() > 1;
22092
22093 for (idx, file) in files.iter().enumerate() {
22094 if show_headers {
22095 if idx > 0 {
22096 println!();
22097 }
22098 println!("==> {} <==", file);
22099 }
22100
22101 let reader: Box<dyn BufRead> = if *file == "-" {
22102 Box::new(BufReader::new(std::io::stdin()))
22103 } else {
22104 match std::fs::File::open(file) {
22105 Ok(f) => Box::new(BufReader::new(f)),
22106 Err(e) => {
22107 eprintln!("tail: {}: {}", file, e);
22108 return 1;
22109 }
22110 }
22111 };
22112
22113 let mut ring: VecDeque<String> = VecDeque::with_capacity(lines);
22114 for line in reader.lines().flatten() {
22115 if ring.len() == lines {
22116 ring.pop_front();
22117 }
22118 ring.push_back(line);
22119 }
22120 for line in ring {
22121 println!("{}", line);
22122 }
22123 }
22124 0
22125 }
22126
22127 fn builtin_wc(&self, args: &[String]) -> i32 {
22128 use std::io::{BufRead, BufReader};
22129
22130 let mut count_lines = false;
22131 let mut count_words = false;
22132 let mut count_chars = false;
22133 let mut files: Vec<&str> = Vec::new();
22134
22135 for arg in args {
22136 match arg.as_str() {
22137 "-l" => count_lines = true,
22138 "-w" => count_words = true,
22139 "-c" | "-m" => count_chars = true,
22140 a if a.starts_with('-') => {
22141 for c in a[1..].chars() {
22142 match c {
22143 'l' => count_lines = true,
22144 'w' => count_words = true,
22145 'c' | 'm' => count_chars = true,
22146 _ => {}
22147 }
22148 }
22149 }
22150 _ => files.push(arg),
22151 }
22152 }
22153
22154 if !count_lines && !count_words && !count_chars {
22155 count_lines = true;
22156 count_words = true;
22157 count_chars = true;
22158 }
22159
22160 if files.is_empty() {
22161 files.push("-");
22162 }
22163
22164 let mut total_lines = 0usize;
22165 let mut total_words = 0usize;
22166 let mut total_chars = 0usize;
22167
22168 for file in &files {
22169 let reader: Box<dyn BufRead> = if *file == "-" {
22170 Box::new(BufReader::new(std::io::stdin()))
22171 } else {
22172 match std::fs::File::open(file) {
22173 Ok(f) => Box::new(BufReader::new(f)),
22174 Err(e) => {
22175 eprintln!("wc: {}: {}", file, e);
22176 return 1;
22177 }
22178 }
22179 };
22180
22181 let mut lines = 0usize;
22182 let mut words = 0usize;
22183 let mut chars = 0usize;
22184
22185 for line in reader.lines().flatten() {
22186 lines += 1;
22187 words += line.split_whitespace().count();
22188 chars += line.len() + 1; }
22190
22191 total_lines += lines;
22192 total_words += words;
22193 total_chars += chars;
22194
22195 let mut out = String::new();
22196 if count_lines {
22197 out.push_str(&format!("{:8}", lines));
22198 }
22199 if count_words {
22200 out.push_str(&format!("{:8}", words));
22201 }
22202 if count_chars {
22203 out.push_str(&format!("{:8}", chars));
22204 }
22205 if *file != "-" {
22206 out.push_str(&format!(" {}", file));
22207 }
22208 println!("{}", out.trim_start());
22209 }
22210
22211 if files.len() > 1 {
22212 let mut out = String::new();
22213 if count_lines {
22214 out.push_str(&format!("{:8}", total_lines));
22215 }
22216 if count_words {
22217 out.push_str(&format!("{:8}", total_words));
22218 }
22219 if count_chars {
22220 out.push_str(&format!("{:8}", total_chars));
22221 }
22222 out.push_str(" total");
22223 println!("{}", out.trim_start());
22224 }
22225 0
22226 }
22227
22228 fn builtin_basename(&self, args: &[String]) -> i32 {
22229 if args.is_empty() {
22230 eprintln!("basename: missing operand");
22231 return 1;
22232 }
22233
22234 let path = &args[0];
22235 let suffix = args.get(1).map(|s| s.as_str());
22236
22237 let mut name = std::path::Path::new(path)
22238 .file_name()
22239 .map(|s| s.to_string_lossy().to_string())
22240 .unwrap_or_else(|| path.clone());
22241
22242 if let Some(suf) = suffix {
22243 if name.ends_with(suf) && name.len() > suf.len() {
22244 name.truncate(name.len() - suf.len());
22245 }
22246 }
22247
22248 println!("{}", name);
22249 0
22250 }
22251
22252 fn builtin_dirname(&self, args: &[String]) -> i32 {
22253 if args.is_empty() {
22254 eprintln!("dirname: missing operand");
22255 return 1;
22256 }
22257
22258 for path in args {
22259 let dir = std::path::Path::new(path)
22260 .parent()
22261 .map(|p| p.to_string_lossy().to_string())
22262 .unwrap_or_else(|| ".".to_string());
22263 println!("{}", if dir.is_empty() { "." } else { &dir });
22264 }
22265 0
22266 }
22267
22268 fn builtin_touch(&self, args: &[String]) -> i32 {
22269 use std::fs::OpenOptions;
22270
22271 if args.is_empty() {
22272 eprintln!("touch: missing file operand");
22273 return 1;
22274 }
22275
22276 let mut status = 0;
22277 for file in args {
22278 if file.starts_with('-') {
22279 continue; }
22281 let path = std::path::Path::new(file);
22282 if path.exists() {
22283 let now = std::time::SystemTime::now();
22285 if let Err(e) = filetime::set_file_mtime(path, filetime::FileTime::from_system_time(now)) {
22286 eprintln!("touch: {}: {}", file, e);
22287 status = 1;
22288 }
22289 } else {
22290 if let Err(e) = OpenOptions::new().create(true).write(true).open(path) {
22292 eprintln!("touch: {}: {}", file, e);
22293 status = 1;
22294 }
22295 }
22296 }
22297 status
22298 }
22299
22300 fn builtin_realpath(&self, args: &[String]) -> i32 {
22301 if args.is_empty() {
22302 eprintln!("realpath: missing operand");
22303 return 1;
22304 }
22305
22306 let mut status = 0;
22307 for path in args {
22308 if path.starts_with('-') {
22309 continue;
22310 }
22311 match std::fs::canonicalize(path) {
22312 Ok(abs) => println!("{}", abs.display()),
22313 Err(e) => {
22314 eprintln!("realpath: {}: {}", path, e);
22315 status = 1;
22316 }
22317 }
22318 }
22319 status
22320 }
22321
22322 fn builtin_sort(&self, args: &[String]) -> i32 {
22323 use std::io::{BufRead, BufReader};
22324
22325 let mut reverse = false;
22326 let mut numeric = false;
22327 let mut unique = false;
22328 let mut files: Vec<&str> = Vec::new();
22329
22330 for arg in args {
22331 match arg.as_str() {
22332 "-r" => reverse = true,
22333 "-n" => numeric = true,
22334 "-u" => unique = true,
22335 "-rn" | "-nr" => { reverse = true; numeric = true; }
22336 a if a.starts_with('-') => {
22337 for c in a[1..].chars() {
22338 match c {
22339 'r' => reverse = true,
22340 'n' => numeric = true,
22341 'u' => unique = true,
22342 _ => {}
22343 }
22344 }
22345 }
22346 _ => files.push(arg),
22347 }
22348 }
22349
22350 let mut lines: Vec<String> = Vec::new();
22351
22352 if files.is_empty() {
22353 let stdin = std::io::stdin();
22354 for line in stdin.lock().lines().flatten() {
22355 lines.push(line);
22356 }
22357 } else {
22358 for file in files {
22359 match std::fs::File::open(file) {
22360 Ok(f) => {
22361 for line in BufReader::new(f).lines().flatten() {
22362 lines.push(line);
22363 }
22364 }
22365 Err(e) => {
22366 eprintln!("sort: {}: {}", file, e);
22367 return 1;
22368 }
22369 }
22370 }
22371 }
22372
22373 if numeric {
22374 lines.sort_by(|a, b| {
22375 let na: f64 = a.split_whitespace().next().and_then(|s| s.parse().ok()).unwrap_or(0.0);
22376 let nb: f64 = b.split_whitespace().next().and_then(|s| s.parse().ok()).unwrap_or(0.0);
22377 na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal)
22378 });
22379 } else {
22380 lines.sort();
22381 }
22382
22383 if reverse {
22384 lines.reverse();
22385 }
22386
22387 if unique {
22388 lines.dedup();
22389 }
22390
22391 for line in lines {
22392 println!("{}", line);
22393 }
22394 0
22395 }
22396
22397 fn builtin_find(&self, args: &[String]) -> i32 {
22398 use std::path::Path;
22399
22400 let mut paths: Vec<&str> = Vec::new();
22401 let mut name_pattern: Option<&str> = None;
22402 let mut type_filter: Option<char> = None;
22403 let mut i = 0;
22404
22405 while i < args.len() {
22406 let arg = &args[i];
22407 match arg.as_str() {
22408 "-name" if i + 1 < args.len() => {
22409 i += 1;
22410 name_pattern = Some(&args[i]);
22411 }
22412 "-type" if i + 1 < args.len() => {
22413 i += 1;
22414 type_filter = args[i].chars().next();
22415 }
22416 a if !a.starts_with('-') => paths.push(a),
22417 _ => {}
22418 }
22419 i += 1;
22420 }
22421
22422 if paths.is_empty() {
22423 paths.push(".");
22424 }
22425
22426 fn walk(dir: &Path, name_pat: Option<&str>, type_f: Option<char>) {
22427 if let Ok(entries) = std::fs::read_dir(dir) {
22428 for entry in entries.flatten() {
22429 let path = entry.path();
22430 let meta = entry.metadata().ok();
22431 let is_dir = meta.as_ref().map(|m| m.is_dir()).unwrap_or(false);
22432 let is_file = meta.as_ref().map(|m| m.is_file()).unwrap_or(false);
22433
22434 let type_match = match type_f {
22435 Some('d') => is_dir,
22436 Some('f') => is_file,
22437 _ => true,
22438 };
22439
22440 let name_match = match name_pat {
22441 Some(pat) => {
22442 let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
22443 glob_match(pat, name)
22444 }
22445 None => true,
22446 };
22447
22448 if type_match && name_match {
22449 println!("{}", path.display());
22450 }
22451
22452 if is_dir {
22453 walk(&path, name_pat, type_f);
22454 }
22455 }
22456 }
22457 }
22458
22459 fn glob_match(pattern: &str, name: &str) -> bool {
22460 if pattern == "*" { return true; }
22461 if let Some(suffix) = pattern.strip_prefix('*') {
22462 return name.ends_with(suffix);
22463 }
22464 if let Some(prefix) = pattern.strip_suffix('*') {
22465 return name.starts_with(prefix);
22466 }
22467 pattern == name
22468 }
22469
22470 for p in paths {
22471 let path = Path::new(p);
22472 if path.is_dir() {
22473 println!("{}", path.display());
22474 walk(path, name_pattern, type_filter);
22475 } else if path.exists() {
22476 println!("{}", path.display());
22477 } else {
22478 eprintln!("find: '{}': No such file or directory", p);
22479 }
22480 }
22481 0
22482 }
22483
22484 fn builtin_uniq(&self, args: &[String]) -> i32 {
22485 use std::io::{BufRead, BufReader};
22486
22487 let mut count = false;
22488 let mut repeated = false;
22489 let mut files: Vec<&str> = Vec::new();
22490
22491 for arg in args {
22492 match arg.as_str() {
22493 "-c" => count = true,
22494 "-d" => repeated = true,
22495 a if !a.starts_with('-') => files.push(a),
22496 _ => {}
22497 }
22498 }
22499
22500 let reader: Box<dyn BufRead> = if files.is_empty() || files[0] == "-" {
22501 Box::new(BufReader::new(std::io::stdin()))
22502 } else {
22503 match std::fs::File::open(files[0]) {
22504 Ok(f) => Box::new(BufReader::new(f)),
22505 Err(e) => {
22506 eprintln!("uniq: {}: {}", files[0], e);
22507 return 1;
22508 }
22509 }
22510 };
22511
22512 let mut prev: Option<String> = None;
22513 let mut cnt = 0usize;
22514
22515 for line in reader.lines().flatten() {
22516 if prev.as_ref() == Some(&line) {
22517 cnt += 1;
22518 } else {
22519 if let Some(p) = prev.take() {
22520 if !repeated || cnt > 1 {
22521 if count {
22522 println!("{:7} {}", cnt, p);
22523 } else {
22524 println!("{}", p);
22525 }
22526 }
22527 }
22528 prev = Some(line);
22529 cnt = 1;
22530 }
22531 }
22532
22533 if let Some(p) = prev {
22534 if !repeated || cnt > 1 {
22535 if count {
22536 println!("{:7} {}", cnt, p);
22537 } else {
22538 println!("{}", p);
22539 }
22540 }
22541 }
22542 0
22543 }
22544
22545 fn builtin_cut(&self, args: &[String]) -> i32 {
22546 use std::io::{BufRead, BufReader};
22547
22548 let mut delimiter = '\t';
22549 let mut fields: Vec<usize> = Vec::new();
22550 let mut files: Vec<&str> = Vec::new();
22551 let mut i = 0;
22552
22553 while i < args.len() {
22554 let arg = &args[i];
22555 if arg == "-d" && i + 1 < args.len() {
22556 i += 1;
22557 delimiter = args[i].chars().next().unwrap_or('\t');
22558 } else if arg.starts_with("-d") {
22559 delimiter = arg[2..].chars().next().unwrap_or('\t');
22560 } else if arg == "-f" && i + 1 < args.len() {
22561 i += 1;
22562 for part in args[i].split(',') {
22563 if let Ok(n) = part.parse::<usize>() {
22564 if n > 0 { fields.push(n - 1); }
22565 }
22566 }
22567 } else if arg.starts_with("-f") {
22568 for part in arg[2..].split(',') {
22569 if let Ok(n) = part.parse::<usize>() {
22570 if n > 0 { fields.push(n - 1); }
22571 }
22572 }
22573 } else if !arg.starts_with('-') {
22574 files.push(arg);
22575 }
22576 i += 1;
22577 }
22578
22579 let reader: Box<dyn BufRead> = if files.is_empty() || files[0] == "-" {
22580 Box::new(BufReader::new(std::io::stdin()))
22581 } else {
22582 match std::fs::File::open(files[0]) {
22583 Ok(f) => Box::new(BufReader::new(f)),
22584 Err(e) => {
22585 eprintln!("cut: {}: {}", files[0], e);
22586 return 1;
22587 }
22588 }
22589 };
22590
22591 for line in reader.lines().flatten() {
22592 let parts: Vec<&str> = line.split(delimiter).collect();
22593 let selected: Vec<&str> = fields.iter()
22594 .filter_map(|&idx| parts.get(idx).copied())
22595 .collect();
22596 println!("{}", selected.join(&delimiter.to_string()));
22597 }
22598 0
22599 }
22600
22601 fn builtin_tr(&self, args: &[String]) -> i32 {
22602 use std::io::Read;
22603
22604 if args.len() < 2 {
22605 eprintln!("tr: missing operand");
22606 return 1;
22607 }
22608
22609 let delete = args.iter().any(|a| a == "-d");
22610 let set1: &str;
22611 let set2: &str;
22612
22613 if delete {
22614 set1 = args.iter().find(|a| !a.starts_with('-')).map(|s| s.as_str()).unwrap_or("");
22615 set2 = "";
22616 } else {
22617 let non_flag: Vec<&str> = args.iter().filter(|a| !a.starts_with('-')).map(|s| s.as_str()).collect();
22618 set1 = non_flag.first().copied().unwrap_or("");
22619 set2 = non_flag.get(1).copied().unwrap_or("");
22620 }
22621
22622 let mut input = String::new();
22623 std::io::stdin().read_to_string(&mut input).ok();
22624
22625 let output: String = if delete {
22626 input.chars().filter(|c| !set1.contains(*c)).collect()
22627 } else {
22628 let s1: Vec<char> = set1.chars().collect();
22629 let s2: Vec<char> = set2.chars().collect();
22630 input.chars().map(|c| {
22631 if let Some(pos) = s1.iter().position(|&x| x == c) {
22632 s2.get(pos).or(s2.last()).copied().unwrap_or(c)
22633 } else {
22634 c
22635 }
22636 }).collect()
22637 };
22638
22639 print!("{}", output);
22640 0
22641 }
22642
22643 fn builtin_seq(&self, args: &[String]) -> i32 {
22644 let nums: Vec<i64> = args.iter()
22645 .filter(|a| !a.starts_with('-') || a.parse::<i64>().is_ok())
22646 .filter_map(|a| a.parse().ok())
22647 .collect();
22648
22649 let (first, inc, last) = match nums.len() {
22650 1 => (1, 1, nums[0]),
22651 2 => (nums[0], 1, nums[1]),
22652 3 => (nums[0], nums[1], nums[2]),
22653 _ => {
22654 eprintln!("seq: missing operand");
22655 return 1;
22656 }
22657 };
22658
22659 if inc == 0 {
22660 eprintln!("seq: zero increment");
22661 return 1;
22662 }
22663
22664 let mut i = first;
22665 if inc > 0 {
22666 while i <= last {
22667 println!("{}", i);
22668 i += inc;
22669 }
22670 } else {
22671 while i >= last {
22672 println!("{}", i);
22673 i += inc;
22674 }
22675 }
22676 0
22677 }
22678
22679 fn builtin_rev(&self, args: &[String]) -> i32 {
22680 use std::io::{BufRead, BufReader};
22681
22682 let reader: Box<dyn BufRead> = if args.is_empty() || args[0] == "-" {
22683 Box::new(BufReader::new(std::io::stdin()))
22684 } else {
22685 match std::fs::File::open(&args[0]) {
22686 Ok(f) => Box::new(BufReader::new(f)),
22687 Err(e) => {
22688 eprintln!("rev: {}: {}", args[0], e);
22689 return 1;
22690 }
22691 }
22692 };
22693
22694 for line in reader.lines().flatten() {
22695 println!("{}", line.chars().rev().collect::<String>());
22696 }
22697 0
22698 }
22699
22700 fn builtin_tee(&self, args: &[String]) -> i32 {
22701 use std::io::{Read, Write};
22702
22703 let append = args.iter().any(|a| a == "-a");
22704 let files: Vec<&str> = args.iter()
22705 .filter(|a| !a.starts_with('-'))
22706 .map(|s| s.as_str())
22707 .collect();
22708
22709 let mut input = Vec::new();
22710 std::io::stdin().read_to_end(&mut input).ok();
22711
22712 std::io::stdout().write_all(&input).ok();
22714
22715 for file in files {
22717 let result = if append {
22718 std::fs::OpenOptions::new().create(true).append(true).open(file)
22719 } else {
22720 std::fs::File::create(file)
22721 };
22722
22723 match result {
22724 Ok(mut f) => { f.write_all(&input).ok(); }
22725 Err(e) => eprintln!("tee: {}: {}", file, e),
22726 }
22727 }
22728 0
22729 }
22730
22731 fn builtin_sleep(&self, args: &[String]) -> i32 {
22732 if args.is_empty() {
22733 eprintln!("sleep: missing operand");
22734 return 1;
22735 }
22736
22737 let mut total_secs = 0.0f64;
22738 for arg in args {
22739 if arg.starts_with('-') { continue; }
22740 let (num, suffix) = if arg.ends_with('s') {
22741 (&arg[..arg.len()-1], 1.0)
22742 } else if arg.ends_with('m') {
22743 (&arg[..arg.len()-1], 60.0)
22744 } else if arg.ends_with('h') {
22745 (&arg[..arg.len()-1], 3600.0)
22746 } else if arg.ends_with('d') {
22747 (&arg[..arg.len()-1], 86400.0)
22748 } else {
22749 (arg.as_str(), 1.0)
22750 };
22751 if let Ok(n) = num.parse::<f64>() {
22752 total_secs += n * suffix;
22753 }
22754 }
22755
22756 std::thread::sleep(std::time::Duration::from_secs_f64(total_secs));
22757 0
22758 }
22759
22760 fn builtin_whoami(&self, _args: &[String]) -> i32 {
22761 if let Ok(user) = std::env::var("USER") {
22762 println!("{}", user);
22763 0
22764 } else {
22765 let uid = unsafe { libc::getuid() };
22766 println!("{}", uid);
22767 0
22768 }
22769 }
22770
22771 fn builtin_id(&self, args: &[String]) -> i32 {
22772 let uid = unsafe { libc::getuid() };
22773 let gid = unsafe { libc::getgid() };
22774 let euid = unsafe { libc::geteuid() };
22775 let egid = unsafe { libc::getegid() };
22776
22777 if args.iter().any(|a| a == "-u") {
22778 println!("{}", uid);
22779 } else if args.iter().any(|a| a == "-g") {
22780 println!("{}", gid);
22781 } else if args.iter().any(|a| a == "-un") {
22782 if let Ok(user) = std::env::var("USER") {
22783 println!("{}", user);
22784 } else {
22785 println!("{}", uid);
22786 }
22787 } else {
22788 let user = std::env::var("USER").unwrap_or_else(|_| uid.to_string());
22789 print!("uid={}({}) gid={}", uid, user, gid);
22790 if euid != uid {
22791 print!(" euid={}", euid);
22792 }
22793 if egid != gid {
22794 print!(" egid={}", egid);
22795 }
22796 println!();
22797 }
22798 0
22799 }
22800
22801 fn builtin_hostname(&self, _args: &[String]) -> i32 {
22802 let mut buf = [0u8; 256];
22803 let result = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut i8, buf.len()) };
22804 if result == 0 {
22805 let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
22806 println!("{}", String::from_utf8_lossy(&buf[..len]));
22807 0
22808 } else {
22809 eprintln!("hostname: cannot get hostname");
22810 1
22811 }
22812 }
22813
22814 fn builtin_uname(&self, args: &[String]) -> i32 {
22815 let mut uts: libc::utsname = unsafe { std::mem::zeroed() };
22816 if unsafe { libc::uname(&mut uts) } != 0 {
22817 eprintln!("uname: cannot get system info");
22818 return 1;
22819 }
22820
22821 let sysname = unsafe { std::ffi::CStr::from_ptr(uts.sysname.as_ptr()) }.to_string_lossy();
22822 let nodename = unsafe { std::ffi::CStr::from_ptr(uts.nodename.as_ptr()) }.to_string_lossy();
22823 let release = unsafe { std::ffi::CStr::from_ptr(uts.release.as_ptr()) }.to_string_lossy();
22824 let version = unsafe { std::ffi::CStr::from_ptr(uts.version.as_ptr()) }.to_string_lossy();
22825 let machine = unsafe { std::ffi::CStr::from_ptr(uts.machine.as_ptr()) }.to_string_lossy();
22826
22827 if args.is_empty() || args.iter().any(|a| a == "-s") {
22828 println!("{}", sysname);
22829 } else if args.iter().any(|a| a == "-a") {
22830 println!("{} {} {} {} {}", sysname, nodename, release, version, machine);
22831 } else if args.iter().any(|a| a == "-n") {
22832 println!("{}", nodename);
22833 } else if args.iter().any(|a| a == "-r") {
22834 println!("{}", release);
22835 } else if args.iter().any(|a| a == "-v") {
22836 println!("{}", version);
22837 } else if args.iter().any(|a| a == "-m") {
22838 println!("{}", machine);
22839 } else {
22840 println!("{}", sysname);
22841 }
22842 0
22843 }
22844
22845 fn builtin_date(&self, args: &[String]) -> i32 {
22846 use std::time::{SystemTime, UNIX_EPOCH};
22847
22848 let now = SystemTime::now()
22849 .duration_since(UNIX_EPOCH)
22850 .unwrap_or_default()
22851 .as_secs() as i64;
22852
22853 let mut format: Option<&str> = None;
22854 for arg in args {
22855 if arg.starts_with('+') {
22856 format = Some(&arg[1..]);
22857 }
22858 }
22859
22860 if let Some(fmt) = format {
22861 let tm = unsafe {
22862 let t = now as libc::time_t;
22863 *libc::localtime(&t)
22864 };
22865 let mut buf = [0i8; 256];
22866 let fmt_cstr = std::ffi::CString::new(fmt).unwrap_or_default();
22867 let len = unsafe {
22868 libc::strftime(buf.as_mut_ptr(), buf.len(), fmt_cstr.as_ptr(), &tm)
22869 };
22870 if len > 0 {
22871 let s = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
22872 println!("{}", s.to_string_lossy());
22873 }
22874 } else {
22875 let tm = unsafe {
22876 let t = now as libc::time_t;
22877 *libc::localtime(&t)
22878 };
22879 let mut buf = [0i8; 256];
22880 let fmt = std::ffi::CString::new("%a %b %e %H:%M:%S %Z %Y").unwrap();
22881 let len = unsafe {
22882 libc::strftime(buf.as_mut_ptr(), buf.len(), fmt.as_ptr(), &tm)
22883 };
22884 if len > 0 {
22885 let s = unsafe { std::ffi::CStr::from_ptr(buf.as_ptr()) };
22886 println!("{}", s.to_string_lossy());
22887 }
22888 }
22889 0
22890 }
22891
22892 fn builtin_mktemp(&self, args: &[String]) -> i32 {
22893 let mut dir = false;
22894 let mut template: Option<&str> = None;
22895
22896 for arg in args {
22897 match arg.as_str() {
22898 "-d" => dir = true,
22899 a if !a.starts_with('-') => template = Some(a),
22900 _ => {}
22901 }
22902 }
22903
22904 let tmpdir = std::env::var("TMPDIR").unwrap_or_else(|_| "/tmp".to_string());
22905 let base = template.unwrap_or("tmp.XXXXXXXXXX");
22906
22907 let rand_suffix: String = (0..10)
22908 .map(|_| {
22909 let idx = (std::time::SystemTime::now()
22910 .duration_since(std::time::UNIX_EPOCH)
22911 .unwrap_or_default()
22912 .subsec_nanos() as usize) % 36;
22913 "abcdefghijklmnopqrstuvwxyz0123456789".chars().nth(idx).unwrap()
22914 })
22915 .collect();
22916
22917 let name = if base.contains("XXXXXX") {
22918 base.replace("XXXXXXXXXX", &rand_suffix)
22919 .replace("XXXXXX", &rand_suffix[..6])
22920 } else {
22921 format!("{}.{}", base, rand_suffix)
22922 };
22923
22924 let path = std::path::Path::new(&tmpdir).join(&name);
22925
22926 if dir {
22927 match std::fs::create_dir(&path) {
22928 Ok(_) => {
22929 println!("{}", path.display());
22930 0
22931 }
22932 Err(e) => {
22933 eprintln!("mktemp: {}: {}", path.display(), e);
22934 1
22935 }
22936 }
22937 } else {
22938 match std::fs::File::create(&path) {
22939 Ok(_) => {
22940 println!("{}", path.display());
22941 0
22942 }
22943 Err(e) => {
22944 eprintln!("mktemp: {}: {}", path.display(), e);
22945 1
22946 }
22947 }
22948 }
22949 }
22950}
22951
22952use std::os::unix::fs::MetadataExt;
22953
22954bitflags::bitflags! {
22955 #[derive(Debug, Clone, Copy, Default)]
22957 pub struct ForkFlags: u32 {
22958 const NOJOB = 1 << 0; const NEWGRP = 1 << 1; const FGTTY = 1 << 2; const KEEPSIGS = 1 << 3; }
22963}
22964
22965bitflags::bitflags! {
22966 #[derive(Debug, Clone, Copy, Default)]
22968 pub struct SubshellFlags: u32 {
22969 const NOMONITOR = 1 << 0; const KEEPFDS = 1 << 1; const KEEPTRAPS = 1 << 2; }
22973}
22974
22975#[derive(Debug)]
22977pub enum ForkResult {
22978 Parent(i32), Child,
22980}
22981
22982#[derive(Debug, Clone, Copy)]
22984pub enum RedirMode {
22985 Dup,
22986 Close,
22987}
22988
22989#[derive(Debug, Clone, Copy)]
22991pub enum BuiltinType {
22992 Normal,
22993 Disabled,
22994}