Apache Mesos
os.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_POSIX_OS_HPP__
14 #define __STOUT_POSIX_OS_HPP__
15 
16 #include <errno.h>
17 #ifdef __sun
18 #include <sys/loadavg.h>
19 #define dirfd(dir) ((dir)->d_fd)
20 #ifndef NAME_MAX
21 #define NAME_MAX MAXNAMLEN
22 #endif // NAME_MAX
23 #else
24 #include <fts.h>
25 #endif // __sun
26 #include <glob.h>
27 #include <grp.h>
28 #include <limits.h>
29 #include <netdb.h>
30 #include <pwd.h>
31 #include <signal.h>
32 #include <stddef.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <utime.h>
37 
38 #include <sys/ioctl.h>
39 
40 #ifdef __linux__
41 #include <linux/version.h>
42 #include <sys/sysinfo.h>
43 #endif // __linux__
44 
45 #include <sys/ioctl.h>
46 #include <sys/utsname.h>
47 #include <sys/wait.h>
48 
49 #include <list>
50 #include <map>
51 #include <mutex>
52 #include <set>
53 #include <string>
54 #include <vector>
55 
56 #include <stout/synchronized.hpp>
57 
58 #include <stout/os/close.hpp>
59 #include <stout/os/environment.hpp>
60 #include <stout/os/fcntl.hpp>
61 #include <stout/os/find.hpp>
62 #include <stout/os/fork.hpp>
63 #include <stout/os/getcwd.hpp>
64 #include <stout/os/killtree.hpp>
65 #include <stout/os/os.hpp>
66 #include <stout/os/permissions.hpp>
67 #include <stout/os/read.hpp>
68 #include <stout/os/rename.hpp>
69 #include <stout/os/sendfile.hpp>
70 #include <stout/os/signals.hpp>
71 #include <stout/os/strerror.hpp>
72 #include <stout/os/write.hpp>
73 
74 #ifdef __FreeBSD__
75 #include <stout/os/freebsd.hpp>
76 #endif
77 #ifdef __linux__
78 #include <stout/os/linux.hpp>
79 #endif // __linux__
80 #include <stout/os/open.hpp>
81 #ifdef __APPLE__
82 #include <stout/os/osx.hpp>
83 #endif // __APPLE__
84 #ifdef __sun
85 #include <stout/os/sunos.hpp>
86 #endif // __sun
87 
88 #include <stout/os/posix/chown.hpp>
90 
91 #include <stout/os/shell.hpp>
92 
93 namespace os {
94 
95 // Import `::gmtime_r` into `os::` namespace.
97 
98 // Import `::hstrerror` into `os::` namespace.
100 
101 // Import `::random` into `os::` namespace.
103 
104 // Forward declarations.
105 inline Try<Nothing> utime(const std::string&);
107 
108 
109 // Suspends execution of the calling process until a child specified by `pid`
110 // has changed state. Unlike the POSIX standard function `::waitpid`, this
111 // function does not use -1 and 0 to signify errors and nonblocking return.
112 // Instead, we return `Result<pid_t>`:
113 // * In case of error, we return `Error` rather than -1. For example, we
114 // would return an `Error` in case of `EINVAL`.
115 // * In case of nonblocking return, we return `None` rather than 0. For
116 // example, if we pass `WNOHANG` in the `options`, we would expect 0 to be
117 // returned in the case that children specified by `pid` exist, but have
118 // not changed state yet. In this case we return `None` instead.
119 //
120 // NOTE: There are important differences between the POSIX and Windows
121 // implementations of this function:
122 // * On POSIX, `pid_t` is a signed number, but on Windows, PIDs are `DWORD`,
123 // which is `unsigned long`. Thus, if we use `DWORD` to represent the `pid`
124 // argument, we would not be able to pass -1 as the `pid`.
125 // * Since it is important to be able to detect -1 has been passed to
126 // `os::waitpid`, as a matter of practicality, we choose to:
127 // (1) Use `long` to represent the `pid` argument.
128 // (2) Disable using any value <= 0 for `pid` on Windows.
129 // * This decision is pragmatic. The reasoning is:
130 // (1) The Windows code paths call `os::waitpid` in only a handful of
131 // places, and in none of these conditions do we need `-1` as a value.
132 // (2) Since PIDs virtually never take on values outside the range of
133 // vanilla signed `long` it is likely that an accidental conversion
134 // will never happen.
135 // (3) Even though it is not formalized in the C specification, the
136 // implementation of `long` on the vast majority of production servers
137 // is 2's complement, so we expect that when we accidentally do
138 // implicitly convert from `unsigned long` to `long`, we will "wrap
139 // around" to negative values. And since we've disabled the negative
140 // `pid` in the Windows implementation, we should error out.
141 inline Result<pid_t> waitpid(pid_t pid, int* status, int options)
142 {
143  const pid_t child_pid = ::waitpid(pid, status, options);
144 
145  if (child_pid == 0) {
146  return None();
147  } else if (child_pid < 0) {
148  return ErrnoError("os::waitpid: Call to `waitpid` failed");
149  } else {
150  return child_pid;
151  }
152 }
153 
154 
155 // Sets the value associated with the specified key in the set of
156 // environment variables.
157 inline void setenv(const std::string& key,
158  const std::string& value,
159  bool overwrite = true)
160 {
161  ::setenv(key.c_str(), value.c_str(), overwrite ? 1 : 0);
162 }
163 
164 
165 // Unsets the value associated with the specified key in the set of
166 // environment variables.
167 inline void unsetenv(const std::string& key)
168 {
169  // NOTE: This function explicitly does not clear from
170  // `/proc/$pid/environ` because that is defined to be the initial
171  // environment that was set when the process was started, so don't
172  // use this to delete secrets! Instead, see `os::eraseenv`.
173  ::unsetenv(key.c_str());
174 }
175 
176 
177 // Erases the value associated with the specified key from the set of
178 // environment variables. By erase, we even clear it from
179 // `/proc/$pid/environ` so that sensitive information conveyed via
180 // environment variables (such as secrets) can be cleaned up.
181 inline void eraseenv(const std::string& key)
182 {
183  char* value = ::getenv(key.c_str());
184 
185  // Erase the old value so that on Linux it can't be inspected
186  // through `/proc/$pid/environ`, useful for secrets.
187  if (value) {
188  ::memset(value, '\0', ::strlen(value));
189  }
190 
191  ::unsetenv(key.c_str());
192 }
193 
194 
195 // This function is a portable version of execvpe ('p' means searching
196 // executable from PATH and 'e' means setting environments). We add
197 // this function because it is not available on all systems.
198 //
199 // NOTE: This function is not thread safe. It is supposed to be used
200 // only after fork (when there is only one thread). This function is
201 // async signal safe.
202 inline int execvpe(const char* file, char** argv, char** envp)
203 {
204  char** saved = os::raw::environment();
205 
206  *os::raw::environmentp() = envp;
207 
208  int result = execvp(file, argv);
209 
210  *os::raw::environmentp() = saved;
211 
212  return result;
213 }
214 
215 
216 inline Try<Nothing> chmod(const std::string& path, int mode)
217 {
218  if (::chmod(path.c_str(), mode) < 0) {
219  return ErrnoError();
220  }
221 
222  return Nothing();
223 }
224 
225 
227  const std::string& path,
228  mode_t mode,
229  dev_t dev)
230 {
231  if (::mknod(path.c_str(), mode, dev) < 0) {
232  return ErrnoError();
233  }
234 
235  return Nothing();
236 }
237 
238 
239 // Suspends execution for the given duration.
240 inline Try<Nothing> sleep(const Duration& duration)
241 {
242  timespec remaining;
243  remaining.tv_sec = static_cast<long>(duration.secs());
244  remaining.tv_nsec =
245  static_cast<long>((duration - Seconds(remaining.tv_sec)).ns());
246 
247  while (nanosleep(&remaining, &remaining) == -1) {
248  if (errno == EINTR) {
249  continue;
250  } else {
251  return ErrnoError();
252  }
253  }
254 
255  return Nothing();
256 }
257 
258 
259 // Returns the list of files that match the given (shell) pattern.
260 inline Try<std::list<std::string>> glob(const std::string& pattern)
261 {
262  glob_t g;
263  int status = ::glob(pattern.c_str(), GLOB_NOSORT, nullptr, &g);
264 
265  std::list<std::string> result;
266 
267  if (status != 0) {
268  if (status == GLOB_NOMATCH) {
269  return result; // Empty list.
270  } else {
271  return ErrnoError();
272  }
273  }
274 
275  for (size_t i = 0; i < g.gl_pathc; ++i) {
276  result.push_back(g.gl_pathv[i]);
277  }
278 
279  globfree(&g); // Best-effort free of dynamically allocated memory.
280 
281  return result;
282 }
283 
284 
285 // Returns the total number of cpus (cores).
286 inline Try<long> cpus()
287 {
288  long cpus = sysconf(_SC_NPROCESSORS_ONLN);
289 
290  if (cpus < 0) {
291  return ErrnoError();
292  }
293  return cpus;
294 }
295 
296 
297 // Returns load struct with average system loads for the last
298 // 1, 5 and 15 minutes respectively.
299 // Load values should be interpreted as usual average loads from
300 // uptime(1).
302 {
303  double loadArray[3];
304  if (getloadavg(loadArray, 3) == -1) {
305  return ErrnoError("Failed to determine system load averages");
306  }
307 
308  Load load;
309  load.one = loadArray[0];
310  load.five = loadArray[1];
311  load.fifteen = loadArray[2];
312 
313  return load;
314 }
315 
316 
317 // Return the system information.
319 {
320  struct utsname name;
321 
322  if (::uname(&name) < 0) {
323  return ErrnoError();
324  }
325 
326  UTSInfo info;
327  info.sysname = name.sysname;
328  info.nodename = name.nodename;
329  info.release = name.release;
330  info.version = name.version;
331  info.machine = name.machine;
332  return info;
333 }
334 
335 
336 // Overload of os::pids for filtering by groups and sessions.
337 // A group / session id of 0 will fitler on the group / session ID
338 // of the calling process.
340 {
341  if (group.isNone() && session.isNone()) {
342  return os::pids();
343  } else if (group.isSome() && group.get() < 0) {
344  return Error("Invalid group");
345  } else if (session.isSome() && session.get() < 0) {
346  return Error("Invalid session");
347  }
348 
350 
351  if (processes.isError()) {
352  return Error(processes.error());
353  }
354 
355  // Obtain the calling process group / session ID when 0 is provided.
356  if (group.isSome() && group.get() == 0) {
357  group = getpgid(0);
358  }
359  if (session.isSome() && session.get() == 0) {
360  session = getsid(0);
361  }
362 
363  std::set<pid_t> result;
364  foreach (const Process& process, processes.get()) {
365  // Group AND Session (intersection).
366  if (group.isSome() && session.isSome()) {
367  if (group.get() == process.group &&
368  process.session.isSome() &&
369  session.get() == process.session.get()) {
370  result.insert(process.pid);
371  }
372  } else if (group.isSome() && group.get() == process.group) {
373  result.insert(process.pid);
374  } else if (session.isSome() && process.session.isSome() &&
375  session.get() == process.session.get()) {
376  result.insert(process.pid);
377  }
378  }
379 
380  return result;
381 }
382 
383 
384 // Creates a tar 'archive' with gzip compression, of the given 'path'.
385 inline Try<Nothing> tar(const std::string& path, const std::string& archive)
386 {
387  Try<std::string> tarOut =
388  os::shell("tar %s %s %s", "-czf", archive.c_str(), path.c_str());
389 
390  if (tarOut.isError()) {
391  return Error("Failed to archive " + path + ": " + tarOut.error());
392  }
393 
394  return Nothing();
395 }
396 
397 
398 // Return the OS release numbers.
400 {
401  Try<UTSInfo> info = uname();
402  if (info.isError()) {
403  return Error(info.error());
404  }
405 
406  int major, minor, patch = 0;
407 #ifndef __FreeBSD__
408  // TODO(karya): Replace sscanf with Version::parse() once Version
409  // starts supporting labels and build metadata.
410  if (::sscanf(
411  info->release.c_str(),
412  "%d.%d.%d",
413  &major,
414  &minor,
415  &patch) != 3) {
416  return Error("Failed to parse: " + info->release);
417  }
418 #else
419  // TODO(dforsyth): Handle FreeBSD patch versions (-pX).
420  if (::sscanf(info->release.c_str(), "%d.%d-%*s", &major, &minor) != 2) {
421  return Error("Failed to parse: " + info->release);
422  }
423 #endif
424  return Version(major, minor, patch);
425 }
426 
427 
429 {
430  return "/var";
431 }
432 
433 
434 inline Try<Nothing> dup2(int oldFd, int newFd)
435 {
436  while (::dup2(oldFd, newFd) == -1) {
437  if (errno == EINTR) {
438  continue;
439  } else {
440  return ErrnoError();
441  }
442  }
443  return Nothing();
444 }
445 
446 
448 {
449  // 'ptsname' is not thread safe. Therefore, we use mutex here to
450  // make this method thread safe.
451  // TODO(jieyu): Consider using ptsname_r for linux.
452  static std::mutex* mutex = new std::mutex;
453 
454  synchronized (mutex) {
455  const char* slavePath = ::ptsname(master);
456  if (slavePath == nullptr) {
457  return ErrnoError();
458  }
459  return slavePath;
460  }
461 }
462 
463 
464 inline Try<Nothing> setctty(int fd)
465 {
466  if (ioctl(fd, TIOCSCTTY, nullptr) == -1) {
467  return ErrnoError();
468  }
469 
470  return Nothing();
471 }
472 
473 
474 // Update the window size for
475 // the terminal represented by fd.
477  int fd,
478  unsigned short rows,
479  unsigned short columns)
480 {
481  struct winsize winsize;
482  winsize.ws_row = rows;
483  winsize.ws_col = columns;
484 
485  if (ioctl(fd, TIOCSWINSZ, &winsize) != 0) {
486  return ErrnoError();
487  }
488 
489  return Nothing();
490 }
491 
492 
493 // Returns a host-specific default for the `PATH` environment variable, based
494 // on the configuration of the host.
495 inline std::string host_default_path()
496 {
497  return "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
498 }
499 
500 } // namespace os {
501 
502 #endif // __STOUT_POSIX_OS_HPP__
Try< Nothing > dup2(int oldFd, int newFd)
Definition: os.hpp:434
Definition: path.hpp:26
Definition: nothing.hpp:16
Definition: errorbase.hpp:36
T & get()&
Definition: try.hpp:73
Try< std::string > patch(const std::string &s, const Diff &diff)
Definition: svn.hpp:157
Definition: master.hpp:27
Try< Nothing > chmod(const std::string &path, int mode)
Definition: os.hpp:216
Definition: check.hpp:33
Try< Nothing > sleep(const Duration &duration)
Definition: os.hpp:240
const pid_t group
Definition: process.hpp:55
Try< std::list< Process > > processes()
Definition: os.hpp:182
Try< Nothing > mknod(const std::string &path, mode_t mode, dev_t dev)
Definition: os.hpp:226
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Definition: errorbase.hpp:50
Definition: posix_signalhandler.hpp:23
const Option< pid_t > session
Definition: process.hpp:56
void setenv(const std::string &key, const std::string &value, bool overwrite=true)
Definition: os.hpp:157
void unsetenv(const std::string &key)
Definition: os.hpp:167
Definition: duration.hpp:32
Definition: check.hpp:30
std::string host_default_path()
Definition: os.hpp:495
char *** environmentp()
Definition: environment.hpp:88
bool isSome() const
Definition: option.hpp:115
std::string release
Definition: os.hpp:48
Definition: os.hpp:25
int execvp(const char *file, char *const argv[])
Definition: shell.hpp:195
std::string version
Definition: os.hpp:49
Try< Load > loadavg()
Definition: os.hpp:301
DWORD pid_t
Definition: windows.hpp:181
Definition: process.hpp:32
int mode_t
Definition: windows.hpp:177
Try< dev_t > dev(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:172
const pid_t pid
Definition: process.hpp:53
URI file(const std::string &path)
Creates a file URI with the given path on the local host.
Definition: file.hpp:33
Try< Nothing > utime(const std::string &path)
Definition: utime.hpp:32
Try< long > cpus()
Definition: os.hpp:286
Definition: duration.hpp:207
Try< Nothing > tar(const std::string &path, const std::string &archive)
Definition: os.hpp:385
std::string hstrerror(int err)=delete
Try< Nothing > setctty(int fd)
Definition: os.hpp:464
const T & get() const &
Definition: option.hpp:118
Option< std::string > getenv(const std::string &key)
Definition: getenv.hpp:29
int random()
Definition: os.hpp:580
Try< Version > release()
Definition: os.hpp:399
double secs() const
Definition: duration.hpp:49
Result< pid_t > waitpid(pid_t pid, int *status, int options)
Definition: os.hpp:141
std::string sysname
Definition: os.hpp:46
Try< UTSInfo > uname()
Definition: os.hpp:318
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: grp.hpp:26
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:71
std::string nodename
Definition: os.hpp:47
Definition: ns.hpp:86
Definition: executor.hpp:48
void eraseenv(const std::string &key)
Definition: os.hpp:181
int execvpe(const std::string &command, const std::vector< std::string > &argv, const std::map< std::string, std::string > &envp)
Definition: shell.hpp:592
Definition: version.hpp:41
double fifteen
Definition: os.hpp:28
Try< mode_t > mode(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:159
bool isNone() const
Definition: option.hpp:116
Try< std::string > var()
Definition: os.hpp:428
Try< std::list< std::string > > glob(const std::string &pattern)
Definition: os.hpp:260
Try< std::set< pid_t > > pids()
Definition: freebsd.hpp:62
double five
Definition: os.hpp:27
std::string machine
Definition: os.hpp:50
Try< Nothing > setWindowSize(int fd, unsigned short rows, unsigned short columns)
Definition: os.hpp:476
Definition: os.hpp:44
tm * gmtime_r(const time_t *timep, tm *result)
Definition: os.hpp:431
double one
Definition: os.hpp:26
Try< std::string > ptsname(int master)
Definition: os.hpp:447
char ** environment()
Definition: environment.hpp:66