diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-05-07 01:12:08 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-05-07 01:12:08 +0200 |
commit | fe73543d41da7417b102a32d74bdceea9c85f9cb (patch) | |
tree | db7910c6032b83ff9c50a565ae66eb21087d2cc1 /Shell | |
parent | e63cc38861e7fcccfedb77c54dd761bf26ce7081 (diff) | |
download | serenity-fe73543d41da7417b102a32d74bdceea9c85f9cb.zip |
Shell: Move the Shell to a separate directory and let's call it "Shell" :^)
Diffstat (limited to 'Shell')
-rw-r--r-- | Shell/.gitignore | 3 | ||||
-rw-r--r-- | Shell/Makefile | 23 | ||||
-rw-r--r-- | Shell/Parser.cpp | 140 | ||||
-rw-r--r-- | Shell/Parser.h | 46 | ||||
-rw-r--r-- | Shell/main.cpp | 472 |
5 files changed, 684 insertions, 0 deletions
diff --git a/Shell/.gitignore b/Shell/.gitignore new file mode 100644 index 0000000000..06bedaa3db --- /dev/null +++ b/Shell/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +Shell diff --git a/Shell/Makefile b/Shell/Makefile new file mode 100644 index 0000000000..535b2b75f5 --- /dev/null +++ b/Shell/Makefile @@ -0,0 +1,23 @@ +include ../Makefile.common + +OBJS = \ + Parser.o \ + main.o + +APP = Shell + +DEFINES += -DUSERLAND + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) -lcore -lc + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +-include $(OBJS:%.o=%.d) + +clean: + @echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d + diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp new file mode 100644 index 0000000000..5adc36cd7b --- /dev/null +++ b/Shell/Parser.cpp @@ -0,0 +1,140 @@ +#include "Parser.h" +#include <stdio.h> +#include <unistd.h> + +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); +} diff --git a/Shell/Parser.h b/Shell/Parser.h new file mode 100644 index 0000000000..3112cc6cb9 --- /dev/null +++ b/Shell/Parser.h @@ -0,0 +1,46 @@ +#pragma once + +#include <AK/AKString.h> +#include <AK/Vector.h> + +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; +}; + diff --git a/Shell/main.cpp b/Shell/main.cpp new file mode 100644 index 0000000000..4d0e9611b1 --- /dev/null +++ b/Shell/main.cpp @@ -0,0 +1,472 @@ +#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> +#include "Parser.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; +} + +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; +} |