Serenity Operating System
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}