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 
20 #include <functional>
21 #include <map>
22 #include <sstream>
23 #include <string>
24 #include <vector>
25 
26 #include <common/libs/fs/shared_fd.h>
27 
28 namespace cvd {
29 class Command;
30 class Subprocess;
31 class SubprocessOptions;
32 using SubprocessStopper = std::function<bool(Subprocess*)>;
33 // Kills a process by sending it the SIGKILL signal.
34 bool KillSubprocess(Subprocess* subprocess);
35 
36 // Keeps track of a running (sub)process. Allows to wait for its completion.
37 // It's an error to wait twice for the same subprocess.
38 class Subprocess {
39  public:
40   enum class StdIOChannel {
41     kStdIn = 0,
42     kStdOut = 1,
43     kStdErr = 2,
44   };
45 
46   Subprocess(pid_t pid, SharedFD control, SubprocessStopper stopper = KillSubprocess)
pid_(pid)47       : pid_(pid),
48         started_(pid > 0),
49         control_socket_(control),
50         stopper_(stopper) {}
51   // The default implementation won't do because we need to reset the pid of the
52   // moved object.
53   Subprocess(Subprocess&&);
54   ~Subprocess() = default;
55   Subprocess& operator=(Subprocess&&);
56   // Waits for the subprocess to complete. Returns zero if completed
57   // successfully, non-zero otherwise.
58   int Wait();
59   // Same as waitpid(2)
60   pid_t Wait(int* wstatus, int options);
61   // Whether the command started successfully. It only says whether the call to
62   // fork() succeeded or not, it says nothing about exec or successful
63   // completion of the command, that's what Wait is for.
Started()64   bool Started() const { return started_; }
control_socket()65   SharedFD control_socket() { return control_socket_; }
pid()66   pid_t pid() const { return pid_; }
Stop()67   bool Stop() { return stopper_(this); }
68 
69  private:
70   // Copy is disabled to avoid waiting twice for the same pid (the first wait
71   // frees the pid, which allows the kernel to reuse it so we may end up waiting
72   // for the wrong process)
73   Subprocess(const Subprocess&) = delete;
74   Subprocess& operator=(const Subprocess&) = delete;
75   pid_t pid_ = -1;
76   bool started_ = false;
77   SharedFD control_socket_;
78   SubprocessStopper stopper_;
79 };
80 
81 class SubprocessOptions {
82   bool with_control_socket_;
83   bool verbose_;
84   bool exit_with_parent_;
85   bool in_group_;
86 public:
SubprocessOptions()87   SubprocessOptions() : with_control_socket_(false), verbose_(true),
88                         exit_with_parent_(true) {}
89 
90   // If with_control_socket is true the Subprocess instance will have a SharedFD
91   // that enables communication with the child process.
WithControlSocket(bool with_control_socket)92   void WithControlSocket(bool with_control_socket) {
93     with_control_socket_ = with_control_socket;
94   }
Verbose(bool verbose)95   void Verbose(bool verbose) {
96     verbose_ = verbose;
97   }
ExitWithParent(bool exit_with_parent)98   void ExitWithParent(bool exit_with_parent) {
99     exit_with_parent_ = exit_with_parent;
100   }
101   // The subprocess runs as head of its own process group.
InGroup(bool in_group)102   void InGroup(bool in_group) {
103     in_group_ = in_group;
104   }
105 
WithControlSocket()106   bool WithControlSocket() const { return with_control_socket_; }
Verbose()107   bool Verbose() const { return verbose_; }
ExitWithParent()108   bool ExitWithParent() const { return exit_with_parent_; }
InGroup()109   bool InGroup() const { return in_group_; }
110 };
111 
112 // An executable command. Multiple subprocesses can be started from the same
113 // command object. This class owns any file descriptors that the subprocess
114 // should inherit.
115 class Command {
116  private:
117   template <typename T>
118   // For every type other than SharedFD (for which there is a specialisation)
BuildParameter(std::stringstream * stream,T t)119   bool BuildParameter(std::stringstream* stream, T t) {
120     *stream << t;
121     return true;
122   }
123   // Special treatment for SharedFD
124   bool BuildParameter(std::stringstream* stream, SharedFD shared_fd);
125   template <typename T, typename... Args>
BuildParameter(std::stringstream * stream,T t,Args...args)126   bool BuildParameter(std::stringstream* stream, T t, Args... args) {
127     return BuildParameter(stream, t) && BuildParameter(stream, args...);
128   }
129 
130  public:
131   class ParameterBuilder {
132    public:
ParameterBuilder(Command * cmd)133     ParameterBuilder(Command* cmd) : cmd_(cmd){};
134     ParameterBuilder(ParameterBuilder&& builder) = default;
135     ~ParameterBuilder();
136 
137     template <typename T>
138     ParameterBuilder& operator<<(T t) {
139       cmd_->BuildParameter(&stream_, t);
140       return *this;
141     }
142 
143     void Build();
144 
145    private:
146     cvd::Command* cmd_;
147     std::stringstream stream_;
148   };
149 
150   // Constructs a command object from the path to an executable binary and an
151   // optional subprocess stopper. When not provided, stopper defaults to sending
152   // SIGKILL to the subprocess.
153   Command(const std::string& executable,
154           SubprocessStopper stopper = KillSubprocess)
subprocess_stopper_(stopper)155       : subprocess_stopper_(stopper) {
156     command_.push_back(executable);
157   }
158   Command(Command&&) = default;
159   // The default copy constructor is unsafe because it would mean multiple
160   // closing of the inherited file descriptors. If needed it can be implemented
161   // using dup(2)
162   Command(const Command&) = delete;
163   Command& operator=(const Command&) = delete;
164   ~Command();
165 
166   // Specify the environment for the subprocesses to be started. By default
167   // subprocesses inherit the parent's environment.
SetEnvironment(const std::vector<std::string> & env)168   void SetEnvironment(const std::vector<std::string>& env) {
169     use_parent_env_ = false;
170     env_ = env;
171   }
172   // Adds a single parameter to the command. All arguments are concatenated into
173   // a single string to form a parameter. If one of those arguments is a
174   // SharedFD a duplicate of it will be used and won't be closed until the
175   // object is destroyed. To add multiple parameters to the command the function
176   // must be called multiple times, one per parameter.
177   template <typename... Args>
AddParameter(Args...args)178   bool AddParameter(Args... args) {
179     std::stringstream ss;
180     if (BuildParameter(&ss, args...)) {
181       command_.push_back(ss.str());
182       return true;
183     }
184     return false;
185   }
186 
GetParameterBuilder()187   ParameterBuilder GetParameterBuilder() { return ParameterBuilder(this); }
188 
189   // Redirects the standard IO of the command.
190   bool RedirectStdIO(Subprocess::StdIOChannel channel, cvd::SharedFD shared_fd);
191   bool RedirectStdIO(Subprocess::StdIOChannel subprocess_channel,
192                      Subprocess::StdIOChannel parent_channel);
193 
194   // Starts execution of the command. This method can be called multiple times,
195   // effectively staring multiple (possibly concurrent) instances.
196   Subprocess Start(SubprocessOptions options = SubprocessOptions()) const;
197 
GetShortName()198   std::string GetShortName() const {
199     // This is safe because the constructor guarantees the name of the binary to
200     // be at index 0 on the vector
201     return command_[0];
202   }
203 
204  private:
205   std::vector<std::string> command_;
206   std::map<cvd::SharedFD, int> inherited_fds_{};
207   std::map<Subprocess::StdIOChannel, int> redirects_{};
208   bool use_parent_env_ = true;
209   std::vector<std::string> env_{};
210   SubprocessStopper subprocess_stopper_;
211 };
212 
213 /*
214  * Consumes a cvd::Command and runs it, optionally managing the stdio channels.
215  *
216  * If `stdin` is set, the subprocess stdin will be pipe providing its contents.
217  * If `stdout` is set, the subprocess stdout will be captured and saved to it.
218  * If `stderr` is set, the subprocess stderr will be captured and saved to it.
219  *
220  * If `command` exits normally, the lower 8 bits of the return code will be
221  * returned in a value between 0 and 255.
222  * If some setup fails, `command` fails to start, or `command` exits due to a
223  * signal, the return value will be negative.
224  */
225 int RunWithManagedStdio(cvd::Command&& command, const std::string* stdin,
226                         std::string* stdout, std::string* stderr,
227                         SubprocessOptions options = SubprocessOptions());
228 
229 // Convenience wrapper around Command and Subprocess class, allows to easily
230 // execute a command and wait for it to complete. The version without the env
231 // parameter starts the command with the same environment as the parent. Returns
232 // zero if the command completed successfully, non zero otherwise.
233 int execute(const std::vector<std::string>& command,
234             const std::vector<std::string>& env);
235 int execute(const std::vector<std::string>& command);
236 
237 }  // namespace cvd
238