| // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "foomatic_shell/shell.h" |
| |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <cstring> |
| #include <string> |
| #include <vector> |
| |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include "foomatic_shell/parser.h" |
| #include "foomatic_shell/process_launcher.h" |
| #include "foomatic_shell/scanner.h" |
| #include "foomatic_shell/verifier.h" |
| |
| namespace foomatic_shell { |
| |
| namespace { |
| |
| // Prints to the stderr an error message. |source| is a source of the script |
| // that failed. |position| points to the part of |source| where the error |
| // occurred. |msg| is an error message. Neither dot nor end-of-line is expected |
| // at the end of |msg|. |
| void PrintErrorMessage(const std::string& source, |
| std::string::const_iterator position, |
| const std::string& msg) { |
| const std::string out = CreateErrorLog(source, position, msg); |
| fprintf(stderr, "%s\n", out.c_str()); |
| } |
| |
| // Sets the position in the given file descriptor |fd| to the beginning and |
| // reads everything from it. Read content is saved to |out|. If the function |
| // succeeds the file descriptor is closed and true is returned. In case of an |
| // error, the content of |out| is replaced by an error message and the function |
| // returns false. |out| must not be nullptr; its initial content is always |
| // deleted at the beginning. The function also fails if the length of the |
| // content is larger than |kMaxSourceSize|. |
| bool ReadAndCloseFd(int fd, std::string* out) { |
| DCHECK(out != nullptr); |
| out->clear(); |
| if (lseek(fd, 0, SEEK_SET) < 0) { |
| *out = "lseek failed: "; |
| *out += strerror(errno); |
| return false; |
| } |
| char buf[1024]; |
| while (true) { |
| const ssize_t length = read(fd, buf, sizeof(buf)); |
| if (length < 0) { |
| // Error occurred. |
| *out = "read failed: "; |
| *out += strerror(errno); |
| return false; |
| } else if (length == 0) { |
| // End of stream was reached. |
| break; |
| } |
| // Success. Add read data to the output string. |
| out->append(buf, length); |
| if (out->size() > kMaxSourceSize) { |
| *out = "Generated script is too long"; |
| return false; |
| } |
| } |
| close(fd); |
| return true; |
| } |
| |
| // Parse and execute a shell script in |source|. This routine works in the |
| // similar way as ExecuteShellScript(...) from shell.h. The only differences |
| // are that the output is saved to the given string |output| instead of a file |
| // descriptor and that the return value is bool instead of int. In case of an |
| // error, the function returns false and |output| is set to an error message. |
| bool ExecuteEmbeddedShellScript(const std::string& source, |
| const bool verbose_mode, |
| const bool verify_mode, |
| const int recursion_level, |
| std::string* output) { |
| DCHECK(output != nullptr); |
| |
| // This limits the number of recursive `...` (backticks). |
| if (recursion_level > 2) { |
| *output = "Too many recursive executions of `...` operator"; |
| return false; |
| } |
| |
| // Generate temporary file descriptor storing data in memory. The name is |
| // set to "foomatic_shell_level_" + |recursion_level|. |
| const std::string temp_name = |
| "foomatic_shell_level_" + base::NumberToString(recursion_level); |
| int temp_fd = memfd_create(temp_name.c_str(), 0); |
| if (temp_fd == -1) { |
| *output = std::string("memfd_create failed: ") + strerror(errno); |
| return false; |
| } |
| |
| // Execute the script. |
| if (ExecuteShellScript(source, temp_fd, verbose_mode, verify_mode, |
| recursion_level + 1)) { |
| *output = "Error when executing `...` operator"; |
| return false; |
| } |
| |
| // Read the generated output to |out|. |
| if (!ReadAndCloseFd(temp_fd, output)) |
| return false; |
| |
| // The trailing end-of-line character is skipped - shell is suppose to |
| // work this way. |
| if (!output->empty() && output->back() == '\n') |
| output->pop_back(); |
| |
| // Success! |
| return true; |
| } |
| |
| } // namespace |
| |
| int ExecuteShellScript(const std::string& source, |
| const int output_fd, |
| const bool verbose_mode, |
| const bool verify_mode, |
| const int recursion_level) { |
| DCHECK_NE(output_fd, 0); |
| DCHECK_NE(output_fd, 2); |
| |
| if (verbose_mode) |
| fprintf(stderr, "EXECUTE SCRIPT: %s\n", source.c_str()); |
| |
| // Scan the source (the first phase of parsing). |
| Scanner scanner(source); |
| std::vector<Token> tokens; |
| if (!scanner.ParseWholeInput(&tokens)) { |
| PrintErrorMessage(source, scanner.GetPosition(), scanner.GetMessage()); |
| return kShellError; |
| } |
| |
| // Execute scripts in `...` (backticks) and replace them with generated |
| // output. |
| for (auto& token : tokens) { |
| if (token.type != Token::Type::kExecutedString) |
| continue; |
| |
| // Execute the script inside `...` (backticks) operator. |
| std::string out; |
| if (!ExecuteEmbeddedShellScript(token.value, verbose_mode, verify_mode, |
| recursion_level, &out)) { |
| PrintErrorMessage(token.value, token.begin, out); |
| return kShellError; |
| } |
| |
| // Replace the token value (content of `...`) with the generated output. |
| token.value = out; |
| } |
| |
| // Parse the list of tokens (the second phase of parsing). |
| Parser parser(tokens); |
| Script parsed_script; |
| if (!parser.ParseWholeInput(&parsed_script)) { |
| PrintErrorMessage(source, parser.GetPosition(), parser.GetMessage()); |
| return kShellError; |
| } |
| |
| // Verify all commands in the parsed script. |
| Verifier verifier; |
| if (!verifier.VerifyScript(&parsed_script)) { |
| PrintErrorMessage(source, verifier.GetPosition(), verifier.GetMessage()); |
| return kShellError; |
| } |
| |
| // Execute the parsed script and store returned code in |exit_code|. |
| int exit_code = 0; |
| if (!verify_mode) { |
| ProcessLauncher launcher(source, verbose_mode); |
| exit_code = launcher.RunScript(parsed_script, 0, output_fd); |
| } |
| |
| // Log status and exit! |
| if (verbose_mode) { |
| if (exit_code == 0) { |
| fprintf(stderr, "SCRIPT COMPLETED SUCCESSFULLY\n"); |
| } else { |
| fprintf(stderr, "SCRIPT FAILED WITH EXIT CODE %d\n", exit_code); |
| } |
| } |
| return exit_code; |
| } |
| |
| } // namespace foomatic_shell |