Serenity Operating System
at portability 1038 lines 30 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "GlobalState.h" 28#include "LineEditor.h" 29#include "Parser.h" 30#include <AK/FileSystemPath.h> 31#include <AK/StringBuilder.h> 32#include <LibCore/DirIterator.h> 33#include <LibCore/ElapsedTimer.h> 34#include <LibCore/File.h> 35#include <errno.h> 36#include <fcntl.h> 37#include <pwd.h> 38#include <signal.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <sys/mman.h> 43#include <sys/stat.h> 44#include <sys/utsname.h> 45#include <sys/wait.h> 46#include <termios.h> 47#include <unistd.h> 48 49//#define SH_DEBUG 50 51GlobalState g; 52static LineEditor editor; 53 54static int run_command(const String&); 55 56static String prompt() 57{ 58 auto* ps1 = getenv("PROMPT"); 59 if (!ps1) { 60 if (g.uid == 0) 61 return "# "; 62 63 StringBuilder builder; 64 builder.appendf("\033]0;%s@%s:%s\007", g.username.characters(), g.hostname, g.cwd.characters()); 65 builder.appendf("\033[31;1m%s\033[0m@\033[37;1m%s\033[0m:\033[32;1m%s\033[0m$> ", g.username.characters(), g.hostname, g.cwd.characters()); 66 return builder.to_string(); 67 } 68 69 StringBuilder builder; 70 for (char* ptr = ps1; *ptr; ++ptr) { 71 if (*ptr == '\\') { 72 ++ptr; 73 if (!*ptr) 74 break; 75 switch (*ptr) { 76 case 'X': 77 builder.append("\033]0;"); 78 break; 79 case 'a': 80 builder.append(0x07); 81 break; 82 case 'e': 83 builder.append(0x1b); 84 break; 85 case 'u': 86 builder.append(g.username); 87 break; 88 case 'h': 89 builder.append(g.hostname); 90 break; 91 case 'w': 92 builder.append(g.cwd); 93 break; 94 case 'p': 95 builder.append(g.uid == 0 ? '#' : '$'); 96 break; 97 } 98 continue; 99 } 100 builder.append(*ptr); 101 } 102 return builder.to_string(); 103} 104 105static int sh_pwd(int, const char**) 106{ 107 printf("%s\n", g.cwd.characters()); 108 return 0; 109} 110 111static int sh_exit(int, const char**) 112{ 113 printf("Good-bye!\n"); 114 exit(0); 115 return 0; 116} 117 118static int sh_export(int argc, const char** argv) 119{ 120 if (argc == 1) { 121 for (int i = 0; environ[i]; ++i) 122 puts(environ[i]); 123 return 0; 124 } 125 auto parts = String(argv[1]).split('='); 126 if (parts.size() != 2) { 127 fprintf(stderr, "usage: export variable=value\n"); 128 return 1; 129 } 130 131 int setenv_return = setenv(parts[0].characters(), parts[1].characters(), 1); 132 133 if (setenv_return == 0 && parts[0] == "PATH") 134 editor.cache_path(); 135 136 return setenv_return; 137} 138 139static int sh_unset(int argc, const char** argv) 140{ 141 if (argc != 2) { 142 fprintf(stderr, "usage: unset variable\n"); 143 return 1; 144 } 145 146 unsetenv(argv[1]); 147 return 0; 148} 149 150static String expand_tilde(const char* expression) 151{ 152 int len = strlen(expression); 153 ASSERT(len > 0 && len + 1 <= PATH_MAX); 154 ASSERT(expression[0] == '~'); 155 156 StringBuilder login_name; 157 int first_slash_index = len; 158 for (int i = 1; i < len; ++i) { 159 if (expression[i] == '/') { 160 first_slash_index = i; 161 break; 162 } 163 login_name.append(expression[i]); 164 } 165 166 StringBuilder path; 167 for (int i = first_slash_index; i < len; ++i) 168 path.append(expression[i]); 169 170 if (login_name.is_empty()) { 171 const char* home = getenv("HOME"); 172 if (!home) { 173 auto passwd = getpwuid(getuid()); 174 ASSERT(passwd && passwd->pw_dir); 175 return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); 176 } 177 return String::format("%s/%s", home, path.to_string().characters()); 178 } 179 180 auto passwd = getpwnam(login_name.to_string().characters()); 181 if (!passwd) 182 return String(expression); 183 ASSERT(passwd->pw_dir); 184 185 return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); 186} 187 188static int sh_cd(int argc, const char** argv) 189{ 190 char pathbuf[PATH_MAX]; 191 192 if (argc == 1) { 193 strcpy(pathbuf, g.home.characters()); 194 } else { 195 if (strcmp(argv[1], "-") == 0) { 196 char* oldpwd = getenv("OLDPWD"); 197 if (oldpwd == nullptr) 198 return 1; 199 size_t len = strlen(oldpwd); 200 ASSERT(len + 1 <= PATH_MAX); 201 memcpy(pathbuf, oldpwd, len + 1); 202 } else if (argv[1][0] == '~') { 203 auto path = expand_tilde(argv[1]); 204 if (path.is_empty()) 205 return 1; 206 strcpy(pathbuf, path.characters()); 207 } else if (argv[1][0] == '/') { 208 memcpy(pathbuf, argv[1], strlen(argv[1]) + 1); 209 } else { 210 sprintf(pathbuf, "%s/%s", g.cwd.characters(), argv[1]); 211 } 212 } 213 214 FileSystemPath canonical_path(pathbuf); 215 if (!canonical_path.is_valid()) { 216 printf("FileSystemPath failed to canonicalize '%s'\n", pathbuf); 217 return 1; 218 } 219 const char* path = canonical_path.string().characters(); 220 221 struct stat st; 222 int rc = stat(path, &st); 223 if (rc < 0) { 224 printf("stat(%s) failed: %s\n", path, strerror(errno)); 225 return 1; 226 } 227 if (!S_ISDIR(st.st_mode)) { 228 printf("Not a directory: %s\n", path); 229 return 1; 230 } 231 rc = chdir(path); 232 if (rc < 0) { 233 printf("chdir(%s) failed: %s\n", path, strerror(errno)); 234 return 1; 235 } 236 setenv("OLDPWD", g.cwd.characters(), 1); 237 g.cwd = canonical_path.string(); 238 setenv("PWD", g.cwd.characters(), 1); 239 return 0; 240} 241 242static int sh_history(int, const char**) 243{ 244 for (size_t i = 0; i < editor.history().size(); ++i) { 245 printf("%6zu %s\n", i, editor.history()[i].characters()); 246 } 247 return 0; 248} 249 250static int sh_time(int argc, const char** argv) 251{ 252 if (argc == 1) { 253 printf("usage: time <command>\n"); 254 return 0; 255 } 256 StringBuilder builder; 257 for (int i = 1; i < argc; ++i) { 258 builder.append(argv[i]); 259 if (i != argc - 1) 260 builder.append(' '); 261 } 262 Core::ElapsedTimer timer; 263 timer.start(); 264 int exit_code = run_command(builder.to_string()); 265 printf("Time: %d ms\n", timer.elapsed()); 266 return exit_code; 267} 268 269static int sh_umask(int argc, const char** argv) 270{ 271 if (argc == 1) { 272 mode_t old_mask = umask(0); 273 printf("%#o\n", old_mask); 274 umask(old_mask); 275 return 0; 276 } 277 if (argc == 2) { 278 unsigned mask; 279 int matches = sscanf(argv[1], "%o", &mask); 280 if (matches == 1) { 281 umask(mask); 282 return 0; 283 } 284 } 285 printf("usage: umask <octal-mask>\n"); 286 return 0; 287} 288 289static int sh_popd(int argc, const char** argv) 290{ 291 if (g.directory_stack.size() <= 1) { 292 fprintf(stderr, "Shell: popd: directory stack empty\n"); 293 return 1; 294 } 295 296 bool should_switch = true; 297 String path = g.directory_stack.take_last(); 298 299 // When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. 300 if (argc == 1) { 301 int rc = chdir(path.characters()); 302 if (rc < 0) { 303 fprintf(stderr, "chdir(%s) failed: %s", path.characters(), strerror(errno)); 304 return 1; 305 } 306 307 g.cwd = path; 308 return 0; 309 } 310 311 for (int i = 1; i < argc; i++) { 312 const char* arg = argv[i]; 313 if (!strcmp(arg, "-n")) { 314 should_switch = false; 315 } 316 } 317 318 FileSystemPath canonical_path(path.characters()); 319 if (!canonical_path.is_valid()) { 320 fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path.characters()); 321 return 1; 322 } 323 324 const char* real_path = canonical_path.string().characters(); 325 326 struct stat st; 327 int rc = stat(real_path, &st); 328 if (rc < 0) { 329 fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); 330 return 1; 331 } 332 333 if (!S_ISDIR(st.st_mode)) { 334 fprintf(stderr, "Not a directory: %s\n", real_path); 335 return 1; 336 } 337 338 if (should_switch) { 339 int rc = chdir(real_path); 340 if (rc < 0) { 341 fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); 342 return 1; 343 } 344 345 g.cwd = canonical_path.string(); 346 } 347 348 return 0; 349} 350 351static int sh_pushd(int argc, const char** argv) 352{ 353 StringBuilder path_builder; 354 bool should_switch = true; 355 356 // From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html 357 // With no arguments, pushd exchanges the top two directories and makes the new top the current directory. 358 if (argc == 1) { 359 if (g.directory_stack.size() < 2) { 360 fprintf(stderr, "pushd: no other directory\n"); 361 return 1; 362 } 363 364 String dir1 = g.directory_stack.take_first(); 365 String dir2 = g.directory_stack.take_first(); 366 g.directory_stack.insert(0, dir2); 367 g.directory_stack.insert(1, dir1); 368 369 int rc = chdir(dir2.characters()); 370 if (rc < 0) { 371 fprintf(stderr, "chdir(%s) failed: %s", dir2.characters(), strerror(errno)); 372 return 1; 373 } 374 375 g.cwd = dir2; 376 377 return 0; 378 } 379 380 // Let's assume the user's typed in 'pushd <dir>' 381 if (argc == 2) { 382 g.directory_stack.append(g.cwd.characters()); 383 if (argv[1][0] == '/') { 384 path_builder.append(argv[1]); 385 } else { 386 path_builder.appendf("%s/%s", g.cwd.characters(), argv[1]); 387 } 388 } else if (argc == 3) { 389 g.directory_stack.append(g.cwd.characters()); 390 for (int i = 1; i < argc; i++) { 391 const char* arg = argv[i]; 392 393 if (arg[0] != '-') { 394 if (arg[0] == '/') { 395 path_builder.append(arg); 396 } else 397 path_builder.appendf("%s/%s", g.cwd.characters(), arg); 398 } 399 400 if (!strcmp(arg, "-n")) 401 should_switch = false; 402 } 403 } 404 405 FileSystemPath canonical_path(path_builder.to_string()); 406 if (!canonical_path.is_valid()) { 407 fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path_builder.to_string().characters()); 408 return 1; 409 } 410 411 const char* real_path = canonical_path.string().characters(); 412 413 struct stat st; 414 int rc = stat(real_path, &st); 415 if (rc < 0) { 416 fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); 417 return 1; 418 } 419 420 if (!S_ISDIR(st.st_mode)) { 421 fprintf(stderr, "Not a directory: %s\n", real_path); 422 return 1; 423 } 424 425 if (should_switch) { 426 int rc = chdir(real_path); 427 if (rc < 0) { 428 fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); 429 return 1; 430 } 431 432 g.cwd = canonical_path.string(); 433 } 434 435 return 0; 436} 437 438static int sh_dirs(int argc, const char** argv) 439{ 440 // The first directory in the stack is ALWAYS the current directory 441 g.directory_stack.at(0) = g.cwd.characters(); 442 443 if (argc == 1) { 444 for (String dir : g.directory_stack) 445 printf("%s ", dir.characters()); 446 447 printf("\n"); 448 return 0; 449 } 450 451 bool printed = false; 452 for (int i = 0; i < argc; i++) { 453 const char* arg = argv[i]; 454 if (!strcmp(arg, "-c")) { 455 for (size_t i = 1; i < g.directory_stack.size(); i++) 456 g.directory_stack.remove(i); 457 458 printed = true; 459 continue; 460 } 461 if (!strcmp(arg, "-p") && !printed) { 462 for (auto& directory : g.directory_stack) 463 printf("%s\n", directory.characters()); 464 465 printed = true; 466 continue; 467 } 468 if (!strcmp(arg, "-v") && !printed) { 469 int idx = 0; 470 for (auto& directory : g.directory_stack) { 471 printf("%d %s\n", idx++, directory.characters()); 472 } 473 474 printed = true; 475 continue; 476 } 477 } 478 479 return 0; 480} 481 482static bool handle_builtin(int argc, const char** argv, int& retval) 483{ 484 if (argc == 0) 485 return false; 486 if (!strcmp(argv[0], "cd")) { 487 retval = sh_cd(argc, argv); 488 return true; 489 } 490 if (!strcmp(argv[0], "pwd")) { 491 retval = sh_pwd(argc, argv); 492 return true; 493 } 494 if (!strcmp(argv[0], "exit")) { 495 retval = sh_exit(argc, argv); 496 return true; 497 } 498 if (!strcmp(argv[0], "export")) { 499 retval = sh_export(argc, argv); 500 return true; 501 } 502 if (!strcmp(argv[0], "unset")) { 503 retval = sh_unset(argc, argv); 504 return true; 505 } 506 if (!strcmp(argv[0], "history")) { 507 retval = sh_history(argc, argv); 508 return true; 509 } 510 if (!strcmp(argv[0], "umask")) { 511 retval = sh_umask(argc, argv); 512 return true; 513 } 514 if (!strcmp(argv[0], "dirs")) { 515 retval = sh_dirs(argc, argv); 516 return true; 517 } 518 if (!strcmp(argv[0], "pushd")) { 519 retval = sh_pushd(argc, argv); 520 return true; 521 } 522 if (!strcmp(argv[0], "popd")) { 523 retval = sh_popd(argc, argv); 524 return true; 525 } 526 if (!strcmp(argv[0], "time")) { 527 retval = sh_time(argc, argv); 528 return true; 529 } 530 return false; 531} 532 533class FileDescriptionCollector { 534public: 535 FileDescriptionCollector() {} 536 ~FileDescriptionCollector() { collect(); } 537 538 void collect() 539 { 540 for (auto fd : m_fds) 541 close(fd); 542 m_fds.clear(); 543 } 544 void add(int fd) { m_fds.append(fd); } 545 546private: 547 Vector<int, 32> m_fds; 548}; 549 550class CommandTimer { 551public: 552 explicit CommandTimer(const String& command) 553 : m_command(command) 554 { 555 m_timer.start(); 556 } 557 ~CommandTimer() 558 { 559 dbg() << "Command \"" << m_command << "\" finished in " << m_timer.elapsed() << " ms"; 560 } 561 562private: 563 Core::ElapsedTimer m_timer; 564 String m_command; 565}; 566 567static bool is_glob(const StringView& s) 568{ 569 for (size_t i = 0; i < s.length(); i++) { 570 char c = s.characters_without_null_termination()[i]; 571 if (c == '*' || c == '?') 572 return true; 573 } 574 return false; 575} 576 577static Vector<StringView> split_path(const StringView& path) 578{ 579 Vector<StringView> parts; 580 581 size_t substart = 0; 582 for (size_t i = 0; i < path.length(); i++) { 583 char ch = path.characters_without_null_termination()[i]; 584 if (ch != '/') 585 continue; 586 size_t sublen = i - substart; 587 if (sublen != 0) 588 parts.append(path.substring_view(substart, sublen)); 589 parts.append(path.substring_view(i, 1)); 590 substart = i + 1; 591 } 592 593 size_t taillen = path.length() - substart; 594 if (taillen != 0) 595 parts.append(path.substring_view(substart, taillen)); 596 597 return parts; 598} 599 600static Vector<String> expand_globs(const StringView& path, const StringView& base) 601{ 602 auto parts = split_path(path); 603 604 StringBuilder builder; 605 builder.append(base); 606 Vector<String> res; 607 608 for (size_t i = 0; i < parts.size(); ++i) { 609 auto& part = parts[i]; 610 if (!is_glob(part)) { 611 builder.append(part); 612 continue; 613 } 614 615 // Found a glob. 616 String new_base = builder.to_string(); 617 StringView new_base_v = new_base; 618 if (new_base_v.is_empty()) 619 new_base_v = "."; 620 Core::DirIterator di(new_base_v, Core::DirIterator::SkipParentAndBaseDir); 621 622 if (di.has_error()) { 623 return res; 624 } 625 626 while (di.has_next()) { 627 String name = di.next_path(); 628 629 // Dotfiles have to be explicitly requested 630 if (name[0] == '.' && part[0] != '.') 631 continue; 632 633 if (name.matches(part, String::CaseSensitivity::CaseSensitive)) { 634 635 StringBuilder nested_base; 636 nested_base.append(new_base); 637 nested_base.append(name); 638 639 StringView remaining_path = path.substring_view_starting_after_substring(part); 640 Vector<String> nested_res = expand_globs(remaining_path, nested_base.to_string()); 641 for (auto& s : nested_res) 642 res.append(s); 643 } 644 } 645 return res; 646 } 647 648 // Found no globs. 649 String new_path = builder.to_string(); 650 if (access(new_path.characters(), F_OK) == 0) 651 res.append(new_path); 652 return res; 653} 654 655static Vector<String> expand_parameters(const StringView& param) 656{ 657 bool is_variable = param.length() > 1 && param[0] == '$'; 658 if (!is_variable) 659 return { param }; 660 661 String variable_name = String(param.substring_view(1, param.length() - 1)); 662 if (variable_name == "?") 663 return { String::number(g.last_return_code) }; 664 else if (variable_name == "$") 665 return { String::number(getpid()) }; 666 667 char* env_value = getenv(variable_name.characters()); 668 if (env_value == nullptr) 669 return { "" }; 670 671 Vector<String> res; 672 String str_env_value = String(env_value); 673 const auto& split_text = str_env_value.split_view(' '); 674 for (auto& part : split_text) 675 res.append(part); 676 return res; 677} 678 679static Vector<String> process_arguments(const Vector<String>& args) 680{ 681 Vector<String> argv_string; 682 for (auto& arg : args) { 683 // This will return the text passed in if it wasn't a variable 684 // This lets us just loop over its values 685 auto expanded_parameters = expand_parameters(arg); 686 687 for (auto& exp_arg : expanded_parameters) { 688 auto expanded_globs = expand_globs(exp_arg, ""); 689 for (auto& path : expanded_globs) 690 argv_string.append(path); 691 692 if (expanded_globs.is_empty()) 693 argv_string.append(exp_arg); 694 } 695 } 696 697 return argv_string; 698} 699 700static int run_command(const String& cmd) 701{ 702 if (cmd.is_empty()) 703 return 0; 704 705 if (cmd.starts_with("#")) 706 return 0; 707 708 auto commands = Parser(cmd).parse(); 709 710#ifdef SH_DEBUG 711 for (auto& command : commands) { 712 for (int i = 0; i < command.subcommands.size(); ++i) { 713 for (int j = 0; j < i; ++j) 714 dbgprintf(" "); 715 for (auto& arg : command.subcommands[i].args) { 716 dbgprintf("<%s> ", arg.characters()); 717 } 718 dbgprintf("\n"); 719 for (auto& redirecton : command.subcommands[i].redirections) { 720 for (int j = 0; j < i; ++j) 721 dbgprintf(" "); 722 dbgprintf(" "); 723 switch (redirecton.type) { 724 case Redirection::Pipe: 725 dbgprintf("Pipe\n"); 726 break; 727 case Redirection::FileRead: 728 dbgprintf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.characters()); 729 break; 730 case Redirection::FileWrite: 731 dbgprintf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.characters()); 732 break; 733 case Redirection::FileWriteAppend: 734 dbgprintf("fd:%d = FileWriteAppend: %s\n", redirecton.fd, redirecton.path.characters()); 735 break; 736 default: 737 break; 738 } 739 } 740 } 741 dbgprintf("\n"); 742 } 743#endif 744 745 struct termios trm; 746 tcgetattr(0, &trm); 747 748 struct SpawnedProcess { 749 String name; 750 pid_t pid; 751 }; 752 753 int return_value = 0; 754 755 for (auto& command : commands) { 756 if (command.subcommands.is_empty()) 757 continue; 758 759 FileDescriptionCollector fds; 760 761 for (size_t i = 0; i < command.subcommands.size(); ++i) { 762 auto& subcommand = command.subcommands[i]; 763 for (auto& redirection : subcommand.redirections) { 764 switch (redirection.type) { 765 case Redirection::Pipe: { 766 int pipefd[2]; 767 int rc = pipe(pipefd); 768 if (rc < 0) { 769 perror("pipe"); 770 return 1; 771 } 772 subcommand.rewirings.append({ STDOUT_FILENO, pipefd[1] }); 773 auto& next_command = command.subcommands[i + 1]; 774 next_command.rewirings.append({ STDIN_FILENO, pipefd[0] }); 775 fds.add(pipefd[0]); 776 fds.add(pipefd[1]); 777 break; 778 } 779 case Redirection::FileWriteAppend: { 780 int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666); 781 if (fd < 0) { 782 perror("open"); 783 return 1; 784 } 785 subcommand.rewirings.append({ redirection.fd, fd }); 786 fds.add(fd); 787 break; 788 } 789 case Redirection::FileWrite: { 790 int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666); 791 if (fd < 0) { 792 perror("open"); 793 return 1; 794 } 795 subcommand.rewirings.append({ redirection.fd, fd }); 796 fds.add(fd); 797 break; 798 } 799 case Redirection::FileRead: { 800 int fd = open(redirection.path.characters(), O_RDONLY); 801 if (fd < 0) { 802 perror("open"); 803 return 1; 804 } 805 subcommand.rewirings.append({ redirection.fd, fd }); 806 fds.add(fd); 807 break; 808 } 809 } 810 } 811 } 812 813 Vector<SpawnedProcess> children; 814 815 CommandTimer timer(cmd); 816 817 for (size_t i = 0; i < command.subcommands.size(); ++i) { 818 auto& subcommand = command.subcommands[i]; 819 Vector<String> argv_string = process_arguments(subcommand.args); 820 Vector<const char*> argv; 821 argv.ensure_capacity(argv_string.size()); 822 for (const auto& s : argv_string) { 823 argv.append(s.characters()); 824 } 825 argv.append(nullptr); 826 827#ifdef SH_DEBUG 828 for (auto& arg : argv) { 829 dbgprintf("<%s> ", arg); 830 } 831 dbgprintf("\n"); 832#endif 833 834 int retval = 0; 835 if (handle_builtin(argv.size() - 1, argv.data(), retval)) 836 return retval; 837 838 pid_t child = fork(); 839 if (!child) { 840 setpgid(0, 0); 841 tcsetpgrp(0, getpid()); 842 tcsetattr(0, TCSANOW, &g.default_termios); 843 for (auto& rewiring : subcommand.rewirings) { 844#ifdef SH_DEBUG 845 dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), rewiring.rewire_fd, rewiring.fd); 846#endif 847 int rc = dup2(rewiring.rewire_fd, rewiring.fd); 848 if (rc < 0) { 849 perror("dup2"); 850 return 1; 851 } 852 } 853 854 fds.collect(); 855 856 int rc = execvp(argv[0], const_cast<char* const*>(argv.data())); 857 if (rc < 0) { 858 if (errno == ENOENT) 859 fprintf(stderr, "%s: Command not found.\n", argv[0]); 860 else 861 fprintf(stderr, "execvp(%s): %s\n", argv[0], strerror(errno)); 862 _exit(1); 863 } 864 ASSERT_NOT_REACHED(); 865 } 866 children.append({ argv[0], child }); 867 } 868 869#ifdef SH_DEBUG 870 dbgprintf("Closing fds in shell process:\n"); 871#endif 872 fds.collect(); 873 874#ifdef SH_DEBUG 875 dbgprintf("Now we gotta wait on children:\n"); 876 for (auto& child : children) 877 dbgprintf(" %d (%s)\n", child.pid, child.name.characters()); 878#endif 879 880 int wstatus = 0; 881 882 for (size_t i = 0; i < children.size(); ++i) { 883 auto& child = children[i]; 884 do { 885 int rc = waitpid(child.pid, &wstatus, 0); 886 if (rc < 0 && errno != EINTR) { 887 if (errno != ECHILD) 888 perror("waitpid"); 889 break; 890 } 891 if (WIFEXITED(wstatus)) { 892 if (WEXITSTATUS(wstatus) != 0) 893 dbg() << "Shell: " << child.name << ":" << child.pid << " exited with status " << WEXITSTATUS(wstatus); 894 if (i == 0) 895 return_value = WEXITSTATUS(wstatus); 896 } else if (WIFSTOPPED(wstatus)) { 897 fprintf(stderr, "Shell: %s(%d) %s\n", child.name.characters(), child.pid, strsignal(WSTOPSIG(wstatus))); 898 } else { 899 if (WIFSIGNALED(wstatus)) { 900 printf("Shell: %s(%d) exited due to signal '%s'\n", child.name.characters(), child.pid, strsignal(WTERMSIG(wstatus))); 901 } else { 902 printf("Shell: %s(%d) exited abnormally\n", child.name.characters(), child.pid); 903 } 904 } 905 } while (errno == EINTR); 906 } 907 } 908 909 g.last_return_code = return_value; 910 911 // FIXME: Should I really have to tcsetpgrp() after my child has exited? 912 // Is the terminal controlling pgrp really still the PGID of the dead process? 913 tcsetpgrp(0, getpid()); 914 tcsetattr(0, TCSANOW, &trm); 915 return return_value; 916} 917 918static String get_history_path() 919{ 920 StringBuilder builder; 921 builder.append(g.home); 922 builder.append("/.history"); 923 return builder.to_string(); 924} 925 926void load_history() 927{ 928 auto history_file = Core::File::construct(get_history_path()); 929 if (!history_file->open(Core::IODevice::ReadOnly)) 930 return; 931 while (history_file->can_read_line()) { 932 auto b = history_file->read_line(1024); 933 // skip the newline and terminating bytes 934 editor.add_to_history(String(reinterpret_cast<const char*>(b.data()), b.size() - 2)); 935 } 936} 937 938void save_history() 939{ 940 auto history_file = Core::File::construct(get_history_path()); 941 if (!history_file->open(Core::IODevice::WriteOnly)) 942 return; 943 for (const auto& line : editor.history()) { 944 history_file->write(line); 945 history_file->write("\n"); 946 } 947} 948 949int main(int argc, char** argv) 950{ 951 if (pledge("stdio rpath wpath cpath proc exec tty", nullptr) < 0) { 952 perror("pledge"); 953 return 1; 954 } 955 956 g.uid = getuid(); 957 tcsetpgrp(0, getpgrp()); 958 tcgetattr(0, &g.default_termios); 959 g.termios = g.default_termios; 960 // Because we use our own line discipline which includes echoing, 961 // we disable ICANON and ECHO. 962 g.termios.c_lflag &= ~(ECHO | ICANON); 963 tcsetattr(0, TCSANOW, &g.termios); 964 965 signal(SIGINT, [](int) { 966 g.was_interrupted = true; 967 }); 968 969 signal(SIGHUP, [](int) { 970 save_history(); 971 }); 972 973 signal(SIGWINCH, [](int) { 974 g.was_resized = true; 975 }); 976 977 int rc = gethostname(g.hostname, sizeof(g.hostname)); 978 if (rc < 0) 979 perror("gethostname"); 980 rc = ttyname_r(0, g.ttyname, sizeof(g.ttyname)); 981 if (rc < 0) 982 perror("ttyname_r"); 983 984 { 985 auto* pw = getpwuid(getuid()); 986 if (pw) { 987 g.username = pw->pw_name; 988 g.home = pw->pw_dir; 989 setenv("HOME", pw->pw_dir, 1); 990 } 991 endpwent(); 992 } 993 994 if (argc > 2 && !strcmp(argv[1], "-c")) { 995 dbgprintf("sh -c '%s'\n", argv[2]); 996 run_command(argv[2]); 997 return 0; 998 } 999 1000 if (argc == 2 && argv[1][0] != '-') { 1001 auto file = Core::File::construct(argv[1]); 1002 if (!file->open(Core::IODevice::ReadOnly)) { 1003 fprintf(stderr, "Failed to open %s: %s\n", file->filename().characters(), file->error_string()); 1004 return 1; 1005 } 1006 for (;;) { 1007 auto line = file->read_line(4096); 1008 if (line.is_null()) 1009 break; 1010 run_command(String::copy(line, Chomp)); 1011 } 1012 return 0; 1013 } 1014 1015 { 1016 auto* cwd = getcwd(nullptr, 0); 1017 g.cwd = cwd; 1018 setenv("PWD", cwd, 1); 1019 free(cwd); 1020 } 1021 1022 g.directory_stack.append(g.cwd); 1023 1024 load_history(); 1025 atexit(save_history); 1026 1027 editor.cache_path(); 1028 1029 for (;;) { 1030 auto line = editor.get_line(prompt()); 1031 if (line.is_empty()) 1032 continue; 1033 run_command(line); 1034 editor.add_to_history(line); 1035 } 1036 1037 return 0; 1038}