Serenity Operating System
at hosted 1236 lines 38 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 "Parser.h" 29#include <AK/FileSystemPath.h> 30#include <AK/Function.h> 31#include <AK/StringBuilder.h> 32#include <LibCore/DirIterator.h> 33#include <LibCore/ElapsedTimer.h> 34#include <LibCore/File.h> 35#include <LibLine/Editor.h> 36#include <errno.h> 37#include <fcntl.h> 38#include <limits.h> 39#include <pwd.h> 40#include <signal.h> 41#include <stdio.h> 42#include <stdlib.h> 43#include <string.h> 44#include <sys/mman.h> 45#include <sys/stat.h> 46#include <sys/utsname.h> 47#include <sys/wait.h> 48#include <termios.h> 49#include <unistd.h> 50 51//#define SH_DEBUG 52 53GlobalState g; 54static Line::Editor editor {}; 55 56static int run_command(const String&); 57void cache_path(); 58 59#ifndef __serenity__ 60extern char **environ; 61#endif 62 63static String prompt() 64{ 65 auto* ps1 = getenv("PROMPT"); 66 if (!ps1) { 67 if (g.uid == 0) 68 return "# "; 69 70 StringBuilder builder; 71 builder.appendf("\033]0;%s@%s:%s\007", g.username.characters(), g.hostname, g.cwd.characters()); 72 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()); 73 return builder.to_string(); 74 } 75 76 StringBuilder builder; 77 for (char* ptr = ps1; *ptr; ++ptr) { 78 if (*ptr == '\\') { 79 ++ptr; 80 if (!*ptr) 81 break; 82 switch (*ptr) { 83 case 'X': 84 builder.append("\033]0;"); 85 break; 86 case 'a': 87 builder.append(0x07); 88 break; 89 case 'e': 90 builder.append(0x1b); 91 break; 92 case 'u': 93 builder.append(g.username); 94 break; 95 case 'h': 96 builder.append(g.hostname); 97 break; 98 case 'w': { 99 String home_path = getenv("HOME"); 100 if (g.cwd.starts_with(home_path)) { 101 builder.append('~'); 102 builder.append(g.cwd.substring_view(home_path.length(), g.cwd.length() - home_path.length())); 103 } else { 104 builder.append(g.cwd); 105 } 106 break; 107 } 108 case 'p': 109 builder.append(g.uid == 0 ? '#' : '$'); 110 break; 111 } 112 continue; 113 } 114 builder.append(*ptr); 115 } 116 return builder.to_string(); 117} 118 119static int sh_pwd(int, const char**) 120{ 121 printf("%s\n", g.cwd.characters()); 122 return 0; 123} 124 125static int sh_exit(int, const char**) 126{ 127 printf("Good-bye!\n"); 128 exit(0); 129 return 0; 130} 131 132static int sh_export(int argc, const char** argv) 133{ 134 if (argc == 1) { 135 for (int i = 0; environ[i]; ++i) 136 puts(environ[i]); 137 return 0; 138 } 139 auto parts = String(argv[1]).split('='); 140 if (parts.size() != 2) { 141 fprintf(stderr, "usage: export variable=value\n"); 142 return 1; 143 } 144 145 int setenv_return = setenv(parts[0].characters(), parts[1].characters(), 1); 146 147 if (setenv_return == 0 && parts[0] == "PATH") 148 cache_path(); 149 150 return setenv_return; 151} 152 153static int sh_unset(int argc, const char** argv) 154{ 155 if (argc != 2) { 156 fprintf(stderr, "usage: unset variable\n"); 157 return 1; 158 } 159 160 unsetenv(argv[1]); 161 return 0; 162} 163 164static String expand_tilde(const String& expression) 165{ 166 ASSERT(expression.starts_with('~')); 167 168 StringBuilder login_name; 169 size_t first_slash_index = expression.length(); 170 for (size_t i = 1; i < expression.length(); ++i) { 171 if (expression[i] == '/') { 172 first_slash_index = i; 173 break; 174 } 175 login_name.append(expression[i]); 176 } 177 178 StringBuilder path; 179 for (size_t i = first_slash_index; i < expression.length(); ++i) 180 path.append(expression[i]); 181 182 if (login_name.is_empty()) { 183 const char* home = getenv("HOME"); 184 if (!home) { 185 auto passwd = getpwuid(getuid()); 186 ASSERT(passwd && passwd->pw_dir); 187 return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); 188 } 189 return String::format("%s/%s", home, path.to_string().characters()); 190 } 191 192 auto passwd = getpwnam(login_name.to_string().characters()); 193 if (!passwd) 194 return expression; 195 ASSERT(passwd->pw_dir); 196 197 return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); 198} 199 200static int sh_cd(int argc, const char** argv) 201{ 202 if (argc > 2) { 203 fprintf(stderr, "cd: too many arguments\n"); 204 return 1; 205 } 206 207 String new_path; 208 209 if (argc == 1) { 210 new_path = g.home; 211 if (g.cd_history.is_empty() || g.cd_history.last() != g.home) 212 g.cd_history.enqueue(g.home); 213 } else { 214 if (g.cd_history.is_empty() || g.cd_history.last() != argv[1]) 215 g.cd_history.enqueue(argv[1]); 216 if (strcmp(argv[1], "-") == 0) { 217 char* oldpwd = getenv("OLDPWD"); 218 if (oldpwd == nullptr) 219 return 1; 220 new_path = oldpwd; 221 } else if (argv[1][0] == '/') { 222 new_path = argv[1]; 223 } else { 224 StringBuilder builder; 225 builder.append(g.cwd); 226 builder.append('/'); 227 builder.append(argv[1]); 228 new_path = builder.to_string(); 229 } 230 } 231 232 FileSystemPath canonical_path(new_path); 233 if (!canonical_path.is_valid()) { 234 printf("FileSystemPath failed to canonicalize '%s'\n", new_path.characters()); 235 return 1; 236 } 237 const char* path = canonical_path.string().characters(); 238 239 struct stat st; 240 int rc = stat(path, &st); 241 if (rc < 0) { 242 printf("stat(%s) failed: %s\n", path, strerror(errno)); 243 return 1; 244 } 245 if (!S_ISDIR(st.st_mode)) { 246 printf("Not a directory: %s\n", path); 247 return 1; 248 } 249 rc = chdir(path); 250 if (rc < 0) { 251 printf("chdir(%s) failed: %s\n", path, strerror(errno)); 252 return 1; 253 } 254 setenv("OLDPWD", g.cwd.characters(), 1); 255 g.cwd = canonical_path.string(); 256 setenv("PWD", g.cwd.characters(), 1); 257 return 0; 258} 259 260static int sh_cdh(int argc, const char** argv) 261{ 262 if (argc > 2) { 263 fprintf(stderr, "usage: cdh [index]\n"); 264 return 1; 265 } 266 267 if (argc == 1) { 268 if (g.cd_history.size() == 0) { 269 printf("cdh: no history available\n"); 270 return 0; 271 } 272 273 for (int i = g.cd_history.size() - 1; i >= 0; --i) 274 printf("%lu: %s\n", g.cd_history.size() - i, g.cd_history.at(i).characters()); 275 return 0; 276 } 277 278 bool ok; 279 size_t cd_history_index = String(argv[1]).to_uint(ok); 280 281 if (!ok || cd_history_index < 1 || cd_history_index > g.cd_history.size()) { 282 fprintf(stderr, "usage: cdh [index]\n"); 283 return 1; 284 } 285 286 const char* path = g.cd_history.at(g.cd_history.size() - cd_history_index).characters(); 287 const char* cd_args[] = { "cd", path }; 288 return sh_cd(2, cd_args); 289} 290 291static int sh_history(int, const char**) 292{ 293 for (size_t i = 0; i < editor.history().size(); ++i) { 294 printf("%6zu %s\n", i, editor.history()[i].characters()); 295 } 296 return 0; 297} 298 299static int sh_time(int argc, const char** argv) 300{ 301 if (argc == 1) { 302 printf("usage: time <command>\n"); 303 return 0; 304 } 305 StringBuilder builder; 306 for (int i = 1; i < argc; ++i) { 307 builder.append(argv[i]); 308 if (i != argc - 1) 309 builder.append(' '); 310 } 311 Core::ElapsedTimer timer; 312 timer.start(); 313 int exit_code = run_command(builder.to_string()); 314 printf("Time: %d ms\n", timer.elapsed()); 315 return exit_code; 316} 317 318static int sh_umask(int argc, const char** argv) 319{ 320 if (argc == 1) { 321 mode_t old_mask = umask(0); 322 printf("%#o\n", old_mask); 323 umask(old_mask); 324 return 0; 325 } 326 if (argc == 2) { 327 unsigned mask; 328 int matches = sscanf(argv[1], "%o", &mask); 329 if (matches == 1) { 330 umask(mask); 331 return 0; 332 } 333 } 334 printf("usage: umask <octal-mask>\n"); 335 return 0; 336} 337 338static int sh_popd(int argc, const char** argv) 339{ 340 if (g.directory_stack.size() <= 1) { 341 fprintf(stderr, "Shell: popd: directory stack empty\n"); 342 return 1; 343 } 344 345 bool should_switch = true; 346 String path = g.directory_stack.take_last(); 347 348 // When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. 349 if (argc == 1) { 350 int rc = chdir(path.characters()); 351 if (rc < 0) { 352 fprintf(stderr, "chdir(%s) failed: %s", path.characters(), strerror(errno)); 353 return 1; 354 } 355 356 g.cwd = path; 357 return 0; 358 } 359 360 for (int i = 1; i < argc; i++) { 361 const char* arg = argv[i]; 362 if (!strcmp(arg, "-n")) { 363 should_switch = false; 364 } 365 } 366 367 FileSystemPath canonical_path(path.characters()); 368 if (!canonical_path.is_valid()) { 369 fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path.characters()); 370 return 1; 371 } 372 373 const char* real_path = canonical_path.string().characters(); 374 375 struct stat st; 376 int rc = stat(real_path, &st); 377 if (rc < 0) { 378 fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); 379 return 1; 380 } 381 382 if (!S_ISDIR(st.st_mode)) { 383 fprintf(stderr, "Not a directory: %s\n", real_path); 384 return 1; 385 } 386 387 if (should_switch) { 388 int rc = chdir(real_path); 389 if (rc < 0) { 390 fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); 391 return 1; 392 } 393 394 g.cwd = canonical_path.string(); 395 } 396 397 return 0; 398} 399 400static int sh_pushd(int argc, const char** argv) 401{ 402 StringBuilder path_builder; 403 bool should_switch = true; 404 405 // From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html 406 // With no arguments, pushd exchanges the top two directories and makes the new top the current directory. 407 if (argc == 1) { 408 if (g.directory_stack.size() < 2) { 409 fprintf(stderr, "pushd: no other directory\n"); 410 return 1; 411 } 412 413 String dir1 = g.directory_stack.take_first(); 414 String dir2 = g.directory_stack.take_first(); 415 g.directory_stack.insert(0, dir2); 416 g.directory_stack.insert(1, dir1); 417 418 int rc = chdir(dir2.characters()); 419 if (rc < 0) { 420 fprintf(stderr, "chdir(%s) failed: %s", dir2.characters(), strerror(errno)); 421 return 1; 422 } 423 424 g.cwd = dir2; 425 426 return 0; 427 } 428 429 // Let's assume the user's typed in 'pushd <dir>' 430 if (argc == 2) { 431 g.directory_stack.append(g.cwd.characters()); 432 if (argv[1][0] == '/') { 433 path_builder.append(argv[1]); 434 } else { 435 path_builder.appendf("%s/%s", g.cwd.characters(), argv[1]); 436 } 437 } else if (argc == 3) { 438 g.directory_stack.append(g.cwd.characters()); 439 for (int i = 1; i < argc; i++) { 440 const char* arg = argv[i]; 441 442 if (arg[0] != '-') { 443 if (arg[0] == '/') { 444 path_builder.append(arg); 445 } else 446 path_builder.appendf("%s/%s", g.cwd.characters(), arg); 447 } 448 449 if (!strcmp(arg, "-n")) 450 should_switch = false; 451 } 452 } 453 454 FileSystemPath canonical_path(path_builder.to_string()); 455 if (!canonical_path.is_valid()) { 456 fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path_builder.to_string().characters()); 457 return 1; 458 } 459 460 const char* real_path = canonical_path.string().characters(); 461 462 struct stat st; 463 int rc = stat(real_path, &st); 464 if (rc < 0) { 465 fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); 466 return 1; 467 } 468 469 if (!S_ISDIR(st.st_mode)) { 470 fprintf(stderr, "Not a directory: %s\n", real_path); 471 return 1; 472 } 473 474 if (should_switch) { 475 int rc = chdir(real_path); 476 if (rc < 0) { 477 fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); 478 return 1; 479 } 480 481 g.cwd = canonical_path.string(); 482 } 483 484 return 0; 485} 486 487static int sh_dirs(int argc, const char** argv) 488{ 489 // The first directory in the stack is ALWAYS the current directory 490 g.directory_stack.at(0) = g.cwd.characters(); 491 492 if (argc == 1) { 493 for (String dir : g.directory_stack) 494 printf("%s ", dir.characters()); 495 496 printf("\n"); 497 return 0; 498 } 499 500 bool printed = false; 501 for (int i = 0; i < argc; i++) { 502 const char* arg = argv[i]; 503 if (!strcmp(arg, "-c")) { 504 for (size_t i = 1; i < g.directory_stack.size(); i++) 505 g.directory_stack.remove(i); 506 507 printed = true; 508 continue; 509 } 510 if (!strcmp(arg, "-p") && !printed) { 511 for (auto& directory : g.directory_stack) 512 printf("%s\n", directory.characters()); 513 514 printed = true; 515 continue; 516 } 517 if (!strcmp(arg, "-v") && !printed) { 518 int idx = 0; 519 for (auto& directory : g.directory_stack) { 520 printf("%d %s\n", idx++, directory.characters()); 521 } 522 523 printed = true; 524 continue; 525 } 526 } 527 528 return 0; 529} 530 531static bool handle_builtin(int argc, const char** argv, int& retval) 532{ 533 if (argc == 0) 534 return false; 535 if (!strcmp(argv[0], "cd")) { 536 retval = sh_cd(argc, argv); 537 return true; 538 } 539 if (!strcmp(argv[0], "cdh")) { 540 retval = sh_cdh(argc, argv); 541 return true; 542 } 543 if (!strcmp(argv[0], "pwd")) { 544 retval = sh_pwd(argc, argv); 545 return true; 546 } 547 if (!strcmp(argv[0], "exit")) { 548 retval = sh_exit(argc, argv); 549 return true; 550 } 551 if (!strcmp(argv[0], "export")) { 552 retval = sh_export(argc, argv); 553 return true; 554 } 555 if (!strcmp(argv[0], "unset")) { 556 retval = sh_unset(argc, argv); 557 return true; 558 } 559 if (!strcmp(argv[0], "history")) { 560 retval = sh_history(argc, argv); 561 return true; 562 } 563 if (!strcmp(argv[0], "umask")) { 564 retval = sh_umask(argc, argv); 565 return true; 566 } 567 if (!strcmp(argv[0], "dirs")) { 568 retval = sh_dirs(argc, argv); 569 return true; 570 } 571 if (!strcmp(argv[0], "pushd")) { 572 retval = sh_pushd(argc, argv); 573 return true; 574 } 575 if (!strcmp(argv[0], "popd")) { 576 retval = sh_popd(argc, argv); 577 return true; 578 } 579 if (!strcmp(argv[0], "time")) { 580 retval = sh_time(argc, argv); 581 return true; 582 } 583 return false; 584} 585 586class FileDescriptionCollector { 587public: 588 FileDescriptionCollector() {} 589 ~FileDescriptionCollector() { collect(); } 590 591 void collect() 592 { 593 for (auto fd : m_fds) 594 close(fd); 595 m_fds.clear(); 596 } 597 void add(int fd) { m_fds.append(fd); } 598 599private: 600 Vector<int, 32> m_fds; 601}; 602 603class CommandTimer { 604public: 605 explicit CommandTimer(const String& command) 606 : m_command(command) 607 { 608 m_timer.start(); 609 } 610 ~CommandTimer() 611 { 612 dbg() << "Command \"" << m_command << "\" finished in " << m_timer.elapsed() << " ms"; 613 } 614 615private: 616 Core::ElapsedTimer m_timer; 617 String m_command; 618}; 619 620static bool is_glob(const StringView& s) 621{ 622 for (size_t i = 0; i < s.length(); i++) { 623 char c = s.characters_without_null_termination()[i]; 624 if (c == '*' || c == '?') 625 return true; 626 } 627 return false; 628} 629 630static Vector<StringView> split_path(const StringView& path) 631{ 632 Vector<StringView> parts; 633 634 size_t substart = 0; 635 for (size_t i = 0; i < path.length(); i++) { 636 char ch = path.characters_without_null_termination()[i]; 637 if (ch != '/') 638 continue; 639 size_t sublen = i - substart; 640 if (sublen != 0) 641 parts.append(path.substring_view(substart, sublen)); 642 parts.append(path.substring_view(i, 1)); 643 substart = i + 1; 644 } 645 646 size_t taillen = path.length() - substart; 647 if (taillen != 0) 648 parts.append(path.substring_view(substart, taillen)); 649 650 return parts; 651} 652 653static Vector<String> expand_globs(const StringView& path, const StringView& base) 654{ 655 auto parts = split_path(path); 656 657 StringBuilder builder; 658 builder.append(base); 659 Vector<String> res; 660 661 for (size_t i = 0; i < parts.size(); ++i) { 662 auto& part = parts[i]; 663 if (!is_glob(part)) { 664 builder.append(part); 665 continue; 666 } 667 668 // Found a glob. 669 String new_base = builder.to_string(); 670 StringView new_base_v = new_base; 671 if (new_base_v.is_empty()) 672 new_base_v = "."; 673 Core::DirIterator di(new_base_v, Core::DirIterator::SkipParentAndBaseDir); 674 675 if (di.has_error()) { 676 return res; 677 } 678 679 while (di.has_next()) { 680 String name = di.next_path(); 681 682 // Dotfiles have to be explicitly requested 683 if (name[0] == '.' && part[0] != '.') 684 continue; 685 686 if (name.matches(part, CaseSensitivity::CaseSensitive)) { 687 688 StringBuilder nested_base; 689 nested_base.append(new_base); 690 nested_base.append(name); 691 692 StringView remaining_path = path.substring_view_starting_after_substring(part); 693 Vector<String> nested_res = expand_globs(remaining_path, nested_base.to_string()); 694 for (auto& s : nested_res) 695 res.append(s); 696 } 697 } 698 return res; 699 } 700 701 // Found no globs. 702 String new_path = builder.to_string(); 703 if (access(new_path.characters(), F_OK) == 0) 704 res.append(new_path); 705 return res; 706} 707 708static Vector<String> expand_parameters(const StringView& param) 709{ 710 if (!param.starts_with('$')) 711 return { param }; 712 713 String variable_name = String(param.substring_view(1, param.length() - 1)); 714 if (variable_name == "?") 715 return { String::number(g.last_return_code) }; 716 else if (variable_name == "$") 717 return { String::number(getpid()) }; 718 719 char* env_value = getenv(variable_name.characters()); 720 if (env_value == nullptr) 721 return { "" }; 722 723 Vector<String> res; 724 String str_env_value = String(env_value); 725 const auto& split_text = str_env_value.split_view(' '); 726 for (auto& part : split_text) 727 res.append(part); 728 return res; 729} 730 731static Vector<String> process_arguments(const Vector<String>& args) 732{ 733 Vector<String> argv_string; 734 for (auto& arg : args) { 735 // This will return the text passed in if it wasn't a variable 736 // This lets us just loop over its values 737 auto expanded_parameters = expand_parameters(arg); 738 739 for (auto& exp_arg : expanded_parameters) { 740 if (exp_arg.starts_with('~')) 741 exp_arg = expand_tilde(exp_arg); 742 743 auto expanded_globs = expand_globs(exp_arg, ""); 744 for (auto& path : expanded_globs) 745 argv_string.append(path); 746 747 if (expanded_globs.is_empty()) 748 argv_string.append(exp_arg); 749 } 750 } 751 752 return argv_string; 753} 754 755static int run_command(const String& cmd) 756{ 757 if (cmd.is_empty()) 758 return 0; 759 760 if (cmd.starts_with("#")) 761 return 0; 762 763 auto commands = Parser(cmd).parse(); 764 765#ifdef SH_DEBUG 766 for (auto& command : commands) { 767 for (size_t i = 0; i < command.subcommands.size(); ++i) { 768 for (size_t j = 0; j < i; ++j) 769 dbgprintf(" "); 770 for (auto& arg : command.subcommands[i].args) { 771 dbgprintf("<%s> ", arg.characters()); 772 } 773 dbgprintf("\n"); 774 for (auto& redirecton : command.subcommands[i].redirections) { 775 for (size_t j = 0; j < i; ++j) 776 dbgprintf(" "); 777 dbgprintf(" "); 778 switch (redirecton.type) { 779 case Redirection::Pipe: 780 dbgprintf("Pipe\n"); 781 break; 782 case Redirection::FileRead: 783 dbgprintf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.characters()); 784 break; 785 case Redirection::FileWrite: 786 dbgprintf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.characters()); 787 break; 788 case Redirection::FileWriteAppend: 789 dbgprintf("fd:%d = FileWriteAppend: %s\n", redirecton.fd, redirecton.path.characters()); 790 break; 791 default: 792 break; 793 } 794 } 795 } 796 dbgprintf("\n"); 797 } 798#endif 799 800 struct termios trm; 801 tcgetattr(0, &trm); 802 803 struct SpawnedProcess { 804 String name; 805 pid_t pid; 806 }; 807 808 int return_value = 0; 809 810 for (auto& command : commands) { 811 if (command.subcommands.is_empty()) 812 continue; 813 814 FileDescriptionCollector fds; 815 816 for (size_t i = 0; i < command.subcommands.size(); ++i) { 817 auto& subcommand = command.subcommands[i]; 818 for (auto& redirection : subcommand.redirections) { 819 switch (redirection.type) { 820 case Redirection::Pipe: { 821 int pipefd[2]; 822 int rc = pipe(pipefd); 823 if (rc < 0) { 824 perror("pipe"); 825 return 1; 826 } 827 subcommand.rewirings.append({ STDOUT_FILENO, pipefd[1] }); 828 auto& next_command = command.subcommands[i + 1]; 829 next_command.rewirings.append({ STDIN_FILENO, pipefd[0] }); 830 fds.add(pipefd[0]); 831 fds.add(pipefd[1]); 832 break; 833 } 834 case Redirection::FileWriteAppend: { 835 int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666); 836 if (fd < 0) { 837 perror("open"); 838 return 1; 839 } 840 subcommand.rewirings.append({ redirection.fd, fd }); 841 fds.add(fd); 842 break; 843 } 844 case Redirection::FileWrite: { 845 int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666); 846 if (fd < 0) { 847 perror("open"); 848 return 1; 849 } 850 subcommand.rewirings.append({ redirection.fd, fd }); 851 fds.add(fd); 852 break; 853 } 854 case Redirection::FileRead: { 855 int fd = open(redirection.path.characters(), O_RDONLY); 856 if (fd < 0) { 857 perror("open"); 858 return 1; 859 } 860 subcommand.rewirings.append({ redirection.fd, fd }); 861 fds.add(fd); 862 break; 863 } 864 } 865 } 866 } 867 868 Vector<SpawnedProcess> children; 869 870 CommandTimer timer(cmd); 871 872 for (size_t i = 0; i < command.subcommands.size(); ++i) { 873 auto& subcommand = command.subcommands[i]; 874 Vector<String> argv_string = process_arguments(subcommand.args); 875 Vector<const char*> argv; 876 argv.ensure_capacity(argv_string.size()); 877 for (const auto& s : argv_string) { 878 argv.append(s.characters()); 879 } 880 argv.append(nullptr); 881 882#ifdef SH_DEBUG 883 for (auto& arg : argv) { 884 dbgprintf("<%s> ", arg); 885 } 886 dbgprintf("\n"); 887#endif 888 889 int retval = 0; 890 if (handle_builtin(argv.size() - 1, argv.data(), retval)) 891 return retval; 892 893 pid_t child = fork(); 894 if (!child) { 895 setpgid(0, 0); 896 tcsetpgrp(0, getpid()); 897 tcsetattr(0, TCSANOW, &g.default_termios); 898 for (auto& rewiring : subcommand.rewirings) { 899#ifdef SH_DEBUG 900 dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), rewiring.rewire_fd, rewiring.fd); 901#endif 902 int rc = dup2(rewiring.rewire_fd, rewiring.fd); 903 if (rc < 0) { 904 perror("dup2"); 905 return 1; 906 } 907 } 908 909 fds.collect(); 910 911 int rc = execvp(argv[0], const_cast<char* const*>(argv.data())); 912 if (rc < 0) { 913 if (errno == ENOENT) 914 fprintf(stderr, "%s: Command not found.\n", argv[0]); 915 else 916 fprintf(stderr, "execvp(%s): %s\n", argv[0], strerror(errno)); 917 _exit(1); 918 } 919 ASSERT_NOT_REACHED(); 920 } 921 children.append({ argv[0], child }); 922 } 923 924#ifdef SH_DEBUG 925 dbgprintf("Closing fds in shell process:\n"); 926#endif 927 fds.collect(); 928 929#ifdef SH_DEBUG 930 dbgprintf("Now we gotta wait on children:\n"); 931 for (auto& child : children) 932 dbgprintf(" %d (%s)\n", child.pid, child.name.characters()); 933#endif 934 935 int wstatus = 0; 936 937 for (size_t i = 0; i < children.size(); ++i) { 938 auto& child = children[i]; 939 do { 940 int rc = waitpid(child.pid, &wstatus, 0); 941 if (rc < 0 && errno != EINTR) { 942 if (errno != ECHILD) 943 perror("waitpid"); 944 break; 945 } 946 if (WIFEXITED(wstatus)) { 947 if (WEXITSTATUS(wstatus) != 0) 948 dbg() << "Shell: " << child.name << ":" << child.pid << " exited with status " << WEXITSTATUS(wstatus); 949 if (i == 0) 950 return_value = WEXITSTATUS(wstatus); 951 } else if (WIFSTOPPED(wstatus)) { 952 fprintf(stderr, "Shell: %s(%d) %s\n", child.name.characters(), child.pid, strsignal(WSTOPSIG(wstatus))); 953 } else { 954 if (WIFSIGNALED(wstatus)) { 955 printf("Shell: %s(%d) exited due to signal '%s'\n", child.name.characters(), child.pid, strsignal(WTERMSIG(wstatus))); 956 } else { 957 printf("Shell: %s(%d) exited abnormally\n", child.name.characters(), child.pid); 958 } 959 } 960 } while (errno == EINTR); 961 } 962 } 963 964 g.last_return_code = return_value; 965 966 // FIXME: Should I really have to tcsetpgrp() after my child has exited? 967 // Is the terminal controlling pgrp really still the PGID of the dead process? 968 tcsetpgrp(0, getpid()); 969 tcsetattr(0, TCSANOW, &trm); 970 return return_value; 971} 972 973static String get_history_path() 974{ 975 StringBuilder builder; 976 builder.append(g.home); 977 builder.append("/.history"); 978 return builder.to_string(); 979} 980 981void load_history() 982{ 983 auto history_file = Core::File::construct(get_history_path()); 984 if (!history_file->open(Core::IODevice::ReadOnly)) 985 return; 986 while (history_file->can_read_line()) { 987 auto b = history_file->read_line(1024); 988 // skip the newline and terminating bytes 989 editor.add_to_history(String(reinterpret_cast<const char*>(b.data()), b.size() - 2)); 990 } 991} 992 993void save_history() 994{ 995 auto history_file = Core::File::open(get_history_path(), Core::IODevice::WriteOnly, 0600); 996 if (!history_file) 997 return; 998 for (const auto& line : editor.history()) { 999 history_file->write(line); 1000 history_file->write("\n"); 1001 } 1002} 1003 1004Vector<String, 256> cached_path; 1005void cache_path() 1006{ 1007 if (!cached_path.is_empty()) 1008 cached_path.clear_with_capacity(); 1009 1010 String path = getenv("PATH"); 1011 if (path.is_empty()) 1012 return; 1013 1014 auto directories = path.split(':'); 1015 for (const auto& directory : directories) { 1016 Core::DirIterator programs(directory.characters(), Core::DirIterator::SkipDots); 1017 while (programs.has_next()) { 1018 auto program = programs.next_path(); 1019 String program_path = String::format("%s/%s", directory.characters(), program.characters()); 1020 struct stat program_status; 1021 int stat_error = stat(program_path.characters(), &program_status); 1022 if (!stat_error && (program_status.st_mode & S_IXUSR)) 1023 cached_path.append(program.characters()); 1024 } 1025 } 1026 1027 quick_sort(cached_path); 1028} 1029 1030int main(int argc, char** argv) 1031{ 1032#ifdef __serenity__ 1033 if (pledge("stdio rpath wpath cpath proc exec tty", nullptr) < 0) { 1034 perror("pledge"); 1035 return 1; 1036 } 1037#endif 1038 1039 g.uid = getuid(); 1040 tcsetpgrp(0, getpgrp()); 1041 1042 editor.initialize(); 1043 g.termios = editor.termios(); 1044 g.default_termios = editor.default_termios(); 1045 1046 editor.on_tab_complete_first_token = [&](const String& token) -> Vector<String> { 1047 auto match = binary_search(cached_path.data(), cached_path.size(), token, [](const String& token, const String& program) -> int { 1048 return strncmp(token.characters(), program.characters(), token.length()); 1049 }); 1050 if (!match) 1051 return Vector<String>(); 1052 1053 String completion = *match; 1054 Vector<String> suggestions; 1055 1056 // Now that we have a program name starting with our token, we look at 1057 // other program names starting with our token and cut off any mismatching 1058 // characters. 1059 1060 bool seen_others = false; 1061 int index = match - cached_path.data(); 1062 for (int i = index - 1; i >= 0 && cached_path[i].starts_with(token); --i) { 1063 suggestions.append(cached_path[i]); 1064 editor.cut_mismatching_chars(completion, cached_path[i], token.length()); 1065 seen_others = true; 1066 } 1067 for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(token); ++i) { 1068 editor.cut_mismatching_chars(completion, cached_path[i], token.length()); 1069 suggestions.append(cached_path[i]); 1070 seen_others = true; 1071 } 1072 suggestions.append(cached_path[index]); 1073 1074 // If we have characters to add, add them. 1075 if (completion.length() > token.length()) 1076 editor.insert(completion.substring(token.length(), completion.length() - token.length())); 1077 // If we have a single match, we add a space, unless we already have one. 1078 if (!seen_others && (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ')) 1079 editor.insert(' '); 1080 1081 return suggestions; 1082 }; 1083 editor.on_tab_complete_other_token = [&](const String& vtoken) -> Vector<String> { 1084 auto token = vtoken; // copy it :( 1085 String path; 1086 Vector<String> suggestions; 1087 1088 ssize_t last_slash = token.length() - 1; 1089 while (last_slash >= 0 && token[last_slash] != '/') 1090 --last_slash; 1091 1092 if (last_slash >= 0) { 1093 // Split on the last slash. We'll use the first part as the directory 1094 // to search and the second part as the token to complete. 1095 path = token.substring(0, last_slash + 1); 1096 if (path[0] != '/') 1097 path = String::format("%s/%s", g.cwd.characters(), path.characters()); 1098 path = canonicalized_path(path); 1099 token = token.substring(last_slash + 1, token.length() - last_slash - 1); 1100 } else { 1101 // We have no slashes, so the directory to search is the current 1102 // directory and the token to complete is just the original token. 1103 path = g.cwd; 1104 } 1105 1106 // This is a bit naughty, but necessary without reordering the loop 1107 // below. The loop terminates early, meaning that 1108 // the suggestions list is incomplete. 1109 // We only do this if the token is empty though. 1110 if (token.is_empty()) { 1111 Core::DirIterator suggested_files(path, Core::DirIterator::SkipDots); 1112 while (suggested_files.has_next()) { 1113 suggestions.append(suggested_files.next_path()); 1114 } 1115 } 1116 1117 String completion; 1118 1119 bool seen_others = false; 1120 Core::DirIterator files(path, Core::DirIterator::SkipDots); 1121 while (files.has_next()) { 1122 auto file = files.next_path(); 1123 if (file.starts_with(token)) { 1124 if (!token.is_empty()) 1125 suggestions.append(file); 1126 if (completion.is_empty()) { 1127 completion = file; // Will only be set once. 1128 } else { 1129 editor.cut_mismatching_chars(completion, file, token.length()); 1130 if (completion.is_empty()) // We cut everything off! 1131 return suggestions; 1132 seen_others = true; 1133 } 1134 } 1135 } 1136 if (completion.is_empty()) 1137 return suggestions; 1138 1139 // If we have characters to add, add them. 1140 if (completion.length() > token.length()) 1141 editor.insert(completion.substring(token.length(), completion.length() - token.length())); 1142 // If we have a single match and it's a directory, we add a slash. If it's 1143 // a regular file, we add a space, unless we already have one. 1144 if (!seen_others) { 1145 String file_path = String::format("%s/%s", path.characters(), completion.characters()); 1146 struct stat program_status; 1147 int stat_error = stat(file_path.characters(), &program_status); 1148 if (!stat_error) { 1149 if (S_ISDIR(program_status.st_mode)) 1150 editor.insert('/'); 1151 else if (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ') 1152 editor.insert(' '); 1153 } 1154 } 1155 1156 return {}; // Return an empty vector 1157 }; 1158 1159 signal(SIGINT, [](int) { 1160 g.was_interrupted = true; 1161 editor.interrupted(); 1162 }); 1163 1164 signal(SIGWINCH, [](int) { 1165 g.was_resized = true; 1166 editor.resized(); 1167 }); 1168 1169 signal(SIGHUP, [](int) { 1170 save_history(); 1171 }); 1172 1173 int rc = gethostname(g.hostname, sizeof(g.hostname)); 1174 if (rc < 0) 1175 perror("gethostname"); 1176 rc = ttyname_r(0, g.ttyname, sizeof(g.ttyname)); 1177 if (rc < 0) 1178 perror("ttyname_r"); 1179 1180 { 1181 auto* cwd = getcwd(nullptr, 0); 1182 if (cwd == nullptr) 1183 cwd = strdup("/"); 1184 g.cwd = cwd; 1185 setenv("PWD", cwd, 1); 1186 free(cwd); 1187 } 1188 1189 { 1190 auto* pw = getpwuid(getuid()); 1191 if (pw) { 1192 g.username = pw->pw_name; 1193 g.home = pw->pw_dir; 1194 setenv("HOME", pw->pw_dir, 1); 1195 } 1196 endpwent(); 1197 } 1198 1199 if (argc > 2 && !strcmp(argv[1], "-c")) { 1200 dbgprintf("sh -c '%s'\n", argv[2]); 1201 run_command(argv[2]); 1202 return 0; 1203 } 1204 1205 if (argc == 2 && argv[1][0] != '-') { 1206 auto file = Core::File::construct(argv[1]); 1207 if (!file->open(Core::IODevice::ReadOnly)) { 1208 fprintf(stderr, "Failed to open %s: %s\n", file->filename().characters(), file->error_string()); 1209 return 1; 1210 } 1211 for (;;) { 1212 auto line = file->read_line(4096); 1213 if (line.is_null()) 1214 break; 1215 run_command(String::copy(line, Chomp)); 1216 } 1217 return 0; 1218 } 1219 1220 g.directory_stack.append(g.cwd); 1221 1222 load_history(); 1223 atexit(save_history); 1224 1225 cache_path(); 1226 1227 for (;;) { 1228 auto line = editor.get_line(prompt()); 1229 if (line.is_empty()) 1230 continue; 1231 run_command(line); 1232 editor.add_to_history(line); 1233 } 1234 1235 return 0; 1236}