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  if(src.find_first_of(L" \t\n\v\"") == src.npos)
169  return src;
170 
171  std::wstring result = L"\"";
172 
173  for(auto it = src.begin();; ++it)
174  {
175  std::size_t NumberBackslashes = 0;
176 
177  while(it != src.end() && *it == L'\\')
178  {
179  ++it;
180  ++NumberBackslashes;
181  }
182 
183  if(it == src.end())
184  {
185  //
186  // Escape all backslashes, but let the terminating
187  // double quotation mark we add below be interpreted
188  // as a metacharacter.
189  //
190 
191  result.append(NumberBackslashes * 2, L'\\');
192  break;
193  }
194  else if(*it == L'"')
195  {
196  //
197  // Escape all backslashes and the following
198  // double quotation mark.
199  //
200 
201  result.append(NumberBackslashes * 2 + 1, L'\\');
202  result.push_back(*it);
203  }
204  else
205  {
206  //
207  // Backslashes aren't special here.
208  //
209 
210  result.append(NumberBackslashes, L'\\');
211  result.push_back(*it);
212  }
213  }
214 
215  result.push_back(L'"');
216 
217  return result;
218 }
219 #endif
220 
221 int run(
222  const std::string &what,
223  const std::vector<std::string> &argv,
224  const std::string &std_input,
225  const std::string &std_output,
226  const std::string &std_error)
227 {
228 #ifdef _WIN32
229  // unicode commandline, quoted
230  std::wstring cmdline;
231 
232  // we replace argv[0] by what
233  cmdline = quote_windows_arg(widen(what));
234 
235  for(std::size_t i = 1; i < argv.size(); i++)
236  {
237  cmdline += L" ";
238  cmdline += quote_windows_arg(widen(argv[i]));
239  }
240 
241  PROCESS_INFORMATION piProcInfo;
242  STARTUPINFOW siStartInfo;
243 
244  ZeroMemory(&piProcInfo, sizeof piProcInfo);
245  ZeroMemory(&siStartInfo, sizeof siStartInfo);
246 
247  siStartInfo.cb = sizeof siStartInfo;
248 
249  siStartInfo.hStdInput = stdio_redirection(STDIN_FILENO, std_input);
250  siStartInfo.hStdOutput = stdio_redirection(STDOUT_FILENO, std_output);
251  siStartInfo.hStdError = stdio_redirection(STDERR_FILENO, std_error);
252 
253  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
254 
255  // CreateProcessW wants to modify the command line
256  std::vector<wchar_t> mutable_cmdline(cmdline.begin(), cmdline.end());
257  mutable_cmdline.push_back(0); // zero termination
258  wchar_t *cmdline_ptr = mutable_cmdline.data();
259 
260  BOOL bSuccess = CreateProcessW(
261  NULL, // application name
262  cmdline_ptr, // command line
263  NULL, // process security attributes
264  NULL, // primary thread security attributes
265  true, // handles are inherited
266  0, // creation flags
267  NULL, // use parent's environment
268  NULL, // use parent's current directory
269  &siStartInfo, // STARTUPINFO
270  &piProcInfo); // PROCESS_INFORMATION
271 
272  if(!bSuccess)
273  {
274  if(!std_input.empty())
275  CloseHandle(siStartInfo.hStdInput);
276  if(!std_output.empty())
277  CloseHandle(siStartInfo.hStdOutput);
278  if(!std_error.empty())
279  CloseHandle(siStartInfo.hStdError);
280  return -1;
281  }
282 
283  // wait for child to finish
284  WaitForSingleObject(piProcInfo.hProcess, INFINITE);
285 
286  if(!std_input.empty())
287  CloseHandle(siStartInfo.hStdInput);
288  if(!std_output.empty())
289  CloseHandle(siStartInfo.hStdOutput);
290  if(!std_error.empty())
291  CloseHandle(siStartInfo.hStdError);
292 
293  DWORD exit_code;
294 
295  // get exit code
296  if(!GetExitCodeProcess(piProcInfo.hProcess, &exit_code))
297  {
298  CloseHandle(piProcInfo.hProcess);
299  CloseHandle(piProcInfo.hThread);
300  return -1;
301  }
302 
303  CloseHandle(piProcInfo.hProcess);
304  CloseHandle(piProcInfo.hThread);
305 
306  return exit_code;
307 
308 #else
309  int stdin_fd = stdio_redirection(STDIN_FILENO, std_input);
310  int stdout_fd = stdio_redirection(STDOUT_FILENO, std_output);
311  int stderr_fd = stdio_redirection(STDERR_FILENO, std_error);
312 
313  if(stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1)
314  return 1;
315 
316  // temporarily suspend all signals
317  sigset_t new_mask, old_mask;
318  sigemptyset(&new_mask);
319  sigprocmask(SIG_SETMASK, &new_mask, &old_mask);
320 
321  /* now create new process */
322  pid_t childpid = fork();
323 
324  if(childpid>=0) /* fork succeeded */
325  {
326  if(childpid==0) /* fork() returns 0 to the child process */
327  {
328  // resume signals
330  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
331 
332  std::vector<char *> _argv(argv.size()+1);
333  for(std::size_t i=0; i<argv.size(); i++)
334  _argv[i]=strdup(argv[i].c_str());
335 
336  _argv[argv.size()]=nullptr;
337 
338  if(stdin_fd!=STDIN_FILENO)
339  dup2(stdin_fd, STDIN_FILENO);
340  if(stdout_fd!=STDOUT_FILENO)
341  dup2(stdout_fd, STDOUT_FILENO);
342  if(stderr_fd != STDERR_FILENO)
343  dup2(stderr_fd, STDERR_FILENO);
344 
345  errno=0;
346  execvp(what.c_str(), _argv.data());
347 
348  /* usually no return */
349  perror(std::string("execvp "+what+" failed").c_str());
350  exit(1);
351  }
352  else /* fork() returns new pid to the parent process */
353  {
354  // must do before resuming signals to avoid race
355  register_child(childpid);
356 
357  // resume signals
358  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
359 
360  int status; /* parent process: child's exit status */
361 
362  /* wait for child to exit, and store its status */
363  while(waitpid(childpid, &status, 0)==-1)
364  {
365  if(errno==EINTR)
366  continue; // try again
367  else
368  {
370 
371  perror("Waiting for child process failed");
372  if(stdin_fd!=STDIN_FILENO)
373  close(stdin_fd);
374  if(stdout_fd!=STDOUT_FILENO)
375  close(stdout_fd);
376  if(stderr_fd != STDERR_FILENO)
377  close(stderr_fd);
378  return 1;
379  }
380  }
381 
383 
384  if(stdin_fd!=STDIN_FILENO)
385  close(stdin_fd);
386  if(stdout_fd!=STDOUT_FILENO)
387  close(stdout_fd);
388  if(stderr_fd != STDERR_FILENO)
389  close(stderr_fd);
390 
391  return WEXITSTATUS(status);
392  }
393  }
394  else /* fork returns -1 on failure */
395  {
396  // resume signals
397  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
398 
399  if(stdin_fd!=STDIN_FILENO)
400  close(stdin_fd);
401  if(stdout_fd!=STDOUT_FILENO)
402  close(stdout_fd);
403  if(stderr_fd != STDERR_FILENO)
404  close(stderr_fd);
405 
406  return 1;
407  }
408 #endif
409 }
410 
412 static std::string shell_quote(const std::string &src)
413 {
414  #ifdef _WIN32
415  // first check if quoting is needed at all
416 
417  if(src.find(' ')==std::string::npos &&
418  src.find('"')==std::string::npos &&
419  src.find('&')==std::string::npos &&
420  src.find('|')==std::string::npos &&
421  src.find('(')==std::string::npos &&
422  src.find(')')==std::string::npos &&
423  src.find('<')==std::string::npos &&
424  src.find('>')==std::string::npos &&
425  src.find('^')==std::string::npos)
426  {
427  // seems fine -- return as is
428  return src;
429  }
430 
431  std::string result;
432 
433  result+='"';
434 
435  for(const char ch : src)
436  {
437  if(ch=='"')
438  result+='"'; // quotes are doubled
439  result+=ch;
440  }
441 
442  result+='"';
443 
444  return result;
445 
446  #else
447 
448  // first check if quoting is needed at all
449 
450  if(src.find(' ')==std::string::npos &&
451  src.find('"')==std::string::npos &&
452  src.find('*')==std::string::npos &&
453  src.find('$')==std::string::npos &&
454  src.find('\\')==std::string::npos &&
455  src.find('?')==std::string::npos &&
456  src.find('&')==std::string::npos &&
457  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  {
463  // seems fine -- return as is
464  return src;
465  }
466 
467  std::string result;
468 
469  // the single quotes catch everything but themselves!
470  result+='\'';
471 
472  for(const char ch : src)
473  {
474  if(ch=='\'')
475  result+="'\\''";
476  result+=ch;
477  }
478 
479  result+='\'';
480 
481  return result;
482  #endif
483 }
484 
485 int run(
486  const std::string &what,
487  const std::vector<std::string> &argv,
488  const std::string &std_input,
489  std::ostream &std_output,
490  const std::string &std_error)
491 {
492  #ifdef _WIN32
493  temporary_filet tmpi("tmp.stdout", "");
494 
495  int result = run(what, argv, std_input, tmpi(), std_error);
496 
497  std::ifstream instream(tmpi());
498 
499  if(instream)
500  std_output << instream.rdbuf(); // copy
501 
502  return result;
503  #else
504  std::string command;
505 
506  bool first = true;
507 
508  // note we use 'what' instead of 'argv[0]' as the name of the executable
509  for(const auto &arg : argv)
510  {
511  if(first) // this is argv[0]
512  {
513  command += shell_quote(what);
514  first = false;
515  }
516  else
517  command += " " + shell_quote(arg);
518  }
519 
520  if(!std_input.empty())
521  command += " < " + shell_quote(std_input);
522 
523  if(!std_error.empty())
524  command += " 2> " + shell_quote(std_error);
525 
526  FILE *stream=popen(command.c_str(), "r");
527 
528  if(stream!=nullptr)
529  {
530  int ch;
531  while((ch=fgetc(stream))!=EOF)
532  std_output << (unsigned char)ch;
533 
534  return pclose(stream);
535  }
536  else
537  return -1;
538  #endif
539 }
unregister_child
void unregister_child()
Definition: signal_catcher.cpp:33
UNREACHABLE
#define UNREACHABLE
This should be used to mark dead code.
Definition: invariant.h:504
tempfile.h
fdt
int fdt
Definition: run.cpp:60
shell_quote
static std::string shell_quote(const std::string &src)
quote a string for bash and CMD
Definition: run.cpp:412
file
Definition: kdev_t.h:19
run
int run(const std::string &what, const std::vector< std::string > &argv)
Definition: run.cpp:49
run.h
stdio_redirection
static fdt stdio_redirection(int fd, const std::string &file)
open given file to replace either stdin, stderr, stdout
Definition: run.cpp:64
widen
std::wstring widen(const char *s)
Definition: unicode.cpp:57
remove_signal_catcher
void remove_signal_catcher()
Definition: signal_catcher.cpp:57
invariant.h
unicode.h
signal_catcher.h
register_child
void register_child(pid_t pid)
Definition: signal_catcher.cpp:27
temporary_filet
Definition: tempfile.h:24