1 /*
2 * Copyright 2008-2020 Max Kellermann <max.kellermann@gmail.com>
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * - Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 * OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "Global.hxx"
31 #include "Request.hxx"
32 #include "event/Loop.hxx"
33 #include "event/SocketEvent.hxx"
34 #include "util/Compiler.h"
35
36 #include <cassert>
37
38 /**
39 * Monitor for one socket created by CURL.
40 */
41 class CurlSocket final {
42 CurlGlobal &global;
43
44 SocketEvent socket_event;
45
46 public:
CurlSocket(CurlGlobal & _global,EventLoop & _loop,SocketDescriptor _fd)47 CurlSocket(CurlGlobal &_global, EventLoop &_loop, SocketDescriptor _fd)
48 :global(_global),
49 socket_event(_loop, BIND_THIS_METHOD(OnSocketReady), _fd) {}
50
~CurlSocket()51 ~CurlSocket() noexcept {
52 /* TODO: sometimes, CURL uses CURL_POLL_REMOVE after
53 closing the socket, and sometimes, it uses
54 CURL_POLL_REMOVE just to move the (still open)
55 connection to the pool; in the first case,
56 Abandon() would be most appropriate, but it breaks
57 the second case - is that a CURL bug? is there a
58 better solution? */
59 }
60
61 CurlSocket(const CurlSocket &) = delete;
62 CurlSocket &operator=(const CurlSocket &) = delete;
63
GetEventLoop() const64 [[nodiscard]] auto &GetEventLoop() const noexcept {
65 return socket_event.GetEventLoop();
66 }
67
68 /**
69 * Callback function for CURLMOPT_SOCKETFUNCTION.
70 */
71 static int SocketFunction(CURL *easy,
72 curl_socket_t s, int action,
73 void *userp, void *socketp) noexcept;
74
75 private:
GetSocket() const76 [[nodiscard]] SocketDescriptor GetSocket() const noexcept {
77 return socket_event.GetSocket();
78 }
79
80 void OnSocketReady(unsigned events) noexcept;
81
FlagsToCurlCSelect(unsigned flags)82 static constexpr int FlagsToCurlCSelect(unsigned flags) noexcept {
83 return (flags & (SocketEvent::READ | SocketEvent::HANGUP) ? CURL_CSELECT_IN : 0) |
84 (flags & SocketEvent::WRITE ? CURL_CSELECT_OUT : 0) |
85 (flags & SocketEvent::ERROR ? CURL_CSELECT_ERR : 0);
86 }
87
88 [[gnu::const]]
CurlPollToFlags(int action)89 static unsigned CurlPollToFlags(int action) noexcept {
90 switch (action) {
91 case CURL_POLL_NONE:
92 return 0;
93
94 case CURL_POLL_IN:
95 return SocketEvent::READ;
96
97 case CURL_POLL_OUT:
98 return SocketEvent::WRITE;
99
100 case CURL_POLL_INOUT:
101 return SocketEvent::READ|SocketEvent::WRITE;
102 }
103
104 assert(false);
105 gcc_unreachable();
106 }
107 };
108
CurlGlobal(EventLoop & _loop)109 CurlGlobal::CurlGlobal(EventLoop &_loop)
110 :defer_read_info(_loop, BIND_THIS_METHOD(ReadInfo)),
111 timeout_event(_loop, BIND_THIS_METHOD(OnTimeout))
112 {
113 multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction);
114 multi.SetOption(CURLMOPT_SOCKETDATA, this);
115
116 multi.SetOption(CURLMOPT_TIMERFUNCTION, TimerFunction);
117 multi.SetOption(CURLMOPT_TIMERDATA, this);
118 }
119
120 int
SocketFunction(CURL * easy,curl_socket_t s,int action,void * userp,void * socketp)121 CurlSocket::SocketFunction([[maybe_unused]] CURL *easy,
122 curl_socket_t s, int action,
123 void *userp, void *socketp) noexcept
124 {
125 auto &global = *(CurlGlobal *)userp;
126 auto *cs = (CurlSocket *)socketp;
127
128 assert(global.GetEventLoop().IsInside());
129
130 if (action == CURL_POLL_REMOVE) {
131 delete cs;
132 return 0;
133 }
134
135 if (cs == nullptr) {
136 cs = new CurlSocket(global, global.GetEventLoop(),
137 SocketDescriptor(s));
138 global.Assign(s, *cs);
139 }
140
141 unsigned flags = CurlPollToFlags(action);
142 if (flags != 0)
143 cs->socket_event.Schedule(flags);
144 return 0;
145 }
146
147 void
OnSocketReady(unsigned flags)148 CurlSocket::OnSocketReady(unsigned flags) noexcept
149 {
150 assert(GetEventLoop().IsInside());
151
152 global.SocketAction(GetSocket().Get(), FlagsToCurlCSelect(flags));
153 }
154
155 void
Add(CurlRequest & r)156 CurlGlobal::Add(CurlRequest &r)
157 {
158 assert(GetEventLoop().IsInside());
159
160 multi.Add(r.Get());
161
162 InvalidateSockets();
163 }
164
165 void
Remove(CurlRequest & r)166 CurlGlobal::Remove(CurlRequest &r) noexcept
167 {
168 assert(GetEventLoop().IsInside());
169
170 multi.Remove(r.Get());
171 }
172
173 /**
174 * Find a request by its CURL "easy" handle.
175 */
176 [[gnu::pure]]
177 static CurlRequest *
ToRequest(CURL * easy)178 ToRequest(CURL *easy) noexcept
179 {
180 void *p;
181 CURLcode code = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &p);
182 if (code != CURLE_OK)
183 return nullptr;
184
185 return (CurlRequest *)p;
186 }
187
188 inline void
ReadInfo()189 CurlGlobal::ReadInfo() noexcept
190 {
191 assert(GetEventLoop().IsInside());
192
193 CURLMsg *msg;
194
195 while ((msg = multi.InfoRead()) != nullptr) {
196 if (msg->msg == CURLMSG_DONE) {
197 auto *request = ToRequest(msg->easy_handle);
198 if (request != nullptr)
199 request->Done(msg->data.result);
200 }
201 }
202 }
203
204 void
SocketAction(curl_socket_t fd,int ev_bitmask)205 CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) noexcept
206 {
207 int running_handles;
208 CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask,
209 &running_handles);
210 (void)mcode;
211
212 defer_read_info.Schedule();
213 }
214
215 inline void
UpdateTimeout(long timeout_ms)216 CurlGlobal::UpdateTimeout(long timeout_ms) noexcept
217 {
218 if (timeout_ms < 0) {
219 timeout_event.Cancel();
220 return;
221 }
222
223 if (timeout_ms < 1)
224 /* CURL's threaded resolver sets a timeout of 0ms, which
225 means we're running in a busy loop. Quite a bad
226 idea to waste so much CPU. Let's use a lower limit
227 of 1ms. */
228 timeout_ms = 1;
229
230 timeout_event.Schedule(std::chrono::milliseconds(timeout_ms));
231 }
232
233 int
TimerFunction(CURLM * _multi,long timeout_ms,void * userp)234 CurlGlobal::TimerFunction([[maybe_unused]] CURLM *_multi, long timeout_ms,
235 void *userp) noexcept
236 {
237 auto &global = *(CurlGlobal *)userp;
238 assert(_multi == global.multi.Get());
239
240 global.UpdateTimeout(timeout_ms);
241 return 0;
242 }
243
244 void
OnTimeout()245 CurlGlobal::OnTimeout() noexcept
246 {
247 SocketAction(CURL_SOCKET_TIMEOUT, 0);
248 }
249