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