Mercurial > irccd
comparison plugins/links/links.c @ 1164:832dbde9d495
plugin links: rewrite a thread-less version with pollable interface
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 28 Feb 2022 21:38:38 +0100 |
parents | f06e9761cc90 |
children |
comparison
equal
deleted
inserted
replaced
1163:ad7673c59ec5 | 1164:832dbde9d495 |
---|---|
17 */ | 17 */ |
18 | 18 |
19 #include <assert.h> | 19 #include <assert.h> |
20 #include <ctype.h> | 20 #include <ctype.h> |
21 #include <errno.h> | 21 #include <errno.h> |
22 #include <pthread.h> | |
23 #include <regex.h> | 22 #include <regex.h> |
24 #include <stdio.h> | 23 #include <stdio.h> |
25 #include <stdlib.h> | 24 #include <stdlib.h> |
26 #include <string.h> | 25 #include <string.h> |
27 | 26 |
28 #include <curl/curl.h> | 27 #include <curl/curl.h> |
29 | 28 |
30 #include <irccd/config.h> | 29 #include <irccd/config.h> |
31 #include <irccd/irccd.h> | 30 #include <irccd/irccd.h> |
32 #include <irccd/limits.h> | 31 #include <irccd/limits.h> |
32 #include <irccd/log.h> | |
33 #include <irccd/server.h> | 33 #include <irccd/server.h> |
34 #include <irccd/subst.h> | 34 #include <irccd/subst.h> |
35 #include <irccd/util.h> | 35 #include <irccd/util.h> |
36 | 36 |
37 /* | 37 /* |
39 * analyse. Use 5MB for now. | 39 * analyse. Use 5MB for now. |
40 */ | 40 */ |
41 #define PAGE_MAX 5242880 | 41 #define PAGE_MAX 5242880 |
42 | 42 |
43 struct req { | 43 struct req { |
44 pthread_t thr; | |
45 CURL *curl; | 44 CURL *curl; |
45 CURLM *multi; | |
46 struct irc_server *server; | 46 struct irc_server *server; |
47 struct irc_pollable pollable; | |
47 int status; | 48 int status; |
48 char *link; | 49 char *link; |
49 char *chan; | 50 char *chan; |
50 char *nickname; | 51 char *nickname; |
51 char *origin; | 52 char *origin; |
55 | 56 |
56 enum { | 57 enum { |
57 TPL_INFO | 58 TPL_INFO |
58 }; | 59 }; |
59 | 60 |
60 static unsigned long timeout = 30; | 61 static unsigned long timeout = 3; |
61 | 62 |
62 static char templates[][512] = { | 63 static char templates[][512] = { |
63 [TPL_INFO] = "link from #{nickname}: #{title}" | 64 [TPL_INFO] = "link from #{nickname}: #{title}" |
64 }; | 65 }; |
65 | 66 |
168 static char line[IRC_MESSAGE_LEN]; | 169 static char line[IRC_MESSAGE_LEN]; |
169 | 170 |
170 struct irc_subst subst = { | 171 struct irc_subst subst = { |
171 .time = time(NULL), | 172 .time = time(NULL), |
172 .flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS, | 173 .flags = IRC_SUBST_DATE | IRC_SUBST_KEYWORDS | IRC_SUBST_IRC_ATTRS, |
173 .keywords = (struct irc_subst_keyword []) { | 174 .keywords = (const struct irc_subst_keyword []) { |
174 { "channel", req->chan }, | 175 { "channel", req->chan }, |
175 { "nickname", req->nickname }, | 176 { "nickname", req->nickname }, |
176 { "origin", req->origin }, | 177 { "origin", req->origin }, |
177 { "server", req->server->name }, | 178 { "server", req->server->name }, |
178 { "title", untitle(title) } | 179 { "title", untitle(title) } |
184 | 185 |
185 return line; | 186 return line; |
186 } | 187 } |
187 | 188 |
188 static void | 189 static void |
189 req_finish(struct req *); | 190 complete(struct req *req) |
190 | 191 { |
191 static void | |
192 complete(void *data) | |
193 { | |
194 struct req *req = data; | |
195 char *title; | 192 char *title; |
196 | 193 |
197 if (req->status && (title = parse(req))) | 194 if ((title = parse(req))) |
198 irc_server_message(req->server, req->chan, fmt(req, title)); | 195 irc_server_message(req->server, req->chan, fmt(req, title)); |
199 | |
200 pthread_join(req->thr, NULL); | |
201 req_finish(req); | |
202 } | |
203 | |
204 /* | |
205 * This function is running in a separate thread. | |
206 */ | |
207 static void * | |
208 routine(void *data) | |
209 { | |
210 struct req *req = data; | |
211 long code = 0; | |
212 | |
213 if (curl_easy_perform(req->curl) == 0) { | |
214 /* We only accept 200 result. */ | |
215 curl_easy_getinfo(req->curl, CURLINFO_RESPONSE_CODE, &code); | |
216 req->status = code == 200; | |
217 } | |
218 | |
219 irc_bot_post(complete, req); | |
220 | |
221 return NULL; | |
222 } | 196 } |
223 | 197 |
224 static void | 198 static void |
225 req_finish(struct req *req) | 199 req_finish(struct req *req) |
226 { | 200 { |
227 assert(req); | 201 if (!req) |
202 return; | |
228 | 203 |
229 if (req->server) | 204 if (req->server) |
230 irc_server_decref(req->server); | 205 irc_server_decref(req->server); |
231 if (req->curl) | 206 |
207 if (req->curl) { | |
208 if (req->multi) | |
209 curl_multi_remove_handle(req->multi, req->curl); | |
210 | |
232 curl_easy_cleanup(req->curl); | 211 curl_easy_cleanup(req->curl); |
212 } | |
213 if (req->multi) | |
214 curl_multi_cleanup(req->multi); | |
215 | |
233 if (req->fp) | 216 if (req->fp) |
234 fclose(req->fp); | 217 fclose(req->fp); |
235 | 218 |
236 free(req->link); | 219 free(req->link); |
237 free(req->chan); | 220 free(req->chan); |
238 free(req->nickname); | 221 free(req->nickname); |
239 free(req->origin); | 222 free(req->origin); |
240 free(req); | 223 free(req); |
241 } | 224 } |
242 | 225 |
243 static struct req * | 226 static int |
244 req_new(struct irc_server *server, const char *origin, const char *channel, char *link) | 227 pollable_fd(void *data) |
228 { | |
229 struct req *r = data; | |
230 fd_set in, out, exc; | |
231 int fd = 0; | |
232 | |
233 FD_ZERO(&in); | |
234 FD_ZERO(&out); | |
235 FD_ZERO(&exc); | |
236 | |
237 curl_multi_fdset(r->multi, &in, &out, &exc, &fd); | |
238 | |
239 return fd; | |
240 } | |
241 | |
242 static void | |
243 pollable_want(int *frecv, int *fsend, void *data) | |
244 { | |
245 struct req *r = data; | |
246 fd_set in, out, exc; | |
247 int maxfd = 0; | |
248 | |
249 FD_ZERO(&in); | |
250 FD_ZERO(&out); | |
251 FD_ZERO(&exc); | |
252 | |
253 curl_multi_fdset(r->multi, &in, &out, &exc, &maxfd); | |
254 | |
255 if (FD_ISSET(maxfd, &in)) | |
256 *frecv = 1; | |
257 if (FD_ISSET(maxfd, &out)) | |
258 *fsend = 1; | |
259 } | |
260 | |
261 static int | |
262 pollable_sync(int frecv, int fsend, void *data) | |
263 { | |
264 (void)frecv; | |
265 (void)fsend; | |
266 | |
267 CURLMsg *msg; | |
268 struct req *r = data; | |
269 int pending, msgsz; | |
270 | |
271 /* | |
272 * CURL does its own job reading/sending without taking action on what | |
273 * have been found. | |
274 */ | |
275 if (curl_multi_perform(r->multi, &pending) < 0) | |
276 return -1; | |
277 | |
278 /* We only have one handle so we can just assume 0 means complete. */ | |
279 if (pending) | |
280 return 0; | |
281 | |
282 while ((msg = curl_multi_info_read(r->multi, &msgsz))) { | |
283 if (msg->msg != CURLMSG_DONE) | |
284 continue; | |
285 | |
286 switch (msg->data.result) { | |
287 case CURLE_OPERATION_TIMEDOUT: | |
288 irc_log_warn("links: %s timed out", r->link); | |
289 break; | |
290 case CURLE_OK: | |
291 complete(r); | |
292 break; | |
293 default: | |
294 break; | |
295 } | |
296 } | |
297 | |
298 return -1; | |
299 } | |
300 | |
301 static void | |
302 pollable_finish(void *data) | |
303 { | |
304 req_finish(data); | |
305 } | |
306 | |
307 static void | |
308 req(struct irc_server *server, const char *origin, const char *channel, char *link) | |
245 { | 309 { |
246 assert(link); | 310 assert(link); |
247 | 311 |
248 struct req *req; | 312 struct req *req; |
249 struct irc_server_user user; | 313 struct irc_server_user user; |
250 | 314 int pending; |
251 if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1))) { | 315 |
252 free(link); | 316 if (!(req = calloc(1, sizeof (*req) + PAGE_MAX + 1))) |
253 return NULL; | 317 goto enomem; |
254 } | |
255 if (!(req->curl = curl_easy_init())) | 318 if (!(req->curl = curl_easy_init())) |
319 goto enomem; | |
320 if (!(req->multi = curl_multi_init())) | |
256 goto enomem; | 321 goto enomem; |
257 if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w"))) | 322 if (!(req->fp = fmemopen(req->buf, PAGE_MAX, "w"))) |
258 goto enomem; | 323 goto enomem; |
259 | 324 |
260 irc_server_incref(server); | 325 irc_server_incref(server); |
261 irc_server_split(origin, &user); | 326 irc_server_split(origin, &user); |
327 | |
262 req->link = link; | 328 req->link = link; |
263 req->server = server; | 329 req->server = server; |
264 req->chan = strdup(channel); | 330 req->chan = irc_util_strdup(channel); |
265 req->nickname = strdup(user.nickname); | 331 req->nickname = irc_util_strdup(user.nickname); |
266 req->origin = strdup(origin); | 332 req->origin = irc_util_strdup(origin); |
267 | 333 |
268 curl_easy_setopt(req->curl, CURLOPT_URL, link); | 334 curl_easy_setopt(req->curl, CURLOPT_URL, link); |
269 curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); | 335 curl_easy_setopt(req->curl, CURLOPT_FOLLOWLOCATION, 1L); |
270 curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback); | 336 curl_easy_setopt(req->curl, CURLOPT_WRITEFUNCTION, callback); |
271 curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); | 337 curl_easy_setopt(req->curl, CURLOPT_WRITEDATA, req); |
272 curl_easy_setopt(req->curl, CURLOPT_TIMEOUT, timeout); | 338 curl_easy_setopt(req->curl, CURLOPT_TIMEOUT, timeout); |
273 | 339 curl_easy_setopt(req->curl, CURLOPT_NOSIGNAL, 1L); |
274 return req; | 340 curl_multi_add_handle(req->multi, req->curl); |
341 | |
342 /* | |
343 * Try immediately to create a socket, otherwise we would poll for a | |
344 * long time before trying to fetch data. | |
345 */ | |
346 if (curl_multi_perform(req->multi, &pending) != CURLM_OK) { | |
347 req_finish(req); | |
348 return; | |
349 } | |
350 | |
351 req->pollable.data = req; | |
352 req->pollable.fd = pollable_fd; | |
353 req->pollable.want = pollable_want; | |
354 req->pollable.sync = pollable_sync; | |
355 req->pollable.finish = pollable_finish; | |
356 irc_bot_pollable_add(&req->pollable); | |
357 | |
358 return; | |
275 | 359 |
276 enomem: | 360 enomem: |
361 free(link); | |
362 req_finish(req); | |
363 | |
277 errno = ENOMEM; | 364 errno = ENOMEM; |
278 req_finish(req); | |
279 | |
280 return NULL; | |
281 } | |
282 | |
283 static void | |
284 req_start(struct req *req) | |
285 { | |
286 if (pthread_create(&req->thr, NULL, routine, req) != 0) | |
287 req_finish(req); | |
288 } | 365 } |
289 | 366 |
290 void | 367 void |
291 links_event(const struct irc_event *ev) | 368 links_event(const struct irc_event *ev) |
292 { | 369 { |
293 struct req *req; | 370 char *loc, *end; |
294 char *loc, *link, *end; | |
295 | 371 |
296 if (ev->type != IRC_EVENT_MESSAGE) | 372 if (ev->type != IRC_EVENT_MESSAGE) |
297 return; | 373 return; |
298 | 374 |
299 /* Parse link. */ | 375 /* Parse link. */ |
303 | 379 |
304 /* Compute end to allocate only what's needed. */ | 380 /* Compute end to allocate only what's needed. */ |
305 for (end = loc; *end && !isspace(*end); ) | 381 for (end = loc; *end && !isspace(*end); ) |
306 ++end; | 382 ++end; |
307 | 383 |
308 link = irc_util_strndup(loc, end - loc); | 384 req(ev->server, ev->message.origin, ev->message.channel, |
309 | 385 irc_util_strndup(loc, end - loc)); |
310 /* If the function fails, link is free'd anyway. */ | |
311 if (!(req = req_new(ev->server, ev->message.origin, ev->message.channel, link))) | |
312 return; | |
313 | |
314 req_start(req); | |
315 } | 386 } |
316 | 387 |
317 void | 388 void |
318 links_set_template(const char *key, const char *value) | 389 links_set_template(const char *key, const char *value) |
319 { | 390 { |