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 <atomic> 22 #include <cstdio> 23 #include <cstring> 24 #include <functional> 25 #include <map> 26 #include <optional> 27 #include <ostream> 28 #include <sstream> 29 #include <string> 30 #include <type_traits> 31 #include <unordered_map> 32 #include <utility> 33 #include <vector> 34 35 #include <android-base/logging.h> 36 #include <android-base/strings.h> 37 38 #include "common/libs/fs/shared_fd.h" 39 40 namespace cuttlefish { 41 42 /* 43 * Does what ArgsToVec(int argc, char**) from flag_parser.h does 44 * without argc. 45 */ 46 std::vector<std::string> ArgsToVec(char** argv); 47 std::unordered_map<std::string, std::string> EnvpToMap(char** envp); 48 49 enum class StopperResult { 50 kStopFailure, /* Failed to stop the subprocess. */ 51 kStopCrash, /* Attempted to stop the subprocess cleanly, but that failed. */ 52 kStopSuccess, /* The subprocess exited in the expected way. */ 53 }; 54 55 class Subprocess; 56 using SubprocessStopper = std::function<StopperResult(Subprocess*)>; 57 // Kills a process by sending it the SIGKILL signal. 58 StopperResult KillSubprocess(Subprocess* subprocess); 59 /* Creates a `SubprocessStopper` that first tries `nice_stopper` then falls back 60 * to `KillSubprocess` if that fails. */ 61 SubprocessStopper KillSubprocessFallback(std::function<StopperResult()>); 62 SubprocessStopper KillSubprocessFallback(SubprocessStopper nice_stopper); 63 64 // Keeps track of a running (sub)process. Allows to wait for its completion. 65 // It's an error to wait twice for the same subprocess. 66 class Subprocess { 67 public: 68 enum class StdIOChannel { 69 kStdIn = 0, 70 kStdOut = 1, 71 kStdErr = 2, 72 }; 73 74 Subprocess(pid_t pid, SubprocessStopper stopper = KillSubprocess) pid_(pid)75 : pid_(pid), started_(pid > 0), stopper_(stopper) {} 76 // The default implementation won't do because we need to reset the pid of the 77 // moved object. 78 Subprocess(Subprocess&&); 79 ~Subprocess() = default; 80 Subprocess& operator=(Subprocess&&); 81 // Waits for the subprocess to complete. Returns zero if completed 82 // successfully, non-zero otherwise. 83 int Wait(); 84 // Same as waitid(2) 85 int Wait(siginfo_t* infop, int options); 86 // Whether the command started successfully. It only says whether the call to 87 // fork() succeeded or not, it says nothing about exec or successful 88 // completion of the command, that's what Wait is for. Started()89 bool Started() const { return started_; } pid()90 pid_t pid() const { return pid_; } Stop()91 StopperResult Stop() { return stopper_(this); } 92 93 Result<void> SendSignal(const int signal); 94 Result<void> SendSignalToGroup(const int signal); 95 96 private: 97 // Copy is disabled to avoid waiting twice for the same pid (the first wait 98 // frees the pid, which allows the kernel to reuse it so we may end up waiting 99 // for the wrong process) 100 Subprocess(const Subprocess&) = delete; 101 Subprocess& operator=(const Subprocess&) = delete; 102 std::atomic<pid_t> pid_ = -1; 103 bool started_ = false; 104 SubprocessStopper stopper_; 105 }; 106 107 class SubprocessOptions { 108 public: SubprocessOptions()109 SubprocessOptions() 110 : verbose_(true), exit_with_parent_(true), in_group_(false) {} 111 SubprocessOptions& Verbose(bool verbose) &; 112 SubprocessOptions Verbose(bool verbose) &&; 113 SubprocessOptions& ExitWithParent(bool exit_with_parent) &; 114 SubprocessOptions ExitWithParent(bool exit_with_parent) &&; 115 SubprocessOptions& SandboxArguments(std::vector<std::string>) &; 116 SubprocessOptions SandboxArguments(std::vector<std::string>) &&; 117 // The subprocess runs as head of its own process group. 118 SubprocessOptions& InGroup(bool in_group) &; 119 SubprocessOptions InGroup(bool in_group) &&; 120 121 SubprocessOptions& Strace(std::string strace_output_path) &; 122 SubprocessOptions Strace(std::string strace_output_path) &&; 123 Verbose()124 bool Verbose() const { return verbose_; } ExitWithParent()125 bool ExitWithParent() const { return exit_with_parent_; } InGroup()126 bool InGroup() const { return in_group_; } Strace()127 const std::string& Strace() const { return strace_; } 128 129 private: 130 bool verbose_; 131 bool exit_with_parent_; 132 bool in_group_; 133 std::string strace_; 134 }; 135 136 // An executable command. Multiple subprocesses can be started from the same 137 // command object. This class owns any file descriptors that the subprocess 138 // should inherit. 139 class Command { 140 private: 141 template <typename T> 142 // For every type other than SharedFD (for which there is a specialisation) BuildParameter(std::stringstream * stream,T t)143 void BuildParameter(std::stringstream* stream, T t) { 144 *stream << t; 145 } 146 // Special treatment for SharedFD 147 void BuildParameter(std::stringstream* stream, SharedFD shared_fd); 148 template <typename T, typename... Args> BuildParameter(std::stringstream * stream,T t,Args...args)149 void BuildParameter(std::stringstream* stream, T t, Args... args) { 150 BuildParameter(stream, t); 151 BuildParameter(stream, args...); 152 } 153 154 public: 155 // Constructs a command object from the path to an executable binary and an 156 // optional subprocess stopper. When not provided, stopper defaults to sending 157 // SIGKILL to the subprocess. 158 Command(std::string executable, SubprocessStopper stopper = KillSubprocess); 159 Command(Command&&) = default; 160 // The default copy constructor is unsafe because it would mean multiple 161 // closing of the inherited file descriptors. If needed it can be implemented 162 // using dup(2) 163 Command(const Command&) = delete; 164 Command& operator=(const Command&) = delete; 165 ~Command(); 166 Executable()167 const std::string& Executable() const { 168 return executable_ ? *executable_ : command_[0]; 169 } 170 SetExecutable(std::string executable)171 Command& SetExecutable(std::string executable) & { 172 executable_ = std::move(executable); 173 return *this; 174 } SetExecutable(std::string executable)175 Command SetExecutable(std::string executable) && { 176 return std::move(SetExecutable(executable)); 177 } 178 SetName(std::string name)179 Command& SetName(std::string name) & { 180 command_[0] = std::move(name); 181 return *this; 182 } SetName(std::string name)183 Command SetName(std::string name) && { 184 return std::move(SetName(std::move(name))); 185 } 186 SetExecutableAndName(std::string name)187 Command& SetExecutableAndName(std::string name) & { 188 return SetExecutable(name).SetName(std::move(name)); 189 } 190 SetExecutableAndName(std::string name)191 Command SetExecutableAndName(std::string name) && { 192 return std::move(SetExecutableAndName(std::move(name))); 193 } 194 SetStopper(SubprocessStopper stopper)195 Command& SetStopper(SubprocessStopper stopper) & { 196 subprocess_stopper_ = std::move(stopper); 197 return *this; 198 } SetStopper(SubprocessStopper stopper)199 Command SetStopper(SubprocessStopper stopper) && { 200 return std::move(SetStopper(std::move(stopper))); 201 } 202 203 // Specify the environment for the subprocesses to be started. By default 204 // subprocesses inherit the parent's environment. SetEnvironment(std::vector<std::string> env)205 Command& SetEnvironment(std::vector<std::string> env) & { 206 env_ = std::move(env); 207 return *this; 208 } SetEnvironment(std::vector<std::string> env)209 Command SetEnvironment(std::vector<std::string> env) && { 210 return std::move(SetEnvironment(std::move(env))); 211 } 212 AddEnvironmentVariable(const std::string & env_var,const std::string & value)213 Command& AddEnvironmentVariable(const std::string& env_var, 214 const std::string& value) & { 215 return AddEnvironmentVariable(env_var + "=" + value); 216 } AddEnvironmentVariable(const std::string & env_var,const std::string & value)217 Command AddEnvironmentVariable(const std::string& env_var, 218 const std::string& value) && { 219 AddEnvironmentVariable(env_var, value); 220 return std::move(*this); 221 } 222 AddEnvironmentVariable(std::string env_var)223 Command& AddEnvironmentVariable(std::string env_var) & { 224 env_.emplace_back(std::move(env_var)); 225 return *this; 226 } AddEnvironmentVariable(std::string env_var)227 Command AddEnvironmentVariable(std::string env_var) && { 228 return std::move(AddEnvironmentVariable(std::move(env_var))); 229 } 230 231 // Specify an environment variable to be unset from the parent's 232 // environment for the subprocesses to be started. UnsetFromEnvironment(const std::string & env_var)233 Command& UnsetFromEnvironment(const std::string& env_var) & { 234 auto it = env_.begin(); 235 while (it != env_.end()) { 236 if (android::base::StartsWith(*it, env_var + "=")) { 237 it = env_.erase(it); 238 } else { 239 ++it; 240 } 241 } 242 return *this; 243 } UnsetFromEnvironment(const std::string & env_var)244 Command UnsetFromEnvironment(const std::string& env_var) && { 245 return std::move(UnsetFromEnvironment(env_var)); 246 } 247 248 // Adds a single parameter to the command. All arguments are concatenated into 249 // a single string to form a parameter. If one of those arguments is a 250 // SharedFD a duplicate of it will be used and won't be closed until the 251 // object is destroyed. To add multiple parameters to the command the function 252 // must be called multiple times, one per parameter. 253 template <typename... Args> AddParameter(Args...args)254 Command& AddParameter(Args... args) & { 255 std::stringstream ss; 256 BuildParameter(&ss, args...); 257 command_.push_back(ss.str()); 258 return *this; 259 } 260 template <typename... Args> AddParameter(Args...args)261 Command AddParameter(Args... args) && { 262 return std::move(AddParameter(std::forward<Args>(args)...)); 263 } 264 // Similar to AddParameter, except the args are appended to the last (most 265 // recently-added) parameter in the command. 266 template <typename... Args> AppendToLastParameter(Args...args)267 Command& AppendToLastParameter(Args... args) & { 268 CHECK(!command_.empty()) << "There is no parameter to append to."; 269 std::stringstream ss; 270 BuildParameter(&ss, args...); 271 command_[command_.size() - 1] += ss.str(); 272 return *this; 273 } 274 template <typename... Args> AppendToLastParameter(Args...args)275 Command AppendToLastParameter(Args... args) && { 276 return std::move(AppendToLastParameter(std::forward<Args>(args)...)); 277 } 278 279 // Redirects the standard IO of the command. 280 Command& RedirectStdIO(Subprocess::StdIOChannel channel, 281 SharedFD shared_fd) &; 282 Command RedirectStdIO(Subprocess::StdIOChannel channel, 283 SharedFD shared_fd) &&; 284 Command& RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, 285 Subprocess::StdIOChannel parent_channel) &; 286 Command RedirectStdIO(Subprocess::StdIOChannel subprocess_channel, 287 Subprocess::StdIOChannel parent_channel) &&; 288 289 Command& SetWorkingDirectory(const std::string& path) &; 290 Command SetWorkingDirectory(const std::string& path) &&; 291 Command& SetWorkingDirectory(SharedFD dirfd) &; 292 Command SetWorkingDirectory(SharedFD dirfd) &&; 293 294 Command& AddPrerequisite(const std::function<Result<void>()>& prerequisite) &; 295 Command AddPrerequisite(const std::function<Result<void>()>& prerequisite) &&; 296 297 // Starts execution of the command. This method can be called multiple times, 298 // effectively staring multiple (possibly concurrent) instances. 299 Subprocess Start(SubprocessOptions options = SubprocessOptions()) const; 300 GetShortName()301 std::string GetShortName() const { 302 // This is safe because the constructor guarantees the name of the binary to 303 // be at index 0 on the vector 304 return command_[0]; 305 } 306 307 // Generates the contents for a bash script that can be used to run this 308 // command. Note that this command must not require any file descriptors 309 // or stdio redirects as those would not be available when the bash script 310 // is run. 311 std::string AsBashScript(const std::string& redirected_stdio_path = "") const; 312 313 private: 314 std::optional<std::string> executable_; // When unset, use command_[0] 315 std::vector<std::string> command_; 316 std::vector<std::function<Result<void>()>> prerequisites_; 317 std::map<SharedFD, int> inherited_fds_{}; 318 std::map<Subprocess::StdIOChannel, int> redirects_{}; 319 std::vector<std::string> env_{}; 320 SubprocessStopper subprocess_stopper_; 321 SharedFD working_directory_; 322 }; 323 324 /* 325 * Consumes a Command and runs it, optionally managing the stdio channels. 326 * 327 * If `stdin` is set, the subprocess stdin will be pipe providing its contents. 328 * If `stdout` is set, the subprocess stdout will be captured and saved to it. 329 * If `stderr` is set, the subprocess stderr will be captured and saved to it. 330 * 331 * If `command` exits normally, the lower 8 bits of the return code will be 332 * returned in a value between 0 and 255. 333 * If some setup fails, `command` fails to start, or `command` exits due to a 334 * signal, the return value will be negative. 335 */ 336 int RunWithManagedStdio(Command&& command, const std::string* stdin, 337 std::string* stdout, std::string* stderr, 338 SubprocessOptions options = SubprocessOptions()); 339 340 /** 341 * Returns the exit status on success, negative values on error 342 * 343 * If failed in fork() or exec(), returns -1. 344 * If the child exited from an unhandled signal, returns -1. 345 * Otherwise, returns the exit status. 346 * 347 * TODO: Changes return type to Result<int> 348 * 349 * For now, too many callsites expects int, and needs quite a lot of changes 350 * if we change the return type. 351 */ 352 int Execute(const std::vector<std::string>& commands); 353 int Execute(const std::vector<std::string>& commands, 354 const std::vector<std::string>& envs); 355 356 /** 357 * Similar as the two above but returns CF_ERR instead of -1, and siginfo_t 358 * instead of the exit status. 359 */ 360 Result<siginfo_t> Execute(const std::vector<std::string>& commands, 361 SubprocessOptions subprocess_options, 362 int wait_options); 363 Result<siginfo_t> Execute(const std::vector<std::string>& commands, 364 const std::vector<std::string>& envs, 365 SubprocessOptions subprocess_options, 366 int wait_options); 367 368 } // namespace cuttlefish 369