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_WINDOWS_OS_HPP__
14 #define __STOUT_WINDOWS_OS_HPP__
15 
16 #include <sys/utime.h>
17 
18 #include <list>
19 #include <map>
20 #include <memory>
21 #include <numeric>
22 #include <set>
23 #include <string>
24 #include <vector>
25 
26 #include <stout/bytes.hpp>
27 #include <stout/duration.hpp>
28 #include <stout/none.hpp>
29 #include <stout/nothing.hpp>
30 #include <stout/option.hpp>
31 #include <stout/path.hpp>
32 #include <stout/stringify.hpp>
33 #include <stout/strings.hpp>
34 #include <stout/try.hpp>
35 #include <stout/version.hpp>
36 #include <stout/windows.hpp>
37 
38 #include <stout/os/os.hpp>
39 #include <stout/os/getenv.hpp>
40 #include <stout/os/process.hpp>
41 #include <stout/os/read.hpp>
42 
44 #include <stout/os/windows/fd.hpp>
45 
46 // NOTE: These system headers must be included after `stout/windows.hpp`
47 // as they may include `Windows.h`. See comments in `stout/windows.hpp`
48 // for why this ordering is important.
49 #include <direct.h>
50 #include <io.h>
51 #include <Psapi.h>
52 #include <TlHelp32.h>
53 #include <Userenv.h>
54 
55 namespace os {
56 namespace internal {
57 
59 {
60  // MSDN documentation states "The names are established at system startup,
61  // when the system reads them from the registry." This is akin to the
62  // Linux `gethostname` which calls `uname`, thus avoiding a DNS lookup.
63  // The `net::getHostname` function can be used for an explicit DNS lookup.
64  //
65  // NOTE: This returns the hostname of the local computer, or the local
66  // node if this computer is part of a cluster.
67  COMPUTER_NAME_FORMAT format = ComputerNamePhysicalDnsHostname;
68  DWORD size = 0;
69  if (::GetComputerNameExW(format, nullptr, &size) == 0) {
70  if (::GetLastError() != ERROR_MORE_DATA) {
71  return WindowsError();
72  }
73  }
74 
75  std::vector<wchar_t> buffer;
76  buffer.reserve(size);
77 
78  if (::GetComputerNameExW(format, buffer.data(), &size) == 0) {
79  return WindowsError();
80  }
81 
82  return stringify(std::wstring(buffer.data()));
83 }
84 
85 } // namespace internal {
86 
87 
88 // Overload of os::pids for filtering by groups and sessions. A group / session
89 // id of 0 will fitler on the group / session ID of the calling process.
90 // NOTE: Windows does not have the concept of a process group, so we need to
91 // enumerate all processes.
93 {
94  DWORD max_items = 4096;
95  DWORD bytes_returned;
96  std::vector<pid_t> processes;
97  size_t size_in_bytes;
98 
99  // Attempt to populate `processes` with PIDs. We repeatedly call
100  // `EnumProcesses` with increasingly large arrays until it "succeeds" at
101  // populating the array with PIDs. The criteria for determining when
102  // `EnumProcesses` has succeeded are:
103  // (1) the return value is nonzero.
104  // (2) the `bytes_returned` is less than the number of bytes in the array.
105  do {
106  // TODO(alexnaparu): Set a limit to the memory that can be used.
107  processes.resize(max_items);
108  size_in_bytes = processes.size() * sizeof(pid_t);
109  CHECK_LE(size_in_bytes, MAXDWORD);
110 
111  BOOL result = ::EnumProcesses(
112  processes.data(),
113  static_cast<DWORD>(size_in_bytes),
114  &bytes_returned);
115 
116  if (!result) {
117  return WindowsError("os::pids: Call to `EnumProcesses` failed");
118  }
119 
120  max_items *= 2;
121  } while (bytes_returned >= size_in_bytes);
122 
123  std::set<pid_t> pids_set(processes.begin(), processes.end());
124 
125  // NOTE: The PID `0` will always be returned by `EnumProcesses`; however, it
126  // is the PID of Windows' System Idle Process. While the PID is valid, using
127  // it for anything is almost always invalid. For instance, `OpenProcess` will
128  // fail with an invalid parameter error if the user tries to get a handle for
129  // PID `0`. In the interest of safety, we prevent the `pids` API from ever
130  // including the PID `0`.
131  pids_set.erase(0);
132  return pids_set;
133 }
134 
135 
136 inline Try<std::set<pid_t>> pids()
137 {
138  return pids(None(), None());
139 }
140 
141 
142 // Sets the value associated with the specified key in the set of
143 // environment variables.
144 inline void setenv(
145  const std::string& key,
146  const std::string& value,
147  bool overwrite = true)
148 {
149  // Do not set the variable if already set and `overwrite` was not specified.
150  //
151  // Per MSDN, `GetEnvironmentVariable` returns 0 on error and sets the
152  // error code to `ERROR_ENVVAR_NOT_FOUND` if the variable was not found.
153  //
154  // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683188(v=vs.85).aspx // NOLINT(whitespace/line_length)
155  if (!overwrite &&
156  ::GetEnvironmentVariableW(wide_stringify(key).data(), nullptr, 0) != 0 &&
157  ::GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
158  return;
159  }
160 
161  // `SetEnvironmentVariable` returns an error code, but we can't act on it.
162  ::SetEnvironmentVariableW(
163  wide_stringify(key).data(), wide_stringify(value).data());
164 }
165 
166 
167 // Unsets the value associated with the specified key in the set of
168 // environment variables.
169 inline void unsetenv(const std::string& key)
170 {
171  // Per MSDN documentation[1], passing `nullptr` as the value will cause
172  // `SetEnvironmentVariable` to delete the key from the process's environment.
173  ::SetEnvironmentVariableW(wide_stringify(key).data(), nullptr);
174 }
175 
176 
177 // Suspends execution of the calling process until a child specified by `pid`
178 // has changed state. Unlike the POSIX standard function `::waitpid`, this
179 // function does not use -1 and 0 to signify errors and nonblocking return.
180 // Instead, we return `Result<pid_t>`:
181 // * In case of error, we return `Error` rather than -1. For example, we
182 // would return an `Error` in case of `EINVAL`.
183 // * In case of nonblocking return, we return `None` rather than 0. For
184 // example, if we pass `WNOHANG` in the `options`, we would expect 0 to be
185 // returned in the case that children specified by `pid` exist, but have
186 // not changed state yet. In this case we return `None` instead.
187 //
188 // NOTE: There are important differences between the POSIX and Windows
189 // implementations of this function:
190 // * On POSIX, `pid_t` is a signed number, but on Windows, PIDs are `DWORD`,
191 // which is `unsigned long`. Thus, if we use `DWORD` to represent the `pid`
192 // argument, passing -1 as the `pid` would (on most modern servers)
193 // silently convert to a really large `pid`. This is undesirable.
194 // * Since it is important to be able to detect -1 has been passed to
195 // `os::waitpid`, as a matter of practicality, we choose to:
196 // (1) Use `long` to represent the `pid` argument.
197 // (2) Disable using any value <= 0 for `pid` on Windows.
198 // * This decision is pragmatic. The reasoning is:
199 // (1) The Windows code paths call `os::waitpid` in only a handful of
200 // places, and in none of these conditions do we need `-1` as a value.
201 // (2) Since PIDs virtually never take on values outside the range of
202 // vanilla signed `long` it is likely that an accidental conversion
203 // will never happen.
204 // (3) Even though it is not formalized in the C specification, the
205 // implementation of `long` on the vast majority of production servers
206 // is 2's complement, so we expect that when we accidentally do
207 // implicitly convert from `unsigned long` to `long`, we will "wrap
208 // around" to negative values. And since we've disabled the negative
209 // `pid` in the Windows implementation, we should error out.
210 // * Finally, on Windows, we currently do not check that the process we are
211 // attempting to await is a child process.
212 inline Result<pid_t> waitpid(long pid, int* status, int options)
213 {
214  const bool wait_for_child = (options & WNOHANG) == 0;
215 
216  // NOTE: Windows does not implement pids <= 0.
217  if (pid <= 0) {
218  errno = ENOSYS;
219  return ErrnoError(
220  "os::waitpid: Value of pid is '" + stringify(pid) +
221  "'; the Windows implementation currently does not allow values <= 0");
222  } else if (options != 0 && options != WNOHANG) {
223  // NOTE: We only support `options == 0` or `options == WNOHANG`. On Windows
224  // no flags other than `WNOHANG` are supported.
225  errno = ENOSYS;
226  return ErrnoError(
227  "os::waitpid: Only flag `WNOHANG` is implemented on Windows");
228  }
229 
230  // TODO(hausdorff): Check that `pid` is one of the child processes. If not,
231  // set `errno` to `ECHILD` and return -1.
232 
233  // Open the child process as a safe `SharedHandle`.
234  const HANDLE process = ::OpenProcess(
235  PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
236  FALSE,
237  static_cast<DWORD>(pid));
238 
239  if (process == nullptr) {
240  return WindowsError("os::waitpid: Failed to open process for pid '" +
241  stringify(pid) + "'");
242  }
243 
244  SharedHandle scoped_process(process, ::CloseHandle);
245 
246  // If `WNOHANG` flag is set, don't wait. Otherwise, wait for child to
247  // terminate.
248  const DWORD wait_time = wait_for_child ? INFINITE : 0;
249  const DWORD wait_results = ::WaitForSingleObject(
250  scoped_process.get(),
251  wait_time);
252 
253  // Verify our wait exited correctly.
254  const bool state_signaled = wait_results == WAIT_OBJECT_0;
255  if (options == 0 && !state_signaled) {
256  // If `WNOHANG` is not set, then we should have stopped waiting only for a
257  // state change in `scoped_process`.
258  errno = ECHILD;
259  return WindowsError(
260  "os::waitpid: Failed to wait for pid '" + stringify(pid) +
261  "'. `::WaitForSingleObject` should have waited for child process to " +
262  "exit, but returned code '" + stringify(wait_results) +
263  "' instead");
264  } else if (wait_for_child && !state_signaled &&
265  wait_results != WAIT_TIMEOUT) {
266  // If `WNOHANG` is set, then a successful wait should report either a
267  // timeout (since we set the time to wait to `0`), or a successful state
268  // change of `scoped_process`. Anything else is an error.
269  errno = ECHILD;
270  return WindowsError(
271  "os::waitpid: Failed to wait for pid '" + stringify(pid) +
272  "'. `ENOHANG` flag was passed in, so `::WaitForSingleObject` should " +
273  "have either returned `WAIT_OBJECT_0` or `WAIT_TIMEOUT` (the " +
274  "timeout was set to 0, because we are not waiting for the child), " +
275  "but instead returned code '" + stringify(wait_results) + "'");
276  }
277 
278  if (!wait_for_child && wait_results == WAIT_TIMEOUT) {
279  // Success. `ENOHANG` was set and we got a timeout, so return `None` (POSIX
280  // `::waitpid` would return 0 here).
281  return None();
282  }
283 
284  // Attempt to retrieve exit code from child process. Store that exit code in
285  // the `status` variable if it's `nullptr`.
286  DWORD child_exit_code = 0;
287  if (!::GetExitCodeProcess(scoped_process.get(), &child_exit_code)) {
288  errno = ECHILD;
289  return WindowsError(
290  "os::waitpid: Successfully waited on child process with pid '" +
291  std::to_string(pid) + "', but could not retrieve exit code");
292  }
293 
294  if (status != nullptr) {
295  *status = child_exit_code;
296  }
297 
298  // Success. Return pid of the child process for which the status is reported.
299  return pid;
300 }
301 
302 
303 inline std::string hstrerror(int err) = delete;
304 
305 
306 inline Try<Nothing> chown(
307  uid_t uid,
308  gid_t gid,
309  const std::string& path,
310  bool recursive) = delete;
311 
312 
313 inline Try<Nothing> chmod(const std::string& path, int mode) = delete;
314 
315 
316 inline Try<Nothing> mknod(
317  const std::string& path,
318  mode_t mode,
319  dev_t dev) = delete;
320 
321 
322 // Suspends execution for the given duration.
323 // NOTE: This implementation features a millisecond-resolution sleep API, while
324 // the POSIX version uses a nanosecond-resolution sleep API. As of this writing,
325 // Mesos only requires millisecond resolution, so this is ok for now.
326 inline Try<Nothing> sleep(const Duration& duration)
327 {
328  ::Sleep(static_cast<DWORD>(duration.ms()));
329 
330  return Nothing();
331 }
332 
333 
334 // Returns the list of files that match the given (shell) pattern.
335 // NOTE: Deleted on Windows, as a POSIX-API-compliant `glob` is much more
336 // trouble than its worth, considering our relatively simple usage.
337 inline Try<std::list<std::string>> glob(const std::string& pattern) = delete;
338 
339 
340 // Returns the total number of cpus (cores).
341 inline Try<long> cpus()
342 {
343  SYSTEM_INFO sys_info;
344  ::GetSystemInfo(&sys_info);
345  return static_cast<long>(sys_info.dwNumberOfProcessors);
346 }
347 
348 // Returns load struct with average system loads for the last
349 // 1, 5 and 15 minutes respectively.
350 // Load values should be interpreted as usual average loads from
351 // uptime(1).
352 inline Try<Load> loadavg()
353 {
354  // No Windows equivalent, return an error until there is a need. We can
355  // construct an approximation of this function by periodically polling
356  // `GetSystemTimes` and using a sliding window of statistics.
357  return WindowsError(ERROR_NOT_SUPPORTED,
358  "Failed to determine system load averages");
359 }
360 
361 
362 // Returns the total size of main and free memory.
363 inline Try<Memory> memory()
364 {
365  Memory memory;
366 
367  MEMORYSTATUSEX memory_status;
368  memory_status.dwLength = sizeof(MEMORYSTATUSEX);
369  if (!::GlobalMemoryStatusEx(&memory_status)) {
370  return WindowsError("os::memory: Call to `GlobalMemoryStatusEx` failed");
371  }
372 
373  memory.total = Bytes(memory_status.ullTotalPhys);
374  memory.free = Bytes(memory_status.ullAvailPhys);
375  memory.totalSwap = Bytes(memory_status.ullTotalPageFile);
376  memory.freeSwap = Bytes(memory_status.ullAvailPageFile);
377 
378  return memory;
379 }
380 
381 
382 inline Try<Version> release() = delete;
383 
384 
385 // Return the system information.
386 inline Try<UTSInfo> uname() = delete;
387 
388 
389 inline tm* gmtime_r(const time_t* timep, tm* result)
390 {
391  return ::gmtime_s(result, timep) == ERROR_SUCCESS ? result : nullptr;
392 }
393 
394 
396 {
397  // Get a snapshot of the processes in the system. NOTE: We should not check
398  // whether the handle is `nullptr`, because this API will always return
399  // `INVALID_HANDLE_VALUE` on error.
400  HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, pid);
401  if (snapshot_handle == INVALID_HANDLE_VALUE) {
402  return WindowsError(
403  "os::process_entry: Call to `CreateToolhelp32Snapshot` failed");
404  }
405 
406  SharedHandle safe_snapshot_handle(snapshot_handle, ::CloseHandle);
407 
408  // Initialize process entry.
409  PROCESSENTRY32W process_entry;
410  memset(&process_entry, 0, sizeof(process_entry));
411  process_entry.dwSize = sizeof(process_entry);
412 
413  // Get first process so that we can loop through process entries until we
414  // find the one we care about.
415  SetLastError(ERROR_SUCCESS);
416  BOOL has_next = Process32First(safe_snapshot_handle.get(), &process_entry);
417  if (has_next == FALSE) {
418  // No first process was found. We should never be here; it is arguable we
419  // should return `None`, since we won't find the PID we're looking for, but
420  // we elect to return `Error` because something terrible has probably
421  // happened.
422  if (::GetLastError() != ERROR_SUCCESS) {
423  return WindowsError("os::process_entry: Call to `Process32First` failed");
424  } else {
425  return Error("os::process_entry: Call to `Process32First` failed");
426  }
427  }
428 
429  // Loop through processes until we find the one we're looking for.
430  while (has_next == TRUE) {
431  if (process_entry.th32ProcessID == pid) {
432  // Process found.
433  return process_entry;
434  }
435 
436  has_next = Process32Next(safe_snapshot_handle.get(), &process_entry);
437  if (has_next == FALSE) {
438  DWORD last_error = ::GetLastError();
439  if (last_error != ERROR_NO_MORE_FILES && last_error != ERROR_SUCCESS) {
440  return WindowsError(
441  "os::process_entry: Call to `Process32Next` failed");
442  }
443  }
444  }
445 
446  return None();
447 }
448 
449 
450 // Generate a `Process` object for the process associated with `pid`. If
451 // process is not found, we return `None`; error is reserved for the case where
452 // something went wrong.
453 inline Result<Process> process(pid_t pid)
454 {
455  if (pid == 0) {
456  // The 0th PID is that of the System Idle Process on Windows. However, it is
457  // invalid to attempt to get a proces handle or else perform any operation
458  // on this pseudo-process.
459  return Error("os::process: Invalid parameter: pid == 0");
460  }
461 
462  // Find process with pid.
464 
465  if (entry.isError()) {
466  return WindowsError(entry.error());
467  } else if (entry.isNone()) {
468  return None();
469  }
470 
471  HANDLE process_handle = ::OpenProcess(
472  PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ,
473  false,
474  pid);
475 
476  // ::OpenProcess returns `NULL`, not `INVALID_HANDLE_VALUE` on failure.
477  if (process_handle == nullptr) {
478  return WindowsError("os::process: Call to `OpenProcess` failed");
479  }
480 
481  SharedHandle safe_process_handle(process_handle, ::CloseHandle);
482 
483  // Get Windows Working set size (Resident set size in linux).
484  PROCESS_MEMORY_COUNTERS proc_mem_counters;
485  BOOL get_process_memory_info = ::GetProcessMemoryInfo(
486  safe_process_handle.get_handle(),
487  &proc_mem_counters,
488  sizeof(proc_mem_counters));
489 
490  if (!get_process_memory_info) {
491  return WindowsError("os::process: Call to `GetProcessMemoryInfo` failed");
492  }
493 
494  // Get session Id.
495  pid_t session_id;
496  BOOL process_id_to_session_id = ::ProcessIdToSessionId(pid, &session_id);
497 
498  if (!process_id_to_session_id) {
499  return WindowsError("os::process: Call to `ProcessIdToSessionId` failed");
500  }
501 
502  // Get Process CPU time.
503  FILETIME create_filetime, exit_filetime, kernel_filetime, user_filetime;
504  BOOL get_process_times = ::GetProcessTimes(
505  safe_process_handle.get_handle(),
506  &create_filetime,
507  &exit_filetime,
508  &kernel_filetime,
509  &user_filetime);
510 
511  if (!get_process_times) {
512  return WindowsError("os::process: Call to `GetProcessTimes` failed");
513  }
514 
515  // Get utime and stime.
516  ULARGE_INTEGER lKernelTime, lUserTime; // In 100 nanoseconds.
517  lKernelTime.HighPart = kernel_filetime.dwHighDateTime;
518  lKernelTime.LowPart = kernel_filetime.dwLowDateTime;
519  lUserTime.HighPart = user_filetime.dwHighDateTime;
520  lUserTime.LowPart = user_filetime.dwLowDateTime;
521 
522  Try<Duration> utime = Nanoseconds(lKernelTime.QuadPart * 100);
523  Try<Duration> stime = Nanoseconds(lUserTime.QuadPart * 100);
524 
525  return Process(
526  pid,
527  entry.get().th32ParentProcessID, // Parent process id.
528  0, // Group id.
529  session_id,
530  Bytes(proc_mem_counters.WorkingSetSize),
531  utime.isSome() ? utime.get() : Option<Duration>::none(),
532  stime.isSome() ? stime.get() : Option<Duration>::none(),
533  stringify(entry.get().szExeFile), // Executable filename.
534  false); // Is not zombie process.
535 }
536 
537 
538 inline int random()
539 {
540  return rand();
541 }
542 
543 
544 // `name_job` maps a `pid` to a `wstring` name for a job object.
545 // Only named job objects are accessible via `OpenJobObject`.
546 // Thus all our job objects must be named. This is essentially a shim
547 // to map the Linux concept of a process tree's root `pid` to a
548 // named job object so that the process group can be treated similarly.
550  Try<std::string> alpha_pid = strings::internal::format("MESOS_JOB_%X", pid);
551  if (alpha_pid.isError()) {
552  return Error(alpha_pid.error());
553  }
554  return wide_stringify(alpha_pid.get());
555 }
556 
557 
558 // `open_job` returns a safe shared handle to the named job object `name`.
559 // `desired_access` is a job object access rights flag.
560 // `inherit_handles` if true, processes created by this
561 // process will inherit the handle. Otherwise, the processes
562 // do not inherit this handle.
564  const DWORD desired_access,
565  const BOOL inherit_handles,
566  const std::wstring& name)
567 {
568  SharedHandle job_handle(
569  ::OpenJobObjectW(
570  desired_access,
571  inherit_handles,
572  name.data()),
573  ::CloseHandle);
574 
575  if (job_handle.get_handle() == nullptr) {
576  return WindowsError(
577  "os::open_job: Call to `OpenJobObject` failed for job: " +
578  stringify(name));
579  }
580 
581  return job_handle;
582 }
583 
584 
585 
587  const DWORD desired_access,
588  const BOOL inherit_handles,
589  const pid_t pid)
590 {
592  if (name.isError()) {
593  return Error(name.error());
594  }
595 
596  return open_job(desired_access, inherit_handles, name.get());
597 }
598 
599 // `create_job` function creates a named job object using `name`.
600 // This returns the safe job handle, which closes the job handle
601 // when destructed. Because the job is destroyed when its last
602 // handle is closed and all associated processes have exited,
603 // a running process must be assigned to the created job
604 // before the returned handle is closed.
605 inline Try<SharedHandle> create_job(const std::wstring& name)
606 {
607  SharedHandle job_handle(
608  ::CreateJobObjectW(
609  nullptr, // Use a default security descriptor, and
610  // the created handle cannot be inherited.
611  name.data()), // The name of the job.
612  ::CloseHandle);
613 
614  if (job_handle.get_handle() == nullptr) {
615  return WindowsError(
616  "os::create_job: Call to `CreateJobObject` failed for job: " +
617  stringify(name));
618  }
619 
620  JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {};
621 
622  // The job object will be terminated when the job handle closes. This allows
623  // the job tree to be terminated in case of errors by closing the handle.
624  // We set this flag so that the death of the agent process will
625  // always kill any running jobs, as the OS will close the remaining open
626  // handles if all destructors failed to run (catastrophic death).
627  info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
628 
629  const BOOL result = ::SetInformationJobObject(
630  job_handle.get_handle(),
631  JobObjectExtendedLimitInformation,
632  &info,
633  sizeof(info));
634 
635  if (result == FALSE) {
636  return WindowsError(
637  "os::create_job: `SetInformationJobObject` failed for job: " +
638  stringify(name));
639  }
640 
641  return job_handle;
642 }
643 
644 
645 // `get_job_info` gets the job object information for the process group
646 // represented by `pid`, assuming it is assigned to a job object. This function
647 // will fail otherwise.
648 //
649 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684925(v=vs.85).aspx // NOLINT(whitespace/line_length)
651 {
652  Try<SharedHandle> job_handle = os::open_job(
653  JOB_OBJECT_QUERY,
654  false,
655  pid);
656  if (job_handle.isError()) {
657  return Error(job_handle.error());
658  }
659 
660  JOBOBJECT_BASIC_ACCOUNTING_INFORMATION info = {};
661 
662  BOOL result = ::QueryInformationJobObject(
663  job_handle.get().get_handle(),
664  JobObjectBasicAccountingInformation,
665  &info,
666  sizeof(info),
667  nullptr);
668  if (result == FALSE) {
669  return WindowsError(
670  "os::get_job_info: call to `QueryInformationJobObject` failed");
671  }
672 
673  return info;
674 }
675 
676 
677 template <size_t max_pids>
679  // This is a statically allocated `JOBOBJECT_BASIC_PROCESS_ID_LIST`. We lie to
680  // the Windows API and construct our own struct to avoid (a) having to do
681  // hairy size calculations and (b) having to allocate dynamically, and then
682  // worry about deallocating.
683  struct {
684  DWORD NumberOfAssignedProcesses;
685  DWORD NumberOfProcessIdsInList;
686  DWORD ProcessIdList[max_pids];
687  } pid_list;
688 
689  BOOL result = ::QueryInformationJobObject(
690  job_handle.get_handle(),
691  JobObjectBasicProcessIdList,
692  reinterpret_cast<JOBOBJECT_BASIC_PROCESS_ID_LIST*>(&pid_list),
693  sizeof(pid_list),
694  nullptr);
695 
696  // `ERROR_MORE_DATA` indicates we need a larger `max_pids`.
697  if (result == FALSE && ::GetLastError() == ERROR_MORE_DATA) {
698  return None();
699  }
700 
701  if (result == FALSE) {
702  return WindowsError(
703  "os::_get_job_processes: call to `QueryInformationJobObject` failed");
704  }
705 
706  std::set<Process> processes;
707  for (DWORD i = 0; i < pid_list.NumberOfProcessIdsInList; ++i) {
708  Result<Process> process = os::process(pid_list.ProcessIdList[i]);
709  if (process.isSome()) {
710  processes.insert(process.get());
711  }
712  }
713 
714  return processes;
715 }
716 
717 
719 {
720  // TODO(andschwa): Overload open_job to use pid.
721  Try<SharedHandle> job_handle = os::open_job(
722  JOB_OBJECT_QUERY,
723  false,
724  pid);
725  if (job_handle.isError()) {
726  return Error(job_handle.error());
727  }
728 
729  // Try to enumerate the processes with three sizes: 32, 1K, and 32K.
730 
731  Result<std::set<Process>> result =
732  os::_get_job_processes<32>(job_handle.get());
733  if (result.isError()) {
734  return Error(result.error());
735  } else if (result.isSome()) {
736  return result.get();
737  }
738 
739  result = os::_get_job_processes<32*32>(job_handle.get());
740  if (result.isError()) {
741  return Error(result.error());
742  } else if (result.isSome()) {
743  return result.get();
744  }
745 
746  result = os::_get_job_processes<32*32*32>(job_handle.get());
747  if (result.isError()) {
748  return Error(result.error());
749  } else if (result.isSome()) {
750  return result.get();
751  }
752 
753  // If it was bigger than 32K, something else has gone wrong.
754 
755  return Error("os::get_job_processes: failed to get processes");
756 }
757 
758 
760  const Try<std::set<Process>> processes = os::get_job_processes(pid);
761  if (processes.isError()) {
762  return Error(processes.error());
763  }
764 
765  return std::accumulate(
766  processes.get().cbegin(),
767  processes.get().cend(),
768  Bytes(0),
769  [](const Bytes& bytes, const Process& process) {
770  if (process.rss.isNone()) {
771  return bytes;
772  }
773 
774  return bytes + process.rss.get();
775  });
776 }
777 
778 
779 // `set_job_cpu_limit` sets a CPU limit for the process represented by
780 // `pid`, assuming it is assigned to a job object. This function will fail
781 // otherwise. This limit is a hard cap enforced by the OS.
782 //
783 // https://msdn.microsoft.com/en-us/library/windows/desktop/hh448384(v=vs.85).aspx // NOLINT(whitespace/line_length)
785 {
786  JOBOBJECT_CPU_RATE_CONTROL_INFORMATION control_info = {};
787  control_info.ControlFlags =
788  JOB_OBJECT_CPU_RATE_CONTROL_ENABLE |
789  JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP;
790 
791  // This `CpuRate` is the number of cycles per 10,000 cycles, or a percentage
792  // times 100, e.g. 20% yields 20 * 100 = 2,000. However, the `cpus` argument
793  // represents 1 CPU core with `1.0`, so a 100% CPU limit on a quad-core
794  // machine would be `4.0 cpus`. Thus a mapping of `cpus` to `CpuRate` is
795  // `(cpus / os::cpus()) * 100 * 100`, or the requested `cpus` divided by the
796  // number of CPUs to obtain a fractional representation, multiplied by 100 to
797  // make it a percentage, multiplied again by 100 to become a `CpuRate`.
798  Try<long> total_cpus = os::cpus();
799  control_info.CpuRate =
800  static_cast<DWORD>((cpus / total_cpus.get()) * 100 * 100);
801  // This must not be set to 0, so 1 is the minimum.
802  if (control_info.CpuRate < 1) {
803  control_info.CpuRate = 1;
804  }
805 
806  Try<SharedHandle> job_handle = os::open_job(
807  JOB_OBJECT_SET_ATTRIBUTES,
808  false,
809  pid);
810  if (job_handle.isError()) {
811  return Error(job_handle.error());
812  }
813 
814  BOOL result = ::SetInformationJobObject(
815  job_handle.get().get_handle(),
816  JobObjectCpuRateControlInformation,
817  &control_info,
818  sizeof(control_info));
819  if (result == FALSE) {
820  return WindowsError(
821  "os::set_job_cpu_limit: call to `SetInformationJobObject` failed");
822  }
823 
824  return Nothing();
825 }
826 
827 
828 // `set_job_mem_limit` sets a memory limit for the process represented by
829 // `pid`, assuming it is assigned to a job object. This function will fail
830 // otherwise. This limit is a hard cap enforced by the OS.
831 //
832 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684156(v=vs.85).aspx // NOLINT(whitespace/line_length)
834 {
835  JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {};
836  info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_JOB_MEMORY;
837  info.JobMemoryLimit = limit.bytes();
838 
839  Try<SharedHandle> job_handle = os::open_job(
840  JOB_OBJECT_SET_ATTRIBUTES,
841  false,
842  pid);
843  if (job_handle.isError()) {
844  return Error(job_handle.error());
845  }
846 
847  BOOL result = ::SetInformationJobObject(
848  job_handle.get().get_handle(),
849  JobObjectExtendedLimitInformation,
850  &info,
851  sizeof(info));
852  if (result == FALSE) {
853  return WindowsError(
854  "os::set_job_mem_limit: call to `SetInformationJobObject` failed");
855  }
856 
857  return Nothing();
858 }
859 
860 
861 // `assign_job` assigns a process with `pid` to the job object `job_handle`.
862 // Every process started by the `pid` process using `CreateProcess`
863 // will also be owned by the job object.
864 inline Try<Nothing> assign_job(SharedHandle job_handle, pid_t pid) {
865  // Get process handle for `pid`.
866  SharedHandle process_handle(
867  ::OpenProcess(
868  // Required access rights to assign to a Job Object.
869  PROCESS_SET_QUOTA | PROCESS_TERMINATE,
870  false, // Don't inherit handle.
871  pid),
872  ::CloseHandle);
873 
874  if (process_handle.get_handle() == nullptr) {
875  return WindowsError(
876  "os::assign_job: Call to `OpenProcess` failed");
877  }
878 
879  const BOOL result = ::AssignProcessToJobObject(
880  job_handle.get_handle(),
881  process_handle.get_handle());
882 
883  if (result == FALSE) {
884  return WindowsError(
885  "os::assign_job: Call to `AssignProcessToJobObject` failed");
886  };
887 
888  return Nothing();
889 }
890 
891 
892 // The `kill_job` function wraps the Windows sytem call `TerminateJobObject`
893 // for the job object `job_handle`. This will call `TerminateProcess`
894 // for every associated child process.
896 {
897  const BOOL result = ::TerminateJobObject(
898  job_handle.get_handle(),
899  // The exit code to be used by all processes in the job object.
900  1);
901 
902  if (result == FALSE) {
903  return WindowsError(
904  "os::kill_job: Call to `TerminateJobObject` failed");
905  }
906 
907  return Nothing();
908 }
909 
910 
911 inline Try<std::string> var()
912 {
913  // Get the `ProgramData` path. First, find the size of the output buffer.
914  // This size includes the null-terminating character.
915  DWORD size = 0;
916  if (::GetAllUsersProfileDirectoryW(nullptr, &size)) {
917  // The expected behavior here is for the function to "fail"
918  // and return `false`, and `size` receives necessary buffer size.
919  return WindowsError(
920  "os::var: `GetAllUsersProfileDirectoryW` succeeded unexpectedly");
921  }
922 
923  std::vector<wchar_t> buffer;
924  buffer.reserve(static_cast<size_t>(size));
925  if (!::GetAllUsersProfileDirectoryW(buffer.data(), &size)) {
926  return WindowsError("os::var: `GetAllUsersProfileDirectoryW` failed");
927  }
928 
929  return stringify(std::wstring(buffer.data()));
930 }
931 
932 
933 // Returns a host-specific default for the `PATH` environment variable, based
934 // on the configuration of the host.
935 inline std::string host_default_path()
936 {
937  // NOTE: On Windows, this code must run on the host where we are
938  // expecting to `exec` the task, because the value of
939  // `%SystemRoot%` is not identical on all platforms.
940  const Option<std::string> system_root_env = os::getenv("SystemRoot");
941  const std::string system_root = system_root_env.isSome()
942  ? system_root_env.get()
943  : path::join("C:", "Windows");
944 
945  return strings::join(";",
946  path::join(system_root, "System32"),
947  system_root,
948  path::join(system_root, "System32", "Wbem"),
949  path::join(system_root, "System32", "WindowsPowerShell", "v1.0"));
950 }
951 
952 } // namespace os {
953 
954 #endif // __STOUT_WINDOWS_OS_HPP__
bool isNone() const
Definition: result.hpp:112
Try< uid_t > uid(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:182
Definition: nothing.hpp:16
Definition: errorbase.hpp:35
Try< Bytes > size(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:100
HANDLE get_handle() const
Definition: windows.hpp:96
Definition: windows.hpp:78
std::stringstream & join(std::stringstream &stream, const std::string &separator, T &&...args)
Definition: strings.hpp:306
Try< Nothing > chmod(const std::string &path, int mode)
Definition: os.hpp:194
Definition: try.hpp:34
Try< Nothing > sleep(const Duration &duration)
Definition: os.hpp:218
static Result< T > error(const std::string &message)
Definition: result.hpp:53
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:204
Result< ProcessStatus > status(pid_t pid)
Definition: proc.hpp:166
Definition: error.hpp:106
#define WNOHANG
Definition: windows.hpp:407
double ms() const
Definition: duration.hpp:100
Definition: errorbase.hpp:49
std::string join(const std::string &path1, const std::string &path2, const char _separator=os::PATH_SEPARATOR)
Definition: path.hpp:56
void setenv(const std::string &key, const std::string &value, bool overwrite=true)
Definition: os.hpp:157
Try< std::wstring > name_job(pid_t pid)
Definition: os.hpp:549
void unsetenv(const std::string &key)
Definition: os.hpp:167
Definition: duration.hpp:32
Definition: result.hpp:40
Try< Nothing > set_job_mem_limit(pid_t pid, Bytes limit)
Definition: os.hpp:833
std::string host_default_path()
Definition: os.hpp:473
bool isSome() const
Definition: option.hpp:115
Try< Nothing > assign_job(SharedHandle job_handle, pid_t pid)
Definition: os.hpp:864
Try< SharedHandle > open_job(const DWORD desired_access, const BOOL inherit_handles, const std::wstring &name)
Definition: os.hpp:563
Try< Load > loadavg()
Definition: os.hpp:279
Try< std::string > nodename()
Definition: os.hpp:58
DWORD pid_t
Definition: windows.hpp:187
Definition: process.hpp:32
int mode_t
Definition: windows.hpp:183
Try< dev_t > dev(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:139
Try< SharedHandle > create_job(const std::wstring &name)
Definition: os.hpp:605
constexpr Handle HANDLE
Definition: ingress.hpp:37
Try< Nothing > utime(const std::string &path)
Definition: utime.hpp:32
Try< long > cpus()
Definition: os.hpp:264
std::string hstrerror(int err)=delete
Try< Nothing > kill_job(SharedHandle job_handle)
Definition: os.hpp:895
int uid_t
Definition: windows.hpp:189
static Option< T > none()
Definition: option.hpp:31
bool isSome() const
Definition: try.hpp:70
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:538
Try< Version > release()
Definition: os.hpp:377
const T & get() const
Definition: result.hpp:115
Result< pid_t > waitpid(pid_t pid, int *status, int options)
Definition: os.hpp:141
Try< UTSInfo > uname()
Definition: os.hpp:296
static Try error(const E &e)
Definition: try.hpp:42
Result< Process > process(pid_t pid)
Definition: freebsd.hpp:30
Definition: grp.hpp:26
Definition: none.hpp:27
bool isError() const
Definition: try.hpp:71
Try< Bytes > get_job_mem(pid_t pid)
Definition: os.hpp:759
Result< std::set< Process > > _get_job_processes(const SharedHandle &job_handle)
Definition: os.hpp:678
uint64_t bytes() const
Definition: bytes.hpp:79
Try< std::set< Process > > get_job_processes(pid_t pid)
Definition: os.hpp:718
Try< Nothing > chown(uid_t uid, gid_t gid, const std::string &path, bool recursive)
Definition: chown.hpp:30
Try< mode_t > mode(const std::string &path, const FollowSymlink follow=FollowSymlink::FOLLOW_SYMLINK)
Definition: stat.hpp:126
Try< JOBOBJECT_BASIC_ACCOUNTING_INFORMATION > get_job_info(pid_t pid)
Definition: os.hpp:650
bool isSome() const
Definition: result.hpp:111
bool isError() const
Definition: result.hpp:113
Try< Nothing > set_job_cpu_limit(pid_t pid, double cpus)
Definition: os.hpp:784
int gid_t
Definition: windows.hpp:190
Try< std::string > format(const std::string &fmt, va_list args)
Definition: format.hpp:68
Result< PROCESSENTRY32W > process_entry(pid_t pid)
Definition: os.hpp:395
Definition: duration.hpp:217
Definition: bytes.hpp:30
Try< std::string > var()
Definition: os.hpp:406
std::string stringify(int flags)
Try< Memory > memory()
Definition: freebsd.hpp:78
Try< std::list< std::string > > glob(const std::string &pattern)
Definition: os.hpp:238
Try< std::set< pid_t > > pids()
Definition: freebsd.hpp:62
const T & get() const
Definition: try.hpp:73
constexpr const char * name
Definition: shell.hpp:41
tm * gmtime_r(const time_t *timep, tm *result)
Definition: os.hpp:389