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 cuttlefish { 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 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, 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<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 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(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 cuttlefish 238