Apache Mesos
shell.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_SHELL_HPP__
14 #define __STOUT_OS_WINDOWS_SHELL_HPP__
15 
16 #include <process.h>
17 #include <processthreadsapi.h>
18 #include <shellapi.h>
19 #include <synchapi.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 Shell {
44 
45 // Canonical constants used as platform-dependent args to `exec` calls.
46 // `name` is the command name, `arg0` is the first argument received
47 // by the callee, usually the command name and `arg1` is the second
48 // command argument received by the callee.
49 constexpr const char* name = "cmd.exe";
50 constexpr const char* arg0 = "cmd.exe";
51 constexpr const char* arg1 = "/c";
52 
53 } // namespace Shell {
54 
55 
56 template <typename... T>
57 Try<std::string> shell(const std::string& fmt, const T&... t)
58 {
59  using std::array;
60  using std::string;
61  using std::vector;
62 
63  const Try<string> command = strings::format(fmt, t...);
64  if (command.isError()) {
65  return Error(command.error());
66  }
67 
68  // This function is intended to pass the arguments to the default
69  // shell, so we first add the arguments `cmd.exe cmd.exe /c`,
70  // followed by the command and arguments given.
71  vector<string> args = {os::Shell::name, os::Shell::arg0, os::Shell::arg1};
72 
73  { // Minimize the lifetime of the system allocated buffer.
74  //
75  // NOTE: This API returns a pointer to an array of `wchar_t*`,
76  // similar to `argv`. Each pointer to a null-terminated Unicode
77  // string represents an individual argument found on the command
78  // line. We use this because we cannot just split on whitespace.
79  int argc;
80  const std::unique_ptr<wchar_t*, decltype(&::LocalFree)> argv(
81  ::CommandLineToArgvW(wide_stringify(command.get()).data(), &argc),
82  &::LocalFree);
83  if (argv == nullptr) {
84  return WindowsError();
85  }
86 
87  for (int i = 0; i < argc; ++i) {
88  args.push_back(stringify(std::wstring(argv.get()[i])));
89  }
90  }
91 
92  // This function is intended to return only the `stdout` of the
93  // command; but since we have to redirect all of `stdin`, `stdout`,
94  // `stderr` if we want to redirect any one of them, we redirect
95  // `stdin` and `stderr` to `NUL`, and `stdout` to a pipe.
96  Try<int_fd> stdin_ = os::open(os::DEV_NULL, O_RDONLY);
97  if (stdin_.isError()) {
98  return Error(stdin_.error());
99  }
100 
101  Try<array<int_fd, 2>> stdout_ = os::pipe();
102  if (stdout_.isError()) {
103  return Error(stdout_.error());
104  }
105 
106  Try<int_fd> stderr_ = os::open(os::DEV_NULL, O_WRONLY);
107  if (stderr_.isError()) {
108  return Error(stderr_.error());
109  }
110 
111  // Ensure the file descriptors are closed when we leave this scope.
112  struct Closer
113  {
114  vector<int_fd> fds;
115  ~Closer()
116  {
117  foreach (int_fd& fd, fds) {
118  os::close(fd);
119  }
120  }
121  } closer = {{stdin_.get(), stdout_.get()[0], stderr_.get()}};
122 
123  array<int_fd, 3> pipes = {stdin_.get(), stdout_.get()[1], stderr_.get()};
124 
125  using namespace os::windows::internal;
126 
127  Try<ProcessData> process_data =
128  create_process(args.front(), args, None(), false, pipes);
129 
130  if (process_data.isError()) {
131  return Error(process_data.error());
132  }
133 
134  // Close the child end of the stdout pipe and then read until EOF.
135  os::close(stdout_.get()[1]);
136  string out;
137  Result<string> part = None();
138  do {
139  part = os::read(stdout_.get()[0], 1024);
140  if (part.isSome()) {
141  out += part.get();
142  }
143  } while (part.isSome());
144 
145  // Wait for the process synchronously.
146  ::WaitForSingleObject(process_data->process_handle.get_handle(), INFINITE);
147 
148  DWORD status;
149  if (!::GetExitCodeProcess(
150  process_data->process_handle.get_handle(), &status)) {
151  return Error("Failed to `GetExitCodeProcess`: " + command.get());
152  }
153 
154  if (status == 0) {
155  return out;
156  }
157 
158  return Error(
159  "Failed to execute '" + command.get() +
160  "'; the command was either "
161  "not found or exited with a non-zero exit status: " +
162  stringify(status));
163 }
164 
165 
166 inline Option<int> system(const std::string& command)
167 {
168  return os::spawn(Shell::name, {Shell::arg0, Shell::arg1, command}, None());
169 }
170 
171 } // namespace os {
172 
173 #endif // __STOUT_OS_WINDOWS_SHELL_HPP__
constexpr const char * arg1
Definition: shell.hpp:43
Try< std::array< int, 2 > > pipe()
Definition: pipe.hpp:33
Definition: errorbase.hpp:36
T & get()&
Definition: try.hpp:80
HANDLE get_handle() const
Definition: windows.hpp:90
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
Try< int_fd > open(const std::string &path, int oflag, mode_t mode=0)
Definition: open.hpp:35
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Definition: error.hpp:108
Definition: posix_signalhandler.hpp:23
SharedHandle process_handle
Definition: exec.hpp:223
Definition: check.hpp:30
constexpr const char * arg0
Definition: shell.hpp:42
Try< Nothing > close(int fd)
Definition: close.hpp:24
static Try error(const E &e)
Definition: try.hpp:43
Result< std::string > read(int_fd fd, size_t size)
Definition: read.hpp:55
Try< std::string > shell(const std::string &fmt, const T &...t)
Definition: shell.hpp:49
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:78
T & get()&
Definition: result.hpp:116
constexpr char DEV_NULL[]
Definition: constants.hpp:30
Definition: exec.hpp:44
bool isSome() const
Definition: result.hpp:112
Try< std::string > format(const std::string &s, const T &...t)
Definition: format.hpp:58
int int_fd
Definition: int_fd.hpp:35
std::string stringify(int flags)
Option< int > spawn(const std::string &file, const std::vector< std::string > &arguments)
Definition: exec.hpp:32
constexpr const char * name
Definition: shell.hpp:41
Option< int > system(const std::string &command)
Definition: shell.hpp:97