• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "src/utils/io/command.h"
16 
17 #include <sys/poll.h>
18 #include <sys/stat.h>
19 #include <sys/wait.h>
20 #include <unistd.h>
21 #include <sstream>
22 #include <vector>
23 
24 namespace tint {
25 namespace utils {
26 
27 namespace {
28 
29 /// File is a simple wrapper around a POSIX file descriptor
30 class File {
31   constexpr static const int kClosed = -1;
32 
33  public:
34   /// Constructor
File()35   File() : handle_(kClosed) {}
36 
37   /// Constructor
File(int handle)38   explicit File(int handle) : handle_(handle) {}
39 
40   /// Destructor
~File()41   ~File() { Close(); }
42 
43   /// Move assignment operator
operator =(File && rhs)44   File& operator=(File&& rhs) {
45     Close();
46     handle_ = rhs.handle_;
47     rhs.handle_ = kClosed;
48     return *this;
49   }
50 
51   /// Closes the file (if it wasn't already closed)
Close()52   void Close() {
53     if (handle_ != kClosed) {
54       close(handle_);
55     }
56     handle_ = kClosed;
57   }
58 
59   /// @returns the file handle
operator int()60   operator int() { return handle_; }
61 
62   /// @returns true if the file is not closed
operator bool()63   operator bool() { return handle_ != kClosed; }
64 
65  private:
66   File(const File&) = delete;
67   File& operator=(const File&) = delete;
68 
69   int handle_ = kClosed;
70 };
71 
72 /// Pipe is a simple wrapper around a POSIX pipe() function
73 class Pipe {
74  public:
75   /// Constructs the pipe
Pipe()76   Pipe() {
77     int pipes[2] = {};
78     if (pipe(pipes) == 0) {
79       read = File(pipes[0]);
80       write = File(pipes[1]);
81     }
82   }
83 
84   /// Closes both the read and write files (if they're not already closed)
Close()85   void Close() {
86     read.Close();
87     write.Close();
88   }
89 
90   /// @returns true if the pipe has an open read or write file
operator bool()91   operator bool() { return read || write; }
92 
93   /// The reader end of the pipe
94   File read;
95 
96   /// The writer end of the pipe
97   File write;
98 };
99 
ExecutableExists(const std::string & path)100 bool ExecutableExists(const std::string& path) {
101   struct stat s {};
102   if (stat(path.c_str(), &s) != 0) {
103     return false;
104   }
105   return s.st_mode & S_IXUSR;
106 }
107 
FindExecutable(const std::string & name)108 std::string FindExecutable(const std::string& name) {
109   if (ExecutableExists(name)) {
110     return name;
111   }
112   if (name.find("/") == std::string::npos) {
113     auto* path_env = getenv("PATH");
114     if (!path_env) {
115       return "";
116     }
117     std::istringstream path{path_env};
118     std::string dir;
119     while (getline(path, dir, ':')) {
120       auto test = dir + "/" + name;
121       if (ExecutableExists(test)) {
122         return test;
123       }
124     }
125   }
126   return "";
127 }
128 
129 }  // namespace
130 
Command(const std::string & path)131 Command::Command(const std::string& path) : path_(path) {}
132 
LookPath(const std::string & executable)133 Command Command::LookPath(const std::string& executable) {
134   return Command(FindExecutable(executable));
135 }
136 
Found() const137 bool Command::Found() const {
138   return ExecutableExists(path_);
139 }
140 
Exec(std::initializer_list<std::string> arguments) const141 Command::Output Command::Exec(
142     std::initializer_list<std::string> arguments) const {
143   if (!Found()) {
144     Output out;
145     out.err = "Executable not found";
146     return out;
147   }
148 
149   // Pipes used for piping std[in,out,err] to / from the target process.
150   Pipe stdin_pipe;
151   Pipe stdout_pipe;
152   Pipe stderr_pipe;
153 
154   if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
155     Output output;
156     output.err = "Command::Exec(): Failed to create pipes";
157     return output;
158   }
159 
160   // execv() and friends replace the current process image with the target
161   // process image. To keep process that called this function going, we need to
162   // fork() this process into a child and parent process.
163   //
164   // The child process is responsible for hooking up the pipes to
165   // std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to
166   // run the target command.
167   //
168   // The parent process is responsible for feeding any input to the stdin_pipe
169   // and collectting output from the std[out,err]_pipes.
170 
171   int child_id = fork();
172   if (child_id < 0) {
173     Output output;
174     output.err = "Command::Exec(): fork() failed";
175     return output;
176   }
177 
178   if (child_id > 0) {
179     // fork() - parent
180 
181     // Close the stdout and stderr writer pipes.
182     // This is required for getting poll() POLLHUP events.
183     stdout_pipe.write.Close();
184     stderr_pipe.write.Close();
185 
186     // Write the input to the child process
187     if (!input_.empty()) {
188       ssize_t n = write(stdin_pipe.write, input_.data(), input_.size());
189       if (n != static_cast<ssize_t>(input_.size())) {
190         Output output;
191         output.err = "Command::Exec(): write() for stdin failed";
192         return output;
193       }
194     }
195     stdin_pipe.write.Close();
196 
197     // Accumulate the stdout and stderr output from the child process
198     pollfd poll_fds[2];
199     poll_fds[0].fd = stdout_pipe.read;
200     poll_fds[0].events = POLLIN;
201     poll_fds[1].fd = stderr_pipe.read;
202     poll_fds[1].events = POLLIN;
203 
204     Output output;
205     bool stdout_open = true;
206     bool stderr_open = true;
207     while (stdout_open || stderr_open) {
208       if (poll(poll_fds, 2, -1) < 0) {
209         break;
210       }
211       char buf[256];
212       if (poll_fds[0].revents & POLLIN) {
213         auto n = read(stdout_pipe.read, buf, sizeof(buf));
214         if (n > 0) {
215           output.out += std::string(buf, buf + n);
216         }
217       }
218       if (poll_fds[0].revents & POLLHUP) {
219         stdout_open = false;
220       }
221       if (poll_fds[1].revents & POLLIN) {
222         auto n = read(stderr_pipe.read, buf, sizeof(buf));
223         if (n > 0) {
224           output.err += std::string(buf, buf + n);
225         }
226       }
227       if (poll_fds[1].revents & POLLHUP) {
228         stderr_open = false;
229       }
230     }
231 
232     // Get the resulting error code
233     waitpid(child_id, &output.error_code, 0);
234 
235     return output;
236   } else {
237     // fork() - child
238 
239     // Redirect the stdin, stdout, stderr pipes for the execv process
240     if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) ||
241         (dup2(stdout_pipe.write, STDOUT_FILENO) == -1) ||
242         (dup2(stderr_pipe.write, STDERR_FILENO) == -1)) {
243       fprintf(stderr, "Command::Exec(): Failed to redirect pipes");
244       exit(errno);
245     }
246 
247     // Close the pipes, once redirected above, we're now done with them.
248     stdin_pipe.Close();
249     stdout_pipe.Close();
250     stderr_pipe.Close();
251 
252     // Run target executable
253     std::vector<const char*> args;
254     args.emplace_back(path_.c_str());
255     for (auto& arg : arguments) {
256       args.emplace_back(arg.c_str());
257     }
258     args.emplace_back(nullptr);
259     auto res = execv(path_.c_str(), const_cast<char* const*>(args.data()));
260     exit(res);
261   }
262 }
263 
264 }  // namespace utils
265 }  // namespace tint
266