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