Apache Mesos
exec.hpp
Go to the documentation of this file.
1 // Licensed under the Apache License, Version 2.0 (the "License");
2 // you may not use this file except in compliance with the License.
3 // You may obtain a copy of the License at
4 //
5 // http://www.apache.org/licenses/LICENSE-2.0
6 //
7 // Unless required by applicable law or agreed to in writing, software
8 // distributed under the License is distributed on an "AS IS" BASIS,
9 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 // See the License for the specific language governing permissions and
11 // limitations under the License.
12 
13 #ifndef __STOUT_OS_WINDOWS_EXEC_HPP__
14 #define __STOUT_OS_WINDOWS_EXEC_HPP__
15 
16 #include <process.h>
17 #include <processthreadsapi.h>
18 #include <synchapi.h>
19 #include <userenv.h>
20 
21 #include <stdarg.h> // For va_list, va_start, etc.
22 
23 #include <algorithm>
24 #include <array>
25 #include <map>
26 #include <string>
27 #include <vector>
28 
29 #include <stout/error.hpp>
30 #include <stout/foreach.hpp>
31 #include <stout/none.hpp>
32 #include <stout/option.hpp>
33 #include <stout/os.hpp>
34 #include <stout/try.hpp>
35 #include <stout/windows.hpp>
36 
37 #include <stout/os/int_fd.hpp>
38 #include <stout/os/pipe.hpp>
39 
41 
42 namespace os {
43 namespace windows {
44 namespace internal {
45 
46 // Retrieves system environment in a `std::map`, ignoring
47 // the current process's environment variables.
49 {
50  std::map<std::wstring, std::wstring> system_env;
51  wchar_t* env_entry = nullptr;
52 
53  // Get the system environment.
54  // The third parameter (bool) tells the function *not* to inherit
55  // variables from the current process.
56  if (!::CreateEnvironmentBlock((LPVOID*)&env_entry, nullptr, FALSE)) {
57  return None();
58  }
59 
60  // Save the environment block in order to destroy it later.
61  wchar_t* env_block = env_entry;
62 
63  while (*env_entry != L'\0') {
64  // Each environment block contains the environment variables as follows:
65  // Var1=Value1\0
66  // Var2=Value2\0
67  // Var3=Value3\0
68  // ...
69  // VarN=ValueN\0\0
70  // The name of an environment variable cannot include an equal sign (=).
71 
72  // Construct a string from the pointer up to the first '\0',
73  // e.g. "Var1=Value1\0", then split into name and value.
74  std::wstring entry(env_entry);
75  std::wstring::size_type separator = entry.find(L"=");
76  std::wstring var_name(entry.substr(0, separator));
77  std::wstring varVal(entry.substr(separator + 1));
78 
79  // Mesos variables are upper case. Convert system variables to
80  // match the name provided by the scheduler in case of a collision.
81  // This is safe because Windows environment variables are case insensitive.
83  var_name.begin(), var_name.end(), var_name.begin(), ::towupper);
84 
85  // The system environment has priority.
86  system_env.insert_or_assign(var_name.data(), varVal.data());
87 
88  // Advance the pointer the length of the entry string plus the '\0'.
89  env_entry += entry.length() + 1;
90  }
91 
92  ::DestroyEnvironmentBlock(env_block);
93 
94  return system_env;
95 }
96 
97 
98 // Creates a null-terminated array of null-terminated strings that will be
99 // passed to `CreateProcessW` as the `lpEnvironment` argument, as described by
100 // MSDN[1]. This array needs to be sorted in alphabetical order, but the `map`
101 // already takes care of that. Note that this function explicitly handles
102 // UTF-16 environments, so it must be used in conjunction with the
103 // `CREATE_UNICODE_ENVIRONMENT` flag.
104 //
105 // NOTE: This function will add the system's environment variables into
106 // the returned string. These variables take precedence over the provided
107 // `env` and are generally necessary in order to launch things on Windows.
108 //
109 // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx
111  const Option<std::map<std::string, std::string>>& env)
112 {
113  if (env.isNone() || (env.isSome() && env.get().size() == 0)) {
114  return None();
115  }
116 
118 
119  // The system environment must be non-empty.
120  // No subprocesses will be able to launch if the system environment is blank.
121  CHECK(system_env.isSome() && system_env.get().size() > 0);
122 
123  std::map<std::wstring, std::wstring> combined_env;
124 
125  // Populate the combined environment first with the system environment.
126  foreachpair (const std::wstring& key,
127  const std::wstring& value,
128  system_env.get()) {
129  combined_env[key] = value;
130  }
131 
132  // Now override with the supplied environment.
133  foreachpair (const std::string& key,
134  const std::string& value,
135  env.get()) {
136  combined_env[wide_stringify(key)] = wide_stringify(value);
137  }
138 
139  std::wstring env_string;
140  foreachpair (const std::wstring& key,
141  const std::wstring& value,
142  combined_env) {
143  env_string += key + L'=' + value + L'\0';
144  }
145 
146  // Append final null terminating character.
147  env_string.push_back(L'\0');
148  return env_string;
149 }
150 
151 
152 // Concatenates multiple command-line arguments and escapes the values.
153 // NOTE: This is necessary even when using Windows APIs that "appear"
154 // to take arguments as a list, because those APIs will themselves
155 // concatenate command-line arguments *without* escaping them.
156 //
157 // This function escapes arguments with the following rules:
158 // 1) Any argument with a space, tab, newline, vertical tab,
159 // or double-quote must be surrounded in double-quotes.
160 // 2) Backslashes at the very end of an argument must be escaped.
161 // 3) Backslashes that precede a double-quote must be escaped.
162 // The double-quote must also be escaped.
163 //
164 // NOTE: The below algorithm is adapted from Daniel Colascione's public domain
165 // algorithm for quoting command line arguments on Windows for `CreateProcess`.
166 //
167 // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
168 // NOLINT(whitespace/line_length)
169 inline std::wstring stringify_args(const std::vector<std::string>& argv)
170 {
171  std::wstring command;
172 
173  for (auto argit = argv.cbegin(); argit != argv.cend(); ++argit) {
174  std::wstring arg = wide_stringify(*argit);
175 
176  // Don't quote empty arguments or those without troublesome characters.
177  if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == arg.npos) {
178  command.append(arg);
179  } else {
180  // Beginning double quotation mark.
181  command.push_back(L'"');
182  for (auto it = arg.cbegin(); it != arg.cend(); ++it) {
183  // Count existent backslashes in argument.
184  unsigned int backslashes = 0;
185  while (it != arg.cend() && *it == L'\\') {
186  ++it;
187  ++backslashes;
188  }
189 
190  if (it == arg.cend()) {
191  // Escape all backslashes, but let the terminating double quotation
192  // mark we add below be interpreted as a metacharacter.
193  command.append(backslashes * 2, L'\\');
194  break;
195  } else if (*it == L'"') {
196  // Escape all backslashes and the following double quotation mark.
197  command.append(backslashes * 2 + 1, L'\\');
198  command.push_back(*it);
199  } else {
200  // Backslashes aren't special here.
201  command.append(backslashes, L'\\');
202  command.push_back(*it);
203  }
204  }
205 
206  // Terminating double quotation mark.
207  command.push_back(L'"');
208  }
209  // Space separate arguments (but don't append at end).
210  if (argit != argv.cend() - 1) {
211  command.push_back(L' ');
212  }
213  }
214 
215  // Append final null terminating character.
216  command.push_back(L'\0');
217  return command;
218 }
219 
220 
222 {
226 };
227 
228 
229 // Provides an interface for creating a child process on Windows.
230 //
231 // The `command` argument is given for compatibility, and is ignored. This is
232 // because the `CreateProcess` will use the first part of `commandLine` as the
233 // module name to execute, and will perform a `PATH` lookup. If `command` were
234 // to be used instead, `CreateProcess` would require an absolute path.
235 // See the MSDN documentation for the complicated rules at play for parsing
236 // and locating the module to execute.
237 //
238 // This takes a string command line because Windows programs receive the
239 // entire command line as a string and perform their own parsing (unlike
240 // POSIX exec which allows you to explicitly pass arguments). However,
241 // many programs (e.g. those written with a libc `main(int argc, char** argv)`)
242 // will use `CommandLineToArgvW` to parse the command line. For callers
243 // of this function that want to pass arguments to such programs, we
244 // provide an overload of `create_process` that takes arguments
245 // explicitly and constructs an appropriate command line string for
246 // `CommandLineToArgvW` to parse it back out as arguments. Notably,
247 // cmd.exe has its own handling of the command line that differs from
248 // `CommandLineToArgvW`.
249 //
250 // If `create_suspended` is `true`, the process will not be started, and the
251 // caller must use `ResumeThread` to start the process.
252 //
253 // The caller can specify explicit `stdin`, `stdout`, and `stderr` handles,
254 // in that order, for the process via the `pipes` argument.
255 //
256 // NOTE: If `pipes` are specified, they will be temporarily set to
257 // inheritable, and then set to uninheritable. This is a side effect
258 // on each `HANDLE`.
259 //
260 // The return value is a `ProcessData` struct, with the process and thread
261 // handles each saved in a `SharedHandle`, ensuring they are closed when struct
262 // goes out of scope.
264  const std::string& command,
265  const std::vector<std::string>& argv,
266  const Option<std::map<std::string, std::string>>& environment,
267  const bool create_suspended = false,
268  const Option<std::array<int_fd, 3>>& pipes = None(),
269  const std::vector<int_fd>& whitelist_fds = {})
270 {
271  // TODO(andschwa): Assert that `command` and `argv[0]` are the same.
272  const std::wstring arg_string = stringify_args(argv);
273  std::vector<wchar_t> arg_buffer(arg_string.begin(), arg_string.end());
274  arg_buffer.push_back(L'\0');
275 
276  // Create the process with a Unicode environment and extended
277  // startup info.
278  DWORD creation_flags =
279  CREATE_UNICODE_ENVIRONMENT | EXTENDED_STARTUPINFO_PRESENT;
280  if (create_suspended) {
281  creation_flags |= CREATE_SUSPENDED;
282  }
283 
284  // Construct the environment that will be passed to `::CreateProcessW`.
286  std::vector<wchar_t> env_buffer;
287  if (env_string.isSome()) {
288  // This string contains the necessary null characters.
289  env_buffer.assign(env_string.get().begin(), env_string.get().end());
290  }
291 
292  wchar_t* process_env = env_buffer.empty() ? nullptr : env_buffer.data();
293 
294  PROCESS_INFORMATION process_info = {};
295 
296  STARTUPINFOEXW startup_info_ex = {};
297  startup_info_ex.StartupInfo.cb = sizeof(startup_info_ex);
298 
299  // Windows provides a way to whitelist a set of handles to be
300  // inherited by the child process.
301  // https://blogs.msdn.microsoft.com/oldnewthing/20111216-00/?p=8873
302  // (1) We're setting the pipe handles and whitelisted handles to be
303  // temporarily inheritable.
304  // (2) We're explicitly whitelisting the handles using a Windows API.
305  // (3) We're then setting the handles to back to non-inheritable
306  // after the child process has been created.
307  std::vector<HANDLE> handles;
308  if (pipes.isSome()) {
309  // Each of these handles must be inheritable.
310  foreach (const int_fd& fd, pipes.get()) {
311  handles.emplace_back(static_cast<HANDLE>(fd));
312  const Try<Nothing> inherit = ::internal::windows::set_inherit(fd, true);
313  if (inherit.isError()) {
314  return Error(inherit.error());
315  }
316  }
317 
318  // Hook up the stdin/out/err pipes and use the `STARTF_USESTDHANDLES`
319  // flag to instruct the child to use them [1].
320  // A more user-friendly example can be found in [2].
321  //
322  // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331(v=vs.85).aspx
323  // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
324  startup_info_ex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
325  startup_info_ex.StartupInfo.hStdInput = std::get<0>(pipes.get());
326  startup_info_ex.StartupInfo.hStdOutput = std::get<1>(pipes.get());
327  startup_info_ex.StartupInfo.hStdError = std::get<2>(pipes.get());
328  }
329 
330  foreach (const int_fd& fd, whitelist_fds) {
331  handles.emplace_back(static_cast<HANDLE>(fd));
332  const Try<Nothing> inherit = ::internal::windows::set_inherit(fd, true);
333  if (inherit.isError()) {
334  return Error(inherit.error());
335  }
336  }
337 
340 
341  if (attribute_list.isError()) {
342  return Error(attribute_list.error());
343  }
344 
345  if (attribute_list.isSome()) {
346  startup_info_ex.lpAttributeList = attribute_list->get();
347  }
348 
349  const BOOL result = ::CreateProcessW(
350  // This is replaced by the first token of `wideCommandLineCopy` string.
351  static_cast<LPCWSTR>(nullptr),
352  static_cast<LPWSTR>(arg_buffer.data()),
353  static_cast<LPSECURITY_ATTRIBUTES>(nullptr),
354  static_cast<LPSECURITY_ATTRIBUTES>(nullptr),
355  TRUE, // Inherit parent process handles (such as those in `pipes`).
356  creation_flags,
357  static_cast<LPVOID>(process_env),
358  static_cast<LPCWSTR>(nullptr), // Inherit working directory.
359  &startup_info_ex.StartupInfo,
360  &process_info);
361 
362  // Save the error from the previous call so that we can proceed to
363  // always revert the inheritance of the handles, and then report
364  // this error, if there was one.
365  const DWORD create_process_error = ::GetLastError();
366 
367  // NOTE: The MSDN documentation for `CreateProcess` states that it
368  // returns before the process has "finished initialization," but is
369  // not clear on precisely what initialization entails. It would seem
370  // that this does not affect inherited handles, as it stands to
371  // reason that the system call to `CreateProcess` causes inheritable
372  // handles to become inherited, and not some "initialization" of the
373  // child process. However, if an inheritance race condition
374  // manifests, this assumption should be re-evaluated.
375  if (pipes.isSome()) {
376  // These handles should no longer be inheritable. This prevents other child
377  // processes from accidentally inheriting the wrong handles.
378  //
379  // NOTE: This is explicit, and does not take into account the
380  // previous inheritance semantics of each `HANDLE`. It is assumed
381  // that users of this function send non-inheritable handles.
382  foreach (const int_fd& fd, pipes.get()) {
383  const Try<Nothing> inherit = ::internal::windows::set_inherit(fd, false);
384  if (inherit.isError()) {
385  return Error(inherit.error());
386  }
387  }
388  }
389 
390  foreach (const int_fd& fd, whitelist_fds) {
391  const Try<Nothing> inherit = ::internal::windows::set_inherit(fd, false);
392  if (inherit.isError()) {
393  return Error(inherit.error());
394  }
395  }
396 
397  if (result == FALSE) {
398  return WindowsError(
399  create_process_error,
400  "Failed to call `CreateProcess`: " + stringify(arg_string));
401  }
402 
403  return ProcessData{SharedHandle{process_info.hProcess, ::CloseHandle},
404  SharedHandle{process_info.hThread, ::CloseHandle},
405  static_cast<pid_t>(process_info.dwProcessId)};
406 }
407 
408 } // namespace internal {
409 } // namespace windows {
410 
411 
413  const std::string& command,
414  const std::vector<std::string>& arguments,
415  const Option<std::map<std::string, std::string>>& environment = None())
416 {
417  using namespace os::windows::internal;
418 
419  Try<ProcessData> process_data =
420  create_process(command, arguments, environment);
421 
422  if (process_data.isError()) {
423  LOG(WARNING) << process_data.error();
424  return None();
425  }
426 
427  // Wait for the process synchronously.
428  ::WaitForSingleObject(process_data->process_handle.get_handle(), INFINITE);
429 
430  DWORD status;
431  if (!::GetExitCodeProcess(
432  process_data->process_handle.get_handle(), &status)) {
433  LOG(WARNING) << "Failed to `GetExitCodeProcess`: " << command;
434  return None();
435  }
436 
437  // Return the child exit code.
438  return static_cast<int>(status);
439 }
440 
441 
442 inline int execvp(
443  const std::string& file,
444  const std::vector<std::string>& argv)
445 {
446  exit(os::spawn(file, argv).getOrElse(-1));
447  return -1;
448 }
449 
450 
451 inline int execvpe(
452  const std::string& file,
453  const std::vector<std::string>& argv,
454  const std::map<std::string, std::string>& envp)
455 {
456  exit(os::spawn(file, argv, envp).getOrElse(-1));
457  return -1;
458 }
459 
460 } // namespace os {
461 
462 #endif // __STOUT_OS_WINDOWS_EXEC_HPP__
Result< std::shared_ptr< AttributeList > > create_attributes_list_for_handles(const std::vector< HANDLE > &handles)
Definition: inherit.hpp:32
Option< std::wstring > create_process_env(const Option< std::map< std::string, std::string >> &env)
Definition: exec.hpp:110
Definition: errorbase.hpp:36
Definition: option.hpp:29
HANDLE get_handle() const
Definition: windows.hpp:90
Definition: windows.hpp:72
Definition: check.hpp:33
Try< ProcessData > create_process(const std::string &command, const std::vector< std::string > &argv, const Option< std::map< std::string, std::string >> &environment, const bool create_suspended=false, const Option< std::array< int_fd, 3 >> &pipes=None(), const std::vector< int_fd > &whitelist_fds={})
Definition: exec.hpp:263
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Definition: error.hpp:108
Try< Nothing > set_inherit(const int_fd &fd, const bool inherit)
Definition: inherit.hpp:98
Definition: posix_signalhandler.hpp:23
SharedHandle process_handle
Definition: exec.hpp:223
Definition: check.hpp:30
SharedHandle thread_handle
Definition: exec.hpp:224
bool isSome() const
Definition: option.hpp:116
int execvp(const char *file, char *const argv[])
Definition: exec.hpp:58
DWORD pid_t
Definition: windows.hpp:181
URI file(const std::string &path)
Creates a file URI with the given path on the local host.
Definition: file.hpp:33
const T & get() const &
Definition: option.hpp:119
#define foreachpair(KEY, VALUE, ELEMS)
Definition: foreach.hpp:51
std::map< std::string, std::string > environment()
Definition: environment.hpp:24
static Try error(const E &e)
Definition: try.hpp:43
Option< std::map< std::wstring, std::wstring > > get_system_env()
Definition: exec.hpp:48
process::Future< Nothing > transform(process::Owned< Reader< T >> &&reader, const std::function< std::string(const T &)> &func, process::http::Pipe::Writer writer)
This is a helper function that reads records from a Reader, applies a transformation to the records a...
Definition: recordio.hpp:112
Definition: none.hpp:27
Definition: attributes.hpp:24
bool isError() const
Definition: try.hpp:78
std::wstring stringify_args(const std::vector< std::string > &argv)
Definition: exec.hpp:169
Definition: exec.hpp:44
pid_t pid
Definition: exec.hpp:225
Definition: exec.hpp:221
int int_fd
Definition: int_fd.hpp:35
std::string stringify(int flags)
int execvpe(const char *file, char **argv, char **envp)
Definition: exec.hpp:64
Option< int > spawn(const std::string &file, const std::vector< std::string > &arguments)
Definition: exec.hpp:32