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 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 
47 
48 template <typename... T>
49 Try<std::string> shell(const std::string& fmt, const T&... t)
50 {
51  const Try<std::string> command = strings::format(fmt, t...);
52  if (command.isError()) {
53  return Error(command.error());
54  }
55 
56  FILE* file;
57  std::ostringstream stdout;
58 
59  if ((file = popen(command->c_str(), "r")) == nullptr) {
60  return Error("Failed to run '" + command.get() + "'");
61  }
62 
63  char line[1024];
64  // NOTE(vinod): Ideally the if and while loops should be interchanged. But
65  // we get a broken pipe error if we don't read the output and simply close.
66  while (fgets(line, sizeof(line), file) != nullptr) {
67  stdout << line;
68  }
69 
70  if (ferror(file) != 0) {
71  pclose(file); // Ignoring result since we already have an error.
72  return Error("Error reading output of '" + command.get() + "'");
73  }
74 
75  int status;
76  if ((status = pclose(file)) == -1) {
77  return Error("Failed to get status of '" + command.get() + "'");
78  }
79 
80  if (WIFSIGNALED(status)) {
81  return Error(
82  "Running '" + command.get() + "' was interrupted by signal '" +
83  strsignal(WTERMSIG(status)) + "'");
84  } else if ((WEXITSTATUS(status) != EXIT_SUCCESS)) {
85  LOG(ERROR) << "Command '" << command.get()
86  << "' failed; this is the output:\n" << stdout.str();
87  return Error(
88  "Failed to execute '" + command.get() + "'; the command was either "
89  "not found or exited with a non-zero exit status: " +
90  stringify(WEXITSTATUS(status)));
91  }
92 
93  return stdout.str();
94 }
95 
96 
97 inline Option<int> system(const std::string& command)
98 {
99  pid_t pid = ::fork();
100  if (pid == -1) {
101  return None();
102  } else if (pid == 0) {
103  // In child process.
104  ::execlp(
105  Shell::name, Shell::arg0, Shell::arg1, command.c_str(), (char*)nullptr);
106  ::exit(127);
107  } else {
108  // In parent process.
109  int status;
110  while (::waitpid(pid, &status, 0) == -1) {
111  if (errno != EINTR) {
112  return None();
113  }
114  }
115 
116  return status;
117  }
118 }
119 
120 } // namespace os {
121 
122 #endif // __STOUT_OS_POSIX_SHELL_HPP__
constexpr const char * arg1
Definition: shell.hpp:43
Definition: errorbase.hpp:36
T & get()&
Definition: try.hpp:80
Definition: check.hpp:33
const char * strsignal(int signum)
Definition: windows.hpp:347
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Definition: posix_signalhandler.hpp:23
constexpr const char * arg0
Definition: shell.hpp:42
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:43
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
Try< std::string > format(const std::string &s, const T &...t)
Definition: format.hpp:58
std::string stringify(int flags)
constexpr const char * name
Definition: shell.hpp:41
Option< int > system(const std::string &command)
Definition: shell.hpp:97