|  | // Copyright 2021 The Dawn & Tint Authors | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | // 1. Redistributions of source code must retain the above copyright notice, this | 
|  | //    list of conditions and the following disclaimer. | 
|  | // | 
|  | // 2. Redistributions in binary form must reproduce the above copyright notice, | 
|  | //    this list of conditions and the following disclaimer in the documentation | 
|  | //    and/or other materials provided with the distribution. | 
|  | // | 
|  | // 3. Neither the name of the copyright holder nor the names of its | 
|  | //    contributors may be used to endorse or promote products derived from | 
|  | //    this software without specific prior written permission. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
|  | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | 
|  | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
|  | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | 
|  | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | 
|  | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | 
|  | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 
|  | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | // GEN_BUILD:CONDITION(tint_build_is_linux || tint_build_is_mac) | 
|  |  | 
|  | #include "src/tint/utils/command/command.h" | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <limits.h> | 
|  | #include <sys/poll.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/wait.h> | 
|  | #include <unistd.h> | 
|  | #include <array> | 
|  | #include <sstream> | 
|  | #include <vector> | 
|  |  | 
|  | #include "src/tint/utils/macros/compiler.h" | 
|  | #include "src/tint/utils/system/executable_path.h" | 
|  |  | 
|  | namespace tint { | 
|  | namespace { | 
|  |  | 
|  | /// File is a simple wrapper around a POSIX file descriptor | 
|  | class File { | 
|  | constexpr static const int kClosed = -1; | 
|  |  | 
|  | public: | 
|  | /// Constructor | 
|  | File() : handle_(kClosed) {} | 
|  |  | 
|  | /// Constructor | 
|  | explicit File(int handle) : handle_(handle) {} | 
|  |  | 
|  | /// Destructor | 
|  | ~File() { Close(); } | 
|  |  | 
|  | /// Move assignment operator | 
|  | File& operator=(File&& rhs) { | 
|  | Close(); | 
|  | handle_ = rhs.handle_; | 
|  | rhs.handle_ = kClosed; | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | /// Closes the file (if it wasn't already closed) | 
|  | void Close() { | 
|  | if (handle_ != kClosed) { | 
|  | close(handle_); | 
|  | } | 
|  | handle_ = kClosed; | 
|  | } | 
|  |  | 
|  | /// @returns the file handle | 
|  | operator int() { return handle_; } | 
|  |  | 
|  | /// @returns true if the file is not closed | 
|  | explicit operator bool() { return handle_ != kClosed; } | 
|  |  | 
|  | private: | 
|  | File(const File&) = delete; | 
|  | File& operator=(const File&) = delete; | 
|  |  | 
|  | int handle_ = kClosed; | 
|  | }; | 
|  |  | 
|  | /// Pipe is a simple wrapper around a POSIX pipe() function | 
|  | class Pipe { | 
|  | public: | 
|  | /// Constructs the pipe | 
|  | Pipe() { | 
|  | std::array<int, 2> pipes = {}; | 
|  | if (pipe(pipes.data()) == 0) { | 
|  | read = File(pipes[0]); | 
|  | write = File(pipes[1]); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Closes both the read and write files (if they're not already closed) | 
|  | void Close() { | 
|  | read.Close(); | 
|  | write.Close(); | 
|  | } | 
|  |  | 
|  | /// @returns true if the pipe has an open read or write file | 
|  | explicit operator bool() { return read || write; } | 
|  |  | 
|  | /// The reader end of the pipe | 
|  | File read; | 
|  |  | 
|  | /// The writer end of the pipe | 
|  | File write; | 
|  | }; | 
|  |  | 
|  | bool ExecutableExists(const std::string& path) { | 
|  | struct stat s {}; | 
|  | if (stat(path.c_str(), &s) != 0) { | 
|  | return false; | 
|  | } | 
|  | return (s.st_mode & S_IXUSR) != 0u; | 
|  | } | 
|  |  | 
|  | std::string GetCWD() { | 
|  | char cwd[PATH_MAX] = ""; | 
|  | [[maybe_unused]] auto res = getcwd(cwd, sizeof(cwd)); | 
|  | return cwd; | 
|  | } | 
|  |  | 
|  | std::string FindExecutable(const std::string& name) { | 
|  | if (name.length() >= 1 && name[0] != '/') { | 
|  | auto in_cwd = GetCWD() + "/" + name; | 
|  | if (ExecutableExists(in_cwd)) { | 
|  | return in_cwd; | 
|  | } | 
|  | auto in_exe_path = tint::ExecutableDirectory() + "/" + name; | 
|  | if (ExecutableExists(in_exe_path)) { | 
|  | return in_exe_path; | 
|  | } | 
|  | } | 
|  | if (ExecutableExists(name)) { | 
|  | return name; | 
|  | } | 
|  | if (name.find("/") == std::string::npos) { | 
|  | auto* path_env = getenv("PATH"); | 
|  | if (!path_env) { | 
|  | return ""; | 
|  | } | 
|  | std::istringstream path{path_env}; | 
|  | std::string dir; | 
|  | while (getline(path, dir, ':')) { | 
|  | auto test = dir + "/" + name; | 
|  | if (ExecutableExists(test)) { | 
|  | return test; | 
|  | } | 
|  | } | 
|  | } | 
|  | return ""; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | Command::Command(const std::string& path) : path_(path) {} | 
|  |  | 
|  | Command Command::LookPath(const std::string& executable) { | 
|  | return Command(FindExecutable(executable)); | 
|  | } | 
|  |  | 
|  | bool Command::Found() const { | 
|  | return ExecutableExists(path_); | 
|  | } | 
|  |  | 
|  | Command::Output Command::Exec(std::initializer_list<std::string> arguments) const { | 
|  | if (!Found()) { | 
|  | Output out; | 
|  | out.err = "Executable not found"; | 
|  | return out; | 
|  | } | 
|  |  | 
|  | // Pipes used for piping std[in,out,err] to / from the target process. | 
|  | Pipe stdin_pipe; | 
|  | Pipe stdout_pipe; | 
|  | Pipe stderr_pipe; | 
|  |  | 
|  | if (!stdin_pipe || !stdout_pipe || !stderr_pipe) { | 
|  | Output output; | 
|  | output.err = "Command::Exec(): Failed to create pipes"; | 
|  | return output; | 
|  | } | 
|  |  | 
|  | // execv() and friends replace the current process image with the target | 
|  | // process image. To keep process that called this function going, we need to | 
|  | // fork() this process into a child and parent process. | 
|  | // | 
|  | // The child process is responsible for hooking up the pipes to | 
|  | // std[in,out,err]_pipes to STD[IN,OUT,ERR]_FILENO and then calling execv() to | 
|  | // run the target command. | 
|  | // | 
|  | // The parent process is responsible for feeding any input to the stdin_pipe | 
|  | // and collecting output from the std[out,err]_pipes. | 
|  |  | 
|  | int child_id = fork(); | 
|  | if (child_id < 0) { | 
|  | Output output; | 
|  | output.err = "Command::Exec(): fork() failed"; | 
|  | return output; | 
|  | } | 
|  |  | 
|  | if (child_id > 0) { | 
|  | // fork() - parent | 
|  |  | 
|  | // Close the stdout and stderr writer pipes. | 
|  | // This is required for getting poll() POLLHUP events. | 
|  | stdout_pipe.write.Close(); | 
|  | stderr_pipe.write.Close(); | 
|  |  | 
|  | // Write the input to the child process | 
|  | if (!input_.empty()) { | 
|  | ssize_t n = write(stdin_pipe.write, input_.data(), input_.size()); | 
|  | if (n != static_cast<ssize_t>(input_.size())) { | 
|  | Output output; | 
|  | output.err = "Command::Exec(): write() for stdin failed"; | 
|  | return output; | 
|  | } | 
|  | } | 
|  | stdin_pipe.write.Close(); | 
|  |  | 
|  | // Accumulate the stdout and stderr output from the child process | 
|  | std::array<pollfd, 2> poll_fds; | 
|  | poll_fds[0].fd = stdout_pipe.read; | 
|  | poll_fds[0].events = POLLIN; | 
|  | poll_fds[1].fd = stderr_pipe.read; | 
|  | poll_fds[1].events = POLLIN; | 
|  |  | 
|  | Output output; | 
|  | bool stdout_open = true; | 
|  | bool stderr_open = true; | 
|  | while (stdout_open || stderr_open) { | 
|  | if (poll(poll_fds.data(), 2, -1) < 0) { | 
|  | break; | 
|  | } | 
|  | std::array<char, 256> buf; | 
|  | if (poll_fds[0].revents & POLLIN) { | 
|  | auto n = read(stdout_pipe.read, buf.data(), buf.size()); | 
|  | if (n > 0) { | 
|  | output.out += std::string(&buf[0], &buf[static_cast<size_t>(n)]); | 
|  | } | 
|  | } | 
|  | if (poll_fds[0].revents & POLLHUP) { | 
|  | stdout_open = false; | 
|  | } | 
|  | if (poll_fds[1].revents & POLLIN) { | 
|  | auto n = read(stderr_pipe.read, buf.data(), buf.size()); | 
|  | if (n > 0) { | 
|  | output.err += std::string(&buf[0], &buf[static_cast<size_t>(n)]); | 
|  | } | 
|  | } | 
|  | if (poll_fds[1].revents & POLLHUP) { | 
|  | stderr_open = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Get the resulting error code | 
|  | waitpid(child_id, &output.error_code, 0); | 
|  |  | 
|  | return output; | 
|  | } else { | 
|  | // fork() - child | 
|  |  | 
|  | // Redirect the stdin, stdout, stderr pipes for the execv process | 
|  | if ((dup2(stdin_pipe.read, STDIN_FILENO) == -1) || | 
|  | (dup2(stdout_pipe.write, STDOUT_FILENO) == -1) || | 
|  | (dup2(stderr_pipe.write, STDERR_FILENO) == -1)) { | 
|  | fprintf(stderr, "Command::Exec(): Failed to redirect pipes"); | 
|  | exit(errno); | 
|  | } | 
|  |  | 
|  | // Close the pipes, once redirected above, we're now done with them. | 
|  | stdin_pipe.Close(); | 
|  | stdout_pipe.Close(); | 
|  | stderr_pipe.Close(); | 
|  |  | 
|  | // Run target executable | 
|  | std::vector<const char*> args; | 
|  | args.emplace_back(path_.c_str()); | 
|  | for (auto& arg : arguments) { | 
|  | if (!arg.empty()) { | 
|  | args.emplace_back(arg.c_str()); | 
|  | } | 
|  | } | 
|  | args.emplace_back(nullptr); | 
|  | auto res = execv(path_.c_str(), const_cast<char* const*>(args.data())); | 
|  | exit(res); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace tint |