blob: 99812e6a709a121341108720eecc7f1f70401fe5 [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/process_launcher.h"
6
7#include <errno.h>
8#include <sys/resource.h>
9#include <sys/time.h>
10#include <sys/wait.h>
11#include <unistd.h>
12
13#include <cstdio>
14#include <cstdlib>
15#include <cstring>
16#include <list>
17#include <map>
18#include <utility>
19
20#include <base/bind.h>
21#include <base/logging.h>
22#include <brillo/process/process.h>
23
24namespace foomatic_shell {
25
26namespace {
27
28// Helper structure holding a pointer to brillo::Process or PID of process.
29struct Subprocess {
30 explicit Subprocess(pid_t pid) : script_pid(pid) {}
31 explicit Subprocess(std::unique_ptr<brillo::Process> process)
32 : command_process(std::move(process)) {}
33 // Exactly one of these two fields is set.
34 std::unique_ptr<brillo::Process> command_process;
35 pid_t script_pid = -1;
36 // The position of the executed fragment of the input script.
37 std::string::const_iterator position;
38};
39
40// This function is called before calling exec(...) in forked process.
41// |vars| contains environment variables to set. In case of an error, the
42// function returns false and prints an error message to stderr.
43bool PreExecSettings(const std::map<std::string, std::string>& vars) {
44 // Set environment variables.
45 for (const auto& name_value : vars) {
46 if (setenv(name_value.first.c_str(), name_value.second.c_str(), 1)) {
47 perror("setenv(...) failed");
48 return false;
49 }
50 }
51
Piotr Pawliczek647b23e2021-03-10 16:34:10 -080052 // Set soft/hard limit for CPU usage (300 sec / 330 sec).
53 const rlimit cpu_limit = {300, 330};
Piotr Pawliczekc3ff7da2020-06-26 14:23:56 -070054 if (setrlimit(RLIMIT_CPU, &cpu_limit)) {
55 perror("setrlimit(RLIMIT_CPU,...) failed");
56 return false;
57 }
58
59 // Set soft/hard limit for memory (256 MB / 288 MB).
60 const rlimit mem_limit = {256 * 1024 * 1024, 288 * 1024 * 1024};
61 if (setrlimit(RLIMIT_DATA, &mem_limit)) {
62 perror("setrlimit(RLIMIT_DATA,...) failed");
63 return false;
64 }
65
66 return true;
67}
68
69// Prints to the stderr an error message. |source| is a source of the script
70// that failed. |position| points to the part of |source| where the error
71// occurred. |msg| is an error message. Neither dot nor end-of-line is expected
72// at the end of |msg|. If |use_errno| is set the function adds to |msg| a
73// string ": " followed by the error message reported by errno.
74void PrintMessage(const std::string& source,
75 std::string::const_iterator position,
76 std::string msg,
77 bool use_errno = false) {
78 if (use_errno) {
79 msg += ": ";
80 msg += strerror(errno);
81 }
82 const std::string out = CreateErrorLog(source, position, msg);
83 fprintf(stderr, "%s\n", out.c_str());
84}
85
86} // namespace
87
88// Creates a new process executing the given |command|. |input_fd| and
89// |output_fd| are input/output descriptors for the new process. The function
90// returns nullptr when error occurs and the process cannot be started.
91std::unique_ptr<brillo::Process> ProcessLauncher::StartProcess(
92 const Command& command, int input_fd, int output_fd) {
93 // Saves to a map all environment variables to set.
94 std::map<std::string, std::string> vars;
95 for (const auto& assignment : command.variables_with_values)
96 vars[assignment.variable.value] = Value(assignment.new_value);
97
98 // Creates and runs the process.
99 std::unique_ptr<brillo::Process> process(new brillo::ProcessImpl());
100 process->AddArg(command.application.value);
101 for (const StringAtom& param : command.parameters)
102 process->AddArg(Value(param));
103 if (input_fd >= 0)
104 process->BindFd(input_fd, 0);
105 if (output_fd >= 0)
106 process->BindFd(output_fd, 1);
107 process->SetCloseUnusedFileDescriptors(true);
108 process->SetSearchPath(true);
109 process->SetPreExecCallback(base::Bind(&PreExecSettings, vars));
110 if (!process->Start()) {
111 PrintMessage(source_, Position(command), "brillo::Process::Start() failed");
112 return nullptr;
113 }
114
115 if (verbose_)
116 fprintf(stderr, "PROCESS %s STARTED\n", command.application.value.c_str());
117 return process;
118}
119
120// This function forks a new process and executes |script| in it. |input_fd| and
121// |output_fd| are standard input/output streams for the new process. |open_fds|
122// is a set with currently open file descriptors; it may contain a special value
123// -1 (incorrect descriptor). This set is used to determine which file
124// descriptors must be closed in the forked (child) process. The function
125// returns PID of the forked process or -1 in case on an error.
126pid_t ProcessLauncher::StartSubshell(const Script& script,
127 int input_fd,
128 int output_fd,
129 std::set<int> open_fds) {
130 // Remove descriptors that must stay open.
131 open_fds.erase(input_fd);
132 open_fds.erase(output_fd);
133 open_fds.erase(0); // stdin
134 open_fds.erase(1); // stdout
135 open_fds.erase(2); // stderr
136 // Incorrect descriptors use -1, we have to remove this value.
137 open_fds.erase(-1);
138
139 pid_t pid = fork();
140 if (pid == 0) {
141 // Inside the child process.
142 // Close all unused file descriptors.
143 for (int fd : open_fds) {
144 if (close(fd) != 0)
145 perror("close(fd) failed");
146 }
147 // Run |script| and exit.
148 const int exit_code = RunScript(script, input_fd, output_fd);
149 exit(exit_code);
150 }
151
152 // Inside the parent process.
153 if (pid < 0) {
154 PrintMessage(source_, Position(script), "fork() failed", true);
155 return (pid_t)-1;
156 }
157
158 if (verbose_)
159 fprintf(stderr, "SUBSHELL STARTED\n");
160 return pid;
161}
162
163// The function runs given |pipeline|. |input_fd| and |output_fd| are
164// input/output descriptors for the whole pipeline. In case of an error the
165// method returns kShellError. Otherwise, the method returns exit code
166// returned by the last command in the pipeline.
167int ProcessLauncher::RunPipeline(const Pipeline& pipeline,
168 int input_fd,
169 int output_fd) {
170 if (verbose_)
171 fprintf(stderr, "EXECUTE PIPELINE\n");
172
173 // List of processes created within this pipeline.
174 std::list<Subprocess> processes;
175
176 // Iterate over the pipeline and create corresponding processes.
177 int next_fd_in = input_fd;
178 for (size_t iSegment = 0; iSegment < pipeline.segments.size(); ++iSegment) {
179 auto& pipe_segment = pipeline.segments[iSegment];
180
181 // Create a pipe connecting the current segment with the next one.
182 const int fd_in = next_fd_in;
183 int fd_out;
184 if (iSegment == pipeline.segments.size() - 1) {
185 // It is the last segment. Instead of creating a new pipe, we just set
186 // the output file descriptor to |output_fd|.
187 next_fd_in = -1;
188 fd_out = output_fd;
189 } else {
190 // Create a new pipe connecting this segment with the next one.
191 int fd[2];
192 if (pipe(fd) != 0) {
193 PrintMessage(source_, Position(pipe_segment), "pipe(...) failed",
194 true /* use_errno */);
195 return kShellError;
196 }
197 next_fd_in = fd[0];
198 fd_out = fd[1];
199 }
200
201 // Create a process corresponding to the current segment.
202 if (pipe_segment.command) {
203 // The current segment is a simple command.
204 auto process = StartProcess(*pipe_segment.command, fd_in, fd_out);
205 if (process != nullptr) {
206 // Success. Save the new process.
207 processes.emplace_back(std::move(process));
208 } else {
209 // Failure. Break the pipeline.
210 return kShellError;
211 }
212 } else {
213 // The current segment is a subshell.
214 const std::set<int> open_fds = {input_fd, output_fd, next_fd_in};
215 pid_t pid = StartSubshell(*pipe_segment.script, fd_in, fd_out, open_fds);
216 if (pid != (pid_t)-1) {
217 // Success. Save the new process.
218 processes.emplace_back(pid);
219 } else {
220 // Failure. Break the pipeline.
221 return kShellError;
222 }
223 }
224 processes.back().position = Position(pipe_segment);
225
226 // Close file descriptors.
227 if (fd_in != input_fd) {
228 if (close(fd_in) != 0)
229 perror("close(fd_in) failed");
230 }
231 if (fd_out != output_fd) {
232 if (close(fd_out) != 0)
233 perror("close(fd_out) failed");
234 }
235 }
236
237 // Wait for all the processes to finish.
238 int exit_code = 0;
239 for (Subprocess& sp : processes) {
240 if (sp.command_process) {
241 exit_code = sp.command_process->Wait();
242 // (|exit_code| == kShellError) means that brillo::Process failed during
243 // initialization of the child process.
244 if (exit_code == kShellError) {
245 PrintMessage(source_, sp.position, "Process failed");
246 return kShellError;
247 }
248 } else {
249 if (waitpid(sp.script_pid, &exit_code, 0) == (pid_t)-1) {
250 PrintMessage(source_, sp.position, "waitpid(...) failed",
251 true /* use_errno */);
252 return kShellError;
253 }
254 // (|exit_code| == kShellError) means that the subshell failed.
255 if (exit_code == kShellError)
256 return kShellError;
257 }
258 // We ignore the exit_code different than kShellError, because the Linux
259 // shell behaves this way. The exit code from the last pipeline segment is
260 // reported as the exit code for the whole pipeline.
261 }
262
263 if (verbose_)
264 fprintf(stderr, "PIPELINE COMPLETED SUCCESSFULLY\n");
265 return exit_code;
266}
267
268int ProcessLauncher::RunScript(const Script& script,
269 int input_fd,
270 int output_fd) {
271 for (auto& pipeline : script.pipelines) {
272 // Try to execute the given |pipeline|.
273 const int exit_code = RunPipeline(pipeline, input_fd, output_fd);
274
275 // We stop execution on the first failing pipeline.
276 if (exit_code != 0)
277 return exit_code;
278 }
279
280 return 0;
281}
282
283} // namespace foomatic_shell