Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// Copyright (c) 2017 Seth Heeren | ||
// | ||
// Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
|
||
#ifndef BOOST_PROCESS_DETAIL_POSIX_FD_RESTRICT_HPP | ||
#define BOOST_PROCESS_DETAIL_POSIX_FD_RESTRICT_HPP | ||
|
||
#include <boost/process/detail/posix/handler.hpp> | ||
#include <unistd.h> | ||
|
||
namespace boost { namespace process { namespace detail { namespace posix { namespace fd_restrict { | ||
// customization point for (custom) properties that need to protect fds | ||
template <typename Property, typename OutputIterator> | ||
void collect_filedescriptors(Property const& /*property*/, OutputIterator& /*outit*/) { | ||
// primary template | ||
} | ||
|
||
// polymorphic function object for ADL dispatch | ||
template <typename OutputIterator> | ||
struct collect_fd_f { | ||
OutputIterator mutable _outit; | ||
|
||
template <typename Property> | ||
void operator()(Property const& property) const { | ||
using boost::process::detail::posix::fd_restrict::collect_filedescriptors; // ADL desired | ||
collect_filedescriptors(property, _outit); | ||
} | ||
}; | ||
|
||
// launch property | ||
template <typename=void> | ||
struct property_ : handler_base_ext | ||
{ | ||
public: | ||
property_(size_t capacity) { | ||
// reserve to avoid allocations between fork/exec which may | ||
// deadlock with threads | ||
_protected_fds.reserve(capacity); | ||
} | ||
|
||
template <class PosixExecutor> | ||
void on_exec_setup(PosixExecutor& exec) const | ||
{ | ||
_protected_fds.resize(0); | ||
auto outit = back_inserter(_protected_fds); | ||
collect_fd_f<decltype(outit)> visit{outit}; | ||
|
||
visit(exec); | ||
boost::fusion::for_each(exec.seq, visit); | ||
|
||
auto begin = _protected_fds.begin(), end = _protected_fds.end(); | ||
std::sort(begin, end); | ||
|
||
for(int fd=0, maxfd=sysconf(_SC_OPEN_MAX); fd<maxfd; ++fd) { | ||
This comment has been minimized.
Sorry, something went wrong. |
||
if (!std::binary_search(begin, end, fd)) | ||
::close(fd); | ||
} | ||
} | ||
|
||
private: | ||
std::vector<int> mutable _protected_fds; | ||
}; | ||
|
||
}}}}} | ||
|
||
/* | ||
* Non-intrusive instrumentation of existing POSIX properties that require filedescriptors | ||
* | ||
* All of these could be done with an inline `friend` definition, like above. | ||
* | ||
* For now I prefer to keep them separate so that | ||
* | ||
* - upstream changes merge cleanly | ||
* - interface changes in fd_restrict can more easily be applied consistently in 1 file | ||
* | ||
* Only bind_fd_ and filedescriptor need friend access, so cannot usefully be kept separate. | ||
*/ | ||
|
||
#include <boost/process/detail/posix/async_in.hpp> | ||
#include <boost/process/detail/posix/async_out.hpp> | ||
#include <boost/process/detail/posix/null_in.hpp> | ||
#include <boost/process/detail/posix/null_out.hpp> | ||
#include <boost/process/detail/posix/file_in.hpp> | ||
#include <boost/process/detail/posix/file_out.hpp> | ||
#include <boost/process/detail/posix/pipe_in.hpp> | ||
#include <boost/process/detail/posix/pipe_out.hpp> | ||
|
||
namespace boost { namespace process { namespace detail { namespace posix { | ||
|
||
template<typename... Ts, typename OutputIterator> | ||
void collect_filedescriptors(async_in_buffer<Ts...> const&, OutputIterator& outit) { | ||
*outit++ = STDIN_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename... Ts, typename OutputIterator> | ||
void collect_filedescriptors(async_out_buffer<p1, p2, Ts...> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename... Ts, typename OutputIterator> | ||
void collect_filedescriptors(async_out_future<p1, p2, Ts...> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
template<typename OutputIterator> | ||
void collect_filedescriptors(file_in const&, OutputIterator& outit) { | ||
*outit++ = STDIN_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename OutputIterator> | ||
void collect_filedescriptors(file_out<p1, p2> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
template<typename OutputIterator> | ||
void collect_filedescriptors(null_in const&, OutputIterator& outit) { | ||
*outit++ = STDIN_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename OutputIterator> | ||
void collect_filedescriptors(null_out<p1, p2> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
template<typename OutputIterator> | ||
void collect_filedescriptors(pipe_in const&, OutputIterator& outit) { | ||
*outit++ = STDIN_FILENO; | ||
} | ||
|
||
template<typename OutputIterator> | ||
void collect_filedescriptors(async_pipe_in const&, OutputIterator& outit) { | ||
*outit++ = STDIN_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename OutputIterator> | ||
void collect_filedescriptors(pipe_out<p1, p2> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
template<int p1, int p2, typename OutputIterator> | ||
void collect_filedescriptors(async_pipe_out<p1, p2> const&, OutputIterator& outit) { | ||
if (p1==1||p2==1) *outit++ = STDOUT_FILENO; | ||
if (p1==2||p2==2) *outit++ = STDERR_FILENO; | ||
} | ||
|
||
}}}} | ||
|
||
#endif |
9 comments
on commit 3d1f77b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a thing. It does WorkForMe™.
It adds a fd_restrict
namespace + ditto launch property that can be added via bp::posix::fg.restrict_inherit()
. If it is specified, only expressly fds expressly required by any Boost Process feature are inherited, and the rest is closed.
The fd collection mechanism is extensible using an ADL-enabled customization point (collect_filedescriptors
). For now, the integration with existing POSIX properties is done non-intrusively when possible, inside the fd_restrict.hpp
header.
Note that the actual action of closing is done in the on_setup_exec
of the fd_restrict
property. It would make the most sense to add this as the last, in case there are inter-dependencies (e.g. other launch properties still require information from open descriptors before execve
.
Bikeshed: The interface may be a bit random. I find it hard to come up with a consistent interface given the presence of "negative" inheriters (
bp::posix::fd.close(fd)
e.g.) and "inherit by default". If you're open to changing those defaults, things could be more natural, and more secure(!).
I have done some testing in our codebase, but the tests don't transfer easily. I'll probably find some time to write tests for the repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sort of works, but it's not extensible enough for me. How about letting every intializer like file_in
inherit a carries_fd
type, so I can filter that by type_traits and thus extend it more easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition you could also count the elements containing file-descriptors at runtime, thus saving an allocation - or having none at all by using std::array
. Not that this is major, but if it's possible...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sort of works, but it's not extensible enough for me. How about letting every intializer like file_in inherit a carries_fd type, so I can filter that by type_traits and thus extend it more easily.
carries_fd
logically doesn't convey which fd, so you parameterize it with the fd that's actually "carried". I I actually explored this option. It turns out to be inconvenient
- because you might carry more than one fd. (e.g. all the current output properties)
- if the FD is not statically known (see posix
executor
,file_descriptor
,bind_fd_
e.g.) you'll still need to have a dynamic interface to get at those values.
Therefore having a carries_fd
trait appears to strictly make things harder. You still have to make it return specific fds (which becomes clumsy for non-static, in effect other than stdin/stdout/stderr) as well as creating the marker typedef.
Summary
I'd like to note that the current setup requires literally ZERO effort to support, because of the default implementation of collect_filedescriptors
.
- I'm open to renaming it to
carried_fds
or similar - Yes, we could reduce some code duplication using traits only for standard filedescriptor handles. That just requires some intrusive changes which I specifically wanted to avoid (because I need to support this patch locally for indefinite time.)
PS. Note also that the current customization point transparently applies to the executor itself.
If I misunderstood your suggestion, perhaps you can show me some (pseudo) code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition you could also count the elements containing file-descriptors at runtime, thus saving an allocation - or having none at all by using std::array. Not that this is major, but if it's possible...
I aimed for genericity. We can ONLY count things if we assume that the number of fds carried by (custom) properties is fixed. And we can ONLY use fixed size containers if those numbers are statically known. (Worse, if it's dynamically known, custom properties might not know the actual number pre-fork).
The only thing I would deem reasonably to add here is protection against inadvertent reallocation (the output iterator doesn't prevent or detect it). This begins to seem like overkill in the light of klemens-morgenstern#65 (comment).
I'd be happy to replace the output iterator by a callback that can actually refuse if capacity is exceeded. This would be a splendid middle-ground IYAM. I admit that I was a little lazy there (or in Proof-Of-Concept mode).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you're right, and for whatever reason a custom initializer might have more than one handle. But that doesn't mean we can't tag them, here's a what I came up with: https://godbolt.org/g/nzllVe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sehe
Did this patch set make into the mainline boost process module ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sraza1 nope. See this post for the lastest status update I have
Short answer: it's untested, might not work. I have dropped Boost Process in favour of hand written code once again, because it was too hard to reason about whether bugs were being caused by me trying to use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard problem to solve as well - since it should be done portably, so what do we do on windows? The current solution just goes by the OS defaults. @sehe, if I give you the function to collect all the used fds, would that help? Or what other hand-coded things are needed?
Btw. if you know what descriptors should not be used, you can do this.
#include <boost/process/posix.hpp>
void foo()
{
namespace bp = boost::process;
bp::child proc{"foo", bp::posix::fd.close({1,3,5,42}));
}
There are many contradictory articles about portably closing all FDs on POSIX. This brute-force approach appears to be the only one guaranteed.
(One implication is that
fd.close()
and friends could still be worthwhile in case you wanted error-checked close of a particular fd.)