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