dawn-cmake/src/tint/utils/io/command_posix.cc
Antonio Maiorano 50bddbffca tint: Fix dxc on Linux
Fixes "dxc failed : unable to parse shader model.".

On windows the called program is responsible for splitting arguments
from one joined string. Command line arguments on 'nix systems need to
be passed as separate strings. This CL makes it so that we pass in the
arg separately, and support ignoring empty string args to make writing
this code easier.

Change-Id: Ia9618c2a743f8fdb49913572e2bbfc4bd1519d3a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/98110
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
2022-08-03 21:40:46 +00:00

265 lines
7.3 KiB
C++

// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/utils/io/command.h"
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sstream>
#include <vector>
namespace tint::utils {
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
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() {
int pipes[2] = {};
if (pipe(pipes) == 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
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;
}
std::string FindExecutable(const std::string& name) {
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
pollfd poll_fds[2];
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, 2, -1) < 0) {
break;
}
char buf[256];
if (poll_fds[0].revents & POLLIN) {
auto n = read(stdout_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.out += std::string(buf, buf + n);
}
}
if (poll_fds[0].revents & POLLHUP) {
stdout_open = false;
}
if (poll_fds[1].revents & POLLIN) {
auto n = read(stderr_pipe.read, buf, sizeof(buf));
if (n > 0) {
output.err += std::string(buf, buf + 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::utils