| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 | /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying   file Copyright.txt or https://cmake.org/licensing for details.  */#include "cmProcess.h"#include "cmAlgorithms.h"#include "cmCTest.h"#include "cmCTestRunTest.h"#include "cmCTestTestHandler.h"#include "cmGetPipes.h"#include "cmsys/Process.h"#include <iostream>#include <signal.h>#include <string>#if defined(_WIN32)#  include "cm_kwiml.h"#endif#include <utility>#define CM_PROCESS_BUF_SIZE 65536cmProcess::cmProcess(cmCTestRunTest& runner)  : Runner(runner)  , Conv(cmProcessOutput::UTF8, CM_PROCESS_BUF_SIZE){  this->Timeout = cmDuration::zero();  this->TotalTime = cmDuration::zero();  this->ExitValue = 0;  this->Id = 0;  this->StartTime = std::chrono::steady_clock::time_point();}cmProcess::~cmProcess() = default;void cmProcess::SetCommand(std::string const& command){  this->Command = command;}void cmProcess::SetCommandArguments(std::vector<std::string> const& args){  this->Arguments = args;}void cmProcess::SetWorkingDirectory(std::string const& dir){  this->WorkingDirectory = dir;}bool cmProcess::StartProcess(uv_loop_t& loop, std::vector<size_t>* affinity){  this->ProcessState = cmProcess::State::Error;  if (this->Command.empty()) {    return false;  }  this->StartTime = std::chrono::steady_clock::now();  this->ProcessArgs.clear();  // put the command as arg0  this->ProcessArgs.push_back(this->Command.c_str());  // now put the command arguments in  for (std::string const& arg : this->Arguments) {    this->ProcessArgs.push_back(arg.c_str());  }  this->ProcessArgs.push_back(nullptr); // null terminate the list  cm::uv_timer_ptr timer;  int status = timer.init(loop, this);  if (status != 0) {    cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,               "Error initializing timer: " << uv_strerror(status)                                            << std::endl);    return false;  }  cm::uv_pipe_ptr pipe_writer;  cm::uv_pipe_ptr pipe_reader;  pipe_writer.init(loop, 0);  pipe_reader.init(loop, 0, this);  int fds[2] = { -1, -1 };  status = cmGetPipes(fds);  if (status != 0) {    cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,               "Error initializing pipe: " << uv_strerror(status)                                           << std::endl);    return false;  }  uv_pipe_open(pipe_reader, fds[0]);  uv_pipe_open(pipe_writer, fds[1]);  uv_stdio_container_t stdio[3];  stdio[0].flags = UV_INHERIT_FD;  stdio[0].data.fd = 0;  stdio[1].flags = UV_INHERIT_STREAM;  stdio[1].data.stream = pipe_writer;  stdio[2] = stdio[1];  uv_process_options_t options = uv_process_options_t();  options.file = this->Command.data();  options.args = const_cast<char**>(this->ProcessArgs.data());  options.stdio_count = 3; // in, out and err  options.exit_cb = &cmProcess::OnExitCB;  options.stdio = stdio;#if !defined(CMAKE_USE_SYSTEM_LIBUV)  std::vector<char> cpumask;  if (affinity && !affinity->empty()) {    cpumask.resize(static_cast<size_t>(uv_cpumask_size()), 0);    for (auto p : *affinity) {      cpumask[p] = 1;    }    options.cpumask = cpumask.data();    options.cpumask_size = cpumask.size();  } else {    options.cpumask = nullptr;    options.cpumask_size = 0;  }#else  static_cast<void>(affinity);#endif  status =    uv_read_start(pipe_reader, &cmProcess::OnAllocateCB, &cmProcess::OnReadCB);  if (status != 0) {    cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,               "Error starting read events: " << uv_strerror(status)                                              << std::endl);    return false;  }  status = this->Process.spawn(loop, options, this);  if (status != 0) {    cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,               "Process not started\n " << this->Command << "\n["                                        << uv_strerror(status) << "]\n");    return false;  }  this->PipeReader = std::move(pipe_reader);  this->Timer = std::move(timer);  this->StartTimer();  this->ProcessState = cmProcess::State::Executing;  return true;}void cmProcess::StartTimer(){  auto properties = this->Runner.GetTestProperties();  auto msec =    std::chrono::duration_cast<std::chrono::milliseconds>(this->Timeout);  if (msec != std::chrono::milliseconds(0) || !properties->ExplicitTimeout) {    this->Timer.start(&cmProcess::OnTimeoutCB,                      static_cast<uint64_t>(msec.count()), 0);  }}bool cmProcess::Buffer::GetLine(std::string& line){  // Scan for the next newline.  for (size_type sz = this->size(); this->Last != sz; ++this->Last) {    if ((*this)[this->Last] == '\n' || (*this)[this->Last] == '\0') {      // Extract the range first..last as a line.      const char* text = this->data() + this->First;      size_type length = this->Last - this->First;      while (length && text[length - 1] == '\r') {        length--;      }      line.assign(text, length);      // Start a new range for the next line.      ++this->Last;      this->First = Last;      // Return the line extracted.      return true;    }  }  // Available data have been exhausted without a newline.  if (this->First != 0) {    // Move the partial line to the beginning of the buffer.    this->erase(this->begin(), this->begin() + this->First);    this->First = 0;    this->Last = this->size();  }  return false;}bool cmProcess::Buffer::GetLast(std::string& line){  // Return the partial last line, if any.  if (!this->empty()) {    line.assign(this->data(), this->size());    this->First = this->Last = 0;    this->clear();    return true;  }  return false;}void cmProcess::OnReadCB(uv_stream_t* stream, ssize_t nread,                         const uv_buf_t* buf){  auto self = static_cast<cmProcess*>(stream->data);  self->OnRead(nread, buf);}void cmProcess::OnRead(ssize_t nread, const uv_buf_t* buf){  std::string line;  if (nread > 0) {    std::string strdata;    this->Conv.DecodeText(buf->base, static_cast<size_t>(nread), strdata);    cmAppend(this->Output, strdata);    while (this->Output.GetLine(line)) {      this->Runner.CheckOutput(line);      line.clear();    }    return;  }  if (nread == 0) {    return;  }  // The process will provide no more data.  if (nread != UV_EOF) {    auto error = static_cast<int>(nread);    cmCTestLog(this->Runner.GetCTest(), ERROR_MESSAGE,               "Error reading stream: " << uv_strerror(error) << std::endl);  }  // Look for partial last lines.  if (this->Output.GetLast(line)) {    this->Runner.CheckOutput(line);  }  this->ReadHandleClosed = true;  this->PipeReader.reset();  if (this->ProcessHandleClosed) {    uv_timer_stop(this->Timer);    this->Runner.FinalizeTest();  }}void cmProcess::OnAllocateCB(uv_handle_t* handle, size_t suggested_size,                             uv_buf_t* buf){  auto self = static_cast<cmProcess*>(handle->data);  self->OnAllocate(suggested_size, buf);}void cmProcess::OnAllocate(size_t /*suggested_size*/, uv_buf_t* buf){  if (this->Buf.size() != CM_PROCESS_BUF_SIZE) {    this->Buf.resize(CM_PROCESS_BUF_SIZE);  }  *buf =    uv_buf_init(this->Buf.data(), static_cast<unsigned int>(this->Buf.size()));}void cmProcess::OnTimeoutCB(uv_timer_t* timer){  auto self = static_cast<cmProcess*>(timer->data);  self->OnTimeout();}void cmProcess::OnTimeout(){  if (this->ProcessState != cmProcess::State::Executing) {    return;  }  this->ProcessState = cmProcess::State::Expired;  bool const was_still_reading = !this->ReadHandleClosed;  if (!this->ReadHandleClosed) {    this->ReadHandleClosed = true;    this->PipeReader.reset();  }  if (!this->ProcessHandleClosed) {    // Kill the child and let our on-exit handler finish the test.    cmsysProcess_KillPID(static_cast<unsigned long>(this->Process->pid));  } else if (was_still_reading) {    // Our on-exit handler already ran but did not finish the test    // because we were still reading output.  We've just dropped    // our read handler, so we need to finish the test now.    this->Runner.FinalizeTest();  }}void cmProcess::OnExitCB(uv_process_t* process, int64_t exit_status,                         int term_signal){  auto self = static_cast<cmProcess*>(process->data);  self->OnExit(exit_status, term_signal);}void cmProcess::OnExit(int64_t exit_status, int term_signal){  if (this->ProcessState != cmProcess::State::Expired) {    if (#if defined(_WIN32)      ((DWORD)exit_status & 0xF0000000) == 0xC0000000#else      term_signal != 0#endif    ) {      this->ProcessState = cmProcess::State::Exception;    } else {      this->ProcessState = cmProcess::State::Exited;    }  }  // Record exit information.  this->ExitValue = exit_status;  this->Signal = term_signal;  this->TotalTime = std::chrono::steady_clock::now() - this->StartTime;  // Because of a processor clock scew the runtime may become slightly  // negative. If someone changed the system clock while the process was  // running this may be even more. Make sure not to report a negative  // duration here.  if (this->TotalTime <= cmDuration::zero()) {    this->TotalTime = cmDuration::zero();  }  this->ProcessHandleClosed = true;  if (this->ReadHandleClosed) {    uv_timer_stop(this->Timer);    this->Runner.FinalizeTest();  }}cmProcess::State cmProcess::GetProcessStatus(){  return this->ProcessState;}void cmProcess::ChangeTimeout(cmDuration t){  this->Timeout = t;  this->StartTimer();}void cmProcess::ResetStartTime(){  this->StartTime = std::chrono::steady_clock::now();}cmProcess::Exception cmProcess::GetExitException(){  auto exception = Exception::None;#if defined(_WIN32) && !defined(__CYGWIN__)  auto exit_code = (DWORD)this->ExitValue;  if ((exit_code & 0xF0000000) != 0xC0000000) {    return exception;  }  if (exit_code) {    switch (exit_code) {      case STATUS_DATATYPE_MISALIGNMENT:      case STATUS_ACCESS_VIOLATION:      case STATUS_IN_PAGE_ERROR:      case STATUS_INVALID_HANDLE:      case STATUS_NONCONTINUABLE_EXCEPTION:      case STATUS_INVALID_DISPOSITION:      case STATUS_ARRAY_BOUNDS_EXCEEDED:      case STATUS_STACK_OVERFLOW:        exception = Exception::Fault;        break;      case STATUS_FLOAT_DENORMAL_OPERAND:      case STATUS_FLOAT_DIVIDE_BY_ZERO:      case STATUS_FLOAT_INEXACT_RESULT:      case STATUS_FLOAT_INVALID_OPERATION:      case STATUS_FLOAT_OVERFLOW:      case STATUS_FLOAT_STACK_CHECK:      case STATUS_FLOAT_UNDERFLOW:#  ifdef STATUS_FLOAT_MULTIPLE_FAULTS      case STATUS_FLOAT_MULTIPLE_FAULTS:#  endif#  ifdef STATUS_FLOAT_MULTIPLE_TRAPS      case STATUS_FLOAT_MULTIPLE_TRAPS:#  endif      case STATUS_INTEGER_DIVIDE_BY_ZERO:      case STATUS_INTEGER_OVERFLOW:        exception = Exception::Numerical;        break;      case STATUS_CONTROL_C_EXIT:        exception = Exception::Interrupt;        break;      case STATUS_ILLEGAL_INSTRUCTION:      case STATUS_PRIVILEGED_INSTRUCTION:        exception = Exception::Illegal;        break;      default:        exception = Exception::Other;    }  }#else  if (this->Signal) {    switch (this->Signal) {      case SIGSEGV:        exception = Exception::Fault;        break;      case SIGFPE:        exception = Exception::Numerical;        break;      case SIGINT:        exception = Exception::Interrupt;        break;      case SIGILL:        exception = Exception::Illegal;        break;      default:        exception = Exception::Other;    }  }#endif  return exception;}std::string cmProcess::GetExitExceptionString(){  std::string exception_str;#if defined(_WIN32)  switch (this->ExitValue) {    case STATUS_CONTROL_C_EXIT:      exception_str = "User interrupt";      break;    case STATUS_FLOAT_DENORMAL_OPERAND:      exception_str = "Floating-point exception (denormal operand)";      break;    case STATUS_FLOAT_DIVIDE_BY_ZERO:      exception_str = "Divide-by-zero";      break;    case STATUS_FLOAT_INEXACT_RESULT:      exception_str = "Floating-point exception (inexact result)";      break;    case STATUS_FLOAT_INVALID_OPERATION:      exception_str = "Invalid floating-point operation";      break;    case STATUS_FLOAT_OVERFLOW:      exception_str = "Floating-point overflow";      break;    case STATUS_FLOAT_STACK_CHECK:      exception_str = "Floating-point stack check failed";      break;    case STATUS_FLOAT_UNDERFLOW:      exception_str = "Floating-point underflow";      break;#  ifdef STATUS_FLOAT_MULTIPLE_FAULTS    case STATUS_FLOAT_MULTIPLE_FAULTS:      exception_str = "Floating-point exception (multiple faults)";      break;#  endif#  ifdef STATUS_FLOAT_MULTIPLE_TRAPS    case STATUS_FLOAT_MULTIPLE_TRAPS:      exception_str = "Floating-point exception (multiple traps)";      break;#  endif    case STATUS_INTEGER_DIVIDE_BY_ZERO:      exception_str = "Integer divide-by-zero";      break;    case STATUS_INTEGER_OVERFLOW:      exception_str = "Integer overflow";      break;    case STATUS_DATATYPE_MISALIGNMENT:      exception_str = "Datatype misalignment";      break;    case STATUS_ACCESS_VIOLATION:      exception_str = "Access violation";      break;    case STATUS_IN_PAGE_ERROR:      exception_str = "In-page error";      break;    case STATUS_INVALID_HANDLE:      exception_str = "Invalid handle";      break;    case STATUS_NONCONTINUABLE_EXCEPTION:      exception_str = "Noncontinuable exception";      break;    case STATUS_INVALID_DISPOSITION:      exception_str = "Invalid disposition";      break;    case STATUS_ARRAY_BOUNDS_EXCEEDED:      exception_str = "Array bounds exceeded";      break;    case STATUS_STACK_OVERFLOW:      exception_str = "Stack overflow";      break;    case STATUS_ILLEGAL_INSTRUCTION:      exception_str = "Illegal instruction";      break;    case STATUS_PRIVILEGED_INSTRUCTION:      exception_str = "Privileged instruction";      break;    case STATUS_NO_MEMORY:    default:      char buf[1024];      const char* fmt = "Exit code 0x%" KWIML_INT_PRIx64 "\n";      _snprintf(buf, 1024, fmt, this->ExitValue);      exception_str.assign(buf);  }#else  switch (this->Signal) {#  ifdef SIGSEGV    case SIGSEGV:      exception_str = "Segmentation fault";      break;#  endif#  ifdef SIGBUS#    if !defined(SIGSEGV) || SIGBUS != SIGSEGV    case SIGBUS:      exception_str = "Bus error";      break;#    endif#  endif#  ifdef SIGFPE    case SIGFPE:      exception_str = "Floating-point exception";      break;#  endif#  ifdef SIGILL    case SIGILL:      exception_str = "Illegal instruction";      break;#  endif#  ifdef SIGINT    case SIGINT:      exception_str = "User interrupt";      break;#  endif#  ifdef SIGABRT    case SIGABRT:      exception_str = "Child aborted";      break;#  endif#  ifdef SIGKILL    case SIGKILL:      exception_str = "Child killed";      break;#  endif#  ifdef SIGTERM    case SIGTERM:      exception_str = "Child terminated";      break;#  endif#  ifdef SIGHUP    case SIGHUP:      exception_str = "SIGHUP";      break;#  endif#  ifdef SIGQUIT    case SIGQUIT:      exception_str = "SIGQUIT";      break;#  endif#  ifdef SIGTRAP    case SIGTRAP:      exception_str = "SIGTRAP";      break;#  endif#  ifdef SIGIOT#    if !defined(SIGABRT) || SIGIOT != SIGABRT    case SIGIOT:      exception_str = "SIGIOT";      break;#    endif#  endif#  ifdef SIGUSR1    case SIGUSR1:      exception_str = "SIGUSR1";      break;#  endif#  ifdef SIGUSR2    case SIGUSR2:      exception_str = "SIGUSR2";      break;#  endif#  ifdef SIGPIPE    case SIGPIPE:      exception_str = "SIGPIPE";      break;#  endif#  ifdef SIGALRM    case SIGALRM:      exception_str = "SIGALRM";      break;#  endif#  ifdef SIGSTKFLT    case SIGSTKFLT:      exception_str = "SIGSTKFLT";      break;#  endif#  ifdef SIGCHLD    case SIGCHLD:      exception_str = "SIGCHLD";      break;#  elif defined(SIGCLD)    case SIGCLD:      exception_str = "SIGCLD";      break;#  endif#  ifdef SIGCONT    case SIGCONT:      exception_str = "SIGCONT";      break;#  endif#  ifdef SIGSTOP    case SIGSTOP:      exception_str = "SIGSTOP";      break;#  endif#  ifdef SIGTSTP    case SIGTSTP:      exception_str = "SIGTSTP";      break;#  endif#  ifdef SIGTTIN    case SIGTTIN:      exception_str = "SIGTTIN";      break;#  endif#  ifdef SIGTTOU    case SIGTTOU:      exception_str = "SIGTTOU";      break;#  endif#  ifdef SIGURG    case SIGURG:      exception_str = "SIGURG";      break;#  endif#  ifdef SIGXCPU    case SIGXCPU:      exception_str = "SIGXCPU";      break;#  endif#  ifdef SIGXFSZ    case SIGXFSZ:      exception_str = "SIGXFSZ";      break;#  endif#  ifdef SIGVTALRM    case SIGVTALRM:      exception_str = "SIGVTALRM";      break;#  endif#  ifdef SIGPROF    case SIGPROF:      exception_str = "SIGPROF";      break;#  endif#  ifdef SIGWINCH    case SIGWINCH:      exception_str = "SIGWINCH";      break;#  endif#  ifdef SIGPOLL    case SIGPOLL:      exception_str = "SIGPOLL";      break;#  endif#  ifdef SIGIO#    if !defined(SIGPOLL) || SIGIO != SIGPOLL    case SIGIO:      exception_str = "SIGIO";      break;#    endif#  endif#  ifdef SIGPWR    case SIGPWR:      exception_str = "SIGPWR";      break;#  endif#  ifdef SIGSYS    case SIGSYS:      exception_str = "SIGSYS";      break;#  endif#  ifdef SIGUNUSED#    if !defined(SIGSYS) || SIGUNUSED != SIGSYS    case SIGUNUSED:      exception_str = "SIGUNUSED";      break;#    endif#  endif    default:      exception_str = "Signal ";      exception_str += std::to_string(this->Signal);  }#endif  return exception_str;}
 |