Apache Mesos
fd.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_WINDOWS_FD_HPP__
14 #define __STOUT_OS_WINDOWS_FD_HPP__
15 
16 #include <fcntl.h> // For `O_RDWR`.
17 #include <io.h> // For `_open_osfhandle`.
18 
19 #include <array>
20 #include <atomic>
21 #include <memory>
22 #include <ostream>
23 #include <type_traits>
24 
25 #include <stout/check.hpp>
26 #include <stout/nothing.hpp>
27 #include <stout/synchronized.hpp>
28 #include <stout/try.hpp>
29 #include <stout/unreachable.hpp>
30 #include <stout/windows.hpp> // For `WinSock2.h`.
31 
32 namespace os {
33 
34 // The `WindowsFD` class exists to provide an common interface with the POSIX
35 // file descriptor. While the bare `int` representation of the POSIX file
36 // descriptor API is undesirable, we rendezvous there in order to maintain the
37 // existing code in Mesos.
38 //
39 // In the platform-agnostic code paths, the `int_fd` type is aliased to
40 // `WindowsFD`. The `os::*` functions return a type appropriate to the platform,
41 // which allows us to write code like this:
42 //
43 // Try<int_fd> fd = os::open(...);
44 //
45 // The `WindowsFD` constructs off one of:
46 // (1) `HANDLE` - from the Win32 API
47 // (2) `SOCKET` - from the WinSock API
48 //
49 // The `os::*` functions then take an instance of `WindowsFD`, examines
50 // the state and dispatches to the appropriate API.
51 
52 class WindowsFD
53 {
54 public:
55  enum class Type
56  {
57  HANDLE,
58  SOCKET
59  };
60 
61  // The `HANDLE` here is expected to be file handles. Specifically,
62  // `HANDLE`s returned by file API such as `CreateFile`. There are
63  // APIs that return `HANDLE`s with different error values, and
64  // therefore must be handled accordingly. For example, a thread API
65  // such as `CreateThread` returns `NULL` as the error value, rather
66  // than `INVALID_HANDLE_VALUE`.
67  //
68  // TODO(mpark): Consider adding a second parameter which tells us
69  // what the error values are.
70  static_assert(
71  std::is_same<HANDLE, void*>::value,
72  "Expected `HANDLE` to be of type `void*`.");
73  explicit WindowsFD(HANDLE handle, bool overlapped = false)
74  : type_(Type::HANDLE),
75  handle_(handle),
76  overlapped_(overlapped),
77  iocp_(std::make_shared<IOCPData>())
78  {}
79 
80  // The `SOCKET` here is expected to be Windows sockets, such as that
81  // used by the Windows Sockets 2 library. The only expected error
82  // value is `INVALID_SOCKET`.
83  //
84  // Note that sockets should almost always be overlapped. We do provide
85  // a way in stout to create non-overlapped sockets, so for completeness, we
86  // have an overlapped parameter in the constructor.
87  static_assert(
88  std::is_same<SOCKET, unsigned __int64>::value,
89  "Expected `SOCKET` to be of type `unsigned __int64`.");
90  explicit WindowsFD(SOCKET socket, bool overlapped = true)
91  : type_(Type::SOCKET),
92  socket_(socket),
93  overlapped_(overlapped),
94  iocp_(std::make_shared<IOCPData>())
95  {}
96 
97  // On Windows, libevent's `evutil_socket_t` is set to `intptr_t`.
98  explicit WindowsFD(intptr_t socket) : WindowsFD(static_cast<SOCKET>(socket))
99  {}
100 
101  // This constructor is provided in so that the canonical integer
102  // file descriptors representing `stdin` (0), `stdout` (1), and
103  // `stderr` (2), and the invalid value of -1 can be used.
104  WindowsFD(int crt) : WindowsFD(INVALID_HANDLE_VALUE)
105  {
106  if (crt == -1) {
107  // No-op, already `INVALID_HANDLE_VALUE`.
108  } else if (crt == 0) {
109  handle_ = ::GetStdHandle(STD_INPUT_HANDLE);
110  } else if (crt == 1) {
111  handle_ = ::GetStdHandle(STD_OUTPUT_HANDLE);
112  } else if (crt == 2) {
113  handle_ = ::GetStdHandle(STD_ERROR_HANDLE);
114  } else {
115  // This would be better enforced at compile-time, but not
116  // currently possible, so this is a sanity check.
117  LOG(FATAL) << "Unexpected construction of `WindowsFD`";
118  }
119  }
120 
121  // Default construct with invalid handle semantics.
122  WindowsFD() : WindowsFD(INVALID_HANDLE_VALUE) {}
123 
124  WindowsFD(const WindowsFD&) = default;
125  WindowsFD(WindowsFD&&) = default;
126 
127  WindowsFD& operator=(const WindowsFD&) = default;
128  WindowsFD& operator=(WindowsFD&&) = default;
129 
130  ~WindowsFD() = default;
131 
132  bool is_valid() const
133  {
134  switch (type()) {
135  case Type::HANDLE: {
136  // Remember that both of these values can represent an invalid
137  // handle.
138  return handle_ != nullptr && handle_ != INVALID_HANDLE_VALUE;
139  }
140  case Type::SOCKET: {
141  // Only this value is used for an invalid socket.
142  return socket_ != INVALID_SOCKET;
143  }
144  default: {
145  return false;
146  }
147  }
148  }
149 
150  // NOTE: This allocates a C run-time file descriptor and associates
151  // it with the handle. At this point, the `HANDLE` should no longer
152  // be closed via `CloseHandle`, but instead close the returned `int`
153  // with `_close`. This method should almost never be used, and
154  // exists only for compatibility with 3rdparty dependencies.
155  int crt() const
156  {
157  CHECK_EQ(Type::HANDLE, type());
158  // TODO(andschwa): Consider if we should overwrite `handle_`.
159  return ::_open_osfhandle(reinterpret_cast<intptr_t>(handle_), O_RDWR);
160  }
161 
162  operator HANDLE() const
163  {
164  // A `SOCKET` can be treated as a regular file `HANDLE` [1]. There are
165  // many Win32 functions that work on a `SOCKET` but have the `HANDLE`
166  // type as a function parameter like `CreateIoCompletionPort`, so we need
167  // to be able to cast a `SOCKET` based `int_fd` to a `HANDLE`.
168  //
169  // [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740522(v=vs.85).aspx // NOLINT(whitespace/line_length)
170  if (type() == Type::SOCKET) {
171  return reinterpret_cast<HANDLE>(socket_);
172  }
173  return handle_;
174  }
175 
176  operator SOCKET() const
177  {
178  CHECK_EQ(Type::SOCKET, type());
179  return socket_;
180  }
181 
182  // On Windows, libevent's `evutil_socket_t` is set to `intptr_t`.
183  operator intptr_t() const
184  {
185  CHECK_EQ(Type::SOCKET, type());
186  return static_cast<intptr_t>(socket_);
187  }
188 
189  Type type() const { return type_; }
190 
191  bool is_overlapped() const { return overlapped_; }
192 
193  // Assigns this `WindowsFD` to an IOCP. Returns `nullptr` is this is
194  // the first time that `this` was assigned to an IOCP. If `this` was
195  // already assigned, then this function no-ops and returns the
196  // assigned IOCP `HANDLE`. We have this function because
197  // `CreateIoCompletionPort` returns an error if a `HANDLE` gets
198  // assigned to an IOCP `HANDLE` twice, but provides no way to check
199  // for that error.
200  //
201  // NOTE: The `key` parameter is `CompletionKey` in
202  // `CreateIoCompletionPort`. As stated by the MSDN docs, this key is
203  // the "per-handle user-defined completion key that is included in
204  // every I/O completion packet for the specified file handle" [1],
205  // and thus, it's only used for bookkeeping by the user and not used
206  // for functional control in `CreateIoCompletionPort`.
207  //
208  // [1]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa363862(v=vs.85).aspx // NOLINT(whitespace/line_length)
209  Try<HANDLE> assign_iocp(HANDLE target, ULONG_PTR key) const
210  {
211  synchronized (iocp_->lock) {
212  const HANDLE prev_handle = iocp_->handle;
213  if (prev_handle == nullptr) {
214  const HANDLE fd_handle =
215  type() == Type::SOCKET ? reinterpret_cast<HANDLE>(socket_) : handle_;
216 
217  // Confusing name, but `::CreateIoCompletionPort` can also
218  // assign a `HANDLE` to an IO completion port.
219  if (::CreateIoCompletionPort(fd_handle, target, key, 0) == nullptr) {
220  return WindowsError();
221  }
222 
223  iocp_->handle = target;
224  }
225  return prev_handle;
226  }
227  }
228 
229  HANDLE get_iocp() const
230  {
231  synchronized (iocp_->lock) {
232  return iocp_->handle;
233  }
234  }
235 
236 private:
237  Type type_;
238 
239  union
240  {
242  SOCKET socket_;
243  };
244 
245  bool overlapped_;
246 
247  // There can be many `int_fd` copies of the same `HANDLE` and many `HANDLE`
248  // can reference the same kernel `FILE_OBJECT`. Since the IOCP affects the
249  // underlying `FILE_OBJECT`, we keep a pointer to the IOCP handle so we can
250  // update it for all int_fds that refer to the same `FILE_OBJECT`.
251  struct IOCPData
252  {
253  std::atomic_flag lock = ATOMIC_FLAG_INIT;
254  HANDLE handle = nullptr;
255  };
256 
257  std::shared_ptr<IOCPData> iocp_;
258 
259  // NOTE: This function is provided only for checking validity, thus
260  // it is private. It provides a view of a `WindowsFD` as an `int`.
261  //
262  // TODO(andschwa): Fix all uses of this conversion to use `is_valid`
263  // directly instead, then remove the comparison operators. This
264  // would require writing an `int_fd` class for POSIX too, instead of
265  // just using `int`.
266  int get_valid() const
267  {
268  if (is_valid()) {
269  return 0;
270  } else {
271  return -1;
272  }
273  }
274 
275  // NOTE: These operators are used solely to support checking a
276  // `WindowsFD` against e.g. -1 or 0 for validity. Nothing else
277  // should have access to `get_valid()`.
278  friend bool operator<(int left, const WindowsFD& right);
279  friend bool operator<(const WindowsFD& left, int right);
280  friend bool operator>(int left, const WindowsFD& right);
281  friend bool operator>(const WindowsFD& left, int right);
282  friend bool operator<=(int left, const WindowsFD& right);
283  friend bool operator<=(const WindowsFD& left, int right);
284  friend bool operator>=(int left, const WindowsFD& right);
285  friend bool operator>=(const WindowsFD& left, int right);
286  friend bool operator==(int left, const WindowsFD& right);
287  friend bool operator==(const WindowsFD& left, int right);
288  friend bool operator!=(int left, const WindowsFD& right);
289  friend bool operator!=(const WindowsFD& left, int right);
290 
291  // `os::dup` needs to modify a `WindowsFD`'s private state, because
292  // when we want to `os::dup` a `WindowsFD`, we need to copy the IOCP
293  // handle and the overlapped state, but NOT the handle value itself.
294  friend Try<WindowsFD> dup(const WindowsFD& fd);
295 };
296 
297 
298 inline std::ostream& operator<<(std::ostream& stream, const WindowsFD::Type& fd)
299 {
300  switch (fd) {
302  stream << "WindowsFD::Type::HANDLE";
303  return stream;
304  }
306  stream << "WindowsFD::Type::SOCKET";
307  return stream;
308  }
309  default: {
310  stream << "WindowsFD::Type::UNKNOWN";
311  return stream;
312  }
313  }
314 }
315 
316 
317 inline std::ostream& operator<<(std::ostream& stream, const WindowsFD& fd)
318 {
319  stream << fd.type() << "=";
320  switch (fd.type()) {
322  stream << static_cast<HANDLE>(fd);
323  return stream;
324  }
326  stream << static_cast<SOCKET>(fd);
327  return stream;
328  }
329  default: {
330  stream << "UNKNOWN";
331  return stream;
332  }
333  }
334 }
335 
336 
337 // NOTE: The following operators implement all the comparisons
338 // possible a `WindowsFD` type and an `int`. The point of this is that
339 // the `WindowsFD` type must act like an `int` for compatibility
340 // reasons (e.g. checking validity through `fd < 0`), without actually
341 // being castable to an `int` to avoid ambiguous types.
342 inline bool operator<(int left, const WindowsFD& right)
343 {
344  return left < right.get_valid();
345 }
346 
347 
348 inline bool operator<(const WindowsFD& left, int right)
349 {
350  return left.get_valid() < right;
351 }
352 
353 
354 inline bool operator>(int left, const WindowsFD& right)
355 {
356  return left > right.get_valid();
357 }
358 
359 
360 inline bool operator>(const WindowsFD& left, int right)
361 {
362  return left.get_valid() > right;
363 }
364 
365 
366 inline bool operator<=(int left, const WindowsFD& right)
367 {
368  return left <= right.get_valid();
369 }
370 
371 
372 inline bool operator<=(const WindowsFD& left, int right)
373 {
374  return left.get_valid() <= right;
375 }
376 
377 
378 inline bool operator>=(int left, const WindowsFD& right)
379 {
380  return left >= right.get_valid();
381 }
382 
383 
384 inline bool operator>=(const WindowsFD& left, int right)
385 {
386  return left.get_valid() >= right;
387 }
388 
389 
390 inline bool operator==(int left, const WindowsFD& right)
391 {
392  return left == right.get_valid();
393 }
394 
395 
396 inline bool operator==(const WindowsFD& left, int right)
397 {
398  return left.get_valid() == right;
399 }
400 
401 
402 inline bool operator!=(int left, const WindowsFD& right)
403 {
404  return left != right.get_valid();
405 }
406 
407 
408 inline bool operator!=(const WindowsFD& left, int right)
409 {
410  return left.get_valid() != right;
411 }
412 
413 
414 // NOTE: This operator exists so that `WindowsFD` can be used in an
415 // `unordered_map` (and other STL containers requiring equality).
416 inline bool operator==(const WindowsFD& left, const WindowsFD& right)
417 {
418  // This is `true` even if the types mismatch because we want
419  // `WindowsFD(-1)` to compare as equivalent to an invalid `HANDLE`
420  // or `SOCKET`, even though it is technically of type `HANDLE`.
421  if (!left.is_valid() && !right.is_valid()) {
422  return true;
423  }
424 
425  // Otherwise mismatched types are not equivalent.
426  if (left.type() != right.type()) {
427  return false;
428  }
429 
430  switch (left.type()) {
432  return static_cast<HANDLE>(left) == static_cast<HANDLE>(right);
433  }
435  return static_cast<SOCKET>(left) == static_cast<SOCKET>(right);
436  }
437  }
438 
439  UNREACHABLE();
440 }
441 
442 } // namespace os {
443 
444 namespace std {
445 
446 // NOTE: This specialization exists so that `WindowsFD` can be used in
447 // an `unordered_map` (and other STL containers requiring a hash).
448 template <>
449 struct hash<os::WindowsFD>
450 {
452  using result_type = size_t;
453 
454  result_type operator()(const argument_type& fd) const noexcept
455  {
456  switch (fd.type()) {
458  return std::hash<HANDLE>{}(static_cast<HANDLE>(fd));
459  }
461  return std::hash<SOCKET>{}(static_cast<SOCKET>(fd));
462  }
463  }
464 
465  UNREACHABLE();
466  }
467 };
468 
469 } // namespace std {
470 
471 #endif // __STOUT_OS_WINDOWS_FD_HPP__
bool is_valid() const
Definition: fd.hpp:132
WindowsFD(SOCKET socket, bool overlapped=true)
Definition: fd.hpp:90
Type type() const
Definition: fd.hpp:189
SOCKET socket_
Definition: fd.hpp:242
WindowsFD(HANDLE handle, bool overlapped=false)
Definition: fd.hpp:73
Definition: fd.hpp:52
Definition: check.hpp:33
Definition: error.hpp:108
Definition: type_utils.hpp:619
friend bool operator<=(int left, const WindowsFD &right)
Definition: fd.hpp:366
~WindowsFD()=default
Definition: posix_signalhandler.hpp:23
WindowsFD()
Definition: fd.hpp:122
result_type operator()(const argument_type &fd) const noexcept
Definition: fd.hpp:454
Try< HANDLE > assign_iocp(HANDLE target, ULONG_PTR key) const
Definition: fd.hpp:209
friend bool operator==(int left, const WindowsFD &right)
Definition: fd.hpp:390
Type
Definition: fd.hpp:55
WindowsFD(int crt)
Definition: fd.hpp:104
friend bool operator!=(int left, const WindowsFD &right)
Definition: fd.hpp:402
friend bool operator<(int left, const WindowsFD &right)
Definition: fd.hpp:342
#define UNREACHABLE()
Definition: unreachable.hpp:22
int crt() const
Definition: fd.hpp:155
friend bool operator>(int left, const WindowsFD &right)
Definition: fd.hpp:354
bool is_overlapped() const
Definition: fd.hpp:191
std::ostream & operator<<(std::ostream &stream, const ProcessTree &tree)
Definition: process.hpp:126
friend bool operator>=(int left, const WindowsFD &right)
Definition: fd.hpp:378
WindowsFD & operator=(const WindowsFD &)=default
WindowsFD(intptr_t socket)
Definition: fd.hpp:98
friend Try< WindowsFD > dup(const WindowsFD &fd)
Try< Netlink< struct nl_sock > > socket(int protocol=NETLINK_ROUTE)
Definition: internal.hpp:91
HANDLE get_iocp() const
Definition: fd.hpp:229
size_t result_type
Definition: fd.hpp:452
HANDLE handle_
Definition: fd.hpp:241