Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
native_handle.hpp
Go to the documentation of this file.
1/* Flow-IPC: Core
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
21#include "ipc/common.hpp"
22#include <boost/scope/unique_resource.hpp>
23#include <boost/core/functor.hpp>
24#include <ostream>
25
26namespace ipc::util
27{
28
29#ifndef FLOW_OS_LINUX
30static_assert(false, "Flow-IPC supports transmitting native handles in Linux only; a core feature. "
31 "Build in Linux only.");
32#endif
33// From this point on (in #including .cpp files as well) POSIX is assumed; and in a few spots specifically Linux.
34
35/* Note: We lack a native_handle_fwd.hpp, as it's such a thin wrapper around an FD (int) that there's no point
36 * forward-declaring it, as it's ~always referred to by value, and almost everything is `constexpr`. */
37
38// Types.
39
40/**
41 * A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
42 * It would have been acceptable to simply use an alias to the native handle type (in POSIX, `int`),
43 * but this way is better stylistically with exactly zero performance overhead. Initialize it, e.g.,
44 * `Native_handle hndl{some_fd};`, where `some_fd` might be some POSIX-y FD (network socket, Unix domain socket,
45 * file descriptor, etc.). Or initialize via no-arg construction which results in a `null() == true` object.
46 * Copy construction, assignment, equality, total ordering, and hashing all work as one would expect and
47 * essentially the same as on the underlying native handles.
48 *
49 * It is either null() or stores a native handle. In the former case, null() being `true` means explicitly
50 * that `m_native_handle == Native_handle::S_NULL_HANDLE`.
51 *
52 * ### Ownership ###
53 * Native_handle is extremely simple. Other than providing move semantics, where the moved-from guy becomes `.null()`,
54 * and an always-manual close() API, it does not add ownership semantics. You're free to, e.g., assign a non-null
55 * socket value, then make a copy, then (e.g.) `.close()` them both -- the second one being an erroneous
56 * double-close. However, if you need added cleverness to avoid such things:
57 *
58 * @see Helper type #Own_native_handle: It adds auto-`close()` behavior, on destruction and being moved-to.
59 * (To get at the Native_handle itself, given an `Own_native_handle x`: Call `x.get()`. The full API
60 * is similar to `unique_ptr`'s basic API: `.reset()`, `.release()`, and so on.)
61 * This can be quite useful when one is juggling `Native_handle`s in some operation and, e.g., does not
62 * want to leak the OS-handles (descriptors) on early `return` due to error conditions. If ultimately
63 * a raw `Native_handle` is required, one can always get it out via `.release()`.
64 */
66{
67 // A method (odd placement b/c needed for Closer below).
68
69 /**
70 * Simply calls `hndl.close()`; helpful for things like `boost::core::functor<static_close>`.
71 *
72 * @param hndl
73 * See above. It can be `.null()`.
74 */
75 static void static_close(Native_handle& hndl) noexcept;
76
77 // Types.
78
79 /// The native handle type. Much logic relies on this type being light-weight (fast to copy).
80 using handle_t = int;
81
82 /**
83 * Function object type (storing no data), suitable for `unique_resource`, whose `(Native_handle& hndl)` operator
84 * invokes Native_handle::close() on `hndl`.
85 */
86 using Closer = boost::core::functor<static_close>;
87
88 // Constants.
89
90 /**
91 * The value for #m_native_handle such that `null() == true`; else it is `false`.
92 * No valid handle ever equals this.
93 */
94 static constexpr handle_t S_NULL_HANDLE = -1;
95
96 // Data.
97
98 /**
99 * The native handle (possibly equal to #S_NULL_HANDLE), the exact payload of this Native_handle.
100 * It can, of course, be assigned and accessed explicitly.
101 */
103
104 // Constructors/destructor.
105
106 /**
107 * Constructs with given payload; also subsumes no-args construction to mean constructing an object with
108 * `null() == true`.
109 *
110 * @param native_handle
111 * Payload.
112 */
113 constexpr Native_handle(handle_t native_handle = S_NULL_HANDLE) noexcept;
114
115 /**
116 * Constructs object equal to `src`, while making `src.null() == true`.
117 *
118 * ### Rationale ###
119 * This is move construction, but it's not about performance at all (as this is all quite cheap anyway);
120 * more to allow for the pattern of making an object from another object without propagating more copies of the
121 * underlying handle. Wouldn't the auto-generated move ctor take care of it? No, because it'd just copy the handle
122 * and not nullify the source.
123 *
124 * However: this is not a replacement for #Own_native_handle.
125 *
126 * @param src
127 * Source object which will be made `null() == true`.
128 */
129 constexpr Native_handle(Native_handle&& src) noexcept;
130
131 /**
132 * Copy constructor.
133 * @param src
134 * Source object.
135 */
136 constexpr Native_handle(const Native_handle& src) noexcept;
137
138 // Methods.
139
140 /**
141 * Move assignment; acts similarly to move ctor; but no-op if `this == &src`.
142 * @param src
143 * Source object which will be made `.null() == true`, unless `this == &src`.
144 * @return `*this`.
145 */
146 constexpr Native_handle& operator=(Native_handle&& src) noexcept;
147
148 /**
149 * Copy assignment; acts similarly to copy ctor.
150 * @param src
151 * Source object.
152 * @return `*this`.
153 */
154 constexpr Native_handle& operator=(const Native_handle& src) noexcept;
155
156 /**
157 * Returns `true` if and only if #m_native_handle equals #S_NULL_HANDLE.
158 * @return See above.
159 */
160 constexpr bool null() const noexcept;
161
162 /**
163 * Little utility that returns #m_native_handle to the OS.
164 *
165 * This is helpful to close, without invoking a native API (`"::close()"` really), a value returned by
166 * `transport::asio_local_stream_socket::Peer_socket::release()` or, perhaps, received over a
167 * `transport::Native_socket_stream`.
168 *
169 * `*this` is nullified (null() shall return `true`). No-op if already so at entry.
170 *
171 * Nothing is logged; no errors are emitted. This is intended for no-questions-asked cleanup.
172 *
173 * @note The Native_handle destructor, such as it is, absolutely does not close() or anything similar.
174 * Similarly move ctor/assignment does not either. In fact nothing else in Native_handle does.
175 * If you want to do it, you must call close() yourself. It is a utility; and Native_handle as a whole
176 * intentionally features minimal intelligence; it is merely an object wrapper around a raw handle.
177 * Move ctor/assignment nullifying the source object is as "intelligent" as we get.
178 *
179 * @see However: #Own_native_handle adds auto-`close()` behavior, on destruction and being moved-to.
180 * This can be quite useful when one is juggling `Native_handle`s and, e.g., does not want to leak
181 * the OS-handle (descriptor) on early `return` due to error conditions.
182 */
183 void close() noexcept;
184
185 /**
186 * Returns whether #m_native_handle is currently an open descriptor in this process; `false` if null().
187 *
188 * @warning Caveats due to the nature of what Native_handle stores:
189 * 1, is_open() is a point-in-time answer (nothing prevents a close immediately after).
190 * 2, returning `true` means *some* descriptor identified by #m_native_handle is open -- a stale value
191 * whose value has since been recycled answers `true`.
192 * So, watch out, particularly when there is asynchronicity and/or concurrency.
193 *
194 * Nothing is logged; no errors are emitted.
195 *
196 * @return See above.
197 */
198 bool is_open() const noexcept;
199
200 /**
201 * Returns a new handle referring to the same underlying OS-level open resource as `*this`, a-la POSIX `"::dup()"`
202 * (in POSIX parlance: a new descript*or* to the same open file descript*ion*). The result is independent
203 * of `*this`: closing either leaves the other valid.
204 *
205 * @see duped_native_handle() to obtain the same in the auto-closing #Own_native_handle form.
206 *
207 * An error is possible but generally indicates a truly exceptional situation, probably classifiable in one of these:
208 * - User error: null() is `true`.
209 * - User error: The stored #m_native_handle is invalid/corrupt.
210 * - Environment is hosed: Ran out of descriptors in the OS table/reached a configured limit/etc.
211 *
212 * @note For all applicable OS: close-on-exec is set on the returned handle.
213 *
214 * @param err_code
215 * See `flow::Error_code` docs for error reporting semantics. #Error_code generated:
216 * transport::error::Code::S_INVALID_ARGUMENT if `*this` is `.null()`,
217 * system error (likely culprits estimated above).
218 * @return On success a non-`.null()` Native_handle; on failure a `.null() == true` one.
219 * (Per usual Flow semantics: on failure: if `!err_code`, exception is thrown instead.)
220 */
221 Native_handle dup(Error_code* err_code = nullptr) const;
222
223 /**
224 * Simply returns `Native_handle{}`, a null handle.
225 *
226 * ### Rationale for existence ###
227 * It is so that Native_handle can be used as its own traits class in `unique_resource<Native_handle>`.
228 * You probably need not worry about it: just use #Own_native_handle when you need those capabilities.
229 *
230 * @return See above.
231 */
232 static constexpr Native_handle make_default() noexcept;
233
234 /**
235 * Simply returns `!hndl.null()`. Rationale for existence: same as make_default().
236 * @param hndl
237 * See above.
238 * @return See above.
239 */
240 static constexpr bool is_allocated(Native_handle hndl) noexcept;
241}; // struct Native_handle
242
243/**
244 * A `unique_resource` wrapper for Native_handle useful to avoid descriptor leaks and double-closing; can be thought
245 * of as a `unique_ptr<Native_handle>` except involves no heap allocation, and nullness is determined by
246 * Native_handle::null().
247 *
248 * For a bit more background on rationale and usage: see Native_handle doc header Ownership section.
249 *
250 * Reminder: if/when ready to get a raw, non-auto-closing Native_handle back: use `.release()`.
251 *
252 * @internal
253 *
254 * ### Impl notes ###
255 * `unique_resource` is almost perfect for what we want here. Just a couple subtleties:
256 * - `boost.scope` itself threw in a `unique_fd` which, in POSIX-land at least, is very similar or identical
257 * to `Own_native_handle`; it uses `unique_resource` too, naturally, but around `int` a/k/a Native_handle::handle_t
258 * as opposed to some wrapper like our `Native_handle`. We contemplated reusing it anyway, for that
259 * particular purpose, but decided that the Native_handle abstraction works for us, so we'd rather re-wrap
260 * it ourselves. As you can see it is not hard: one line here plus the traits functions
261 * Native_handle::make_default() and Native_handle::is_allocated().
262 * - The resulting thing, due to our use of traits, is quite tight perf-wise: Its size is that of `handle_t` --
263 * an `int` in POSIX at least -- and, combining `constexpr`ness and `unique_resource`'s design, all the
264 * basic operations (assign, compare, construct...) should be trivially fast.
265 * - There is one *little* asterisk on that front: Given a *non-null object*: the close operation (through
266 * `.reset()`, dtor, being assigned-onto or -from) will check for `m_native_handle == S_NULL_HANDLE`
267 * twice: 1, `unique_resource` through Native_handle::is_allocated() (traits); 2,
268 * in Native_handle::close() again. It'll be `false` both times, so then `"::close(m_native_handle)"`
269 * will occur. The 2nd equality check is redundant. (If it's a null object, then the initial check
270 * returns `true` => GTFO; no problem there.)
271 * - The reason: `.close()` is defensive, as it should be; and Native_handle::Closer inherits that -- why
272 * shouldn't it, in and of itself? -- and then it is convenient to use `Closer` for
273 * our `unique_resource` setup. Could we "defeat" it by using a, like, `Closer_unchecked` as the deleter?
274 * Yes; but pragmatically speaking that's over-engineering. A redundant `int` compare -- probably optimized
275 * away at some point anyway -- only when the close OS-call will be invoked? Not a real problem.
276 */
277using Own_native_handle = boost::scope::unique_resource<Native_handle, Native_handle::Closer, Native_handle>;
278
279// Free functions.
280
281/**
282 * Returns Native_handle constructed by relieving an existing auto-closing #Own_native_handle of its ownership of a
283 * particular raw handle and storing it, sans auto-closing protection, in that Native_handle. That is, it is equal to:
284 *
285 * ~~~
286 * // src is Own_native_handle.
287 * Native_handle ret{src.get()};
288 * src.release();
289 * return ret;
290 * ~~~
291 *
292 * @relatesalso Native_handle
293 *
294 * @param src
295 * Source object; post-condition is `src.get().null() == true`.
296 * @return See above.
297 */
299
300/**
301 * Returns `Own_native_handle{src.dup(err_code)}`: an auto-closing duplicate of `src`; on error the result is
302 * `.get().null()`. See Native_handle::dup() for details.
303 *
304 * @relatesalso Native_handle
305 *
306 * @param src
307 * Handle to duplicate; it is unaffected.
308 * @param err_code
309 * See Native_handle::dup().
310 * @return See above.
311 */
313
314/**
315 * Returns `true` if and only if the two Native_handle objects are the same underlying handle.
316 *
317 * @relatesalso Native_handle
318 *
319 * @param val1
320 * Object.
321 * @param val2
322 * Object.
323 * @return See above.
324 */
325constexpr bool operator==(Native_handle val1, Native_handle val2) noexcept;
326
327/**
328 * Negation of similar `==`.
329 *
330 * @relatesalso Native_handle
331 *
332 * @param val1
333 * Object.
334 * @param val2
335 * Object.
336 * @return See above.
337 */
338constexpr bool operator!=(Native_handle val1, Native_handle val2) noexcept;
339
340/**
341 * Returns a less-than comparison of two Native_handle objects, with the usual total ordering guarantees.
342 *
343 * @relatesalso Native_handle
344 *
345 * @param val1
346 * Left-hand side object.
347 * @param val2
348 * Right-hand side object.
349 * @return Whether left side is considered strictly less-than right side.
350 */
351constexpr bool operator<(Native_handle val1, Native_handle val2) noexcept;
352
353/**
354 * Hasher of Native_handle for boost.unordered et al.
355 *
356 * @relatesalso Native_handle
357 *
358 * @param val
359 * Object to hash.
360 * @return See above.
361 */
362size_t hash_value(Native_handle val) noexcept;
363
364/**
365 * Standard swap. (Proper pattern is `using std::swap;` and then `swap()` in any using function.)
366 *
367 * ### Rationale for existence ###
368 * `std::swap()` would perform 3 move-assigns; Native_handle move-assignment/construction involves nullifying
369 * the source Native_handle::m_native_handle; that's not necessary in a swap and wastes cycles.
370 *
371 * @relatesalso Native_handle
372 *
373 * @param val1
374 * Object to swap.
375 * @param val2
376 * Object to swap.
377 */
378constexpr void swap(Native_handle& val1, Native_handle& val2) noexcept;
379
380/**
381 * Prints string representation of the given Native_handle to the given `ostream`.
382 *
383 * @relatesalso Native_handle
384 *
385 * @param os
386 * Stream to which to write.
387 * @param val
388 * Object to serialize.
389 * @return `os`.
390 */
391std::ostream& operator<<(std::ostream& os, const Native_handle& val);
392
393// constexpr implementations.
394
395constexpr Native_handle::Native_handle(handle_t native_handle) noexcept :
396 m_native_handle(native_handle)
397{
398 // Nope.
399}
400
402 Native_handle(src)
403{
404 // It's why we exist: prevent duplication of src.m_native_handle.
405 src.m_native_handle = S_NULL_HANDLE;
406}
407
408constexpr Native_handle::Native_handle(const Native_handle&) noexcept = default;
409
411{
412 if (this != &src)
413 {
414 m_native_handle = src.m_native_handle;
415 src.m_native_handle = S_NULL_HANDLE;
416 }
417 return *this;
418}
419
420constexpr Native_handle& Native_handle::operator=(const Native_handle&) noexcept = default;
421
422constexpr bool Native_handle::null() const noexcept
423{
425}
426
427constexpr Native_handle Native_handle::make_default() noexcept // Static.
428{
429 return {};
430}
431
432constexpr bool Native_handle::is_allocated(Native_handle hndl) noexcept // Static.
433{
434 return !hndl.null();
435}
436
437constexpr bool operator==(Native_handle val1, Native_handle val2) noexcept
438{
439 return val1.m_native_handle == val2.m_native_handle;
440}
441
442constexpr bool operator!=(Native_handle val1, Native_handle val2) noexcept
443{
444 return !operator==(val1, val2);
445}
446
447constexpr void swap(Native_handle& val1, Native_handle& val2) noexcept
448{
449 // (See `Rationale for existence` in our doc header.)
450
451 // Looks like this std::swap() is not constexpr (C++17), so let's just:
452 const auto tmp = val1.m_native_handle;
453 val1.m_native_handle = val2.m_native_handle;
454 val2.m_native_handle = tmp;
455}
456
457constexpr bool operator<(Native_handle val1, Native_handle val2) noexcept
458{
459 /* In POSIX and Windows this is valid. So don't worry about #ifdef'ing for OS, static_assert(false), etc.
460 * I did check that even in Windows they are formally integers still. Update: Actually... not sure... depends
461 * on what's a Native_handle; if it's SOCKET then sure, but otherwise -- who knows? There's probably a ticket
462 * about that whole topic (porting for Windows, that is; a big part would be figuring out what Native_handle might
463 * even be... if anything). */
464 return val1.m_native_handle < val2.m_native_handle;
465}
466
467} // namespace ipc::util
Flow-IPC module containing miscellaneous general-use facilities that are ubiquitously used by ~all Fl...
Definition: util.cpp:31
boost::scope::unique_resource< Native_handle, Native_handle::Closer, Native_handle > Own_native_handle
A unique_resource wrapper for Native_handle useful to avoid descriptor leaks and double-closing; can ...
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
static constexpr Native_handle make_default() noexcept
Simply returns Native_handle{}, a null handle.
bool is_open() const noexcept
Returns whether m_native_handle is currently an open descriptor in this process; false if null().
constexpr Native_handle & operator=(const Native_handle &src) noexcept
Copy assignment; acts similarly to copy ctor.
static constexpr handle_t S_NULL_HANDLE
The value for m_native_handle such that null() == true; else it is false.
boost::core::functor< static_close > Closer
Function object type (storing no data), suitable for unique_resource, whose (Native_handle& hndl) ope...
constexpr bool null() const noexcept
Returns true if and only if m_native_handle equals S_NULL_HANDLE.
Native_handle disowned_native_handle(Own_native_handle &&src) noexcept
Returns Native_handle constructed by relieving an existing auto-closing Own_native_handle of its owne...
constexpr Native_handle(const Native_handle &src) noexcept
Copy constructor.
constexpr Native_handle(handle_t native_handle=S_NULL_HANDLE) noexcept
Constructs with given payload; also subsumes no-args construction to mean constructing an object with...
constexpr Native_handle & operator=(Native_handle &&src) noexcept
Move assignment; acts similarly to move ctor; but no-op if this == &src.
constexpr void swap(Native_handle &val1, Native_handle &val2) noexcept
Standard swap.
Own_native_handle duped_native_handle(Native_handle src, Error_code *err_code=nullptr)
Returns Own_native_handle{src.dup(err_code)}: an auto-closing duplicate of src; on error the result i...
void close() noexcept
Little utility that returns m_native_handle to the OS.
int handle_t
The native handle type. Much logic relies on this type being light-weight (fast to copy).
static void static_close(Native_handle &hndl) noexcept
Simply calls hndl.close(); helpful for things like boost::core::functor<static_close>.
constexpr bool operator<(Native_handle val1, Native_handle val2) noexcept
Returns a less-than comparison of two Native_handle objects, with the usual total ordering guarantees...
constexpr bool operator==(Native_handle val1, Native_handle val2) noexcept
Returns true if and only if the two Native_handle objects are the same underlying handle.
size_t hash_value(Native_handle val) noexcept
Hasher of Native_handle for boost.unordered et al.
handle_t m_native_handle
The native handle (possibly equal to S_NULL_HANDLE), the exact payload of this Native_handle.
static constexpr bool is_allocated(Native_handle hndl) noexcept
Simply returns !hndl.null().
constexpr bool operator!=(Native_handle val1, Native_handle val2) noexcept
Negation of similar ==.
Native_handle dup(Error_code *err_code=nullptr) const
Returns a new handle referring to the same underlying OS-level open resource as *this,...