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_POSIX_SHELL_HPP__
14 #define __STOUT_OS_POSIX_SHELL_HPP__
15 
16 #include <stdarg.h> // For va_list, va_start, etc.
17 #include <stdio.h> // For ferror, fgets, FILE, pclose, popen.
18 
19 #include <sys/wait.h> // For waitpid.
20 
21 #include <ostream>
22 #include <string>
23 
24 #include <glog/logging.h>
25 
26 #include <stout/error.hpp>
27 #include <stout/format.hpp>
28 #include <stout/none.hpp>
29 #include <stout/option.hpp>
30 #include <stout/try.hpp>
31 
32 #include <stout/os/raw/argv.hpp>
33 
34 namespace os {
35 
36 namespace Shell {
37 
38 // Canonical constants used as platform-dependent args to `exec`
39 // calls. `name` is the command name, `arg0` is the first argument
40 // received by the callee, usually the command name and `arg1` is the
41 // second command argument received by the callee.
42 
43 constexpr const char* name = "sh";
44 constexpr const char* arg0 = "sh";
45 constexpr const char* arg1 = "-c";
46 
47 } // namespace Shell {
48 
72 template <typename... T>
73 Try<std::string> shell(const std::string& fmt, const T&... t)
74 {
75  const Try<std::string> command = strings::internal::format(fmt, t...);
76  if (command.isError()) {
77  return Error(command.error());
78  }
79 
80  FILE* file;
81  std::ostringstream stdout;
82 
83  if ((file = popen(command->c_str(), "r")) == nullptr) {
84  return Error("Failed to run '" + command.get() + "'");
85  }
86 
87  char line[1024];
88  // NOTE(vinod): Ideally the if and while loops should be interchanged. But
89  // we get a broken pipe error if we don't read the output and simply close.
90  while (fgets(line, sizeof(line), file) != nullptr) {
91  stdout << line;
92  }
93 
94  if (ferror(file) != 0) {
95  pclose(file); // Ignoring result since we already have an error.
96  return Error("Error reading output of '" + command.get() + "'");
97  }
98 
99  int status;
100  if ((status = pclose(file)) == -1) {
101  return Error("Failed to get status of '" + command.get() + "'");
102  }
103 
104  if (WIFSIGNALED(status)) {
105  return Error(
106  "Running '" + command.get() + "' was interrupted by signal '" +
107  strsignal(WTERMSIG(status)) + "'");
108  } else if ((WEXITSTATUS(status) != EXIT_SUCCESS)) {
109  LOG(ERROR) << "Command '" << command.get()
110  << "' failed; this is the output:\n" << stdout.str();
111  return Error(
112  "Failed to execute '" + command.get() + "'; the command was either "
113  "not found or exited with a non-zero exit status: " +
114  stringify(WEXITSTATUS(status)));
115  }
116 
117  return stdout.str();
118 }
119 
120 
121 // Executes a command by calling "/bin/sh -c <command>", and returns
122 // after the command has been completed. Returns the exit code on success
123 // and `None` on error (e.g., fork/exec/waitpid failed). This function
124 // is async signal safe. We return an `Option<int>` instead of a `Try<int>`,
125 // because although `Try` does not dynamically allocate, `Error` uses
126 // `std::string`, which is not async signal safe.
127 //
128 // Note: Be cautious about shell injection
129 // (https://en.wikipedia.org/wiki/Code_injection#Shell_injection)
130 // when using this method and use proper validation and sanitization
131 // on the `command`. For this reason in general `os::spawn` is
132 // preferred if a shell is not required.
133 inline Option<int> system(const std::string& command)
134 {
135  pid_t pid = ::fork();
136  if (pid == -1) {
137  return None();
138  } else if (pid == 0) {
139  // In child process.
140  ::execlp(
141  Shell::name, Shell::arg0, Shell::arg1, command.c_str(), (char*)nullptr);
142  ::exit(127);
143  } else {
144  // In parent process.
145  int status;
146  while (::waitpid(pid, &status, 0) == -1) {
147  if (errno != EINTR) {
148  return None();
149  }
150  }
151 
152  return status;
153  }
154 }
155 
156 // Executes a command by calling "<command> <arguments...>", and returns after
157 // the command has been completed. Returns the exit code on success and `None`
158 // on error (e.g., fork/exec/waitpid failed). This function is async signal
159 // safe. We return an `Option<int>` instead of a `Try<int>`, because although
160 // `Try` does not dynamically allocate, `Error` uses `std::string`, which is
161 // not async signal safe.
163  const std::string& command,
164  const std::vector<std::string>& arguments)
165 {
166  pid_t pid = ::fork();
167 
168  if (pid == -1) {
169  return None();
170  } else if (pid == 0) {
171  // In child process.
172  ::execvp(command.c_str(), os::raw::Argv(arguments));
173  ::exit(127);
174  } else {
175  // In parent process.
176  int status;
177  while (::waitpid(pid, &status, 0) == -1) {
178  if (errno != EINTR) {
179  return None();
180  }
181  }
182 
183  return status;
184  }
185 }
186 
187 
188 template<typename... T>
189 inline int execlp(const char* file, T... t)
190 {
191  return ::execlp(file, t...);
192 }
193 
194 
195 inline int execvp(const char* file, char* const argv[])
196 {
197  return ::execvp(file, argv);
198 }
199 
200 } // namespace os {
201 
202 #endif // __STOUT_OS_POSIX_SHELL_HPP__
constexpr const char * arg1
Definition: shell.hpp:45
Definition: errorbase.hpp:36
T & get()&
Definition: try.hpp:73
Definition: check.hpp:33
int execlp(const char *file, T...t)
Definition: shell.hpp:189
const char * strsignal(int signum)
Definition: windows.hpp:347
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Option< int > spawn(const std::string &command, const std::vector< std::string > &arguments)
Definition: shell.hpp:162
Definition: posix_signalhandler.hpp:23
constexpr const char * arg0
Definition: shell.hpp:44
int execvp(const char *file, char *const argv[])
Definition: shell.hpp:195
Represent the argument list expected by execv routines.
Definition: argv.hpp:36
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
#define WIFSIGNALED(x)
Definition: windows.hpp:376
Result< pid_t > waitpid(pid_t pid, int *status, int options)
Definition: os.hpp:141
#define WEXITSTATUS(x)
Definition: windows.hpp:368
static Try error(const E &e)
Definition: try.hpp:42
Try< std::string > shell(const std::string &fmt, const T &...t)
Runs a shell command with optional arguments.
Definition: shell.hpp:73
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:71
int execvp(const std::string &command, const std::vector< std::string > &argv)
Definition: shell.hpp:580
Try< std::string > format(const std::string &fmt, va_list args)
Definition: format.hpp:68
std::string stringify(int flags)
constexpr const char * name
Definition: shell.hpp:43
Option< int > system(const std::string &command)
Definition: shell.hpp:133