Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 1 | // Copyright 2020 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "foomatic_shell/shell.h" |
| 6 | |
| 7 | #include <sys/mman.h> |
| 8 | #include <sys/types.h> |
| 9 | #include <unistd.h> |
| 10 | |
| 11 | #include <cstring> |
| 12 | #include <string> |
| 13 | #include <vector> |
| 14 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame] | 15 | #include <base/check.h> |
| 16 | #include <base/check_op.h> |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 17 | #include <base/logging.h> |
| 18 | #include <base/strings/string_number_conversions.h> |
| 19 | |
| 20 | #include "foomatic_shell/parser.h" |
| 21 | #include "foomatic_shell/process_launcher.h" |
| 22 | #include "foomatic_shell/scanner.h" |
| 23 | #include "foomatic_shell/verifier.h" |
| 24 | |
| 25 | namespace foomatic_shell { |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | // Prints to the stderr an error message. |source| is a source of the script |
| 30 | // that failed. |position| points to the part of |source| where the error |
| 31 | // occurred. |msg| is an error message. Neither dot nor end-of-line is expected |
| 32 | // at the end of |msg|. |
| 33 | void PrintErrorMessage(const std::string& source, |
| 34 | std::string::const_iterator position, |
| 35 | const std::string& msg) { |
| 36 | const std::string out = CreateErrorLog(source, position, msg); |
| 37 | fprintf(stderr, "%s\n", out.c_str()); |
| 38 | } |
| 39 | |
| 40 | // Sets the position in the given file descriptor |fd| to the beginning and |
| 41 | // reads everything from it. Read content is saved to |out|. If the function |
| 42 | // succeeds the file descriptor is closed and true is returned. In case of an |
| 43 | // error, the content of |out| is replaced by an error message and the function |
| 44 | // returns false. |out| must not be nullptr; its initial content is always |
| 45 | // deleted at the beginning. The function also fails if the length of the |
| 46 | // content is larger than |kMaxSourceSize|. |
| 47 | bool ReadAndCloseFd(int fd, std::string* out) { |
| 48 | DCHECK(out != nullptr); |
| 49 | out->clear(); |
| 50 | if (lseek(fd, 0, SEEK_SET) < 0) { |
| 51 | *out = "lseek failed: "; |
| 52 | *out += strerror(errno); |
| 53 | return false; |
| 54 | } |
| 55 | char buf[1024]; |
| 56 | while (true) { |
| 57 | const ssize_t length = read(fd, buf, sizeof(buf)); |
| 58 | if (length < 0) { |
| 59 | // Error occurred. |
| 60 | *out = "read failed: "; |
| 61 | *out += strerror(errno); |
| 62 | return false; |
| 63 | } else if (length == 0) { |
| 64 | // End of stream was reached. |
| 65 | break; |
| 66 | } |
| 67 | // Success. Add read data to the output string. |
| 68 | out->append(buf, length); |
| 69 | if (out->size() > kMaxSourceSize) { |
| 70 | *out = "Generated script is too long"; |
| 71 | return false; |
| 72 | } |
| 73 | } |
| 74 | close(fd); |
| 75 | return true; |
| 76 | } |
| 77 | |
| 78 | // Parse and execute a shell script in |source|. This routine works in the |
| 79 | // similar way as ExecuteShellScript(...) from shell.h. The only differences |
| 80 | // are that the output is saved to the given string |output| instead of a file |
| 81 | // descriptor and that the return value is bool instead of int. In case of an |
| 82 | // error, the function returns false and |output| is set to an error message. |
| 83 | bool ExecuteEmbeddedShellScript(const std::string& source, |
| 84 | const bool verbose_mode, |
Pranav Batra | 3ea2057 | 2020-11-03 08:47:07 +0000 | [diff] [blame] | 85 | const bool verify_mode, |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 86 | const int recursion_level, |
| 87 | std::string* output) { |
| 88 | DCHECK(output != nullptr); |
| 89 | |
Piotr Pawliczek | 3a4c9fa | 2020-09-21 10:54:10 -0700 | [diff] [blame] | 90 | // This limits the number of recursive `...` (backticks). |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 91 | if (recursion_level > 2) { |
| 92 | *output = "Too many recursive executions of `...` operator"; |
| 93 | return false; |
| 94 | } |
| 95 | |
| 96 | // Generate temporary file descriptor storing data in memory. The name is |
| 97 | // set to "foomatic_shell_level_" + |recursion_level|. |
| 98 | const std::string temp_name = |
| 99 | "foomatic_shell_level_" + base::NumberToString(recursion_level); |
| 100 | int temp_fd = memfd_create(temp_name.c_str(), 0); |
| 101 | if (temp_fd == -1) { |
| 102 | *output = std::string("memfd_create failed: ") + strerror(errno); |
| 103 | return false; |
| 104 | } |
| 105 | |
| 106 | // Execute the script. |
Pranav Batra | 3ea2057 | 2020-11-03 08:47:07 +0000 | [diff] [blame] | 107 | if (ExecuteShellScript(source, temp_fd, verbose_mode, verify_mode, |
| 108 | recursion_level + 1)) { |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 109 | *output = "Error when executing `...` operator"; |
| 110 | return false; |
| 111 | } |
| 112 | |
| 113 | // Read the generated output to |out|. |
| 114 | if (!ReadAndCloseFd(temp_fd, output)) |
| 115 | return false; |
| 116 | |
| 117 | // The trailing end-of-line character is skipped - shell is suppose to |
| 118 | // work this way. |
| 119 | if (!output->empty() && output->back() == '\n') |
| 120 | output->pop_back(); |
| 121 | |
| 122 | // Success! |
| 123 | return true; |
| 124 | } |
| 125 | |
| 126 | } // namespace |
| 127 | |
| 128 | int ExecuteShellScript(const std::string& source, |
| 129 | const int output_fd, |
| 130 | const bool verbose_mode, |
Pranav Batra | 3ea2057 | 2020-11-03 08:47:07 +0000 | [diff] [blame] | 131 | const bool verify_mode, |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 132 | const int recursion_level) { |
| 133 | DCHECK_NE(output_fd, 0); |
| 134 | DCHECK_NE(output_fd, 2); |
| 135 | |
| 136 | if (verbose_mode) |
| 137 | fprintf(stderr, "EXECUTE SCRIPT: %s\n", source.c_str()); |
| 138 | |
| 139 | // Scan the source (the first phase of parsing). |
| 140 | Scanner scanner(source); |
| 141 | std::vector<Token> tokens; |
| 142 | if (!scanner.ParseWholeInput(&tokens)) { |
| 143 | PrintErrorMessage(source, scanner.GetPosition(), scanner.GetMessage()); |
| 144 | return kShellError; |
| 145 | } |
| 146 | |
| 147 | // Execute scripts in `...` (backticks) and replace them with generated |
| 148 | // output. |
| 149 | for (auto& token : tokens) { |
| 150 | if (token.type != Token::Type::kExecutedString) |
| 151 | continue; |
| 152 | |
| 153 | // Execute the script inside `...` (backticks) operator. |
| 154 | std::string out; |
Pranav Batra | 3ea2057 | 2020-11-03 08:47:07 +0000 | [diff] [blame] | 155 | if (!ExecuteEmbeddedShellScript(token.value, verbose_mode, verify_mode, |
| 156 | recursion_level, &out)) { |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 157 | PrintErrorMessage(token.value, token.begin, out); |
| 158 | return kShellError; |
| 159 | } |
| 160 | |
| 161 | // Replace the token value (content of `...`) with the generated output. |
| 162 | token.value = out; |
| 163 | } |
| 164 | |
| 165 | // Parse the list of tokens (the second phase of parsing). |
| 166 | Parser parser(tokens); |
| 167 | Script parsed_script; |
| 168 | if (!parser.ParseWholeInput(&parsed_script)) { |
| 169 | PrintErrorMessage(source, parser.GetPosition(), parser.GetMessage()); |
| 170 | return kShellError; |
| 171 | } |
| 172 | |
| 173 | // Verify all commands in the parsed script. |
| 174 | Verifier verifier; |
| 175 | if (!verifier.VerifyScript(&parsed_script)) { |
| 176 | PrintErrorMessage(source, verifier.GetPosition(), verifier.GetMessage()); |
| 177 | return kShellError; |
| 178 | } |
| 179 | |
| 180 | // Execute the parsed script and store returned code in |exit_code|. |
Pranav Batra | 3ea2057 | 2020-11-03 08:47:07 +0000 | [diff] [blame] | 181 | int exit_code = 0; |
| 182 | if (!verify_mode) { |
| 183 | ProcessLauncher launcher(source, verbose_mode); |
| 184 | exit_code = launcher.RunScript(parsed_script, 0, output_fd); |
| 185 | } |
Piotr Pawliczek | c3ff7da | 2020-06-26 14:23:56 -0700 | [diff] [blame] | 186 | |
| 187 | // Log status and exit! |
| 188 | if (verbose_mode) { |
| 189 | if (exit_code == 0) { |
| 190 | fprintf(stderr, "SCRIPT COMPLETED SUCCESSFULLY\n"); |
| 191 | } else { |
| 192 | fprintf(stderr, "SCRIPT FAILED WITH EXIT CODE %d\n", exit_code); |
| 193 | } |
| 194 | } |
| 195 | return exit_code; |
| 196 | } |
| 197 | |
| 198 | } // namespace foomatic_shell |