Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/lib/cshutdn.c
2065 views
1
/***************************************************************************
2
* _ _ ____ _
3
* Project ___| | | | _ \| |
4
* / __| | | | |_) | |
5
* | (__| |_| | _ <| |___
6
* \___|\___/|_| \_\_____|
7
*
8
* Copyright (C) Linus Nielsen Feltzing, <[email protected]>
9
* Copyright (C) Daniel Stenberg, <[email protected]>, et al.
10
*
11
* This software is licensed as described in the file COPYING, which
12
* you should have received as part of this distribution. The terms
13
* are also available at https://curl.se/docs/copyright.html.
14
*
15
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
16
* copies of the Software, and permit persons to whom the Software is
17
* furnished to do so, under the terms of the COPYING file.
18
*
19
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20
* KIND, either express or implied.
21
*
22
* SPDX-License-Identifier: curl
23
*
24
***************************************************************************/
25
26
#include "curl_setup.h"
27
28
#include <curl/curl.h>
29
30
#include "urldata.h"
31
#include "url.h"
32
#include "cfilters.h"
33
#include "progress.h"
34
#include "multiif.h"
35
#include "multi_ev.h"
36
#include "sendf.h"
37
#include "cshutdn.h"
38
#include "http_negotiate.h"
39
#include "http_ntlm.h"
40
#include "sigpipe.h"
41
#include "connect.h"
42
#include "select.h"
43
#include "strcase.h"
44
#include "curlx/strparse.h"
45
46
/* The last 3 #include files should be in this order */
47
#include "curl_printf.h"
48
#include "curl_memory.h"
49
#include "memdebug.h"
50
51
52
static void cshutdn_run_conn_handler(struct Curl_easy *data,
53
struct connectdata *conn)
54
{
55
if(!conn->bits.shutdown_handler) {
56
57
/* Cleanup NTLM connection-related data */
58
Curl_http_auth_cleanup_ntlm(conn);
59
60
/* Cleanup NEGOTIATE connection-related data */
61
Curl_http_auth_cleanup_negotiate(conn);
62
63
if(conn->handler && conn->handler->disconnect) {
64
/* Some disconnect handlers do a blocking wait on server responses.
65
* FTP/IMAP/SMTP and SFTP are among them. When using the internal
66
* handle, set an overall short timeout so we do not hang for the
67
* default 120 seconds. */
68
if(data->state.internal) {
69
data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS;
70
(void)Curl_pgrsTime(data, TIMER_STARTOP);
71
}
72
73
/* This is set if protocol-specific cleanups should be made */
74
DEBUGF(infof(data, "connection #%" FMT_OFF_T
75
", shutdown protocol handler (aborted=%d)",
76
conn->connection_id, conn->bits.aborted));
77
/* There are protocol handlers that block on retrieving
78
* server responses here (FTP). Set a short timeout. */
79
conn->handler->disconnect(data, conn, conn->bits.aborted);
80
}
81
82
conn->bits.shutdown_handler = TRUE;
83
}
84
}
85
86
static void cshutdn_run_once(struct Curl_easy *data,
87
struct connectdata *conn,
88
bool *done)
89
{
90
CURLcode r1, r2;
91
bool done1, done2;
92
93
/* We expect to be attached when called */
94
DEBUGASSERT(data->conn == conn);
95
96
cshutdn_run_conn_handler(data, conn);
97
98
if(conn->bits.shutdown_filters) {
99
*done = TRUE;
100
return;
101
}
102
103
if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
104
r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
105
else {
106
r1 = CURLE_OK;
107
done1 = TRUE;
108
}
109
110
if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
111
r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
112
else {
113
r2 = CURLE_OK;
114
done2 = TRUE;
115
}
116
117
/* we are done when any failed or both report success */
118
*done = (r1 || r2 || (done1 && done2));
119
if(*done)
120
conn->bits.shutdown_filters = TRUE;
121
}
122
123
void Curl_cshutdn_run_once(struct Curl_easy *data,
124
struct connectdata *conn,
125
bool *done)
126
{
127
DEBUGASSERT(!data->conn);
128
Curl_attach_connection(data, conn);
129
cshutdn_run_once(data, conn, done);
130
CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
131
Curl_detach_connection(data);
132
}
133
134
135
void Curl_cshutdn_terminate(struct Curl_easy *data,
136
struct connectdata *conn,
137
bool do_shutdown)
138
{
139
struct Curl_easy *admin = data;
140
bool done;
141
142
/* there must be a connection to close */
143
DEBUGASSERT(conn);
144
/* it must be removed from the connection pool */
145
DEBUGASSERT(!conn->bits.in_cpool);
146
/* the transfer must be detached from the connection */
147
DEBUGASSERT(data && !data->conn);
148
149
/* If we can obtain an internal admin handle, use that to attach
150
* and terminate the connection. Some protocol will try to mess with
151
* `data` during shutdown and we do not want that with a `data` from
152
* the application. */
153
if(data->multi && data->multi->admin)
154
admin = data->multi->admin;
155
156
Curl_attach_connection(admin, conn);
157
158
cshutdn_run_conn_handler(admin, conn);
159
if(do_shutdown) {
160
/* Make a last attempt to shutdown handlers and filters, if
161
* not done so already. */
162
cshutdn_run_once(admin, conn, &done);
163
}
164
CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T,
165
conn->bits.shutdown_filters ? "" : "force ",
166
conn->connection_id);
167
Curl_conn_close(admin, SECONDARYSOCKET);
168
Curl_conn_close(admin, FIRSTSOCKET);
169
Curl_detach_connection(admin);
170
171
if(data->multi)
172
Curl_multi_ev_conn_done(data->multi, data, conn);
173
Curl_conn_free(admin, conn);
174
175
if(data->multi) {
176
CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
177
Curl_multi_connchanged(data->multi);
178
}
179
}
180
181
static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn,
182
struct Curl_easy *data,
183
const char *destination)
184
{
185
struct Curl_llist_node *e;
186
struct connectdata *conn;
187
188
e = Curl_llist_head(&cshutdn->list);
189
while(e) {
190
conn = Curl_node_elem(e);
191
if(!destination || !strcmp(destination, conn->destination))
192
break;
193
e = Curl_node_next(e);
194
}
195
196
if(e) {
197
SIGPIPE_VARIABLE(pipe_st);
198
conn = Curl_node_elem(e);
199
Curl_node_remove(e);
200
sigpipe_init(&pipe_st);
201
sigpipe_apply(data, &pipe_st);
202
Curl_cshutdn_terminate(data, conn, FALSE);
203
sigpipe_restore(&pipe_st);
204
return TRUE;
205
}
206
return FALSE;
207
}
208
209
bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
210
const char *destination)
211
{
212
if(data && data->multi) {
213
struct cshutdn *csd = &data->multi->cshutdn;
214
return cshutdn_destroy_oldest(csd, data, destination);
215
}
216
return FALSE;
217
}
218
219
#define NUM_POLLS_ON_STACK 10
220
221
static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
222
struct Curl_easy *data,
223
int timeout_ms)
224
{
225
struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
226
struct curl_pollfds cpfds;
227
CURLcode result;
228
229
Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
230
231
result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
232
if(result)
233
goto out;
234
235
Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
236
237
out:
238
Curl_pollfds_cleanup(&cpfds);
239
return result;
240
}
241
242
243
static void cshutdn_perform(struct cshutdn *cshutdn,
244
struct Curl_easy *data)
245
{
246
struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
247
struct Curl_llist_node *enext;
248
struct connectdata *conn;
249
struct curltime *nowp = NULL;
250
struct curltime now;
251
timediff_t next_expire_ms = 0, ms;
252
bool done;
253
254
if(!e)
255
return;
256
257
CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
258
Curl_llist_count(&cshutdn->list));
259
while(e) {
260
enext = Curl_node_next(e);
261
conn = Curl_node_elem(e);
262
Curl_cshutdn_run_once(data, conn, &done);
263
if(done) {
264
Curl_node_remove(e);
265
Curl_cshutdn_terminate(data, conn, FALSE);
266
}
267
else {
268
/* idata has one timer list, but maybe more than one connection.
269
* Set EXPIRE_SHUTDOWN to the smallest time left for all. */
270
if(!nowp) {
271
now = curlx_now();
272
nowp = &now;
273
}
274
ms = Curl_conn_shutdown_timeleft(conn, nowp);
275
if(ms && ms < next_expire_ms)
276
next_expire_ms = ms;
277
}
278
e = enext;
279
}
280
281
if(next_expire_ms)
282
Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN);
283
}
284
285
286
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
287
struct Curl_easy *data,
288
int timeout_ms)
289
{
290
struct curltime started = curlx_now();
291
struct Curl_llist_node *e;
292
SIGPIPE_VARIABLE(pipe_st);
293
294
DEBUGASSERT(cshutdn);
295
DEBUGASSERT(data);
296
297
CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
298
sigpipe_init(&pipe_st);
299
sigpipe_apply(data, &pipe_st);
300
301
while(Curl_llist_head(&cshutdn->list)) {
302
timediff_t timespent;
303
int remain_ms;
304
305
cshutdn_perform(cshutdn, data);
306
307
if(!Curl_llist_head(&cshutdn->list)) {
308
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
309
break;
310
}
311
312
/* wait for activity, timeout or "nothing" */
313
timespent = curlx_timediff(curlx_now(), started);
314
if(timespent >= (timediff_t)timeout_ms) {
315
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
316
(timeout_ms > 0) ? "timeout" : "best effort done");
317
break;
318
}
319
320
remain_ms = timeout_ms - (int)timespent;
321
if(cshutdn_wait(cshutdn, data, remain_ms)) {
322
CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
323
break;
324
}
325
}
326
327
/* Terminate any remaining. */
328
e = Curl_llist_head(&cshutdn->list);
329
while(e) {
330
struct connectdata *conn = Curl_node_elem(e);
331
Curl_node_remove(e);
332
Curl_cshutdn_terminate(data, conn, FALSE);
333
e = Curl_llist_head(&cshutdn->list);
334
}
335
DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
336
337
sigpipe_restore(&pipe_st);
338
}
339
340
341
int Curl_cshutdn_init(struct cshutdn *cshutdn,
342
struct Curl_multi *multi)
343
{
344
DEBUGASSERT(multi);
345
cshutdn->multi = multi;
346
Curl_llist_init(&cshutdn->list, NULL);
347
cshutdn->initialised = TRUE;
348
return 0; /* good */
349
}
350
351
352
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
353
struct Curl_easy *data)
354
{
355
if(cshutdn->initialised && data) {
356
int timeout_ms = 0;
357
/* Just for testing, run graceful shutdown */
358
#ifdef DEBUGBUILD
359
{
360
const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
361
if(p) {
362
curl_off_t l;
363
if(!curlx_str_number(&p, &l, INT_MAX))
364
timeout_ms = (int)l;
365
}
366
}
367
#endif
368
369
CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
370
Curl_llist_count(&cshutdn->list), timeout_ms);
371
cshutdn_terminate_all(cshutdn, data, timeout_ms);
372
}
373
cshutdn->multi = NULL;
374
}
375
376
size_t Curl_cshutdn_count(struct Curl_easy *data)
377
{
378
if(data && data->multi) {
379
struct cshutdn *csd = &data->multi->cshutdn;
380
return Curl_llist_count(&csd->list);
381
}
382
return 0;
383
}
384
385
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
386
const char *destination)
387
{
388
if(data && data->multi) {
389
struct cshutdn *csd = &data->multi->cshutdn;
390
size_t n = 0;
391
struct Curl_llist_node *e = Curl_llist_head(&csd->list);
392
while(e) {
393
struct connectdata *conn = Curl_node_elem(e);
394
if(!strcmp(destination, conn->destination))
395
++n;
396
e = Curl_node_next(e);
397
}
398
return n;
399
}
400
return 0;
401
}
402
403
404
static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
405
struct Curl_easy *data,
406
struct connectdata *conn)
407
{
408
CURLMcode mresult;
409
410
DEBUGASSERT(cshutdn);
411
DEBUGASSERT(cshutdn->multi->socket_cb);
412
413
Curl_attach_connection(data, conn);
414
mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
415
Curl_detach_connection(data);
416
return mresult;
417
}
418
419
420
void Curl_cshutdn_add(struct cshutdn *cshutdn,
421
struct connectdata *conn,
422
size_t conns_in_pool)
423
{
424
struct Curl_easy *data = cshutdn->multi->admin;
425
size_t max_total = (cshutdn->multi->max_total_connections > 0) ?
426
(size_t)cshutdn->multi->max_total_connections : 0;
427
428
/* Add the connection to our shutdown list for non-blocking shutdown
429
* during multi processing. */
430
if(max_total > 0 && (max_total <=
431
(conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
432
CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
433
"due to connection limit of %zu", max_total);
434
cshutdn_destroy_oldest(cshutdn, data, NULL);
435
}
436
437
if(cshutdn->multi->socket_cb) {
438
if(cshutdn_update_ev(cshutdn, data, conn)) {
439
CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
440
FMT_OFF_T, conn->connection_id);
441
Curl_cshutdn_terminate(data, conn, FALSE);
442
return;
443
}
444
}
445
446
Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
447
CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
448
" to shutdowns, now %zu conns in shutdown",
449
conn->connection_id, Curl_llist_count(&cshutdn->list));
450
}
451
452
453
static void cshutdn_multi_socket(struct cshutdn *cshutdn,
454
struct Curl_easy *data,
455
curl_socket_t s)
456
{
457
struct Curl_llist_node *e;
458
struct connectdata *conn;
459
bool done;
460
461
DEBUGASSERT(cshutdn->multi->socket_cb);
462
e = Curl_llist_head(&cshutdn->list);
463
while(e) {
464
conn = Curl_node_elem(e);
465
if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
466
Curl_cshutdn_run_once(data, conn, &done);
467
if(done || cshutdn_update_ev(cshutdn, data, conn)) {
468
Curl_node_remove(e);
469
Curl_cshutdn_terminate(data, conn, FALSE);
470
}
471
break;
472
}
473
e = Curl_node_next(e);
474
}
475
}
476
477
478
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
479
struct Curl_easy *data,
480
curl_socket_t s)
481
{
482
if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb))
483
cshutdn_perform(cshutdn, data);
484
else
485
cshutdn_multi_socket(cshutdn, data, s);
486
}
487
488
/* return fd_set info about the shutdown connections */
489
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
490
struct Curl_easy *data,
491
fd_set *read_fd_set, fd_set *write_fd_set,
492
int *maxfd)
493
{
494
if(Curl_llist_head(&cshutdn->list)) {
495
struct Curl_llist_node *e;
496
497
for(e = Curl_llist_head(&cshutdn->list); e;
498
e = Curl_node_next(e)) {
499
struct easy_pollset ps;
500
unsigned int i;
501
struct connectdata *conn = Curl_node_elem(e);
502
memset(&ps, 0, sizeof(ps));
503
Curl_attach_connection(data, conn);
504
Curl_conn_adjust_pollset(data, conn, &ps);
505
Curl_detach_connection(data);
506
507
for(i = 0; i < ps.num; i++) {
508
#if defined(__DJGPP__)
509
#pragma GCC diagnostic push
510
#pragma GCC diagnostic ignored "-Warith-conversion"
511
#endif
512
if(ps.actions[i] & CURL_POLL_IN)
513
FD_SET(ps.sockets[i], read_fd_set);
514
if(ps.actions[i] & CURL_POLL_OUT)
515
FD_SET(ps.sockets[i], write_fd_set);
516
#if defined(__DJGPP__)
517
#pragma GCC diagnostic pop
518
#endif
519
if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
520
((int)ps.sockets[i] > *maxfd))
521
*maxfd = (int)ps.sockets[i];
522
}
523
}
524
}
525
}
526
527
/* return information about the shutdown connections */
528
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
529
struct Curl_easy *data,
530
struct Curl_waitfds *cwfds)
531
{
532
unsigned int need = 0;
533
534
if(Curl_llist_head(&cshutdn->list)) {
535
struct Curl_llist_node *e;
536
struct easy_pollset ps;
537
struct connectdata *conn;
538
539
for(e = Curl_llist_head(&cshutdn->list); e;
540
e = Curl_node_next(e)) {
541
conn = Curl_node_elem(e);
542
memset(&ps, 0, sizeof(ps));
543
Curl_attach_connection(data, conn);
544
Curl_conn_adjust_pollset(data, conn, &ps);
545
Curl_detach_connection(data);
546
547
need += Curl_waitfds_add_ps(cwfds, &ps);
548
}
549
}
550
return need;
551
}
552
553
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
554
struct Curl_easy *data,
555
struct curl_pollfds *cpfds)
556
{
557
CURLcode result = CURLE_OK;
558
559
if(Curl_llist_head(&cshutdn->list)) {
560
struct Curl_llist_node *e;
561
struct easy_pollset ps;
562
struct connectdata *conn;
563
564
for(e = Curl_llist_head(&cshutdn->list); e;
565
e = Curl_node_next(e)) {
566
conn = Curl_node_elem(e);
567
memset(&ps, 0, sizeof(ps));
568
Curl_attach_connection(data, conn);
569
Curl_conn_adjust_pollset(data, conn, &ps);
570
Curl_detach_connection(data);
571
572
result = Curl_pollfds_add_ps(cpfds, &ps);
573
if(result) {
574
Curl_pollfds_cleanup(cpfds);
575
goto out;
576
}
577
}
578
}
579
out:
580
return result;
581
}
582
583