| 1 | // |
| 2 | // fd_passing_stream_server.cpp |
| 3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 4 | // |
| 5 | // Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
| 6 | // Copyright (c) 2021 Heiko Hund (heiko at openvpn dot net) |
| 7 | // |
| 8 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
| 9 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
| 10 | // |
| 11 | |
| 12 | // Demonstrates how to pass file descriptors between processes with Asio. |
| 13 | // The client sends a file name to the server. The server opens the file and |
| 14 | // passes the associated file descriptor back to the client. |
| 15 | |
| 16 | #include <array> |
| 17 | #include <cstdio> |
| 18 | #include <cassert> |
| 19 | #include <iostream> |
| 20 | #include <memory> |
| 21 | #include <boost/asio.hpp> |
| 22 | |
| 23 | #if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) |
| 24 | |
| 25 | using boost::asio::local::stream_protocol; |
| 26 | |
| 27 | class session |
| 28 | : public std::enable_shared_from_this<session> |
| 29 | { |
| 30 | public: |
| 31 | session(stream_protocol::socket sock) |
| 32 | : socket_(std::move(sock)) |
| 33 | { |
| 34 | } |
| 35 | |
| 36 | void start() |
| 37 | { |
| 38 | do_read(); |
| 39 | } |
| 40 | |
| 41 | private: |
| 42 | void do_read() |
| 43 | { |
| 44 | auto self(shared_from_this()); |
| 45 | socket_.async_read_some(buffers: boost::asio::buffer(data&: data_), |
| 46 | token: [this, self](boost::system::error_code ec, std::size_t length) |
| 47 | { |
| 48 | if (ec) |
| 49 | return; |
| 50 | |
| 51 | assert(length < data_.size()); |
| 52 | data_[length] = 0; |
| 53 | do_write(filename: data_.data()); |
| 54 | }); |
| 55 | } |
| 56 | |
| 57 | void do_write(const char* filename) |
| 58 | { |
| 59 | auto self(shared_from_this()); |
| 60 | socket_.async_wait(w: stream_protocol::socket::wait_write, |
| 61 | token: [this, self, filename](boost::system::error_code ec) |
| 62 | { |
| 63 | if (ec) |
| 64 | return; |
| 65 | |
| 66 | FILE* f(::fopen(filename: filename, modes: "w+" )); |
| 67 | if (!f) |
| 68 | return; |
| 69 | |
| 70 | struct msghdr msg = {}; |
| 71 | char buf[] = { 0 }; |
| 72 | struct iovec iov = { .iov_base: buf, .iov_len: sizeof(buf) }; |
| 73 | msg.msg_iov = &iov; |
| 74 | msg.msg_iovlen = 1; |
| 75 | |
| 76 | union |
| 77 | { |
| 78 | struct cmsghdr align; |
| 79 | char buf[CMSG_SPACE(sizeof(int))]; |
| 80 | } cmsgu; |
| 81 | msg.msg_control = cmsgu.buf; |
| 82 | msg.msg_controllen = sizeof(cmsgu.buf); |
| 83 | |
| 84 | struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); |
| 85 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); |
| 86 | cmsg->cmsg_level = SOL_SOCKET; |
| 87 | cmsg->cmsg_type = SCM_RIGHTS; |
| 88 | int fd = ::fileno(stream: f); |
| 89 | std::memcpy(CMSG_DATA(cmsg), src: &fd, n: sizeof(int)); |
| 90 | |
| 91 | ssize_t s(::sendmsg(fd: socket_.native_handle(), message: &msg, flags: 0)); |
| 92 | ::fclose(stream: f); |
| 93 | if (s != -1) |
| 94 | do_read(); |
| 95 | }); |
| 96 | } |
| 97 | |
| 98 | // The socket used to communicate with the client. |
| 99 | stream_protocol::socket socket_; |
| 100 | |
| 101 | // Buffer used to store data received from the client. |
| 102 | std::array<char, 1024> data_; |
| 103 | }; |
| 104 | |
| 105 | class server |
| 106 | { |
| 107 | public: |
| 108 | server(boost::asio::io_context& io_context, const std::string& file) |
| 109 | : acceptor_(io_context, stream_protocol::endpoint(file)) |
| 110 | { |
| 111 | do_accept(); |
| 112 | } |
| 113 | |
| 114 | private: |
| 115 | void do_accept() |
| 116 | { |
| 117 | acceptor_.async_accept( |
| 118 | token: [this](boost::system::error_code ec, stream_protocol::socket socket) |
| 119 | { |
| 120 | if (!ec) |
| 121 | { |
| 122 | std::make_shared<session>(args: std::move(socket))->start(); |
| 123 | } |
| 124 | |
| 125 | do_accept(); |
| 126 | }); |
| 127 | } |
| 128 | |
| 129 | stream_protocol::acceptor acceptor_; |
| 130 | }; |
| 131 | |
| 132 | int main(int argc, char* argv[]) |
| 133 | { |
| 134 | try |
| 135 | { |
| 136 | if (argc != 2) |
| 137 | { |
| 138 | std::cerr << "Usage: fd_passing_stream_server <socketfile>\n" ; |
| 139 | std::cerr << "*** WARNING: existing file is removed ***\n" ; |
| 140 | return 1; |
| 141 | } |
| 142 | |
| 143 | boost::asio::io_context io_context; |
| 144 | |
| 145 | std::remove(filename: argv[1]); |
| 146 | server s(io_context, argv[1]); |
| 147 | |
| 148 | io_context.run(); |
| 149 | } |
| 150 | catch (std::exception& e) |
| 151 | { |
| 152 | std::cerr << "Exception: " << e.what() << "\n" ; |
| 153 | } |
| 154 | |
| 155 | return 0; |
| 156 | } |
| 157 | |
| 158 | #else // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) |
| 159 | # error Local sockets not available on this platform. |
| 160 | #endif // defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) |
| 161 | |