Serenity Operating System
1/*
2 * Copyright (c) 2020-2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "AST.h"
8#include "Formatter.h"
9#include "PosixParser.h"
10#include "Shell.h"
11#include <AK/DeprecatedString.h>
12#include <AK/LexicalPath.h>
13#include <AK/ScopeGuard.h>
14#include <AK/Statistics.h>
15#include <LibCore/ArgsParser.h>
16#include <LibCore/DeprecatedFile.h>
17#include <LibCore/EventLoop.h>
18#include <errno.h>
19#include <inttypes.h>
20#include <limits.h>
21#include <signal.h>
22#include <sys/wait.h>
23#include <unistd.h>
24
25extern char** environ;
26
27namespace Shell {
28
29ErrorOr<int> Shell::builtin_noop(Main::Arguments)
30{
31 return 0;
32}
33
34ErrorOr<int> Shell::builtin_dump(Main::Arguments arguments)
35{
36 bool posix = false;
37 StringView source;
38
39 Core::ArgsParser parser;
40 parser.add_positional_argument(source, "Shell code to parse and dump", "source");
41 parser.add_option(posix, "Use the POSIX parser", "posix", 'p');
42
43 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
44 return 1;
45
46 TRY((posix ? Posix::Parser { source }.parse() : Parser { source }.parse())->dump(0));
47 return 0;
48}
49
50enum FollowSymlinks {
51 Yes,
52 No
53};
54
55static Vector<DeprecatedString> find_matching_executables_in_path(StringView filename, FollowSymlinks follow_symlinks = FollowSymlinks::No)
56{
57 // Edge cases in which there are guaranteed no solutions
58 if (filename.is_empty() || filename.contains('/'))
59 return {};
60
61 char const* path_str = getenv("PATH");
62 auto path = DEFAULT_PATH_SV;
63 if (path_str != nullptr) // maybe && *path_str
64 path = { path_str, strlen(path_str) };
65
66 Vector<DeprecatedString> executables;
67 auto directories = path.split_view(':');
68 for (auto directory : directories) {
69 auto file = DeprecatedString::formatted("{}/{}", directory, filename);
70
71 if (follow_symlinks == FollowSymlinks::Yes) {
72 auto path_or_error = Core::DeprecatedFile::read_link(file);
73 if (!path_or_error.is_error())
74 file = path_or_error.release_value();
75 }
76 if (access(file.characters(), X_OK) == 0)
77 executables.append(move(file));
78 }
79
80 return executables;
81}
82
83ErrorOr<int> Shell::builtin_where(Main::Arguments arguments)
84{
85 Vector<StringView> values_to_lookup;
86 bool do_only_path_search { false };
87 bool do_follow_symlinks { false };
88 bool do_print_only_type { false };
89
90 Core::ArgsParser parser;
91 parser.add_positional_argument(values_to_lookup, "List of shell builtins, aliases or executables", "arguments");
92 parser.add_option(do_only_path_search, "Search only for executables in the PATH environment variable", "path-only", 'p');
93 parser.add_option(do_follow_symlinks, "Follow symlinks and print the symlink free path", "follow-symlink", 's');
94 parser.add_option(do_print_only_type, "Print the argument type instead of a human readable description", "type", 'w');
95
96 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
97 return 1;
98
99 auto const lookup_alias = [do_only_path_search, &m_aliases = this->m_aliases](StringView alias) -> Optional<DeprecatedString> {
100 if (do_only_path_search)
101 return {};
102 return m_aliases.get(alias);
103 };
104
105 auto const lookup_builtin = [do_only_path_search](StringView builtin) -> Optional<DeprecatedString> {
106 if (do_only_path_search)
107 return {};
108 for (auto const& _builtin : builtin_names) {
109 if (_builtin == builtin) {
110 return builtin;
111 }
112 }
113 return {};
114 };
115
116 bool at_least_one_succeded { false };
117 for (auto const& argument : values_to_lookup) {
118 auto const alias = lookup_alias(argument);
119 if (alias.has_value()) {
120 if (do_print_only_type)
121 outln("{}: alias", argument);
122 else
123 outln("{}: aliased to {}", argument, alias.value());
124 at_least_one_succeded = true;
125 }
126
127 auto const builtin = lookup_builtin(argument);
128 if (builtin.has_value()) {
129 if (do_print_only_type)
130 outln("{}: builtin", builtin.value());
131 else
132 outln("{}: shell built-in command", builtin.value());
133 at_least_one_succeded = true;
134 }
135
136 auto const executables = find_matching_executables_in_path(argument, do_follow_symlinks ? FollowSymlinks::Yes : FollowSymlinks::No);
137 for (auto const& path : executables) {
138 if (do_print_only_type)
139 outln("{}: command", argument);
140 else
141 outln(path);
142 at_least_one_succeded = true;
143 }
144 if (!at_least_one_succeded)
145 warnln("{} not found", argument);
146 }
147 return at_least_one_succeded ? 0 : 1;
148}
149
150ErrorOr<int> Shell::builtin_alias(Main::Arguments arguments)
151{
152 Vector<DeprecatedString> aliases;
153
154 Core::ArgsParser parser;
155 parser.add_positional_argument(aliases, "List of name[=values]'s", "name[=value]", Core::ArgsParser::Required::No);
156
157 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
158 return 1;
159
160 if (aliases.is_empty()) {
161 for (auto& alias : m_aliases)
162 printf("%s=%s\n", escape_token(alias.key).characters(), escape_token(alias.value).characters());
163 return 0;
164 }
165
166 bool fail = false;
167 for (auto& argument : aliases) {
168 auto parts = argument.split_limit('=', 2, SplitBehavior::KeepEmpty);
169 if (parts.size() == 1) {
170 auto alias = m_aliases.get(parts[0]);
171 if (alias.has_value()) {
172 printf("%s=%s\n", escape_token(parts[0]).characters(), escape_token(alias.value()).characters());
173 } else {
174 fail = true;
175 }
176 } else {
177 m_aliases.set(parts[0], parts[1]);
178 add_entry_to_cache({ RunnablePath::Kind::Alias, parts[0] });
179 }
180 }
181
182 return fail ? 1 : 0;
183}
184
185ErrorOr<int> Shell::builtin_unalias(Main::Arguments arguments)
186{
187 bool remove_all { false };
188 Vector<DeprecatedString> aliases;
189
190 Core::ArgsParser parser;
191 parser.set_general_help("Remove alias from the list of aliases");
192 parser.add_option(remove_all, "Remove all aliases", nullptr, 'a');
193 parser.add_positional_argument(aliases, "List of aliases to remove", "alias", Core::ArgsParser::Required::Yes);
194
195 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
196 return 1;
197
198 if (remove_all) {
199 m_aliases.clear();
200 cache_path();
201 return 0;
202 }
203
204 bool failed { false };
205 for (auto& argument : aliases) {
206 if (!m_aliases.contains(argument)) {
207 warnln("unalias: {}: alias not found", argument);
208 failed = true;
209 continue;
210 }
211 m_aliases.remove(argument);
212 remove_entry_from_cache(argument);
213 }
214
215 return failed ? 1 : 0;
216}
217
218ErrorOr<int> Shell::builtin_bg(Main::Arguments arguments)
219{
220 int job_id = -1;
221 bool is_pid = false;
222
223 Core::ArgsParser parser;
224 parser.add_positional_argument(Core::ArgsParser::Arg {
225 .help_string = "Job ID or Jobspec to run in background",
226 .name = "job-id",
227 .min_values = 0,
228 .max_values = 1,
229 .accept_value = [&](StringView value) -> bool {
230 // Check if it's a pid (i.e. literal integer)
231 if (auto number = value.to_uint(); number.has_value()) {
232 job_id = number.value();
233 is_pid = true;
234 return true;
235 }
236
237 // Check if it's a jobspec
238 if (auto id = resolve_job_spec(value); id.has_value()) {
239 job_id = id.value();
240 is_pid = false;
241 return true;
242 }
243
244 return false;
245 } });
246
247 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
248 return 1;
249
250 if (job_id == -1 && !jobs.is_empty())
251 job_id = find_last_job_id();
252
253 auto* job = const_cast<Job*>(find_job(job_id, is_pid));
254
255 if (!job) {
256 if (job_id == -1) {
257 warnln("bg: No current job");
258 } else {
259 warnln("bg: Job with id/pid {} not found", job_id);
260 }
261 return 1;
262 }
263
264 job->set_running_in_background(true);
265 job->set_should_announce_exit(true);
266 job->set_shell_did_continue(true);
267
268 dbgln("Resuming {} ({})", job->pid(), job->cmd());
269 warnln("Resuming job {} - {}", job->job_id(), job->cmd());
270
271 // Try using the PGID, but if that fails, just use the PID.
272 if (killpg(job->pgid(), SIGCONT) < 0) {
273 if (kill(job->pid(), SIGCONT) < 0) {
274 perror("kill");
275 return 1;
276 }
277 }
278
279 return 0;
280}
281
282ErrorOr<int> Shell::builtin_type(Main::Arguments arguments)
283{
284 Vector<DeprecatedString> commands;
285 bool dont_show_function_source = false;
286
287 Core::ArgsParser parser;
288 parser.set_general_help("Display information about commands.");
289 parser.add_positional_argument(commands, "Command(s) to list info about", "command");
290 parser.add_option(dont_show_function_source, "Do not show functions source.", "no-fn-source", 'f');
291
292 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
293 return 1;
294
295 bool something_not_found = false;
296
297 for (auto& command : commands) {
298 // check if it is an alias
299 if (auto alias = m_aliases.get(command); alias.has_value()) {
300 printf("%s is aliased to `%s`\n", escape_token(command).characters(), escape_token(alias.value()).characters());
301 continue;
302 }
303
304 // check if it is a function
305 if (auto function = m_functions.get(command); function.has_value()) {
306 auto fn = function.value();
307 printf("%s is a function\n", command.characters());
308 if (!dont_show_function_source) {
309 StringBuilder builder;
310 builder.append(fn.name);
311 builder.append('(');
312 for (size_t i = 0; i < fn.arguments.size(); i++) {
313 builder.append(fn.arguments[i]);
314 if (!(i == fn.arguments.size() - 1))
315 builder.append(' ');
316 }
317 builder.append(") {\n"sv);
318 if (fn.body) {
319 auto formatter = Formatter(*fn.body);
320 builder.append(formatter.format());
321 printf("%s\n}\n", builder.to_deprecated_string().characters());
322 } else {
323 printf("%s\n}\n", builder.to_deprecated_string().characters());
324 }
325 }
326 continue;
327 }
328
329 // check if its a builtin
330 if (has_builtin(command)) {
331 printf("%s is a shell builtin\n", command.characters());
332 continue;
333 }
334
335 // check if its an executable in PATH
336 auto fullpath = Core::DeprecatedFile::resolve_executable_from_environment(command);
337 if (fullpath.has_value()) {
338 printf("%s is %s\n", command.characters(), escape_token(fullpath.release_value()).characters());
339 continue;
340 }
341 something_not_found = true;
342 printf("type: %s not found\n", command.characters());
343 }
344
345 if (something_not_found)
346 return 1;
347 else
348 return 0;
349}
350
351ErrorOr<int> Shell::builtin_cd(Main::Arguments arguments)
352{
353 StringView arg_path;
354
355 Core::ArgsParser parser;
356 parser.add_positional_argument(arg_path, "Path to change to", "path", Core::ArgsParser::Required::No);
357
358 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
359 return 1;
360
361 DeprecatedString new_path;
362
363 if (arg_path.is_empty()) {
364 new_path = home;
365 } else {
366 if (arg_path == "-"sv) {
367 char* oldpwd = getenv("OLDPWD");
368 if (oldpwd == nullptr)
369 return 1;
370 new_path = oldpwd;
371 } else {
372 new_path = arg_path;
373 }
374 }
375
376 auto real_path = Core::DeprecatedFile::real_path_for(new_path);
377 if (real_path.is_empty()) {
378 warnln("Invalid path '{}'", new_path);
379 return 1;
380 }
381
382 if (cd_history.is_empty() || cd_history.last() != real_path)
383 cd_history.enqueue(real_path);
384
385 auto path_relative_to_current_directory = LexicalPath::relative_path(real_path, cwd);
386 if (path_relative_to_current_directory.is_empty())
387 path_relative_to_current_directory = real_path;
388 char const* path = path_relative_to_current_directory.characters();
389
390 int rc = chdir(path);
391 if (rc < 0) {
392 if (errno == ENOTDIR) {
393 warnln("Not a directory: {}", path);
394 } else {
395 warnln("chdir({}) failed: {}", path, strerror(errno));
396 }
397 return 1;
398 }
399 setenv("OLDPWD", cwd.characters(), 1);
400 cwd = move(real_path);
401 setenv("PWD", cwd.characters(), 1);
402 return 0;
403}
404
405ErrorOr<int> Shell::builtin_cdh(Main::Arguments arguments)
406{
407 int index = -1;
408
409 Core::ArgsParser parser;
410 parser.add_positional_argument(index, "Index of the cd history entry (leave out for a list)", "index", Core::ArgsParser::Required::No);
411
412 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
413 return 1;
414
415 if (index == -1) {
416 if (cd_history.is_empty()) {
417 warnln("cdh: no history available");
418 return 0;
419 }
420
421 for (ssize_t i = cd_history.size() - 1; i >= 0; --i)
422 printf("%zu: %s\n", cd_history.size() - i, cd_history.at(i).characters());
423 return 0;
424 }
425
426 if (index < 1 || (size_t)index > cd_history.size()) {
427 warnln("cdh: history index out of bounds: {} not in (0, {})", index, cd_history.size());
428 return 1;
429 }
430
431 StringView path = cd_history.at(cd_history.size() - index);
432 StringView cd_args[] = { "cd"sv, path };
433 return Shell::builtin_cd({ .argc = 0, .argv = 0, .strings = cd_args });
434}
435
436ErrorOr<int> Shell::builtin_dirs(Main::Arguments arguments)
437{
438 // The first directory in the stack is ALWAYS the current directory
439 directory_stack.at(0) = cwd.characters();
440
441 bool clear = false;
442 bool print = false;
443 bool number_when_printing = false;
444 char separator = ' ';
445
446 Vector<DeprecatedString> paths;
447
448 Core::ArgsParser parser;
449 parser.add_option(clear, "Clear the directory stack", "clear", 'c');
450 parser.add_option(print, "Print directory entries one per line", "print", 'p');
451 parser.add_option(number_when_printing, "Number the directories in the stack when printing", "number", 'v');
452 parser.add_positional_argument(paths, "Extra paths to put on the stack", "path", Core::ArgsParser::Required::No);
453
454 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
455 return 1;
456
457 // -v implies -p
458 print = print || number_when_printing;
459
460 if (print) {
461 if (!paths.is_empty()) {
462 warnln("dirs: 'print' and 'number' are not allowed when any path is specified");
463 return 1;
464 }
465 separator = '\n';
466 }
467
468 if (clear) {
469 for (size_t i = 1; i < directory_stack.size(); i++)
470 directory_stack.remove(i);
471 }
472
473 for (auto& path : paths)
474 directory_stack.append(path);
475
476 if (print || (!clear && paths.is_empty())) {
477 int index = 0;
478 for (auto& directory : directory_stack) {
479 if (number_when_printing)
480 printf("%d ", index++);
481 print_path(directory);
482 fputc(separator, stdout);
483 }
484 }
485
486 return 0;
487}
488
489ErrorOr<int> Shell::builtin_exec(Main::Arguments arguments)
490{
491 if (arguments.strings.size() < 2) {
492 warnln("Shell: No command given to exec");
493 return 1;
494 }
495
496 TRY(execute_process(arguments.strings.slice(1)));
497 // NOTE: Won't get here.
498 return 0;
499}
500
501ErrorOr<int> Shell::builtin_exit(Main::Arguments arguments)
502{
503 int exit_code = 0;
504 Core::ArgsParser parser;
505 parser.add_positional_argument(exit_code, "Exit code", "code", Core::ArgsParser::Required::No);
506 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
507 return 1;
508
509 if (m_is_interactive) {
510 if (!jobs.is_empty()) {
511 if (!m_should_ignore_jobs_on_next_exit) {
512 warnln("Shell: You have {} active job{}, run 'exit' again to really exit.", jobs.size(), jobs.size() > 1 ? "s" : "");
513 m_should_ignore_jobs_on_next_exit = true;
514 return 1;
515 }
516 }
517 }
518 stop_all_jobs();
519 if (m_is_interactive) {
520 m_editor->save_history(get_history_path());
521 printf("Good-bye!\n");
522 }
523 exit(exit_code);
524}
525
526ErrorOr<int> Shell::builtin_export(Main::Arguments arguments)
527{
528 Vector<DeprecatedString> vars;
529
530 Core::ArgsParser parser;
531 parser.add_positional_argument(vars, "List of variable[=value]'s", "values", Core::ArgsParser::Required::No);
532
533 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
534 return 1;
535
536 if (vars.is_empty()) {
537 for (size_t i = 0; environ[i]; ++i)
538 puts(environ[i]);
539 return 0;
540 }
541
542 for (auto& value : vars) {
543 auto parts = value.split_limit('=', 2);
544 if (parts.is_empty()) {
545 warnln("Shell: Invalid export spec '{}', expected `variable=value' or `variable'", value);
546 return 1;
547 }
548
549 if (parts.size() == 1) {
550 auto value = TRY(lookup_local_variable(parts[0]));
551 if (value) {
552 auto values = TRY(const_cast<AST::Value&>(*value).resolve_as_list(*this));
553 StringBuilder builder;
554 builder.join(' ', values);
555 parts.append(builder.to_deprecated_string());
556 } else {
557 // Ignore the export.
558 continue;
559 }
560 }
561
562 int setenv_return = setenv(parts[0].characters(), parts[1].characters(), 1);
563
564 if (setenv_return != 0) {
565 perror("setenv");
566 return 1;
567 }
568
569 if (parts[0] == "PATH")
570 cache_path();
571 }
572
573 return 0;
574}
575
576ErrorOr<int> Shell::builtin_glob(Main::Arguments arguments)
577{
578 Vector<DeprecatedString> globs;
579 Core::ArgsParser parser;
580 parser.add_positional_argument(globs, "Globs to resolve", "glob");
581
582 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
583 return 1;
584
585 for (auto& glob : globs) {
586 for (auto& expanded : expand_globs(glob, cwd))
587 outln("{}", expanded);
588 }
589
590 return 0;
591}
592
593ErrorOr<int> Shell::builtin_fg(Main::Arguments arguments)
594{
595 int job_id = -1;
596 bool is_pid = false;
597
598 Core::ArgsParser parser;
599 parser.add_positional_argument(Core::ArgsParser::Arg {
600 .help_string = "Job ID or Jobspec to bring to foreground",
601 .name = "job-id",
602 .min_values = 0,
603 .max_values = 1,
604 .accept_value = [&](StringView value) -> bool {
605 // Check if it's a pid (i.e. literal integer)
606 if (auto number = value.to_uint(); number.has_value()) {
607 job_id = number.value();
608 is_pid = true;
609 return true;
610 }
611
612 // Check if it's a jobspec
613 if (auto id = resolve_job_spec(value); id.has_value()) {
614 job_id = id.value();
615 is_pid = false;
616 return true;
617 }
618
619 return false;
620 } });
621
622 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
623 return 1;
624
625 if (job_id == -1 && !jobs.is_empty())
626 job_id = find_last_job_id();
627
628 RefPtr<Job> job = find_job(job_id, is_pid);
629
630 if (!job) {
631 if (job_id == -1) {
632 warnln("fg: No current job");
633 } else {
634 warnln("fg: Job with id/pid {} not found", job_id);
635 }
636 return 1;
637 }
638
639 job->set_running_in_background(false);
640 job->set_shell_did_continue(true);
641
642 dbgln("Resuming {} ({})", job->pid(), job->cmd());
643 warnln("Resuming job {} - {}", job->job_id(), job->cmd());
644
645 tcsetpgrp(STDOUT_FILENO, job->pgid());
646 tcsetpgrp(STDIN_FILENO, job->pgid());
647
648 // Try using the PGID, but if that fails, just use the PID.
649 if (killpg(job->pgid(), SIGCONT) < 0) {
650 if (kill(job->pid(), SIGCONT) < 0) {
651 perror("kill");
652 return 1;
653 }
654 }
655
656 block_on_job(job);
657
658 if (job->exited())
659 return job->exit_code();
660 else
661 return 0;
662}
663
664ErrorOr<int> Shell::builtin_disown(Main::Arguments arguments)
665{
666 Vector<int> job_ids;
667 Vector<bool> id_is_pid;
668
669 Core::ArgsParser parser;
670 parser.add_positional_argument(Core::ArgsParser::Arg {
671 .help_string = "Job IDs or Jobspecs to disown",
672 .name = "job-id",
673 .min_values = 0,
674 .max_values = INT_MAX,
675 .accept_value = [&](StringView value) -> bool {
676 // Check if it's a pid (i.e. literal integer)
677 if (auto number = value.to_uint(); number.has_value()) {
678 job_ids.append(number.value());
679 id_is_pid.append(true);
680 return true;
681 }
682
683 // Check if it's a jobspec
684 if (auto id = resolve_job_spec(value); id.has_value()) {
685 job_ids.append(id.value());
686 id_is_pid.append(false);
687 return true;
688 }
689
690 return false;
691 } });
692
693 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
694 return 1;
695
696 if (job_ids.is_empty()) {
697 job_ids.append(find_last_job_id());
698 id_is_pid.append(false);
699 }
700
701 Vector<Job const*> jobs_to_disown;
702
703 for (size_t i = 0; i < job_ids.size(); ++i) {
704 auto id = job_ids[i];
705 auto is_pid = id_is_pid[i];
706 auto job = find_job(id, is_pid);
707 if (!job)
708 warnln("disown: Job with id/pid {} not found", id);
709 else
710 jobs_to_disown.append(job);
711 }
712
713 if (jobs_to_disown.is_empty()) {
714 if (job_ids.is_empty())
715 warnln("disown: No current job");
716 // An error message has already been printed about the nonexistence of each listed job.
717 return 1;
718 }
719
720 for (auto job : jobs_to_disown) {
721 job->deactivate();
722
723 if (!job->is_running_in_background())
724 warnln("disown warning: Job {} is currently not running, 'kill -{} {}' to make it continue", job->job_id(), SIGCONT, job->pid());
725
726 jobs.remove(job->pid());
727 }
728
729 return 0;
730}
731
732ErrorOr<int> Shell::builtin_history(Main::Arguments)
733{
734 for (size_t i = 0; i < m_editor->history().size(); ++i) {
735 printf("%6zu %s\n", i + 1, m_editor->history()[i].entry.characters());
736 }
737 return 0;
738}
739
740ErrorOr<int> Shell::builtin_jobs(Main::Arguments arguments)
741{
742 bool list = false, show_pid = false;
743
744 Core::ArgsParser parser;
745 parser.add_option(list, "List all information about jobs", "list", 'l');
746 parser.add_option(show_pid, "Display the PID of the jobs", "pid", 'p');
747
748 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
749 return 1;
750
751 Job::PrintStatusMode mode = Job::PrintStatusMode::Basic;
752
753 if (show_pid)
754 mode = Job::PrintStatusMode::OnlyPID;
755
756 if (list)
757 mode = Job::PrintStatusMode::ListAll;
758
759 for (auto& it : jobs) {
760 if (!it.value->print_status(mode))
761 return 1;
762 }
763
764 return 0;
765}
766
767ErrorOr<int> Shell::builtin_popd(Main::Arguments arguments)
768{
769 if (directory_stack.size() <= 1) {
770 warnln("Shell: popd: directory stack empty");
771 return 1;
772 }
773
774 bool should_not_switch = false;
775 Core::ArgsParser parser;
776 parser.add_option(should_not_switch, "Do not switch dirs", "no-switch", 'n');
777
778 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
779 return 1;
780
781 auto popped_path = directory_stack.take_last();
782
783 if (should_not_switch)
784 return 0;
785
786 auto new_path = LexicalPath::canonicalized_path(popped_path);
787 if (chdir(new_path.characters()) < 0) {
788 warnln("chdir({}) failed: {}", new_path, strerror(errno));
789 return 1;
790 }
791 cwd = new_path;
792 return 0;
793}
794
795ErrorOr<int> Shell::builtin_pushd(Main::Arguments arguments)
796{
797 StringBuilder path_builder;
798 bool should_switch = true;
799
800 // From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html
801 // With no arguments, pushd exchanges the top two directories and makes the new top the current directory.
802 if (arguments.strings.size() == 1) {
803 if (directory_stack.size() < 2) {
804 warnln("pushd: no other directory");
805 return 1;
806 }
807
808 DeprecatedString dir1 = directory_stack.take_first();
809 DeprecatedString dir2 = directory_stack.take_first();
810 directory_stack.insert(0, dir2);
811 directory_stack.insert(1, dir1);
812
813 int rc = chdir(dir2.characters());
814 if (rc < 0) {
815 warnln("chdir({}) failed: {}", dir2, strerror(errno));
816 return 1;
817 }
818
819 cwd = dir2;
820
821 return 0;
822 }
823
824 // Let's assume the user's typed in 'pushd <dir>'
825 if (arguments.strings.size() == 2) {
826 directory_stack.append(cwd.characters());
827 if (arguments.strings[1].starts_with('/')) {
828 path_builder.append(arguments.strings[1]);
829 } else {
830 path_builder.appendff("{}/{}", cwd, arguments.strings[1]);
831 }
832 } else if (arguments.strings.size() == 3) {
833 directory_stack.append(cwd.characters());
834 for (size_t i = 1; i < arguments.strings.size(); i++) {
835 auto arg = arguments.strings[i];
836
837 if (arg.starts_with('-')) {
838 if (arg.starts_with('/')) {
839 path_builder.append(arg);
840 } else {
841 path_builder.appendff("{}/{}", cwd, arg);
842 }
843 }
844
845 if (arg == "-n"sv)
846 should_switch = false;
847 }
848 }
849
850 auto real_path = LexicalPath::canonicalized_path(path_builder.to_deprecated_string());
851
852 struct stat st;
853 int rc = stat(real_path.characters(), &st);
854 if (rc < 0) {
855 warnln("stat({}) failed: {}", real_path, strerror(errno));
856 return 1;
857 }
858
859 if (!S_ISDIR(st.st_mode)) {
860 warnln("Not a directory: {}", real_path);
861 return 1;
862 }
863
864 if (should_switch) {
865 int rc = chdir(real_path.characters());
866 if (rc < 0) {
867 warnln("chdir({}) failed: {}", real_path, strerror(errno));
868 return 1;
869 }
870
871 cwd = real_path;
872 }
873
874 return 0;
875}
876
877ErrorOr<int> Shell::builtin_pwd(Main::Arguments)
878{
879 print_path(cwd);
880 fputc('\n', stdout);
881 return 0;
882}
883
884ErrorOr<int> Shell::builtin_setopt(Main::Arguments arguments)
885{
886 if (arguments.strings.size() == 1) {
887#define __ENUMERATE_SHELL_OPTION(name, default_, description) \
888 if (options.name) \
889 warnln("{}", #name);
890
891 ENUMERATE_SHELL_OPTIONS();
892
893#undef __ENUMERATE_SHELL_OPTION
894 }
895
896 Core::ArgsParser parser;
897#define __ENUMERATE_SHELL_OPTION(name, default_, description) \
898 bool name = false; \
899 bool not_##name = false; \
900 parser.add_option(name, "Enable: " description, #name, '\0'); \
901 parser.add_option(not_##name, "Disable: " description, "no_" #name, '\0');
902
903 ENUMERATE_SHELL_OPTIONS();
904
905#undef __ENUMERATE_SHELL_OPTION
906
907 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
908 return 1;
909
910#define __ENUMERATE_SHELL_OPTION(name, default_, description) \
911 if (name) \
912 options.name = true; \
913 if (not_##name) \
914 options.name = false;
915
916 ENUMERATE_SHELL_OPTIONS();
917
918#undef __ENUMERATE_SHELL_OPTION
919
920 return 0;
921}
922
923ErrorOr<int> Shell::builtin_shift(Main::Arguments arguments)
924{
925 int count = 1;
926
927 Core::ArgsParser parser;
928 parser.add_positional_argument(count, "Shift count", "count", Core::ArgsParser::Required::No);
929
930 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
931 return 1;
932
933 if (count < 1)
934 return 0;
935
936 auto argv_ = TRY(lookup_local_variable("ARGV"sv));
937 if (!argv_) {
938 warnln("shift: ARGV is unset");
939 return 1;
940 }
941
942 if (!argv_->is_list())
943 argv_ = adopt_ref(*new AST::ListValue({ const_cast<AST::Value&>(*argv_) }));
944
945 auto& values = const_cast<AST::ListValue*>(static_cast<AST::ListValue const*>(argv_.ptr()))->values();
946 if ((size_t)count > values.size()) {
947 warnln("shift: shift count must not be greater than {}", values.size());
948 return 1;
949 }
950
951 for (auto i = 0; i < count; ++i)
952 (void)values.take_first();
953
954 return 0;
955}
956
957ErrorOr<int> Shell::builtin_source(Main::Arguments arguments)
958{
959 StringView file_to_source;
960 Vector<StringView> args;
961
962 Core::ArgsParser parser;
963 parser.add_positional_argument(file_to_source, "File to read commands from", "path");
964 parser.add_positional_argument(args, "ARGV for the sourced file", "args", Core::ArgsParser::Required::No);
965
966 if (!parser.parse(arguments))
967 return 1;
968
969 auto previous_argv = TRY(lookup_local_variable("ARGV"sv));
970 ScopeGuard guard { [&] {
971 if (!args.is_empty())
972 set_local_variable("ARGV", const_cast<AST::Value&>(*previous_argv));
973 } };
974
975 if (!args.is_empty()) {
976 Vector<String> arguments;
977 arguments.ensure_capacity(args.size());
978 for (auto& arg : args)
979 arguments.append(TRY(String::from_utf8(arg)));
980
981 set_local_variable("ARGV", AST::make_ref_counted<AST::ListValue>(move(arguments)));
982 }
983
984 if (!run_file(file_to_source, true))
985 return 126;
986
987 return 0;
988}
989
990ErrorOr<int> Shell::builtin_time(Main::Arguments arguments)
991{
992 Vector<StringView> args;
993
994 int number_of_iterations = 1;
995
996 Core::ArgsParser parser;
997 parser.add_option(number_of_iterations, "Number of iterations", "iterations", 'n', "iterations");
998 parser.set_stop_on_first_non_option(true);
999 parser.add_positional_argument(args, "Command to execute with arguments", "command", Core::ArgsParser::Required::Yes);
1000
1001 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
1002 return 1;
1003
1004 if (number_of_iterations < 1)
1005 return 1;
1006
1007 AST::Command command;
1008 TRY(command.argv.try_ensure_capacity(args.size()));
1009 for (auto& arg : args)
1010 command.argv.append(TRY(String::from_utf8(arg)));
1011
1012 auto commands = TRY(expand_aliases({ move(command) }));
1013
1014 AK::Statistics iteration_times;
1015
1016 int exit_code = 1;
1017 for (int i = 0; i < number_of_iterations; ++i) {
1018 auto timer = Core::ElapsedTimer::start_new();
1019 for (auto& job : run_commands(commands)) {
1020 block_on_job(job);
1021 exit_code = job->exit_code();
1022 }
1023 iteration_times.add(static_cast<float>(timer.elapsed()));
1024 }
1025
1026 if (number_of_iterations == 1) {
1027 warnln("Time: {} ms", iteration_times.values().first());
1028 } else {
1029 AK::Statistics iteration_times_excluding_first;
1030 for (size_t i = 1; i < iteration_times.size(); i++)
1031 iteration_times_excluding_first.add(iteration_times.values()[i]);
1032
1033 warnln("Timing report: {} ms", iteration_times.sum());
1034 warnln("==============");
1035 warnln("Command: {}", DeprecatedString::join(' ', arguments.strings));
1036 warnln("Average time: {:.2} ms (median: {}, stddev: {:.2}, min: {}, max:{})",
1037 iteration_times.average(), iteration_times.median(),
1038 iteration_times.standard_deviation(),
1039 iteration_times.min(), iteration_times.max());
1040 warnln("Excluding first: {:.2} ms (median: {}, stddev: {:.2}, min: {}, max:{})",
1041 iteration_times_excluding_first.average(), iteration_times_excluding_first.median(),
1042 iteration_times_excluding_first.standard_deviation(),
1043 iteration_times_excluding_first.min(), iteration_times_excluding_first.max());
1044 }
1045
1046 return exit_code;
1047}
1048
1049ErrorOr<int> Shell::builtin_umask(Main::Arguments arguments)
1050{
1051 StringView mask_text;
1052
1053 Core::ArgsParser parser;
1054 parser.add_positional_argument(mask_text, "New mask (omit to get current mask)", "octal-mask", Core::ArgsParser::Required::No);
1055
1056 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
1057 return 1;
1058
1059 if (mask_text.is_empty()) {
1060 mode_t old_mask = umask(0);
1061 printf("%#o\n", old_mask);
1062 umask(old_mask);
1063 return 0;
1064 }
1065
1066 unsigned mask = 0;
1067 auto matches = true;
1068
1069 // FIXME: There's currently no way to parse an StringView as an octal integer,
1070 // when that becomes available, replace this code.
1071 for (auto byte : mask_text.bytes()) {
1072 if (isspace(byte))
1073 continue;
1074 if (!is_ascii_octal_digit(byte)) {
1075 matches = false;
1076 break;
1077 }
1078
1079 mask = (mask << 3) + (byte - '0');
1080 }
1081 if (matches) {
1082 umask(mask);
1083 return 0;
1084 }
1085
1086 warnln("umask: Invalid mask '{}'", mask_text);
1087 return 1;
1088}
1089
1090ErrorOr<int> Shell::builtin_wait(Main::Arguments arguments)
1091{
1092 Vector<int> job_ids;
1093 Vector<bool> id_is_pid;
1094
1095 Core::ArgsParser parser;
1096 parser.add_positional_argument(Core::ArgsParser::Arg {
1097 .help_string = "Job IDs or Jobspecs to wait for",
1098 .name = "job-id",
1099 .min_values = 0,
1100 .max_values = INT_MAX,
1101 .accept_value = [&](StringView value) -> bool {
1102 // Check if it's a pid (i.e. literal integer)
1103 if (auto number = value.to_uint(); number.has_value()) {
1104 job_ids.append(number.value());
1105 id_is_pid.append(true);
1106 return true;
1107 }
1108
1109 // Check if it's a jobspec
1110 if (auto id = resolve_job_spec(value); id.has_value()) {
1111 job_ids.append(id.value());
1112 id_is_pid.append(false);
1113 return true;
1114 }
1115
1116 return false;
1117 } });
1118
1119 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
1120 return 1;
1121
1122 Vector<NonnullRefPtr<Job>> jobs_to_wait_for;
1123
1124 for (size_t i = 0; i < job_ids.size(); ++i) {
1125 auto id = job_ids[i];
1126 auto is_pid = id_is_pid[i];
1127 auto job = find_job(id, is_pid);
1128 if (!job)
1129 warnln("wait: Job with id/pid {} not found", id);
1130 else
1131 jobs_to_wait_for.append(*job);
1132 }
1133
1134 if (job_ids.is_empty()) {
1135 for (auto const& it : jobs)
1136 jobs_to_wait_for.append(it.value);
1137 }
1138
1139 for (auto& job : jobs_to_wait_for) {
1140 job->set_running_in_background(false);
1141 block_on_job(job);
1142 }
1143
1144 return 0;
1145}
1146
1147ErrorOr<int> Shell::builtin_unset(Main::Arguments arguments)
1148{
1149 Vector<DeprecatedString> vars;
1150
1151 Core::ArgsParser parser;
1152 parser.add_positional_argument(vars, "List of variables", "variables", Core::ArgsParser::Required::Yes);
1153
1154 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage))
1155 return 1;
1156
1157 bool did_touch_path = false;
1158 for (auto& value : vars) {
1159 if (!did_touch_path && value == "PATH"sv)
1160 did_touch_path = true;
1161
1162 if (TRY(lookup_local_variable(value)) != nullptr) {
1163 unset_local_variable(value);
1164 } else {
1165 unsetenv(value.characters());
1166 }
1167 }
1168
1169 if (did_touch_path)
1170 cache_path();
1171
1172 return 0;
1173}
1174
1175ErrorOr<int> Shell::builtin_not(Main::Arguments arguments)
1176{
1177 Vector<StringView> args;
1178
1179 Core::ArgsParser parser;
1180 parser.set_stop_on_first_non_option(true);
1181 parser.add_positional_argument(args, "Command to run followed by its arguments", "string");
1182
1183 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::Ignore))
1184 return 1;
1185
1186 AST::Command command;
1187 TRY(command.argv.try_ensure_capacity(args.size()));
1188 for (auto& arg : args)
1189 command.argv.unchecked_append(TRY(String::from_utf8(arg)));
1190
1191 auto commands = TRY(expand_aliases({ move(command) }));
1192 int exit_code = 1;
1193 auto found_a_job = false;
1194 for (auto& job : run_commands(commands)) {
1195 found_a_job = true;
1196 block_on_job(job);
1197 exit_code = job->exit_code();
1198 }
1199 // In case it was a function.
1200 if (!found_a_job)
1201 exit_code = last_return_code.value_or(0);
1202 return exit_code == 0 ? 1 : 0;
1203}
1204
1205ErrorOr<int> Shell::builtin_kill(Main::Arguments arguments)
1206{
1207 // Simply translate the arguments and pass them to `kill'
1208 Vector<String> replaced_values;
1209 auto kill_path = Core::DeprecatedFile::resolve_executable_from_environment("kill"sv);
1210 if (!kill_path.has_value()) {
1211 warnln("kill: `kill' not found in PATH");
1212 return 126;
1213 }
1214
1215 replaced_values.append(TRY(String::from_deprecated_string(kill_path.release_value())));
1216 for (size_t i = 1; i < arguments.strings.size(); ++i) {
1217 if (auto job_id = resolve_job_spec(arguments.strings[i]); job_id.has_value()) {
1218 auto job = find_job(job_id.value());
1219 if (job) {
1220 replaced_values.append(TRY(String::number(job->pid())));
1221 } else {
1222 warnln("kill: Job with pid {} not found", job_id.value());
1223 return 1;
1224 }
1225 } else {
1226 replaced_values.append(TRY(String::from_utf8(arguments.strings[i])));
1227 }
1228 }
1229
1230 // Now just run `kill'
1231 AST::Command command;
1232 command.argv = move(replaced_values);
1233 command.position = m_source_position.has_value() ? m_source_position->position : Optional<AST::Position> {};
1234
1235 auto exit_code = 1;
1236 auto job_result = run_command(command);
1237 if (job_result.is_error()) {
1238 warnln("kill: Failed to run {}: {}", command.argv.first(), job_result.error());
1239 return exit_code;
1240 }
1241
1242 if (auto job = job_result.release_value()) {
1243 block_on_job(job);
1244 exit_code = job->exit_code();
1245 }
1246 return exit_code;
1247}
1248
1249ErrorOr<bool> Shell::run_builtin(const AST::Command& command, Vector<NonnullRefPtr<AST::Rewiring>> const& rewirings, int& retval)
1250{
1251 if (command.argv.is_empty())
1252 return false;
1253
1254 if (!has_builtin(command.argv.first()))
1255 return false;
1256
1257 Vector<StringView> arguments;
1258 TRY(arguments.try_ensure_capacity(command.argv.size()));
1259 for (auto& arg : command.argv)
1260 arguments.unchecked_append(arg);
1261
1262 Main::Arguments arguments_object {
1263 .argc = 0,
1264 .argv = nullptr,
1265 .strings = arguments
1266 };
1267
1268 StringView name = command.argv.first();
1269
1270 SavedFileDescriptors fds { rewirings };
1271
1272 for (auto& rewiring : rewirings) {
1273 int rc = dup2(rewiring->old_fd, rewiring->new_fd);
1274 if (rc < 0) {
1275 perror("dup2(run)");
1276 return false;
1277 }
1278 }
1279
1280 Core::EventLoop loop;
1281 setup_signals();
1282
1283 if (name == ":"sv)
1284 name = "noop"sv;
1285
1286#define __ENUMERATE_SHELL_BUILTIN(builtin) \
1287 if (name == #builtin) { \
1288 retval = TRY(builtin_##builtin(arguments_object)); \
1289 if (!has_error(ShellError::None)) \
1290 raise_error(m_error, m_error_description, command.position); \
1291 fflush(stdout); \
1292 fflush(stderr); \
1293 return true; \
1294 }
1295
1296 ENUMERATE_SHELL_BUILTINS();
1297
1298#undef __ENUMERATE_SHELL_BUILTIN
1299 return false;
1300}
1301
1302ErrorOr<int> Shell::builtin_argsparser_parse(Main::Arguments arguments)
1303{
1304 // argsparser_parse
1305 // --add-option variable [--type (bool | string | i32 | u32 | double | size)] --help-string "" --long-name "" --short-name "" [--value-name "" <if not --type bool>] --list
1306 // --add-positional-argument variable [--type (bool | string | i32 | u32 | double | size)] ([--min n] [--max n] | [--required]) --help-string "" --value-name ""
1307 // [--general-help ""]
1308 // [--stop-on-first-non-option]
1309 // --
1310 // $args_to_parse
1311 Core::ArgsParser parser;
1312
1313 Core::ArgsParser user_parser;
1314
1315 Vector<StringView> descriptors;
1316 Variant<Core::ArgsParser::Option, Core::ArgsParser::Arg, Empty> current;
1317 DeprecatedString current_variable;
1318 // if max > 1 or min < 1, or explicit `--list`.
1319 bool treat_arg_as_list = false;
1320 enum class Type {
1321 Bool,
1322 String,
1323 I32,
1324 U32,
1325 Double,
1326 Size,
1327 };
1328
1329 auto type = Type::String;
1330
1331 auto try_convert = [](StringView value, Type type) -> ErrorOr<Optional<RefPtr<AST::Value>>> {
1332 switch (type) {
1333 case Type::Bool:
1334 return AST::make_ref_counted<AST::StringValue>(TRY("true"_string));
1335 case Type::String:
1336 return AST::make_ref_counted<AST::StringValue>(TRY(String::from_utf8(value)));
1337 case Type::I32:
1338 if (auto number = value.to_int(); number.has_value())
1339 return AST::make_ref_counted<AST::StringValue>(TRY(String::number(*number)));
1340
1341 warnln("Invalid value for type i32: {}", value);
1342 return OptionalNone {};
1343 case Type::U32:
1344 case Type::Size:
1345 if (auto number = value.to_uint(); number.has_value())
1346 return AST::make_ref_counted<AST::StringValue>(TRY(String::number(*number)));
1347
1348 warnln("Invalid value for type u32|size: {}", value);
1349 return OptionalNone {};
1350 case Type::Double: {
1351 DeprecatedString string = value;
1352 char* endptr = nullptr;
1353 auto number = strtod(string.characters(), &endptr);
1354 if (endptr != string.characters() + string.length()) {
1355 warnln("Invalid value for type double: {}", value);
1356 return OptionalNone {};
1357 }
1358
1359 return AST::make_ref_counted<AST::StringValue>(TRY(String::number(number)));
1360 }
1361 default:
1362 VERIFY_NOT_REACHED();
1363 }
1364 };
1365
1366 auto enlist = [&](auto name, auto value) -> ErrorOr<NonnullRefPtr<AST::Value>> {
1367 auto variable = TRY(lookup_local_variable(name));
1368 if (variable) {
1369 auto list = TRY(const_cast<AST::Value&>(*variable).resolve_as_list(*this));
1370 auto new_value = TRY(value->resolve_as_string(*this));
1371 list.append(move(new_value));
1372 return try_make_ref_counted<AST::ListValue>(move(list));
1373 }
1374 return *value;
1375 };
1376
1377 // FIXME: We cannot return ErrorOr<T> from Core::ArgsParser::Option::accept_value(), fix the MUST's here whenever that function is ErrorOr-aware.
1378 auto commit = [&] {
1379 return current.visit(
1380 [&](Core::ArgsParser::Option& option) {
1381 if (!option.long_name && !option.short_name) {
1382 warnln("Defined option must have at least one of --long-name or --short-name");
1383 return false;
1384 }
1385 option.accept_value = [&, current_variable, treat_arg_as_list, type](StringView value) {
1386 auto result = MUST(try_convert(value, type));
1387 if (result.has_value()) {
1388 auto value = result.release_value();
1389 if (treat_arg_as_list)
1390 value = MUST(enlist(current_variable, move(value)));
1391 this->set_local_variable(current_variable, move(value), true);
1392 return true;
1393 }
1394
1395 return false;
1396 };
1397 user_parser.add_option(move(option));
1398 type = Type::String;
1399 treat_arg_as_list = false;
1400 return true;
1401 },
1402 [&](Core::ArgsParser::Arg& arg) {
1403 if (!arg.name) {
1404 warnln("Defined positional argument must have a name");
1405 return false;
1406 }
1407 arg.accept_value = [&, current_variable, treat_arg_as_list, type](StringView value) {
1408 auto result = MUST(try_convert(value, type));
1409 if (result.has_value()) {
1410 auto value = result.release_value();
1411 if (treat_arg_as_list)
1412 value = MUST(enlist(current_variable, move(value)));
1413 this->set_local_variable(current_variable, move(value), true);
1414 return true;
1415 }
1416
1417 return false;
1418 };
1419 user_parser.add_positional_argument(move(arg));
1420 type = Type::String;
1421 treat_arg_as_list = false;
1422 return true;
1423 },
1424 [&](Empty) {
1425 return true;
1426 });
1427 };
1428
1429 parser.add_option(Core::ArgsParser::Option {
1430 .argument_mode = Core::ArgsParser::OptionArgumentMode::None,
1431 .help_string = "Stop processing descriptors after a non-argument parameter is seen",
1432 .long_name = "stop-on-first-non-option",
1433 .accept_value = [&](auto) {
1434 user_parser.set_stop_on_first_non_option(true);
1435 return true;
1436 },
1437 });
1438 parser.add_option(Core::ArgsParser::Option {
1439 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1440 .help_string = "Set the general help string for the parser",
1441 .long_name = "general-help",
1442 .value_name = "string",
1443 .accept_value = [&](StringView value) {
1444 VERIFY(strlen(value.characters_without_null_termination()) == value.length());
1445 user_parser.set_general_help(value.characters_without_null_termination());
1446 return true;
1447 },
1448 });
1449 parser.add_option(Core::ArgsParser::Option {
1450 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1451 .help_string = "Start describing an option",
1452 .long_name = "add-option",
1453 .value_name = "variable-name",
1454 .accept_value = [&](auto name) {
1455 if (!commit())
1456 return false;
1457
1458 current = Core::ArgsParser::Option {};
1459 current_variable = name;
1460 if (current_variable.is_empty() || !all_of(current_variable, [](auto ch) { return ch == '_' || isalnum(ch); })) {
1461 warnln("Option variable name must be a valid identifier");
1462 return false;
1463 }
1464
1465 return true;
1466 },
1467 });
1468 parser.add_option(Core::ArgsParser::Option {
1469 .argument_mode = Core::ArgsParser::OptionArgumentMode::None,
1470 .help_string = "Accept multiple of the current option being given",
1471 .long_name = "list",
1472 .accept_value = [&](auto) {
1473 if (!current.has<Core::ArgsParser::Option>()) {
1474 warnln("Must be defining an option to use --list");
1475 return false;
1476 }
1477 treat_arg_as_list = true;
1478 return true;
1479 },
1480 });
1481 parser.add_option(Core::ArgsParser::Option {
1482 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1483 .help_string = "Define the type of the option or argument being described",
1484 .long_name = "type",
1485 .value_name = "type",
1486 .accept_value = [&](StringView ty) {
1487 if (current.has<Empty>()) {
1488 warnln("Must be defining an argument or option to use --type");
1489 return false;
1490 }
1491
1492 if (ty == "bool") {
1493 if (auto option = current.get_pointer<Core::ArgsParser::Option>()) {
1494 if (option->value_name != nullptr) {
1495 warnln("Type 'bool' does not apply to options with a value (value name is set to {})", option->value_name);
1496 return false;
1497 }
1498 }
1499 type = Type::Bool;
1500 } else if (ty == "string") {
1501 type = Type::String;
1502 } else if (ty == "i32") {
1503 type = Type::I32;
1504 } else if (ty == "u32") {
1505 type = Type::U32;
1506 } else if (ty == "double") {
1507 type = Type::Double;
1508 } else if (ty == "size") {
1509 type = Type::Size;
1510 } else {
1511 warnln("Invalid type '{}', expected one of bool | string | i32 | u32 | double | size", ty);
1512 return false;
1513 }
1514
1515 if (type == Type::Bool) {
1516 set_local_variable(
1517 current_variable,
1518 make_ref_counted<AST::StringValue>(MUST("false"_string)),
1519 true);
1520 }
1521 return true;
1522 },
1523 });
1524 parser.add_option(Core::ArgsParser::Option {
1525 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1526 .help_string = "Set the help string of the option or argument being defined",
1527 .long_name = "help-string",
1528 .value_name = "string",
1529 .accept_value = [&](StringView value) {
1530 return current.visit(
1531 [](Empty) {
1532 warnln("Must be defining an option or argument to use --help-string");
1533 return false;
1534 },
1535 [&](auto& option) {
1536 VERIFY(value.length() == strlen(value.characters_without_null_termination()));
1537 option.help_string = value.characters_without_null_termination();
1538 return true;
1539 });
1540 },
1541 });
1542 parser.add_option(Core::ArgsParser::Option {
1543 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1544 .help_string = "Set the long name of the option being defined",
1545 .long_name = "long-name",
1546 .value_name = "name",
1547 .accept_value = [&](StringView value) {
1548 auto option = current.get_pointer<Core::ArgsParser::Option>();
1549 if (!option) {
1550 warnln("Must be defining an option to use --long-name");
1551 return false;
1552 }
1553 if (option->long_name) {
1554 warnln("Repeated application of --long-name is not allowed, current option has long name set to \"{}\"", option->long_name);
1555 return false;
1556 }
1557 VERIFY(value.length() == strlen(value.characters_without_null_termination()));
1558 option->long_name = value.characters_without_null_termination();
1559 return true;
1560 },
1561 });
1562 parser.add_option(Core::ArgsParser::Option {
1563 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1564 .help_string = "Set the short name of the option being defined",
1565 .long_name = "short-name",
1566 .value_name = "char",
1567 .accept_value = [&](StringView value) {
1568 auto option = current.get_pointer<Core::ArgsParser::Option>();
1569 if (!option) {
1570 warnln("Must be defining an option to use --short-name");
1571 return false;
1572 }
1573 if (value.length() != 1) {
1574 warnln("Option short name ('{}') must be exactly one character long", value);
1575 return false;
1576 }
1577 if (option->short_name) {
1578 warnln("Repeated application of --short-name is not allowed, current option has short name set to '{}'", option->short_name);
1579 return false;
1580 }
1581 option->short_name = value[0];
1582 return true;
1583 },
1584 });
1585 parser.add_option(Core::ArgsParser::Option {
1586 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1587 .help_string = "Set the value name of the option being defined",
1588 .long_name = "value-name",
1589 .value_name = "string",
1590 .accept_value = [&](StringView value) {
1591 return current.visit(
1592 [](Empty) {
1593 warnln("Must be defining an option or a positional argument to use --value-name");
1594 return false;
1595 },
1596 [&](Core::ArgsParser::Option& option) {
1597 if (option.value_name) {
1598 warnln("Repeated application of --value-name is not allowed, current option has value name set to \"{}\"", option.value_name);
1599 return false;
1600 }
1601 if (type == Type::Bool) {
1602 warnln("Options of type bool cannot have a value name");
1603 return false;
1604 }
1605
1606 VERIFY(value.length() == strlen(value.characters_without_null_termination()));
1607 option.value_name = value.characters_without_null_termination();
1608 return true;
1609 },
1610 [&](Core::ArgsParser::Arg& arg) {
1611 if (arg.name) {
1612 warnln("Repeated application of --value-name is not allowed, current argument has value name set to \"{}\"", arg.name);
1613 return false;
1614 }
1615
1616 VERIFY(value.length() == strlen(value.characters_without_null_termination()));
1617 arg.name = value.characters_without_null_termination();
1618 return true;
1619 });
1620 },
1621 });
1622 parser.add_option(Core::ArgsParser::Option {
1623 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1624 .help_string = "Start describing a positional argument",
1625 .long_name = "add-positional-argument",
1626 .value_name = "variable",
1627 .accept_value = [&](auto value) {
1628 if (!commit())
1629 return false;
1630
1631 current = Core::ArgsParser::Arg {};
1632 current_variable = value;
1633 if (current_variable.is_empty() || !all_of(current_variable, [](auto ch) { return ch == '_' || isalnum(ch); })) {
1634 warnln("Argument variable name must be a valid identifier");
1635 return false;
1636 }
1637
1638 return true;
1639 },
1640 });
1641 parser.add_option(Core::ArgsParser::Option {
1642 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1643 .help_string = "Set the minimum required number of positional descriptors for the argument being described",
1644 .long_name = "min",
1645 .value_name = "n",
1646 .accept_value = [&](StringView value) {
1647 auto arg = current.get_pointer<Core::ArgsParser::Arg>();
1648 if (!arg) {
1649 warnln("Must be describing a positional argument to use --min");
1650 return false;
1651 }
1652
1653 auto number = value.to_uint();
1654 if (!number.has_value()) {
1655 warnln("Invalid value for --min: '{}', expected a non-negative number", value);
1656 return false;
1657 }
1658
1659 if (static_cast<unsigned>(arg->max_values) < *number) {
1660 warnln("Invalid value for --min: {}, min must not be larger than max ({})", *number, arg->max_values);
1661 return false;
1662 }
1663
1664 arg->min_values = *number;
1665 treat_arg_as_list = arg->max_values > 1 || arg->min_values < 1;
1666 return true;
1667 },
1668 });
1669 parser.add_option(Core::ArgsParser::Option {
1670 .argument_mode = Core::ArgsParser::OptionArgumentMode::Required,
1671 .help_string = "Set the maximum required number of positional descriptors for the argument being described",
1672 .long_name = "max",
1673 .value_name = "n",
1674 .accept_value = [&](StringView value) {
1675 auto arg = current.get_pointer<Core::ArgsParser::Arg>();
1676 if (!arg) {
1677 warnln("Must be describing a positional argument to use --max");
1678 return false;
1679 }
1680
1681 auto number = value.to_uint();
1682 if (!number.has_value()) {
1683 warnln("Invalid value for --max: '{}', expected a non-negative number", value);
1684 return false;
1685 }
1686
1687 if (static_cast<unsigned>(arg->min_values) > *number) {
1688 warnln("Invalid value for --max: {}, max must not be smaller than min ({})", *number, arg->min_values);
1689 return false;
1690 }
1691
1692 arg->max_values = *number;
1693 treat_arg_as_list = arg->max_values > 1 || arg->min_values < 1;
1694 return true;
1695 },
1696 });
1697 parser.add_option(Core::ArgsParser::Option {
1698 .argument_mode = Core::ArgsParser::OptionArgumentMode::None,
1699 .help_string = "Mark the positional argument being described as required (shorthand for --min 1)",
1700 .long_name = "required",
1701 .accept_value = [&](auto) {
1702 auto arg = current.get_pointer<Core::ArgsParser::Arg>();
1703 if (!arg) {
1704 warnln("Must be describing a positional argument to use --required");
1705 return false;
1706 }
1707 arg->min_values = 1;
1708 if (arg->max_values < arg->min_values)
1709 arg->max_values = 1;
1710 treat_arg_as_list = arg->max_values > 1 || arg->min_values < 1;
1711 return true;
1712 },
1713 });
1714 parser.add_positional_argument(descriptors, "Arguments to parse via the described ArgsParser configuration", "arg", Core::ArgsParser::Required::No);
1715
1716 if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::Ignore))
1717 return 2;
1718
1719 if (!commit())
1720 return 2;
1721
1722 if (!user_parser.parse(descriptors, Core::ArgsParser::FailureBehavior::Ignore))
1723 return 1;
1724
1725 return 0;
1726}
1727
1728bool Shell::has_builtin(StringView name) const
1729{
1730 if (name == ":"sv)
1731 return true;
1732
1733#define __ENUMERATE_SHELL_BUILTIN(builtin) \
1734 if (name == #builtin) { \
1735 return true; \
1736 }
1737
1738 ENUMERATE_SHELL_BUILTINS();
1739
1740#undef __ENUMERATE_SHELL_BUILTIN
1741 return false;
1742}
1743}