From 6b8834675e7372a8a6a6920bff7fc6d447d7913b Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 22 Feb 2022 08:47:33 +1100 Subject: [PATCH] Terminal: Handle tabs, LSh: support environment variables and argumetns in quotes --- Applications/LSh/main.cpp | 176 ++++++++++++++++++++++++++------- Applications/Terminal/escape.h | 1 + Applications/Terminal/main.cpp | 3 + 3 files changed, 144 insertions(+), 36 deletions(-) diff --git a/Applications/LSh/main.cpp b/Applications/LSh/main.cpp index 708e0a65..7e0e1d87 100755 --- a/Applications/LSh/main.cpp +++ b/Applications/LSh/main.cpp @@ -1,15 +1,17 @@ #include +#include #include #include #include +#include #include #include #include -#include #include #include #include #include +#include #include termios execAttributes; // Set before executing @@ -18,7 +20,7 @@ termios readAttributes; // Set on ReadLine std::string ln; int lnPos = 0; -typedef void (*builtin_call_t)(int, char**); +typedef int (*builtin_call_t)(int, char**); typedef struct { const char* name; @@ -33,36 +35,46 @@ std::list builtins; std::vector history; -void LShBuiltin_Cd(int argc, char** argv) { +int LShBuiltin_Cd(int argc, char** argv) { if (argc > 3) { printf("cd: Too many arguments"); + return 1; } else if (argc == 2) { if (chdir(argv[1])) { printf("cd: Error changing working directory to %s\n", argv[1]); } + + return 1; } else chdir("/"); getcwd(currentDir, PATH_MAX); + return 0; } -void LShBuiltin_Pwd(int argc, char** argv) { +int LShBuiltin_Pwd(int argc, char** argv) { getcwd(currentDir, PATH_MAX); printf("%s\n", currentDir); + + return 0; } -void LShBuiltin_Export(int argc, char** argv) { +int LShBuiltin_Export(int argc, char** argv) { for (int i = 1; i < argc; i++) { putenv(argv[i]); } + + return 0; } -void LShBuiltin_Clear(int argc, char** argv) { +int LShBuiltin_Clear(int argc, char** argv) { printf("\033c"); fflush(stdout); + + return 0; } -void LShBuiltin_Exit(int argc, char** argv) { exit(0); } +[[noreturn]] int LShBuiltin_Exit(int argc, char** argv) { exit(0); } builtin_t builtinCd = {.name = "cd", .func = LShBuiltin_Cd}; builtin_t builtinPwd = {.name = "pwd", .func = LShBuiltin_Pwd}; @@ -72,8 +84,10 @@ builtin_t builtinExit = {.name = "exit", .func = LShBuiltin_Exit}; pid_t job = -1; -void InterruptSignalHandler(int sig){ - if(job > 0){ +int commandResult = 0; // Return code of last command + +void InterruptSignalHandler(int sig) { + if (job > 0) { // If we have a current job, send signal to child. kill(job, sig); } @@ -85,7 +99,7 @@ void ReadLine() { bool esc = false; // Escape sequence bool csi = false; // Control sequence indicator - lnPos = 0; // Line cursor position + lnPos = 0; // Line cursor position int historyIndex = -1; // History index ln.clear(); @@ -183,33 +197,121 @@ void ReadLine() { } void ParseLine() { - if (!ln.length()) { + if (ln.empty()) { return; } + enum { + ParseNormal, + ParseSingleQuotes, + ParseDoubleQuotes, + ParseEnv, + ParseEscape, + }; + + std::stack state; + state.push(ParseNormal); + history.push_back(ln); + ln.push_back('\n'); // Insert a '\n' at the end - int argc = 0; std::vector argv; - char* lnC = strdup(ln.c_str()); + std::string lineBuf; + std::string envBuf; - if (!lnC) { - return; + auto pushArg = [&]() -> void { + if(lineBuf.empty()) { + return; + } + + assert(lineBuf.c_str()); + + argv.push_back(strdup(lineBuf.c_str())); + lineBuf.clear(); + }; + + auto pushEnv = [&](std::string val) -> void { + lineBuf += std::move(val); + envBuf.clear(); + }; + + auto isLineSeperator = [](char c) -> bool { return c == ' ' || c == '\n'; }; + + for (auto it = ln.begin(); it != ln.end(); it++) { + char c = *it; + switch (state.top()) { + case ParseNormal: + if (c == '\'') { + state.push(ParseSingleQuotes); + break; + } else if (c == '\"') { + state.push(ParseDoubleQuotes); + break; + } else if (isLineSeperator(c)) { + pushArg(); + break; + } + + [[fallthrough]]; + case ParseDoubleQuotes: + if (c == '\"') { + // The fallthrough will not matter as + // this case is already tested for ParseNormal + state.pop(); + break; + } + + if (c == '\\') { + state.push(ParseEscape); + } else if (c == '$') { + state.push(ParseEnv); + } else { + lineBuf += c; + } + break; + case ParseSingleQuotes: + if (c == '\'') { // End single quotes + state.pop(); + } else { + lineBuf += c; + } + break; + case ParseEnv: + if (envBuf.empty()) { + if (c == '$') { // Shell Process ID + pushEnv(std::to_string(getpid())); + state.pop(); + } else if (c == '?') { // Return value of last command + pushEnv(std::to_string(commandResult)); + state.pop(); + } + } + + if (isalnum(c)) { + envBuf += c; + } else if (isLineSeperator(c) || c == '\\' || c == '\'' || c == '\"') { + pushEnv(getenv(envBuf.c_str())); + state.pop(); + it--; // The previous state deals with the separator + } + break; + case ParseEscape: + lineBuf += c; + state.pop(); + break; + } } - char* tok = strtok(lnC, " \t\n"); - argv.push_back(tok); - argc++; + assert(lineBuf.empty()); - while ((tok = strtok(NULL, " \t\n"))) { - argv.push_back(tok); - argc++; + if(!argv.size()) { + return; } for (builtin_t builtin : builtins) { if (strcmp(builtin.name, argv[0]) == 0) { - builtin.func(argc, argv.data()); + commandResult = builtin.func(argv.size(), argv.data()); return; } } @@ -217,13 +319,15 @@ void ParseLine() { errno = 0; if (strchr(argv[0], '/')) { - job = lemon_spawn(argv[0], argc, argv.data(), 1); + job = lemon_spawn(argv[0], argv.size(), argv.data(), 1); if (job > 0) { int status = 0; int ret = 0; - while((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR)) + while ((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR)) ; + commandResult = WEXITSTATUS(status); + job = -1; } else if (errno == ENOENT) { printf("\nNo such file or directory: %s\n", argv[0]); @@ -231,19 +335,20 @@ void ParseLine() { perror("Error spawning process"); } - if (lnC) - free(lnC); - return; } else for (std::string path : path) { - job = lemon_spawn((path + "/" + argv[0]).c_str(), argc, argv.data(), 1); + assert(!path.empty()); + + job = lemon_spawn((path + "/" + argv[0]).c_str(), argv.size(), argv.data(), 1); if (job > 0) { int status = 0; int ret = 0; - while((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR)) + while ((ret = waitpid(job, &status, 0)) == 0 || (ret < 0 && errno == EINTR)) ; + commandResult = WEXITSTATUS(status); + job = -1; return; } else if (errno == ENOENT) { @@ -256,9 +361,6 @@ void ParseLine() { printf("\nUnknown Command: %s\n", argv[0]); - if (lnC) - free(lnC); - return; } @@ -269,6 +371,8 @@ int main() { chdir(h); } + setenv("SHELL", "/system/bin/lsh", 1); + getcwd(currentDir, PATH_MAX); tcgetattr(STDOUT_FILENO, &execAttributes); readAttributes = execAttributes; @@ -281,12 +385,12 @@ int main() { sigemptyset(&action.sa_mask); // Send both SIGINT and SIGWINCH to child - if(sigaction(SIGINT, &action, nullptr)){ + if (sigaction(SIGINT, &action, nullptr)) { perror("sigaction"); return 99; } - if(sigaction(SIGWINCH, &action, nullptr)){ + if (sigaction(SIGWINCH, &action, nullptr)) { perror("sigaction"); return 99; } @@ -298,9 +402,9 @@ int main() { builtins.push_back(builtinExit); std::string pathEnv = getenv("PATH"); - std::string temp; + std::string temp = ""; for (char c : pathEnv) { - if (c == ':' && temp.length()) { + if (c == ':' && !temp.empty()) { path.push_back(temp); temp.clear(); } else { diff --git a/Applications/Terminal/escape.h b/Applications/Terminal/escape.h index 85276688..8234898d 100755 --- a/Applications/Terminal/escape.h +++ b/Applications/Terminal/escape.h @@ -5,6 +5,7 @@ enum { Control_Backspace = '\b', // Backspaces one column, however not past the beginning of the line Control_LineFeed = '\n', // Line feed, by default will also carriage return Control_CarriageReturn = '\r', // Carriage return + Control_Tab = '\t', }; #define ESC_SAVE_CURSOR '7' diff --git a/Applications/Terminal/main.cpp b/Applications/Terminal/main.cpp index 2b2e4775..91eab948 100644 --- a/Applications/Terminal/main.cpp +++ b/Applications/Terminal/main.cpp @@ -148,6 +148,8 @@ void ParseChar(char ch) { if (parseState == State_Normal) { if (isprint(ch) || ch == ' ') { PrintChar(ch); + } else if(ch == Control_Tab) { + cursorPosition.x = std::min(cursorPosition.x + 8, terminalSize.x); } else if (ch == Control_Escape) { escapeBuffer.clear(); parseState = State_Escape; @@ -448,6 +450,7 @@ int main(int argc, char** argv) { int ptySlaveFd = -1; setenv("TERM", "xterm-256color", 1); + setenv("COLORTERM", "truecolor", 1); struct sigaction action = { .sa_handler = SIGCHLDHandler,