/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmUVProcessChain.h" #include #include #include #include // IWYU pragma: keep #include #include #include #include "cm_fileno.hxx" #include "cmGetPipes.h" #include "cmUVHandlePtr.h" struct cmUVProcessChain::InternalData { struct StreamData { int BuiltinStream = -1; uv_stdio_container_t Stdio; }; struct ProcessData { cmUVProcessChain::InternalData* Data; cm::uv_process_ptr Process; cm::uv_pipe_ptr InputPipe; cm::uv_pipe_ptr OutputPipe; Status ProcessStatus; void Finish(); }; const cmUVProcessChainBuilder* Builder = nullptr; bool Valid = false; cm::uv_loop_ptr BuiltinLoop; uv_loop_t* Loop; StreamData InputStreamData; StreamData OutputStreamData; StreamData ErrorStreamData; cm::uv_pipe_ptr TempOutputPipe; cm::uv_pipe_ptr TempErrorPipe; unsigned int ProcessesCompleted = 0; std::vector> Processes; bool Prepare(const cmUVProcessChainBuilder* builder); void SpawnProcess( std::size_t index, const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first, bool last); void Finish(); }; cmUVProcessChainBuilder::cmUVProcessChainBuilder() = default; cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand( const std::vector& arguments) { if (!arguments.empty()) { this->Processes.emplace_back(); this->Processes.back().Arguments = arguments; } return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinLoop() { this->Loop = nullptr; return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalLoop( uv_loop_t& loop) { this->Loop = &loop; return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio) { switch (stdio) { case Stream_INPUT: case Stream_OUTPUT: case Stream_ERROR: { auto& streamData = this->Stdio[stdio]; streamData.Type = None; break; } } return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream( Stream stdio) { switch (stdio) { case Stream_INPUT: // FIXME break; case Stream_OUTPUT: case Stream_ERROR: { auto& streamData = this->Stdio[stdio]; streamData.Type = Builtin; break; } } return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream( Stream stdio, int fd) { switch (stdio) { case Stream_INPUT: case Stream_OUTPUT: case Stream_ERROR: { auto& streamData = this->Stdio[stdio]; streamData.Type = External; streamData.FileDescriptor = fd; break; } } return *this; } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream( Stream stdio, FILE* stream) { int fd = cm_fileno(stream); if (fd >= 0) { return this->SetExternalStream(stdio, fd); } return this->SetNoStream(stdio); } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetMergedBuiltinStreams() { this->MergedBuiltinStreams = true; return this->SetBuiltinStream(Stream_OUTPUT).SetBuiltinStream(Stream_ERROR); } cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetWorkingDirectory( std::string dir) { this->WorkingDirectory = std::move(dir); return *this; } uv_loop_t* cmUVProcessChainBuilder::GetLoop() const { return this->Loop; } cmUVProcessChain cmUVProcessChainBuilder::Start() const { cmUVProcessChain chain; if (!chain.Data->Prepare(this)) { return chain; } for (std::size_t i = 0; i < this->Processes.size(); i++) { chain.Data->SpawnProcess(i, this->Processes[i], i == 0, i == this->Processes.size() - 1); } chain.Data->Finish(); return chain; } bool cmUVProcessChain::InternalData::Prepare( const cmUVProcessChainBuilder* builder) { this->Builder = builder; if (this->Builder->Loop) { this->Loop = this->Builder->Loop; } else { this->BuiltinLoop.init(); this->Loop = this->BuiltinLoop; } auto const& input = this->Builder->Stdio[cmUVProcessChainBuilder::Stream_INPUT]; auto& inputData = this->InputStreamData; switch (input.Type) { case cmUVProcessChainBuilder::None: inputData.Stdio.flags = UV_IGNORE; break; case cmUVProcessChainBuilder::Builtin: { // FIXME break; } case cmUVProcessChainBuilder::External: inputData.Stdio.flags = UV_INHERIT_FD; inputData.Stdio.data.fd = input.FileDescriptor; break; } auto const& error = this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR]; auto& errorData = this->ErrorStreamData; switch (error.Type) { case cmUVProcessChainBuilder::None: errorData.Stdio.flags = UV_IGNORE; break; case cmUVProcessChainBuilder::Builtin: { int pipeFd[2]; if (cmGetPipes(pipeFd) < 0) { return false; } errorData.BuiltinStream = pipeFd[0]; errorData.Stdio.flags = UV_INHERIT_FD; errorData.Stdio.data.fd = pipeFd[1]; if (this->TempErrorPipe.init(*this->Loop, 0) < 0) { return false; } if (uv_pipe_open(this->TempErrorPipe, errorData.Stdio.data.fd) < 0) { return false; } break; } case cmUVProcessChainBuilder::External: errorData.Stdio.flags = UV_INHERIT_FD; errorData.Stdio.data.fd = error.FileDescriptor; break; } auto const& output = this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT]; auto& outputData = this->OutputStreamData; switch (output.Type) { case cmUVProcessChainBuilder::None: outputData.Stdio.flags = UV_IGNORE; break; case cmUVProcessChainBuilder::Builtin: if (this->Builder->MergedBuiltinStreams) { outputData.BuiltinStream = errorData.BuiltinStream; outputData.Stdio.flags = UV_INHERIT_FD; outputData.Stdio.data.fd = errorData.Stdio.data.fd; } else { int pipeFd[2]; if (cmGetPipes(pipeFd) < 0) { return false; } outputData.BuiltinStream = pipeFd[0]; outputData.Stdio.flags = UV_INHERIT_FD; outputData.Stdio.data.fd = pipeFd[1]; if (this->TempOutputPipe.init(*this->Loop, 0) < 0) { return false; } if (uv_pipe_open(this->TempOutputPipe, outputData.Stdio.data.fd) < 0) { return false; } } break; case cmUVProcessChainBuilder::External: outputData.Stdio.flags = UV_INHERIT_FD; outputData.Stdio.data.fd = output.FileDescriptor; break; } bool first = true; for (std::size_t i = 0; i < this->Builder->Processes.size(); i++) { this->Processes.emplace_back(cm::make_unique()); auto& process = *this->Processes.back(); process.Data = this; process.ProcessStatus.Finished = false; if (!first) { auto& prevProcess = *this->Processes[i - 1]; int pipeFd[2]; if (cmGetPipes(pipeFd) < 0) { return false; } if (prevProcess.OutputPipe.init(*this->Loop, 0) < 0) { return false; } if (uv_pipe_open(prevProcess.OutputPipe, pipeFd[1]) < 0) { return false; } if (process.InputPipe.init(*this->Loop, 0) < 0) { return false; } if (uv_pipe_open(process.InputPipe, pipeFd[0]) < 0) { return false; } } first = false; } return true; } void cmUVProcessChain::InternalData::SpawnProcess( std::size_t index, const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first, bool last) { auto& process = *this->Processes[index]; auto options = uv_process_options_t(); // Bounds were checked at add time, first element is guaranteed to exist options.file = config.Arguments[0].c_str(); std::vector arguments; arguments.reserve(config.Arguments.size()); for (auto const& arg : config.Arguments) { arguments.push_back(arg.c_str()); } arguments.push_back(nullptr); options.args = const_cast(arguments.data()); options.flags = UV_PROCESS_WINDOWS_HIDE; #if UV_VERSION_MAJOR > 1 || \ (UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR >= 48) || \ !defined(CMAKE_USE_SYSTEM_LIBUV) options.flags |= UV_PROCESS_WINDOWS_FILE_PATH_EXACT_NAME; #endif if (!this->Builder->WorkingDirectory.empty()) { options.cwd = this->Builder->WorkingDirectory.c_str(); } std::array stdio; if (first) { stdio[0] = this->InputStreamData.Stdio; } else { stdio[0] = uv_stdio_container_t(); stdio[0].flags = UV_INHERIT_STREAM; stdio[0].data.stream = process.InputPipe; } if (last) { stdio[1] = this->OutputStreamData.Stdio; } else { stdio[1] = uv_stdio_container_t(); stdio[1].flags = UV_INHERIT_STREAM; stdio[1].data.stream = process.OutputPipe; } stdio[2] = this->ErrorStreamData.Stdio; options.stdio = stdio.data(); options.stdio_count = 3; options.exit_cb = [](uv_process_t* handle, int64_t exitStatus, int termSignal) { auto* processData = static_cast(handle->data); processData->ProcessStatus.ExitStatus = exitStatus; processData->ProcessStatus.TermSignal = termSignal; processData->Finish(); }; if ((process.ProcessStatus.SpawnResult = process.Process.spawn(*this->Loop, options, &process)) < 0) { process.Finish(); } process.InputPipe.reset(); process.OutputPipe.reset(); } void cmUVProcessChain::InternalData::Finish() { this->TempOutputPipe.reset(); this->TempErrorPipe.reset(); this->Valid = true; } cmUVProcessChain::cmUVProcessChain() : Data(cm::make_unique()) { } cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept : Data(std::move(other.Data)) { } cmUVProcessChain::~cmUVProcessChain() = default; cmUVProcessChain& cmUVProcessChain::operator=( cmUVProcessChain&& other) noexcept { this->Data = std::move(other.Data); return *this; } uv_loop_t& cmUVProcessChain::GetLoop() { return *this->Data->Loop; } int cmUVProcessChain::OutputStream() { return this->Data->OutputStreamData.BuiltinStream; } int cmUVProcessChain::ErrorStream() { return this->Data->ErrorStreamData.BuiltinStream; } bool cmUVProcessChain::Valid() const { return this->Data->Valid; } bool cmUVProcessChain::Wait(uint64_t milliseconds) { bool timeout = false; cm::uv_timer_ptr timer; if (milliseconds > 0) { timer.init(*this->Data->Loop, &timeout); timer.start( [](uv_timer_t* handle) { auto* timeoutPtr = static_cast(handle->data); *timeoutPtr = true; }, milliseconds, 0); } while (!timeout && this->Data->ProcessesCompleted < this->Data->Processes.size()) { uv_run(this->Data->Loop, UV_RUN_ONCE); } return !timeout; } std::vector cmUVProcessChain::GetStatus() const { std::vector statuses( this->Data->Processes.size(), nullptr); for (std::size_t i = 0; i < statuses.size(); i++) { statuses[i] = &this->GetStatus(i); } return statuses; } const cmUVProcessChain::Status& cmUVProcessChain::GetStatus( std::size_t index) const { return this->Data->Processes[index]->ProcessStatus; } bool cmUVProcessChain::Finished() const { return this->Data->ProcessesCompleted >= this->Data->Processes.size(); } std::pair cmUVProcessChain::Status::GetException() const { if (this->SpawnResult) { return std::make_pair(ExceptionCode::Spawn, uv_strerror(this->SpawnResult)); } #ifdef _WIN32 if (this->Finished && (this->ExitStatus & 0xF0000000) == 0xC0000000) { // Child terminated due to exceptional behavior. switch (this->ExitStatus) { case STATUS_CONTROL_C_EXIT: return std::make_pair(ExceptionCode::Interrupt, "User interrupt"); case STATUS_FLOAT_DENORMAL_OPERAND: return std::make_pair(ExceptionCode::Numerical, "Floating-point exception (denormal operand)"); case STATUS_FLOAT_DIVIDE_BY_ZERO: return std::make_pair(ExceptionCode::Numerical, "Divide-by-zero"); case STATUS_FLOAT_INEXACT_RESULT: return std::make_pair(ExceptionCode::Numerical, "Floating-point exception (inexact result)"); case STATUS_FLOAT_INVALID_OPERATION: return std::make_pair(ExceptionCode::Numerical, "Invalid floating-point operation"); case STATUS_FLOAT_OVERFLOW: return std::make_pair(ExceptionCode::Numerical, "Floating-point overflow"); case STATUS_FLOAT_STACK_CHECK: return std::make_pair(ExceptionCode::Numerical, "Floating-point stack check failed"); case STATUS_FLOAT_UNDERFLOW: return std::make_pair(ExceptionCode::Numerical, "Floating-point underflow"); # ifdef STATUS_FLOAT_MULTIPLE_FAULTS case STATUS_FLOAT_MULTIPLE_FAULTS: return std::make_pair(ExceptionCode::Numerical, "Floating-point exception (multiple faults)"); # endif # ifdef STATUS_FLOAT_MULTIPLE_TRAPS case STATUS_FLOAT_MULTIPLE_TRAPS: return std::make_pair(ExceptionCode::Numerical, "Floating-point exception (multiple traps)"); # endif case STATUS_INTEGER_DIVIDE_BY_ZERO: return std::make_pair(ExceptionCode::Numerical, "Integer divide-by-zero"); case STATUS_INTEGER_OVERFLOW: return std::make_pair(ExceptionCode::Numerical, "Integer overflow"); case STATUS_DATATYPE_MISALIGNMENT: return std::make_pair(ExceptionCode::Fault, "Datatype misalignment"); case STATUS_ACCESS_VIOLATION: return std::make_pair(ExceptionCode::Fault, "Access violation"); case STATUS_IN_PAGE_ERROR: return std::make_pair(ExceptionCode::Fault, "In-page error"); case STATUS_INVALID_HANDLE: return std::make_pair(ExceptionCode::Fault, "Invalid handle"); case STATUS_NONCONTINUABLE_EXCEPTION: return std::make_pair(ExceptionCode::Fault, "Noncontinuable exception"); case STATUS_INVALID_DISPOSITION: return std::make_pair(ExceptionCode::Fault, "Invalid disposition"); case STATUS_ARRAY_BOUNDS_EXCEEDED: return std::make_pair(ExceptionCode::Fault, "Array bounds exceeded"); case STATUS_STACK_OVERFLOW: return std::make_pair(ExceptionCode::Fault, "Stack overflow"); case STATUS_ILLEGAL_INSTRUCTION: return std::make_pair(ExceptionCode::Illegal, "Illegal instruction"); case STATUS_PRIVILEGED_INSTRUCTION: return std::make_pair(ExceptionCode::Illegal, "Privileged instruction"); case STATUS_NO_MEMORY: default: { char buf[256]; snprintf(buf, sizeof(buf), "Exit code 0x%x\n", static_cast(this->ExitStatus)); return std::make_pair(ExceptionCode::Other, buf); } } } #else if (this->Finished && this->TermSignal) { switch (this->TermSignal) { # ifdef SIGSEGV case SIGSEGV: return std::make_pair(ExceptionCode::Fault, "Segmentation fault"); # endif # ifdef SIGBUS # if !defined(SIGSEGV) || SIGBUS != SIGSEGV case SIGBUS: return std::make_pair(ExceptionCode::Fault, "Bus error"); # endif # endif # ifdef SIGFPE case SIGFPE: return std::make_pair(ExceptionCode::Numerical, "Floating-point exception"); # endif # ifdef SIGILL case SIGILL: return std::make_pair(ExceptionCode::Illegal, "Illegal instruction"); # endif # ifdef SIGINT case SIGINT: return std::make_pair(ExceptionCode::Interrupt, "User interrupt"); # endif # ifdef SIGABRT case SIGABRT: return std::make_pair(ExceptionCode::Other, "Subprocess aborted"); # endif # ifdef SIGKILL case SIGKILL: return std::make_pair(ExceptionCode::Other, "Subprocess killed"); # endif # ifdef SIGTERM case SIGTERM: return std::make_pair(ExceptionCode::Other, "Subprocess terminated"); # endif # ifdef SIGHUP case SIGHUP: return std::make_pair(ExceptionCode::Other, "SIGHUP"); # endif # ifdef SIGQUIT case SIGQUIT: return std::make_pair(ExceptionCode::Other, "SIGQUIT"); # endif # ifdef SIGTRAP case SIGTRAP: return std::make_pair(ExceptionCode::Other, "SIGTRAP"); # endif # ifdef SIGIOT # if !defined(SIGABRT) || SIGIOT != SIGABRT case SIGIOT: return std::make_pair(ExceptionCode::Other, "SIGIOT"); # endif # endif # ifdef SIGUSR1 case SIGUSR1: return std::make_pair(ExceptionCode::Other, "SIGUSR1"); # endif # ifdef SIGUSR2 case SIGUSR2: return std::make_pair(ExceptionCode::Other, "SIGUSR2"); # endif # ifdef SIGPIPE case SIGPIPE: return std::make_pair(ExceptionCode::Other, "SIGPIPE"); # endif # ifdef SIGALRM case SIGALRM: return std::make_pair(ExceptionCode::Other, "SIGALRM"); # endif # ifdef SIGSTKFLT case SIGSTKFLT: return std::make_pair(ExceptionCode::Other, "SIGSTKFLT"); # endif # ifdef SIGCHLD case SIGCHLD: return std::make_pair(ExceptionCode::Other, "SIGCHLD"); # elif defined(SIGCLD) case SIGCLD: return std::make_pair(ExceptionCode::Other, "SIGCLD"); # endif # ifdef SIGCONT case SIGCONT: return std::make_pair(ExceptionCode::Other, "SIGCONT"); # endif # ifdef SIGSTOP case SIGSTOP: return std::make_pair(ExceptionCode::Other, "SIGSTOP"); # endif # ifdef SIGTSTP case SIGTSTP: return std::make_pair(ExceptionCode::Other, "SIGTSTP"); # endif # ifdef SIGTTIN case SIGTTIN: return std::make_pair(ExceptionCode::Other, "SIGTTIN"); # endif # ifdef SIGTTOU case SIGTTOU: return std::make_pair(ExceptionCode::Other, "SIGTTOU"); # endif # ifdef SIGURG case SIGURG: return std::make_pair(ExceptionCode::Other, "SIGURG"); # endif # ifdef SIGXCPU case SIGXCPU: return std::make_pair(ExceptionCode::Other, "SIGXCPU"); # endif # ifdef SIGXFSZ case SIGXFSZ: return std::make_pair(ExceptionCode::Other, "SIGXFSZ"); # endif # ifdef SIGVTALRM case SIGVTALRM: return std::make_pair(ExceptionCode::Other, "SIGVTALRM"); # endif # ifdef SIGPROF case SIGPROF: return std::make_pair(ExceptionCode::Other, "SIGPROF"); # endif # ifdef SIGWINCH case SIGWINCH: return std::make_pair(ExceptionCode::Other, "SIGWINCH"); # endif # ifdef SIGPOLL case SIGPOLL: return std::make_pair(ExceptionCode::Other, "SIGPOLL"); # endif # ifdef SIGIO # if !defined(SIGPOLL) || SIGIO != SIGPOLL case SIGIO: return std::make_pair(ExceptionCode::Other, "SIGIO"); # endif # endif # ifdef SIGPWR case SIGPWR: return std::make_pair(ExceptionCode::Other, "SIGPWR"); # endif # ifdef SIGSYS case SIGSYS: return std::make_pair(ExceptionCode::Other, "SIGSYS"); # endif # ifdef SIGUNUSED # if !defined(SIGSYS) || SIGUNUSED != SIGSYS case SIGUNUSED: return std::make_pair(ExceptionCode::Other, "SIGUNUSED"); # endif # endif default: { char buf[256]; snprintf(buf, sizeof(buf), "Signal %d", this->TermSignal); return std::make_pair(ExceptionCode::Other, buf); } } } #endif return std::make_pair(ExceptionCode::None, ""); } void cmUVProcessChain::InternalData::ProcessData::Finish() { this->ProcessStatus.Finished = true; this->Data->ProcessesCompleted++; }