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