cprover
run.cpp
Go to the documentation of this file.
1 /*******************************************************************\
2 
3 Module:
4 
5 Author: Daniel Kroening
6 
7 Date: August 2012
8 
9 \*******************************************************************/
10 
11 #include "run.h"
12 
13 #ifdef _WIN32
14 // clang-format off
15 #include <util/pragma_push.def>
16 #ifdef _MSC_VER
17 #pragma warning(disable:4668)
18  // using #if/#elif on undefined macro
19 #pragma warning(disable:5039)
20 // pointer or reference to potentially throwing function passed to extern C
21 #endif
22 #include <process.h>
23 #include <windows.h>
24 #include <util/pragma_pop.def>
25 // clang-format on
26 #else
27 
28 #include <cstring>
29 #include <cerrno>
30 #include <cstdio>
31 #include <cstdlib>
32 
33 #include <fcntl.h>
34 #include <signal.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <unistd.h>
39 
40 #endif
41 
42 #include <fstream>
43 
44 #include "invariant.h"
45 #include "signal_catcher.h"
46 #include "tempfile.h"
47 #include "unicode.h"
48 
49 int run(const std::string &what, const std::vector<std::string> &argv)
50 {
51  return run(what, argv, "", "", "");
52 }
53 
54 #ifdef _WIN32
55 #define STDIN_FILENO 0
56 #define STDOUT_FILENO 1
57 #define STDERR_FILENO 2
58 using fdt = HANDLE;
59 #else
60 using fdt = int;
61 #endif
62 
64 static fdt stdio_redirection(int fd, const std::string &file)
65 {
66 #ifdef _WIN32
67  fdt result_fd = INVALID_HANDLE_VALUE;
68  std::string name;
69 
70  SECURITY_ATTRIBUTES SecurityAttributes;
71  ZeroMemory(&SecurityAttributes, sizeof SecurityAttributes);
72  SecurityAttributes.bInheritHandle = true;
73 
74  switch(fd)
75  {
76  case STDIN_FILENO:
77  name = "stdin";
78  if(file.empty())
79  result_fd = GetStdHandle(STD_INPUT_HANDLE);
80  else
81  result_fd = CreateFileW(
82  widen(file).c_str(),
83  GENERIC_READ,
84  0,
85  &SecurityAttributes,
86  OPEN_EXISTING,
87  FILE_ATTRIBUTE_READONLY,
88  NULL);
89  break;
90 
91  case STDOUT_FILENO:
92  name = "stdout";
93  if(file.empty())
94  result_fd = GetStdHandle(STD_OUTPUT_HANDLE);
95  else
96  result_fd = CreateFileW(
97  widen(file).c_str(),
98  GENERIC_WRITE,
99  0,
100  &SecurityAttributes,
101  CREATE_ALWAYS,
102  FILE_ATTRIBUTE_NORMAL,
103  NULL);
104  break;
105 
106  case STDERR_FILENO:
107  name = "stderr";
108  if(file.empty())
109  result_fd = GetStdHandle(STD_ERROR_HANDLE);
110  else
111  result_fd = CreateFileW(
112  widen(file).c_str(),
113  GENERIC_WRITE,
114  0,
115  &SecurityAttributes,
116  CREATE_ALWAYS,
117  FILE_ATTRIBUTE_NORMAL,
118  NULL);
119  break;
120 
121  default:
122  UNREACHABLE;
123  }
124 
125  if(result_fd == INVALID_HANDLE_VALUE)
126  perror(("Failed to open " + name + " file " + file).c_str());
127 
128 #else
129 
130  if(file.empty())
131  return fd;
132 
133  int flags = 0, mode = 0;
134  std::string name;
135 
136  switch(fd)
137  {
138  case STDIN_FILENO:
139  flags = O_RDONLY;
140  name = "stdin";
141  break;
142 
143  case STDOUT_FILENO:
144  case STDERR_FILENO:
145  flags = O_CREAT | O_WRONLY;
146  mode = S_IRUSR | S_IWUSR;
147  name = fd == STDOUT_FILENO ? "stdout" : "stderr";
148  break;
149 
150  default:
151  UNREACHABLE;
152  }
153 
154  const fdt result_fd = open(file.c_str(), flags, mode);
155 
156  if(result_fd == -1)
157  perror(("Failed to open " + name + " file " + file).c_str());
158 #endif
159 
160  return result_fd;
161 }
162 
163 #ifdef _WIN32
164 // Read
165 // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
166 std::wstring quote_windows_arg(const std::wstring &src)
167 {
168  // note that an empty argument requires quotes
169  if(src.find_first_of(L" \t\n\v\"") == src.npos && !src.empty())
170  return src;
171 
172  std::wstring result = L"\"";
173 
174  for(auto it = src.begin();; ++it)
175  {
176  std::size_t NumberBackslashes = 0;
177 
178  while(it != src.end() && *it == L'\\')
179  {
180  ++it;
181  ++NumberBackslashes;
182  }
183 
184  if(it == src.end())
185  {
186  //
187  // Escape all backslashes, but let the terminating
188  // double quotation mark we add below be interpreted
189  // as a metacharacter.
190  //
191 
192  result.append(NumberBackslashes * 2, L'\\');
193  break;
194  }
195  else if(*it == L'"')
196  {
197  //
198  // Escape all backslashes and the following
199  // double quotation mark.
200  //
201 
202  result.append(NumberBackslashes * 2 + 1, L'\\');
203  result.push_back(*it);
204  }
205  else
206  {
207  //
208  // Backslashes aren't special here.
209  //
210 
211  result.append(NumberBackslashes, L'\\');
212  result.push_back(*it);
213  }
214  }
215 
216  result.push_back(L'"');
217 
218  return result;
219 }
220 #endif
221 
222 #ifdef _WIN32
223 // https://stackoverflow.com/a/17387176
224 // Returns the last Win32 error, in string format. Returns an empty string if
225 // there is no error.
226 std::string get_last_error_as_string()
227 {
228  // Get the error message, if any.
229  DWORD error_message_id = GetLastError();
230  if(error_message_id == 0)
231  return {};
232 
233  LPWSTR message_buffer = nullptr;
234  std::size_t size = FormatMessageW(
235  FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
236  FORMAT_MESSAGE_IGNORE_INSERTS,
237  NULL,
238  error_message_id,
239  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
240  (LPWSTR)&message_buffer,
241  0,
242  NULL);
243 
244  std::wstring message(message_buffer, size);
245 
246  // Free the buffer.
247  LocalFree(message_buffer);
248 
249  return narrow(message);
250 }
251 #endif
252 
253 int run(
254  const std::string &what,
255  const std::vector<std::string> &argv,
256  const std::string &std_input,
257  const std::string &std_output,
258  const std::string &std_error)
259 {
260 #ifdef _WIN32
261  // unicode commandline, quoted
262  std::wstring cmdline;
263 
264  // we replace argv[0] by what
265  cmdline = quote_windows_arg(widen(what));
266 
267  for(std::size_t i = 1; i < argv.size(); i++)
268  {
269  cmdline += L" ";
270  cmdline += quote_windows_arg(widen(argv[i]));
271  }
272 
273  PROCESS_INFORMATION piProcInfo;
274  STARTUPINFOW siStartInfo;
275 
276  ZeroMemory(&piProcInfo, sizeof piProcInfo);
277  ZeroMemory(&siStartInfo, sizeof siStartInfo);
278 
279  siStartInfo.cb = sizeof siStartInfo;
280 
281  siStartInfo.hStdInput = stdio_redirection(STDIN_FILENO, std_input);
282  siStartInfo.hStdOutput = stdio_redirection(STDOUT_FILENO, std_output);
283  siStartInfo.hStdError = stdio_redirection(STDERR_FILENO, std_error);
284 
285  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
286 
287  // CreateProcessW wants to modify the command line
288  std::vector<wchar_t> mutable_cmdline(cmdline.begin(), cmdline.end());
289  mutable_cmdline.push_back(0); // zero termination
290  wchar_t *cmdline_ptr = mutable_cmdline.data();
291 
292  BOOL bSuccess = CreateProcessW(
293  NULL, // application name
294  cmdline_ptr, // command line
295  NULL, // process security attributes
296  NULL, // primary thread security attributes
297  true, // handles are inherited
298  0, // creation flags
299  NULL, // use parent's environment
300  NULL, // use parent's current directory
301  &siStartInfo, // STARTUPINFO
302  &piProcInfo); // PROCESS_INFORMATION
303 
304  if(!bSuccess)
305  {
306  // obtain the error message before doing further system calls
307  std::string windows_error = get_last_error_as_string();
308 
309  if(!std_input.empty())
310  CloseHandle(siStartInfo.hStdInput);
311  if(!std_output.empty())
312  CloseHandle(siStartInfo.hStdOutput);
313  if(!std_error.empty())
314  CloseHandle(siStartInfo.hStdError);
315 
316  // now re-open the file and write the above error message
317  std::ofstream stderr_stream(std_error);
318  stderr_stream << windows_error;
319 
320  return -1;
321  }
322 
323  // wait for child to finish
324  WaitForSingleObject(piProcInfo.hProcess, INFINITE);
325 
326  if(!std_input.empty())
327  CloseHandle(siStartInfo.hStdInput);
328  if(!std_output.empty())
329  CloseHandle(siStartInfo.hStdOutput);
330  if(!std_error.empty())
331  CloseHandle(siStartInfo.hStdError);
332 
333  DWORD exit_code;
334 
335  // get exit code
336  if(!GetExitCodeProcess(piProcInfo.hProcess, &exit_code))
337  {
338  CloseHandle(piProcInfo.hProcess);
339  CloseHandle(piProcInfo.hThread);
340  return -1;
341  }
342 
343  CloseHandle(piProcInfo.hProcess);
344  CloseHandle(piProcInfo.hThread);
345 
346  return exit_code;
347 
348 #else
349  int stdin_fd = stdio_redirection(STDIN_FILENO, std_input);
350  int stdout_fd = stdio_redirection(STDOUT_FILENO, std_output);
351  int stderr_fd = stdio_redirection(STDERR_FILENO, std_error);
352 
353  if(stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1)
354  return 1;
355 
356  // temporarily suspend all signals
357  sigset_t new_mask, old_mask;
358  sigemptyset(&new_mask);
359  sigprocmask(SIG_SETMASK, &new_mask, &old_mask);
360 
361  /* now create new process */
362  pid_t childpid = fork();
363 
364  if(childpid>=0) /* fork succeeded */
365  {
366  if(childpid==0) /* fork() returns 0 to the child process */
367  {
368  // resume signals
370  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
371 
372  std::vector<char *> _argv(argv.size()+1);
373  for(std::size_t i=0; i<argv.size(); i++)
374  _argv[i]=strdup(argv[i].c_str());
375 
376  _argv[argv.size()]=nullptr;
377 
378  if(stdin_fd!=STDIN_FILENO)
379  dup2(stdin_fd, STDIN_FILENO);
380  if(stdout_fd!=STDOUT_FILENO)
381  dup2(stdout_fd, STDOUT_FILENO);
382  if(stderr_fd != STDERR_FILENO)
383  dup2(stderr_fd, STDERR_FILENO);
384 
385  errno=0;
386  execvp(what.c_str(), _argv.data());
387 
388  /* usually no return */
389  perror(std::string("execvp "+what+" failed").c_str());
390  exit(1);
391  }
392  else /* fork() returns new pid to the parent process */
393  {
394  // must do before resuming signals to avoid race
395  register_child(childpid);
396 
397  // resume signals
398  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
399 
400  int status; /* parent process: child's exit status */
401 
402  /* wait for child to exit, and store its status */
403  while(waitpid(childpid, &status, 0)==-1)
404  {
405  if(errno==EINTR)
406  continue; // try again
407  else
408  {
410 
411  perror("Waiting for child process failed");
412  if(stdin_fd!=STDIN_FILENO)
413  close(stdin_fd);
414  if(stdout_fd!=STDOUT_FILENO)
415  close(stdout_fd);
416  if(stderr_fd != STDERR_FILENO)
417  close(stderr_fd);
418  return 1;
419  }
420  }
421 
423 
424  if(stdin_fd!=STDIN_FILENO)
425  close(stdin_fd);
426  if(stdout_fd!=STDOUT_FILENO)
427  close(stdout_fd);
428  if(stderr_fd != STDERR_FILENO)
429  close(stderr_fd);
430 
431  return WEXITSTATUS(status);
432  }
433  }
434  else /* fork returns -1 on failure */
435  {
436  // resume signals
437  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
438 
439  if(stdin_fd!=STDIN_FILENO)
440  close(stdin_fd);
441  if(stdout_fd!=STDOUT_FILENO)
442  close(stdout_fd);
443  if(stderr_fd != STDERR_FILENO)
444  close(stderr_fd);
445 
446  return 1;
447  }
448 #endif
449 }
450 
452 static std::string shell_quote(const std::string &src)
453 {
454  #ifdef _WIN32
455  // first check if quoting is needed at all
456 
457  if(src.find(' ')==std::string::npos &&
458  src.find('"')==std::string::npos &&
459  src.find('&')==std::string::npos &&
460  src.find('|')==std::string::npos &&
461  src.find('(')==std::string::npos &&
462  src.find(')')==std::string::npos &&
463  src.find('<')==std::string::npos &&
464  src.find('>')==std::string::npos &&
465  src.find('^')==std::string::npos)
466  {
467  // seems fine -- return as is
468  return src;
469  }
470 
471  std::string result;
472 
473  result+='"';
474 
475  for(const char ch : src)
476  {
477  if(ch=='"')
478  result+='"'; // quotes are doubled
479  result+=ch;
480  }
481 
482  result+='"';
483 
484  return result;
485 
486  #else
487 
488  // first check if quoting is needed at all
489 
490  if(src.find(' ')==std::string::npos &&
491  src.find('"')==std::string::npos &&
492  src.find('*')==std::string::npos &&
493  src.find('$')==std::string::npos &&
494  src.find('\\')==std::string::npos &&
495  src.find('?')==std::string::npos &&
496  src.find('&')==std::string::npos &&
497  src.find('|')==std::string::npos &&
498  src.find('>')==std::string::npos &&
499  src.find('<')==std::string::npos &&
500  src.find('^')==std::string::npos &&
501  src.find('\'')==std::string::npos)
502  {
503  // seems fine -- return as is
504  return src;
505  }
506 
507  std::string result;
508 
509  // the single quotes catch everything but themselves!
510  result+='\'';
511 
512  for(const char ch : src)
513  {
514  if(ch=='\'')
515  result+="'\\''";
516  result+=ch;
517  }
518 
519  result+='\'';
520 
521  return result;
522  #endif
523 }
524 
525 int run(
526  const std::string &what,
527  const std::vector<std::string> &argv,
528  const std::string &std_input,
529  std::ostream &std_output,
530  const std::string &std_error)
531 {
532  #ifdef _WIN32
533  temporary_filet tmpi("tmp.stdout", "");
534 
535  int result = run(what, argv, std_input, tmpi(), std_error);
536 
537  std::ifstream instream(tmpi());
538 
539  if(instream)
540  std_output << instream.rdbuf(); // copy
541 
542  return result;
543  #else
544  std::string command;
545 
546  bool first = true;
547 
548  // note we use 'what' instead of 'argv[0]' as the name of the executable
549  for(const auto &arg : argv)
550  {
551  if(first) // this is argv[0]
552  {
553  command += shell_quote(what);
554  first = false;
555  }
556  else
557  command += " " + shell_quote(arg);
558  }
559 
560  if(!std_input.empty())
561  command += " < " + shell_quote(std_input);
562 
563  if(!std_error.empty())
564  command += " 2> " + shell_quote(std_error);
565 
566  FILE *stream=popen(command.c_str(), "r");
567 
568  if(stream!=nullptr)
569  {
570  int ch;
571  while((ch=fgetc(stream))!=EOF)
572  std_output << (unsigned char)ch;
573 
574  return pclose(stream);
575  }
576  else
577  return -1;
578  #endif
579 }
output_type narrow(input_type input)
Run-time checked narrowing cast.
Definition: narrow.h:34
int fdt
Definition: run.cpp:60
static std::string shell_quote(const std::string &src)
quote a string for bash and CMD
Definition: run.cpp:452
int run(const std::string &what, const std::vector< std::string > &argv)
Definition: run.cpp:49
static fdt stdio_redirection(int fd, const std::string &file)
open given file to replace either stdin, stderr, stdout
Definition: run.cpp:64
void remove_signal_catcher()
void register_child(pid_t pid)
void unregister_child()
#define UNREACHABLE
This should be used to mark dead code.
Definition: invariant.h:504
static const char * message(const statust &status)
Makes a status message string from a status.
Definition: kdev_t.h:19
std::wstring widen(const char *s)
Definition: unicode.cpp:50