blob: 65e3b380714daf184121886e9b561cc676e45d73 [file] [log] [blame]
// 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/utils/command.h"
#define WIN32_LEAN_AND_MEAN 1
#include <Windows.h>
#include <sstream>
#include <string>
namespace tint {
namespace utils {
namespace {
/// Handle is a simple wrapper around the Win32 HANDLE
class Handle {
public:
/// Constructor
Handle() : handle_(nullptr) {}
/// Constructor
explicit Handle(HANDLE handle) : handle_(handle) {}
/// Destructor
~Handle() { Close(); }
/// Move assignment operator
Handle& operator=(Handle&& rhs) {
Close();
handle_ = rhs.handle_;
rhs.handle_ = nullptr;
return *this;
}
/// Closes the handle (if it wasn't already closed)
void Close() {
if (handle_) {
CloseHandle(handle_);
}
handle_ = nullptr;
}
/// @returns the handle
operator HANDLE() { return handle_; }
/// @returns true if the handle is not invalid
operator bool() { return handle_ != nullptr; }
private:
Handle(const Handle&) = delete;
Handle& operator=(const Handle&) = delete;
HANDLE handle_ = nullptr;
};
/// Pipe is a simple wrapper around a Win32 CreatePipe() function
class Pipe {
public:
/// Constructs the pipe
Pipe() {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = nullptr;
HANDLE hread;
HANDLE hwrite;
if (CreatePipe(&hread, &hwrite, &sa, 0)) {
read = Handle(hread);
write = Handle(hwrite);
// Ensure the read handle to the pipe is not inherited
if (!SetHandleInformation(read, HANDLE_FLAG_INHERIT, 0)) {
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
Handle read;
/// The writer end of the pipe
Handle write;
};
bool ExecutableExists(const std::string& path) {
DWORD type = 0;
return GetBinaryTypeA(path.c_str(), &type);
}
std::string FindExecutable(const std::string& name) {
if (ExecutableExists(name)) {
return name;
}
if (ExecutableExists(name + ".exe")) {
return name + ".exe";
}
if (name.find("/") == std::string::npos &&
name.find("\\") == std::string::npos) {
char* path_env = nullptr;
size_t path_env_len = 0;
if (_dupenv_s(&path_env, &path_env_len, "PATH")) {
return "";
}
std::istringstream path{path_env};
free(path_env);
std::string dir;
while (getline(path, dir, ';')) {
auto test = dir + "\\" + name;
if (ExecutableExists(test)) {
return test;
}
if (ExecutableExists(test + ".exe")) {
return test + ".exe";
}
}
}
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 {
Pipe stdout_pipe;
Pipe stderr_pipe;
Pipe stdin_pipe;
if (!stdin_pipe || !stdout_pipe || !stderr_pipe) {
Output output;
output.err = "Command::Exec(): Failed to create pipes";
return output;
}
if (!input_.empty()) {
if (!WriteFile(stdin_pipe.write, input_.data(), input_.size(), nullptr,
nullptr)) {
Output output;
output.err = "Command::Exec() Failed to write stdin";
return output;
}
}
stdin_pipe.write.Close();
STARTUPINFOA si{};
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdOutput = stdout_pipe.write;
si.hStdError = stderr_pipe.write;
si.hStdInput = stdin_pipe.read;
std::stringstream args;
args << path_;
for (auto& arg : arguments) {
args << " " << arg;
}
PROCESS_INFORMATION pi{};
if (!CreateProcessA(nullptr, // No module name (use command line)
const_cast<LPSTR>(args.str().c_str()), // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Handles are inherited
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi)) { // Pointer to PROCESS_INFORMATION structure
Output out;
out.err = "Command::Exec() CreateProcess() failed";
return out;
}
stdout_pipe.write.Close();
stderr_pipe.write.Close();
Output output;
char buf[256];
HANDLE handles[] = {stdout_pipe.read, stderr_pipe.read};
bool stdout_open = true;
bool stderr_open = true;
while (stdout_open || stderr_open) {
auto res = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
switch (res) {
case WAIT_FAILED:
output.err = "Command::Exec() WaitForMultipleObjects() returned " +
std::to_string(res);
return output;
case WAIT_OBJECT_0: { // stdout
DWORD n = 0;
if (ReadFile(stdout_pipe.read, buf, sizeof(buf), &n, NULL)) {
output.out += std::string(buf, buf + n);
} else {
stdout_open = false;
}
break;
}
case WAIT_OBJECT_0 + 1: { // stderr
DWORD n = 0;
if (ReadFile(stderr_pipe.read, buf, sizeof(buf), &n, NULL)) {
output.err += std::string(buf, buf + n);
} else {
stderr_open = false;
}
break;
}
}
}
WaitForSingleObject(pi.hProcess, INFINITE);
return output;
}
} // namespace utils
} // namespace tint