summaryrefslogtreecommitdiff
path: root/Userland/sh.cpp
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-05-07 01:12:08 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-05-07 01:12:08 +0200
commitfe73543d41da7417b102a32d74bdceea9c85f9cb (patch)
treedb7910c6032b83ff9c50a565ae66eb21087d2cc1 /Userland/sh.cpp
parente63cc38861e7fcccfedb77c54dd761bf26ce7081 (diff)
downloadserenity-fe73543d41da7417b102a32d74bdceea9c85f9cb.zip
Shell: Move the Shell to a separate directory and let's call it "Shell" :^)
Diffstat (limited to 'Userland/sh.cpp')
-rw-r--r--Userland/sh.cpp649
1 files changed, 0 insertions, 649 deletions
diff --git a/Userland/sh.cpp b/Userland/sh.cpp
deleted file mode 100644
index d5d63f2277..0000000000
--- a/Userland/sh.cpp
+++ /dev/null
@@ -1,649 +0,0 @@
-#include <errno.h>
-#include <pwd.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <termios.h>
-#include <ctype.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/utsname.h>
-#include <AK/FileSystemPath.h>
-#include <LibCore/CElapsedTimer.h>
-
-//#define SH_DEBUG
-
-struct GlobalState {
- String cwd;
- String username;
- String home;
- char ttyname[32];
- char hostname[32];
- pid_t sid;
- uid_t uid;
- struct termios termios;
- bool was_interrupted { false };
-};
-static GlobalState* g;
-
-static void prompt()
-{
- if (g->uid == 0)
- printf("# ");
- else {
- printf("\033]0;%s@%s:%s\007", g->username.characters(), g->hostname, g->cwd.characters());
- printf("\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());
- }
- fflush(stdout);
-}
-
-static int sh_pwd(int, char**)
-{
- printf("%s\n", g->cwd.characters());
- return 0;
-}
-
-static volatile bool g_got_signal = false;
-
-void did_receive_signal(int signum)
-{
- printf("\nMy word, I've received a signal with number %d\n", signum);
- g_got_signal = true;
-}
-
-void handle_sigint(int)
-{
- g->was_interrupted = true;
-}
-
-static int sh_exit(int, char**)
-{
- printf("Good-bye!\n");
- exit(0);
- return 0;
-}
-
-static int sh_export(int argc, char** argv)
-{
- if (argc == 1) {
- for (int i = 0; environ[i]; ++i)
- puts(environ[i]);
- return 0;
- }
- auto parts = String(argv[1]).split('=');
- if (parts.size() != 2) {
- fprintf(stderr, "usage: export variable=value\n");
- return 1;
- }
- putenv(const_cast<char*>(String::format("%s=%s", parts[0].characters(), parts[1].characters()).characters()));
- return 0;
-}
-
-static int sh_cd(int argc, char** argv)
-{
- char pathbuf[PATH_MAX];
-
- if (argc == 1) {
- strcpy(pathbuf, g->home.characters());
- } else {
- if (argv[1][0] == '/')
- memcpy(pathbuf, argv[1], strlen(argv[1]) + 1);
- else
- sprintf(pathbuf, "%s/%s", g->cwd.characters(), argv[1]);
- }
-
- FileSystemPath canonical_path(pathbuf);
- if (!canonical_path.is_valid()) {
- printf("FileSystemPath failed to canonicalize '%s'\n", pathbuf);
- return 1;
- }
- const char* path = canonical_path.string().characters();
-
- struct stat st;
- int rc = stat(path, &st);
- if (rc < 0) {
- printf("lstat(%s) failed: %s\n", path, strerror(errno));
- return 1;
- }
- if (!S_ISDIR(st.st_mode)) {
- printf("Not a directory: %s\n", path);
- return 1;
- }
- rc = chdir(path);
- if (rc < 0) {
- printf("chdir(%s) failed: %s\n", path, strerror(errno));
- return 1;
- }
- g->cwd = canonical_path.string();
- return 0;
-}
-
-static bool handle_builtin(int argc, char** argv, int& retval)
-{
- if (argc == 0)
- return false;
- if (!strcmp(argv[0], "cd")) {
- retval = sh_cd(argc, argv);
- return true;
- }
- if (!strcmp(argv[0], "pwd")) {
- retval = sh_pwd(argc, argv);
- return true;
- }
- if (!strcmp(argv[0], "exit")) {
- retval = sh_exit(argc, argv);
- return true;
- }
- if (!strcmp(argv[0], "export")) {
- retval = sh_export(argc, argv);
- return true;
- }
- return false;
-}
-
-struct Redirection {
- enum Type { Pipe, FileWrite, FileRead, Rewire };
- Type type;
- int fd { -1 };
- int rewire_fd { -1 };
- String path { };
-};
-
-struct Subcommand {
- Vector<String> args;
- Vector<Redirection> redirections;
-};
-
-class Parser {
-public:
- explicit Parser(const String& input) : m_input(input) { }
-
- Vector<Subcommand> parse();
-
-private:
- void commit_token();
- void commit_subcommand();
- void do_pipe();
- void begin_redirect_read(int fd);
- void begin_redirect_write(int fd);
-
- enum State {
- Free,
- InSingleQuotes,
- InDoubleQuotes,
- InRedirectionPath,
- };
- State m_state { Free };
- String m_input;
-
- Vector<Subcommand> m_subcommands;
- Vector<String> m_tokens;
- Vector<Redirection> m_redirections;
- Vector<char> m_token;
-};
-
-void Parser::commit_token()
-{
- if (m_token.is_empty())
- return;
- if (m_state == InRedirectionPath) {
- m_redirections.last().path = String::copy(m_token);
- m_token.clear_with_capacity();
- return;
- }
- m_tokens.append(String::copy(m_token));
- m_token.clear_with_capacity();
-};
-
-void Parser::commit_subcommand()
-{
- if (m_tokens.is_empty())
- return;
- m_subcommands.append({ move(m_tokens), move(m_redirections) });
-}
-
-void Parser::do_pipe()
-{
- m_redirections.append({ Redirection::Pipe, STDOUT_FILENO });
- commit_subcommand();
-}
-
-void Parser::begin_redirect_read(int fd)
-{
- m_redirections.append({ Redirection::FileRead, fd });
-}
-
-void Parser::begin_redirect_write(int fd)
-{
- m_redirections.append({ Redirection::FileWrite, fd });
-}
-
-Vector<Subcommand> Parser::parse()
-{
- for (int i = 0; i < m_input.length(); ++i) {
- char ch = m_input.characters()[i];
- switch (m_state) {
- case State::Free:
- if (ch == ' ') {
- commit_token();
- break;
- }
- if (ch == '|') {
- commit_token();
- if (m_tokens.is_empty()) {
- fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
- return { };
- }
- do_pipe();
- break;
- }
- if (ch == '>') {
- commit_token();
- begin_redirect_write(STDOUT_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '<') {
- commit_token();
- begin_redirect_read(STDIN_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '\'') {
- m_state = State::InSingleQuotes;
- break;
- }
- if (ch == '\"') {
- m_state = State::InDoubleQuotes;
- break;
- }
- m_token.append(ch);
- break;
- case State::InRedirectionPath:
- if (ch == '<') {
- commit_token();
- begin_redirect_read(STDIN_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '>') {
- commit_token();
- begin_redirect_read(STDOUT_FILENO);
- m_state = State::InRedirectionPath;
- break;
- }
- if (ch == '|') {
- commit_token();
- if (m_tokens.is_empty()) {
- fprintf(stderr, "Syntax error: Nothing before pipe (|)\n");
- return { };
- }
- do_pipe();
- m_state = State::Free;
- break;
- }
- if (ch == ' ')
- break;
- m_token.append(ch);
- break;
- case State::InSingleQuotes:
- if (ch == '\'') {
- commit_token();
- m_state = State::Free;
- break;
- }
- m_token.append(ch);
- break;
- case State::InDoubleQuotes:
- if (ch == '\"') {
- commit_token();
- m_state = State::Free;
- break;
- }
- m_token.append(ch);
- break;
- };
- }
- commit_token();
- commit_subcommand();
-
- if (!m_subcommands.is_empty()) {
- for (auto& redirection : m_subcommands.last().redirections) {
- if (redirection.type == Redirection::Pipe) {
- fprintf(stderr, "Syntax error: Nothing after last pipe (|)\n");
- return { };
- }
- }
- }
-
- return move(m_subcommands);
-}
-
-class FileDescriptorCollector {
-public:
- FileDescriptorCollector() { }
- ~FileDescriptorCollector() { collect(); }
-
- void collect()
- {
- for (auto fd : m_fds)
- close(fd);
- m_fds.clear();
- }
- void add(int fd) { m_fds.append(fd); }
-
-private:
- Vector<int, 32> m_fds;
-};
-
-struct CommandTimer {
- CommandTimer()
- {
- timer.start();
- }
- ~CommandTimer()
- {
- dbgprintf("sh: command finished in %d ms\n", timer.elapsed());
- }
-
- CElapsedTimer timer;
-};
-
-static int run_command(const String& cmd)
-{
- if (cmd.is_empty())
- return 0;
-
- auto subcommands = Parser(cmd).parse();
-
-#ifdef SH_DEBUG
- for (int i = 0; i < subcommands.size(); ++i) {
- for (int j = 0; j < i; ++j)
- printf(" ");
- for (auto& arg : subcommands[i].args) {
- printf("<%s> ", arg.characters());
- }
- printf("\n");
- for (auto& redirecton : subcommands[i].redirections) {
- for (int j = 0; j < i; ++j)
- printf(" ");
- printf(" ");
- switch (redirecton.type) {
- case Redirection::Pipe:
- printf("Pipe\n");
- break;
- case Redirection::FileRead:
- printf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.characters());
- break;
- case Redirection::FileWrite:
- printf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.characters());
- break;
- default:
- break;
- }
- }
- }
-#endif
-
- if (subcommands.is_empty())
- return 0;
-
- FileDescriptorCollector fds;
-
- for (int i = 0; i < subcommands.size(); ++i) {
- auto& subcommand = subcommands[i];
- for (auto& redirection : subcommand.redirections) {
- if (redirection.type == Redirection::Pipe) {
- int pipefd[2];
- int rc = pipe(pipefd);
- if (rc < 0) {
- perror("pipe");
- return 1;
- }
- subcommand.redirections.append({ Redirection::Rewire, STDOUT_FILENO, pipefd[1] });
- auto& next_command = subcommands[i + 1];
- next_command.redirections.append({ Redirection::Rewire, STDIN_FILENO, pipefd[0] });
- fds.add(pipefd[0]);
- fds.add(pipefd[1]);
- }
- if (redirection.type == Redirection::FileWrite) {
- int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT, 0666);
- if (fd < 0) {
- perror("open");
- return 1;
- }
- subcommand.redirections.append({ Redirection::Rewire, redirection.fd, fd });
- fds.add(fd);
- }
- if (redirection.type == Redirection::FileRead) {
- int fd = open(redirection.path.characters(), O_RDONLY);
- if (fd < 0) {
- perror("open");
- return 1;
- }
- subcommand.redirections.append({ Redirection::Rewire, redirection.fd, fd });
- fds.add(fd);
- }
- }
- }
-
- struct termios trm;
- tcgetattr(0, &trm);
-
- Vector<pid_t> children;
-
- CommandTimer timer;
-
- for (int i = 0; i < subcommands.size(); ++i) {
- auto& subcommand = subcommands[i];
- Vector<const char*> argv;
- for (auto& arg : subcommand.args)
- argv.append(arg.characters());
- argv.append(nullptr);
-
- int retval = 0;
- if (handle_builtin(argv.size() - 1, const_cast<char**>(argv.data()), retval))
- return retval;
-
- pid_t child = fork();
- if (!child) {
- setpgid(0, 0);
- tcsetpgrp(0, getpid());
- for (auto& redirection : subcommand.redirections) {
- if (redirection.type == Redirection::Rewire) {
-#ifdef SH_DEBUG
- dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), redirection.rewire_fd, redirection.fd);
-#endif
- int rc = dup2(redirection.rewire_fd, redirection.fd);
- if (rc < 0) {
- perror("dup2");
- return 1;
- }
- }
- }
-
- fds.collect();
-
- int rc = execvp(argv[0], const_cast<char* const*>(argv.data()));
- if (rc < 0) {
- perror("execvp");
- exit(1);
- }
- ASSERT_NOT_REACHED();
- }
- children.append(child);
- }
-
-#ifdef SH_DEBUG
- dbgprintf("Closing fds in shell process:\n");
-#endif
- fds.collect();
-
-#ifdef SH_DEBUG
- dbgprintf("Now we gotta wait on children:\n");
- for (auto& child : children)
- dbgprintf(" %d\n", child);
-#endif
-
-
- int wstatus = 0;
- int rc;
-
- for (auto& child : children) {
- do {
- rc = waitpid(child, &wstatus, 0);
- if (rc < 0 && errno != EINTR) {
- perror("waitpid");
- break;
- }
- } while(errno == EINTR);
- }
-
- // FIXME: Should I really have to tcsetpgrp() after my child has exited?
- // Is the terminal controlling pgrp really still the PGID of the dead process?
- tcsetpgrp(0, getpid());
-
- tcsetattr(0, TCSANOW, &trm);
-
- if (WIFEXITED(wstatus)) {
- if (WEXITSTATUS(wstatus) != 0)
- printf("Exited with status %d\n", WEXITSTATUS(wstatus));
- return WEXITSTATUS(wstatus);
- } else {
- if (WIFSIGNALED(wstatus)) {
- switch (WTERMSIG(wstatus)) {
- case SIGINT:
- printf("Interrupted\n");
- break;
- default:
- printf("Terminated by signal %d\n", WTERMSIG(wstatus));
- break;
- }
- } else {
- printf("Exited abnormally\n");
- return 1;
- }
- }
- return 0;
-}
-
-int main(int argc, char** argv)
-{
- g = new GlobalState;
- g->uid = getuid();
- g->sid = setsid();
- tcsetpgrp(0, getpgrp());
- tcgetattr(0, &g->termios);
-
- {
- struct sigaction sa;
- sa.sa_handler = handle_sigint;
- sa.sa_flags = 0;
- sa.sa_mask = 0;
- int rc = sigaction(SIGINT, &sa, nullptr);
- assert(rc == 0);
- }
-
- int rc = gethostname(g->hostname, sizeof(g->hostname));
- if (rc < 0)
- perror("gethostname");
- rc = ttyname_r(0, g->ttyname, sizeof(g->ttyname));
- if (rc < 0)
- perror("ttyname_r");
-
- {
- auto* pw = getpwuid(getuid());
- if (pw) {
- g->username = pw->pw_name;
- g->home = pw->pw_dir;
- putenv(const_cast<char*>(String::format("HOME=%s", pw->pw_dir).characters()));
- }
- endpwent();
- }
-
- if (argc > 1 && !strcmp(argv[1], "-c")) {
- fprintf(stderr, "FIXME: Implement /bin/sh -c\n");
- return 1;
- }
-
- Vector<char, 256> line_buffer;
-
- {
- auto* cwd = getcwd(nullptr, 0);
- g->cwd = cwd;
- free(cwd);
- }
- prompt();
- for (;;) {
- char keybuf[16];
- ssize_t nread = read(0, keybuf, sizeof(keybuf));
- if (nread == 0)
- return 0;
- if (nread < 0) {
- if (errno == EINTR) {
- if (g->was_interrupted) {
- if (!line_buffer.is_empty())
- printf("^C");
- }
- g->was_interrupted = false;
- line_buffer.clear();
- putchar('\n');
- prompt();
- continue;
- } else {
- perror("read failed");
- return 2;
- }
- }
- for (ssize_t i = 0; i < nread; ++i) {
- char ch = keybuf[i];
- if (ch == 0)
- continue;
- if (ch == 8 || ch == g->termios.c_cc[VERASE]) {
- if (line_buffer.is_empty())
- continue;
- line_buffer.take_last();
- putchar(8);
- fflush(stdout);
- continue;
- }
- if (ch == g->termios.c_cc[VWERASE]) {
- bool has_seen_nonspace = false;
- while (!line_buffer.is_empty()) {
- if (isspace(line_buffer.last())) {
- if (has_seen_nonspace)
- break;
- } else {
- has_seen_nonspace = true;
- }
- putchar(0x8);
- line_buffer.take_last();
- }
- fflush(stdout);
- continue;
- }
- if (ch == g->termios.c_cc[VKILL]) {
- if (line_buffer.is_empty())
- continue;
- for (int i = 0; i < line_buffer.size(); ++i)
- putchar(0x8);
- line_buffer.clear();
- fflush(stdout);
- continue;
- }
- putchar(ch);
- fflush(stdout);
- if (ch != '\n') {
- line_buffer.append(ch);
- } else {
- run_command(String::copy(line_buffer));
- line_buffer.clear();
- prompt();
- }
- }
- }
- return 0;
-}