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 int run(
223  const std::string &what,
224  const std::vector<std::string> &argv,
225  const std::string &std_input,
226  const std::string &std_output,
227  const std::string &std_error)
228 {
229 #ifdef _WIN32
230  // unicode commandline, quoted
231  std::wstring cmdline;
232 
233  // we replace argv[0] by what
234  cmdline = quote_windows_arg(widen(what));
235 
236  for(std::size_t i = 1; i < argv.size(); i++)
237  {
238  cmdline += L" ";
239  cmdline += quote_windows_arg(widen(argv[i]));
240  }
241 
242  PROCESS_INFORMATION piProcInfo;
243  STARTUPINFOW siStartInfo;
244 
245  ZeroMemory(&piProcInfo, sizeof piProcInfo);
246  ZeroMemory(&siStartInfo, sizeof siStartInfo);
247 
248  siStartInfo.cb = sizeof siStartInfo;
249 
250  siStartInfo.hStdInput = stdio_redirection(STDIN_FILENO, std_input);
251  siStartInfo.hStdOutput = stdio_redirection(STDOUT_FILENO, std_output);
252  siStartInfo.hStdError = stdio_redirection(STDERR_FILENO, std_error);
253 
254  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
255 
256  // CreateProcessW wants to modify the command line
257  std::vector<wchar_t> mutable_cmdline(cmdline.begin(), cmdline.end());
258  mutable_cmdline.push_back(0); // zero termination
259  wchar_t *cmdline_ptr = mutable_cmdline.data();
260 
261  BOOL bSuccess = CreateProcessW(
262  NULL, // application name
263  cmdline_ptr, // command line
264  NULL, // process security attributes
265  NULL, // primary thread security attributes
266  true, // handles are inherited
267  0, // creation flags
268  NULL, // use parent's environment
269  NULL, // use parent's current directory
270  &siStartInfo, // STARTUPINFO
271  &piProcInfo); // PROCESS_INFORMATION
272 
273  if(!bSuccess)
274  {
275  if(!std_input.empty())
276  CloseHandle(siStartInfo.hStdInput);
277  if(!std_output.empty())
278  CloseHandle(siStartInfo.hStdOutput);
279  if(!std_error.empty())
280  CloseHandle(siStartInfo.hStdError);
281  return -1;
282  }
283 
284  // wait for child to finish
285  WaitForSingleObject(piProcInfo.hProcess, INFINITE);
286 
287  if(!std_input.empty())
288  CloseHandle(siStartInfo.hStdInput);
289  if(!std_output.empty())
290  CloseHandle(siStartInfo.hStdOutput);
291  if(!std_error.empty())
292  CloseHandle(siStartInfo.hStdError);
293 
294  DWORD exit_code;
295 
296  // get exit code
297  if(!GetExitCodeProcess(piProcInfo.hProcess, &exit_code))
298  {
299  CloseHandle(piProcInfo.hProcess);
300  CloseHandle(piProcInfo.hThread);
301  return -1;
302  }
303 
304  CloseHandle(piProcInfo.hProcess);
305  CloseHandle(piProcInfo.hThread);
306 
307  return exit_code;
308 
309 #else
310  int stdin_fd = stdio_redirection(STDIN_FILENO, std_input);
311  int stdout_fd = stdio_redirection(STDOUT_FILENO, std_output);
312  int stderr_fd = stdio_redirection(STDERR_FILENO, std_error);
313 
314  if(stdin_fd == -1 || stdout_fd == -1 || stderr_fd == -1)
315  return 1;
316 
317  // temporarily suspend all signals
318  sigset_t new_mask, old_mask;
319  sigemptyset(&new_mask);
320  sigprocmask(SIG_SETMASK, &new_mask, &old_mask);
321 
322  /* now create new process */
323  pid_t childpid = fork();
324 
325  if(childpid>=0) /* fork succeeded */
326  {
327  if(childpid==0) /* fork() returns 0 to the child process */
328  {
329  // resume signals
331  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
332 
333  std::vector<char *> _argv(argv.size()+1);
334  for(std::size_t i=0; i<argv.size(); i++)
335  _argv[i]=strdup(argv[i].c_str());
336 
337  _argv[argv.size()]=nullptr;
338 
339  if(stdin_fd!=STDIN_FILENO)
340  dup2(stdin_fd, STDIN_FILENO);
341  if(stdout_fd!=STDOUT_FILENO)
342  dup2(stdout_fd, STDOUT_FILENO);
343  if(stderr_fd != STDERR_FILENO)
344  dup2(stderr_fd, STDERR_FILENO);
345 
346  errno=0;
347  execvp(what.c_str(), _argv.data());
348 
349  /* usually no return */
350  perror(std::string("execvp "+what+" failed").c_str());
351  exit(1);
352  }
353  else /* fork() returns new pid to the parent process */
354  {
355  // must do before resuming signals to avoid race
356  register_child(childpid);
357 
358  // resume signals
359  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
360 
361  int status; /* parent process: child's exit status */
362 
363  /* wait for child to exit, and store its status */
364  while(waitpid(childpid, &status, 0)==-1)
365  {
366  if(errno==EINTR)
367  continue; // try again
368  else
369  {
371 
372  perror("Waiting for child process failed");
373  if(stdin_fd!=STDIN_FILENO)
374  close(stdin_fd);
375  if(stdout_fd!=STDOUT_FILENO)
376  close(stdout_fd);
377  if(stderr_fd != STDERR_FILENO)
378  close(stderr_fd);
379  return 1;
380  }
381  }
382 
384 
385  if(stdin_fd!=STDIN_FILENO)
386  close(stdin_fd);
387  if(stdout_fd!=STDOUT_FILENO)
388  close(stdout_fd);
389  if(stderr_fd != STDERR_FILENO)
390  close(stderr_fd);
391 
392  return WEXITSTATUS(status);
393  }
394  }
395  else /* fork returns -1 on failure */
396  {
397  // resume signals
398  sigprocmask(SIG_SETMASK, &old_mask, nullptr);
399 
400  if(stdin_fd!=STDIN_FILENO)
401  close(stdin_fd);
402  if(stdout_fd!=STDOUT_FILENO)
403  close(stdout_fd);
404  if(stderr_fd != STDERR_FILENO)
405  close(stderr_fd);
406 
407  return 1;
408  }
409 #endif
410 }
411 
413 static std::string shell_quote(const std::string &src)
414 {
415  #ifdef _WIN32
416  // first check if quoting is needed at all
417 
418  if(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  src.find('^')==std::string::npos)
427  {
428  // seems fine -- return as is
429  return src;
430  }
431 
432  std::string result;
433 
434  result+='"';
435 
436  for(const char ch : src)
437  {
438  if(ch=='"')
439  result+='"'; // quotes are doubled
440  result+=ch;
441  }
442 
443  result+='"';
444 
445  return result;
446 
447  #else
448 
449  // first check if quoting is needed at all
450 
451  if(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  src.find('\'')==std::string::npos)
463  {
464  // seems fine -- return as is
465  return src;
466  }
467 
468  std::string result;
469 
470  // the single quotes catch everything but themselves!
471  result+='\'';
472 
473  for(const char ch : src)
474  {
475  if(ch=='\'')
476  result+="'\\''";
477  result+=ch;
478  }
479 
480  result+='\'';
481 
482  return result;
483  #endif
484 }
485 
486 int run(
487  const std::string &what,
488  const std::vector<std::string> &argv,
489  const std::string &std_input,
490  std::ostream &std_output,
491  const std::string &std_error)
492 {
493  #ifdef _WIN32
494  temporary_filet tmpi("tmp.stdout", "");
495 
496  int result = run(what, argv, std_input, tmpi(), std_error);
497 
498  std::ifstream instream(tmpi());
499 
500  if(instream)
501  std_output << instream.rdbuf(); // copy
502 
503  return result;
504  #else
505  std::string command;
506 
507  bool first = true;
508 
509  // note we use 'what' instead of 'argv[0]' as the name of the executable
510  for(const auto &arg : argv)
511  {
512  if(first) // this is argv[0]
513  {
514  command += shell_quote(what);
515  first = false;
516  }
517  else
518  command += " " + shell_quote(arg);
519  }
520 
521  if(!std_input.empty())
522  command += " < " + shell_quote(std_input);
523 
524  if(!std_error.empty())
525  command += " 2> " + shell_quote(std_error);
526 
527  FILE *stream=popen(command.c_str(), "r");
528 
529  if(stream!=nullptr)
530  {
531  int ch;
532  while((ch=fgetc(stream))!=EOF)
533  std_output << (unsigned char)ch;
534 
535  return pclose(stream);
536  }
537  else
538  return -1;
539  #endif
540 }
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:413
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:50
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