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