blob: 5604ca817ef1be51768238c37aef7fb78a703f10 [file] [log] [blame]
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -07001// 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 Fan713061e2021-03-08 15:45:12 +090015#include <base/check.h>
16#include <base/check_op.h>
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -070017#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
25namespace foomatic_shell {
26
27namespace {
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|.
33void 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|.
47bool 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.
83bool ExecuteEmbeddedShellScript(const std::string& source,
84 const bool verbose_mode,
Pranav Batra3ea20572020-11-03 08:47:07 +000085 const bool verify_mode,
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -070086 const int recursion_level,
87 std::string* output) {
88 DCHECK(output != nullptr);
89
Piotr Pawliczek3a4c9fa2020-09-21 10:54:10 -070090 // This limits the number of recursive `...` (backticks).
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -070091 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 Batra3ea20572020-11-03 08:47:07 +0000107 if (ExecuteShellScript(source, temp_fd, verbose_mode, verify_mode,
108 recursion_level + 1)) {
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -0700109 *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
128int ExecuteShellScript(const std::string& source,
129 const int output_fd,
130 const bool verbose_mode,
Pranav Batra3ea20572020-11-03 08:47:07 +0000131 const bool verify_mode,
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -0700132 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 Batra3ea20572020-11-03 08:47:07 +0000155 if (!ExecuteEmbeddedShellScript(token.value, verbose_mode, verify_mode,
156 recursion_level, &out)) {
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -0700157 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 Batra3ea20572020-11-03 08:47:07 +0000181 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 Pawliczekc3ff7da2020-06-26 14:23:56 -0700186
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