• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include <sys/types.h>
19 #include <sys/wait.h>
20 
21 #include <android-base/logging.h>
22 #include <android-base/strings.h>
23 
24 #include <cstdio>
25 #include <cstring>
26 #include <functional>
27 #include <map>
28 #include <optional>
29 #include <ostream>
30 #include <sstream>
31 #include <string>
32 #include <type_traits>
33 #include <utility>
34 #include <vector>
35 
36 #include "common/libs/fs/shared_fd.h"
37 
38 namespace cuttlefish {
39 
40 enum class StopperResult {
41   kStopFailure, /* Failed to stop the subprocess. */
42   kStopCrash,   /* Attempted to stop the subprocess cleanly, but that failed. */
43   kStopSuccess, /* The subprocess exited in the expected way. */
44 };
45 
46 class Subprocess;
47 using SubprocessStopper = std::function<StopperResult(Subprocess*)>;
48 // Kills a process by sending it the SIGKILL signal.
49 StopperResult KillSubprocess(Subprocess* subprocess);
50 
51 // Keeps track of a running (sub)process. Allows to wait for its completion.
52 // It's an error to wait twice for the same subprocess.
53 class Subprocess {
54  public:
55   enum class StdIOChannel {
56     kStdIn = 0,
57     kStdOut = 1,
58     kStdErr = 2,
59   };
60 
61   Subprocess(pid_t pid, SubprocessStopper stopper = KillSubprocess)
pid_(pid)62       : pid_(pid),
63         started_(pid > 0),
64         stopper_(stopper) {}
65   // The default implementation won't do because we need to reset the pid of the
66   // moved object.
67   Subprocess(Subprocess&&);
68   ~Subprocess() = default;
69   Subprocess& operator=(Subprocess&&);
70   // Waits for the subprocess to complete. Returns zero if completed
71   // successfully, non-zero otherwise.
72   int Wait();
73   // Same as waitid(2)
74   int Wait(siginfo_t* infop, int options);
75   // Whether the command started successfully. It only says whether the call to
76   // fork() succeeded or not, it says nothing about exec or successful
77   // completion of the command, that's what Wait is for.
Started()78   bool Started() const { return started_; }
pid()79   pid_t pid() const { return pid_; }
Stop()80   StopperResult Stop() { return stopper_(this); }
81 
82  private:
83   // Copy is disabled to avoid waiting twice for the same pid (the first wait
84   // frees the pid, which allows the kernel to reuse it so we may end up waiting
85   // for the wrong process)
86   Subprocess(const Subprocess&) = delete;
87   Subprocess& operator=(const Subprocess&) = delete;
88   pid_t pid_ = -1;
89   bool started_ = false;
90   SubprocessStopper stopper_;
91 };
92 
93 class SubprocessOptions {
94  public:
SubprocessOptions()95   SubprocessOptions()
96       : verbose_(true), exit_with_parent_(true), in_group_(false) {}
97 
98   SubprocessOptions& Verbose(bool verbose) &;
99   SubprocessOptions Verbose(bool verbose) &&;
100   SubprocessOptions& ExitWithParent(bool exit_with_parent) &;
101   SubprocessOptions ExitWithParent(bool exit_with_parent) &&;
102   // The subprocess runs as head of its own process group.
103   SubprocessOptions& InGroup(bool in_group) &;
104   SubprocessOptions InGroup(bool in_group) &&;
105 
Verbose()106   bool Verbose() const { return verbose_; }
ExitWithParent()107   bool ExitWithParent() const { return exit_with_parent_; }
InGroup()108   bool InGroup() const { return in_group_; }
109 
110  private:
111   bool verbose_;
112   bool exit_with_parent_;
113   bool in_group_;
114 };
115 
116 // An executable command. Multiple subprocesses can be started from the same
117 // command object. This class owns any file descriptors that the subprocess
118 // should inherit.
119 class Command {
120  private:
121   template <typename T>
122   // For every type other than SharedFD (for which there is a specialisation)
BuildParameter(std::stringstream * stream,T t)123   void BuildParameter(std::stringstream* stream, T t) {
124     *stream << t;
125   }
126   // Special treatment for SharedFD
127   void BuildParameter(std::stringstream* stream, SharedFD shared_fd);
128   template <typename T, typename... Args>
BuildParameter(std::stringstream * stream,T t,Args...args)129   void BuildParameter(std::stringstream* stream, T t, Args... args) {
130     BuildParameter(stream, t);
131     BuildParameter(stream, args...);
132   }
133 
134  public:
135   // Constructs a command object from the path to an executable binary and an
136   // optional subprocess stopper. When not provided, stopper defaults to sending
137   // SIGKILL to the subprocess.
138   Command(std::string executable, SubprocessStopper stopper = KillSubprocess);
139   Command(Command&&) = default;
140   // The default copy constructor is unsafe because it would mean multiple
141   // closing of the inherited file descriptors. If needed it can be implemented
142   // using dup(2)
143   Command(const Command&) = delete;
144   Command& operator=(const Command&) = delete;
145   ~Command();
146 
Executable()147   const std::string& Executable() const {
148     return executable_ ? *executable_ : command_[0];
149   }
150 
SetExecutable(std::string executable)151   Command& SetExecutable(std::string executable) & {
152     executable_ = std::move(executable);
153     return *this;
154   }
SetExecutable(std::string executable)155   Command SetExecutable(std::string executable) && {
156     return std::move(SetExecutable(executable));
157   }
158 
SetName(std::string name)159   Command& SetName(std::string name) & {
160     command_[0] = std::move(name);
161     return *this;
162   }
SetName(std::string name)163   Command SetName(std::string name) && {
164     return std::move(SetName(std::move(name)));
165   }
166 
SetExecutableAndName(std::string name)167   Command& SetExecutableAndName(std::string name) & {
168     return SetExecutable(name).SetName(std::move(name));
169   }
170 
SetExecutableAndName(std::string name)171   Command SetExecutableAndName(std::string name) && {
172     return std::move(SetExecutableAndName(std::move(name)));
173   }
174 
SetStopper(SubprocessStopper stopper)175   Command& SetStopper(SubprocessStopper stopper) & {
176     subprocess_stopper_ = std::move(stopper);
177     return *this;
178   }
SetStopper(SubprocessStopper stopper)179   Command SetStopper(SubprocessStopper stopper) && {
180     return std::move(SetStopper(std::move(stopper)));
181   }
182 
183   // Specify the environment for the subprocesses to be started. By default
184   // subprocesses inherit the parent's environment.
SetEnvironment(std::vector<std::string> env)185   Command& SetEnvironment(std::vector<std::string> env) & {
186     env_ = std::move(env);
187     return *this;
188   }
SetEnvironment(std::vector<std::string> env)189   Command SetEnvironment(std::vector<std::string> env) && {
190     return std::move(SetEnvironment(std::move(env)));
191   }
192 
AddEnvironmentVariable(const std::string & env_var,const std::string & value)193   Command& AddEnvironmentVariable(const std::string& env_var,
194                                   const std::string& value) & {
195     return AddEnvironmentVariable(env_var + "=" + value);
196   }
AddEnvironmentVariable(const std::string & env_var,const std::string & value)197   Command AddEnvironmentVariable(const std::string& env_var,
198                                  const std::string& value) && {
199     AddEnvironmentVariable(env_var, value);
200     return std::move(*this);
201   }
202 
AddEnvironmentVariable(std::string env_var)203   Command& AddEnvironmentVariable(std::string env_var) & {
204     env_.emplace_back(std::move(env_var));
205     return *this;
206   }
AddEnvironmentVariable(std::string env_var)207   Command AddEnvironmentVariable(std::string env_var) && {
208     return std::move(AddEnvironmentVariable(std::move(env_var)));
209   }
210 
211   // Specify an environment variable to be unset from the parent's
212   // environment for the subprocesses to be started.
UnsetFromEnvironment(const std::string & env_var)213   Command& UnsetFromEnvironment(const std::string& env_var) & {
214     auto it = env_.begin();
215     while (it != env_.end()) {
216       if (android::base::StartsWith(*it, env_var + "=")) {
217         it = env_.erase(it);
218       } else {
219         ++it;
220       }
221     }
222     return *this;
223   }
UnsetFromEnvironment(const std::string & env_var)224   Command UnsetFromEnvironment(const std::string& env_var) && {
225     return std::move(UnsetFromEnvironment(env_var));
226   }
227 
228   // Adds a single parameter to the command. All arguments are concatenated into
229   // a single string to form a parameter. If one of those arguments is a
230   // SharedFD a duplicate of it will be used and won't be closed until the
231   // object is destroyed. To add multiple parameters to the command the function
232   // must be called multiple times, one per parameter.
233   template <typename... Args>
AddParameter(Args...args)234   Command& AddParameter(Args... args) & {
235     std::stringstream ss;
236     BuildParameter(&ss, args...);
237     command_.push_back(ss.str());
238     return *this;
239   }
240   template <typename... Args>
AddParameter(Args...args)241   Command AddParameter(Args... args) && {
242     return std::move(AddParameter(std::forward<Args>(args)...));
243   }
244   // Similar to AddParameter, except the args are appended to the last (most
245   // recently-added) parameter in the command.
246   template <typename... Args>
AppendToLastParameter(Args...args)247   Command& AppendToLastParameter(Args... args) & {
248     CHECK(!command_.empty()) << "There is no parameter to append to.";
249     std::stringstream ss;
250     BuildParameter(&ss, args...);
251     command_[command_.size() - 1] += ss.str();
252     return *this;
253   }
254   template <typename... Args>
AppendToLastParameter(Args...args)255   Command AppendToLastParameter(Args... args) && {
256     return std::move(AppendToLastParameter(std::forward<Args>(args)...));
257   }
258 
259   // Redirects the standard IO of the command.
260   Command& RedirectStdIO(Subprocess::StdIOChannel channel,
261                          SharedFD shared_fd) &;
262   Command RedirectStdIO(Subprocess::StdIOChannel channel,
263                         SharedFD shared_fd) &&;
264   Command& RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
265                          Subprocess::StdIOChannel parent_channel) &;
266   Command RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
267                         Subprocess::StdIOChannel parent_channel) &&;
268 
269   Command& SetWorkingDirectory(const std::string& path) &;
270   Command SetWorkingDirectory(const std::string& path) &&;
271   Command& SetWorkingDirectory(SharedFD dirfd) &;
272   Command SetWorkingDirectory(SharedFD dirfd) &&;
273 
274   // Starts execution of the command. This method can be called multiple times,
275   // effectively staring multiple (possibly concurrent) instances.
276   Subprocess Start(SubprocessOptions options = SubprocessOptions()) const;
277 
GetShortName()278   std::string GetShortName() const {
279     // This is safe because the constructor guarantees the name of the binary to
280     // be at index 0 on the vector
281     return command_[0];
282   }
283 
284   // Generates the contents for a bash script that can be used to run this
285   // command. Note that this command must not require any file descriptors
286   // or stdio redirects as those would not be available when the bash script
287   // is run.
288   std::string AsBashScript(const std::string& redirected_stdio_path = "") const;
289 
290  private:
291   std::optional<std::string> executable_;  // When unset, use command_[0]
292   std::vector<std::string> command_;
293   std::map<SharedFD, int> inherited_fds_{};
294   std::map<Subprocess::StdIOChannel, int> redirects_{};
295   std::vector<std::string> env_{};
296   SubprocessStopper subprocess_stopper_;
297   SharedFD working_directory_;
298 };
299 
300 /*
301  * Consumes a Command and runs it, optionally managing the stdio channels.
302  *
303  * If `stdin` is set, the subprocess stdin will be pipe providing its contents.
304  * If `stdout` is set, the subprocess stdout will be captured and saved to it.
305  * If `stderr` is set, the subprocess stderr will be captured and saved to it.
306  *
307  * If `command` exits normally, the lower 8 bits of the return code will be
308  * returned in a value between 0 and 255.
309  * If some setup fails, `command` fails to start, or `command` exits due to a
310  * signal, the return value will be negative.
311  */
312 int RunWithManagedStdio(Command&& command, const std::string* stdin,
313                         std::string* stdout, std::string* stderr,
314                         SubprocessOptions options = SubprocessOptions());
315 
316 // Convenience wrapper around Command and Subprocess class, allows to easily
317 // execute a command and wait for it to complete. The version without the env
318 // parameter starts the command with the same environment as the parent. Returns
319 // zero if the command completed successfully, non zero otherwise.
320 int execute(const std::vector<std::string>& command,
321             const std::vector<std::string>& env);
322 int execute(const std::vector<std::string>& command);
323 
324 }  // namespace cuttlefish
325