Serenity Operating System
at master 1743 lines 58 kB view raw
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}