1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/utils/port/detail/NativeFd.h"
8
9 #include "td/utils/format.h"
10 #include "td/utils/logging.h"
11 #include "td/utils/SliceBuilder.h"
12
13 #if TD_PORT_POSIX
14 #include <fcntl.h>
15 #include <unistd.h>
16 #endif
17
18 #if TD_FD_DEBUG
19 #include <mutex>
20 #include <set>
21 #endif
22
23 namespace td {
24
25 int VERBOSITY_NAME(fd) = VERBOSITY_NAME(DEBUG) + 9;
26
27 #if TD_FD_DEBUG
28 class FdSet {
29 public:
on_create_fd(NativeFd::Fd fd)30 void on_create_fd(NativeFd::Fd fd) {
31 if (!is_valid(fd)) {
32 return;
33 }
34 if (is_stdio(fd)) {
35 return;
36 }
37 std::unique_lock<std::mutex> guard(mutex_);
38 if (fds_.count(fd) >= 1) {
39 LOG(FATAL) << "Create duplicated fd: " << fd;
40 }
41 fds_.insert(fd);
42 }
43
validate(NativeFd::Fd fd)44 Status validate(NativeFd::Fd fd) {
45 if (!is_valid(fd)) {
46 return Status::Error(PSLICE() << "Invalid fd: " << fd);
47 }
48 if (is_stdio(fd)) {
49 return Status::OK();
50 }
51 std::unique_lock<std::mutex> guard(mutex_);
52 if (fds_.count(fd) != 1) {
53 return Status::Error(PSLICE() << "Unknown fd: " << fd);
54 }
55 return Status::OK();
56 }
57
on_close_fd(NativeFd::Fd fd)58 void on_close_fd(NativeFd::Fd fd) {
59 if (!is_valid(fd)) {
60 return;
61 }
62 if (is_stdio(fd)) {
63 return;
64 }
65 std::unique_lock<std::mutex> guard(mutex_);
66 if (fds_.count(fd) != 1) {
67 LOG(FATAL) << "Close unknown fd: " << fd;
68 }
69 fds_.erase(fd);
70 }
71
72 private:
73 std::mutex mutex_;
74 std::set<NativeFd::Fd> fds_;
75
is_stdio(NativeFd::Fd fd) const76 bool is_stdio(NativeFd::Fd fd) const {
77 #if TD_PORT_WINDOWS
78 #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM)
79 return fd == GetStdHandle(STD_INPUT_HANDLE) || fd == GetStdHandle(STD_OUTPUT_HANDLE) ||
80 fd == GetStdHandle(STD_ERROR_HANDLE);
81 #else
82 return false;
83 #endif
84 #else
85 return fd >= 0 && fd <= 2;
86 #endif
87 }
88
is_valid(NativeFd::Fd fd) const89 bool is_valid(NativeFd::Fd fd) const {
90 #if TD_PORT_WINDOWS
91 return fd != INVALID_HANDLE_VALUE;
92 #else
93 return fd >= 0;
94 #endif
95 }
96 };
97
98 namespace {
get_fd_set()99 FdSet &get_fd_set() {
100 static FdSet res;
101 return res;
102 }
103 } // namespace
104
105 #endif
106
validate() const107 Status NativeFd::validate() const {
108 #if TD_FD_DEBUG
109 return get_fd_set().validate(fd_);
110 #else
111 return Status::OK();
112 #endif
113 }
114
NativeFd(Fd fd)115 NativeFd::NativeFd(Fd fd) : fd_(fd) {
116 VLOG(fd) << *this << " create";
117 #if TD_FD_DEBUG
118 get_fd_set().on_create_fd(fd_);
119 #endif
120 }
121
NativeFd(Fd fd,bool nolog)122 NativeFd::NativeFd(Fd fd, bool nolog) : fd_(fd) {
123 #if TD_FD_DEBUG
124 get_fd_set().on_create_fd(fd_);
125 #endif
126 }
127
128 #if TD_PORT_WINDOWS
NativeFd(Socket socket)129 NativeFd::NativeFd(Socket socket) : fd_(reinterpret_cast<Fd>(socket)), is_socket_(true) {
130 VLOG(fd) << *this << " create";
131 #if TD_FD_DEBUG
132 get_fd_set().on_create_fd(fd_);
133 #endif
134 }
135 #endif
136
NativeFd(NativeFd && other)137 NativeFd::NativeFd(NativeFd &&other) noexcept : fd_(other.fd_) {
138 #if TD_PORT_WINDOWS
139 is_socket_ = other.is_socket_;
140 #endif
141 other.fd_ = empty_fd();
142 }
143
operator =(NativeFd && other)144 NativeFd &NativeFd::operator=(NativeFd &&other) noexcept {
145 CHECK(this != &other);
146 close();
147 fd_ = other.fd_;
148 #if TD_PORT_WINDOWS
149 is_socket_ = other.is_socket_;
150 #endif
151 other.fd_ = empty_fd();
152 return *this;
153 }
154
~NativeFd()155 NativeFd::~NativeFd() {
156 close();
157 }
158
operator bool() const159 NativeFd::operator bool() const {
160 return fd_ != empty_fd();
161 }
162
empty_fd()163 NativeFd::Fd NativeFd::empty_fd() {
164 #if TD_PORT_POSIX
165 return -1;
166 #elif TD_PORT_WINDOWS
167 return INVALID_HANDLE_VALUE;
168 #endif
169 }
170
fd() const171 NativeFd::Fd NativeFd::fd() const {
172 return fd_;
173 }
174
socket() const175 NativeFd::Socket NativeFd::socket() const {
176 #if TD_PORT_POSIX
177 return fd();
178 #elif TD_PORT_WINDOWS
179 CHECK(is_socket_);
180 return reinterpret_cast<Socket>(fd_);
181 #endif
182 }
183
set_is_blocking(bool is_blocking) const184 Status NativeFd::set_is_blocking(bool is_blocking) const {
185 #if TD_PORT_POSIX
186 auto old_flags = fcntl(fd(), F_GETFL);
187 if (old_flags == -1) {
188 return OS_SOCKET_ERROR("Failed to get socket flags");
189 }
190 auto new_flags = is_blocking ? old_flags & ~O_NONBLOCK : old_flags | O_NONBLOCK;
191 if (new_flags != old_flags && fcntl(fd(), F_SETFL, new_flags) == -1) {
192 return OS_SOCKET_ERROR("Failed to set socket flags");
193 }
194
195 return Status::OK();
196 #elif TD_PORT_WINDOWS
197 return set_is_blocking_unsafe(is_blocking);
198 #endif
199 }
200
set_is_blocking_unsafe(bool is_blocking) const201 Status NativeFd::set_is_blocking_unsafe(bool is_blocking) const {
202 #if TD_PORT_POSIX
203 if (fcntl(fd(), F_SETFL, is_blocking ? 0 : O_NONBLOCK) == -1) {
204 #elif TD_PORT_WINDOWS
205 u_long mode = is_blocking;
206 if (ioctlsocket(socket(), FIONBIO, &mode) != 0) {
207 #endif
208 return OS_SOCKET_ERROR("Failed to change socket flags");
209 }
210 return Status::OK();
211 }
212
213 Status NativeFd::duplicate(const NativeFd &to) const {
214 #if TD_PORT_POSIX
215 CHECK(*this);
216 CHECK(to);
217 if (dup2(fd(), to.fd()) == -1) {
218 return OS_ERROR("Failed to duplicate file descriptor");
219 }
220 return Status::OK();
221 #elif TD_PORT_WINDOWS
222 return Status::Error("Not supported");
223 #endif
224 }
225
226 void NativeFd::close() {
227 if (!*this) {
228 return;
229 }
230
231 #if TD_FD_DEBUG
232 get_fd_set().on_close_fd(fd());
233 #endif
234
235 VLOG(fd) << *this << " close";
236 #if TD_PORT_WINDOWS
237 if (is_socket_ ? closesocket(socket()) : !CloseHandle(fd())) {
238 #elif TD_PORT_POSIX
239 if (::close(fd()) < 0) {
240 #endif
241 auto error = OS_ERROR("Close fd");
242 LOG(ERROR) << error;
243 }
244 fd_ = empty_fd();
245 }
246
247 NativeFd::Fd NativeFd::release() {
248 VLOG(fd) << *this << " release";
249 auto res = fd_;
250 fd_ = empty_fd();
251 #if TD_FD_DEBUG
252 get_fd_set().on_close_fd(res);
253 #endif
254 return res;
255 }
256
257 StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd) {
258 return sb << tag("fd", fd.fd());
259 }
260
261 } // namespace td
262