Coverage Report

Created: 2026-09-04 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/cshutdn.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
9
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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
#include "curl_setup.h"
26
27
#include "urldata.h"
28
#include "url.h"
29
#include "cfilters.h"
30
#include "progress.h"
31
#include "multiif.h"
32
#include "multi_ev.h"
33
#include "curl_trc.h"
34
#include "cshutdn.h"
35
#include "sigpipe.h"
36
#include "connect.h"
37
#include "select.h"
38
#include "curlx/strparse.h"
39
40
41
struct cshutdn *Curl_cshutdn_get(struct Curl_easy *data)
42
43.4k
{
43
43.4k
  if(data && data->multi)
44
43.4k
    return &data->multi->cshutdn;
45
0
  return NULL;
46
43.4k
}
47
48
static void cshutdn_run_conn_handler(struct Curl_easy *data,
49
                                     struct connectdata *conn)
50
34.1k
{
51
34.1k
  if(!conn->bits.shutdown_handler) {
52
53
21.7k
    if(conn->scheme && conn->scheme->run->disconnect) {
54
      /* Some disconnect handlers do a blocking wait on server responses.
55
       * FTP/IMAP/SMTP and SFTP are among them. When using the internal
56
       * handle, set an overall short timeout so we do not hang for the
57
       * default 120 seconds. */
58
0
      if(data->state.internal) {
59
0
        data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS;
60
0
        Curl_pgrsTime(data, TIMER_STARTOP);
61
0
      }
62
63
      /* This is set if protocol-specific cleanups should be made */
64
0
      DEBUGF(infof(data, "connection #%" FMT_OFF_T
65
0
                   ", shutdown protocol handler (aborted=%d)",
66
0
                   conn->connection_id, conn->bits.aborted));
67
      /* There are protocol handlers that block on retrieving
68
       * server responses here (FTP). Set a short timeout. */
69
0
      conn->scheme->run->disconnect(data, conn, (bool)conn->bits.aborted);
70
0
    }
71
72
21.7k
    conn->bits.shutdown_handler = TRUE;
73
21.7k
  }
74
34.1k
}
75
76
static void cshutdn_run_once(struct Curl_easy *data,
77
                             struct connectdata *conn,
78
                             bool *done)
79
12.4k
{
80
12.4k
  CURLcode r1, r2;
81
12.4k
  bool done1, done2;
82
83
  /* We expect to be attached when called */
84
12.4k
  DEBUGASSERT(data->conn == conn);
85
86
12.4k
  if(!Curl_shutdown_started(conn, FIRSTSOCKET)) {
87
12.3k
    Curl_shutdown_start(data, FIRSTSOCKET, 0);
88
12.3k
  }
89
90
12.4k
  cshutdn_run_conn_handler(data, conn);
91
92
12.4k
  if(conn->bits.shutdown_filters) {
93
0
    *done = TRUE;
94
0
    return;
95
0
  }
96
97
12.4k
  if(!conn->bits.connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
98
12.4k
    r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
99
0
  else {
100
0
    r1 = CURLE_OK;
101
0
    done1 = TRUE;
102
0
  }
103
104
12.4k
  if(!conn->bits.connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
105
0
    r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
106
12.4k
  else {
107
12.4k
    r2 = CURLE_OK;
108
12.4k
    done2 = TRUE;
109
12.4k
  }
110
111
  /* we are done when any failed or both report success */
112
12.4k
  *done = (r1 || r2 || (done1 && done2));
113
12.4k
  if(*done)
114
12.3k
    conn->bits.shutdown_filters = TRUE;
115
12.4k
}
116
117
void Curl_conn_shutdown_once(struct Curl_easy *admin,
118
                           struct connectdata *conn,
119
                           bool *done)
120
12.4k
{
121
12.4k
  DEBUGASSERT(!admin->conn);
122
12.4k
  DEBUGASSERT(!admin->mid);
123
12.4k
  Curl_attach_connection(admin, conn, FALSE);
124
12.4k
  cshutdn_run_once(admin, conn, done);
125
12.4k
  CURL_TRC_M(admin, "[SHUTDOWN] shutdown, done=%d", *done);
126
12.4k
  Curl_detach_connection(admin);
127
12.4k
}
128
129
void Curl_conn_terminate(struct Curl_easy *admin,
130
                         struct connectdata *conn,
131
                         bool do_shutdown)
132
21.7k
{
133
21.7k
  bool done;
134
135
  /* there must be a connection to close */
136
21.7k
  DEBUGASSERT(conn);
137
  /* it must be removed from the connection pool */
138
21.7k
  DEBUGASSERT(!conn->bits.in_cpool);
139
  /* the transfer must be detached from the connection */
140
21.7k
  DEBUGASSERT(admin && !admin->conn);
141
21.7k
  DEBUGASSERT(!admin->mid);
142
143
21.7k
  Curl_attach_connection(admin, conn, FALSE);
144
145
21.7k
  cshutdn_run_conn_handler(admin, conn);
146
21.7k
  if(do_shutdown) {
147
    /* Make a last attempt to shutdown handlers and filters, if
148
     * not done so already. */
149
0
    cshutdn_run_once(admin, conn, &done);
150
0
  }
151
21.7k
  CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T,
152
21.7k
             conn->bits.shutdown_filters ? "" : "force ",
153
21.7k
             conn->connection_id);
154
21.7k
  Curl_conn_cf_discard_all(admin, conn, SECONDARYSOCKET);
155
21.7k
  Curl_conn_cf_discard_all(admin, conn, FIRSTSOCKET);
156
21.7k
  Curl_detach_connection(admin);
157
158
21.7k
  if(admin->multi)
159
21.7k
    Curl_multi_ev_conn_done(admin->multi, admin, conn);
160
21.7k
  Curl_conn_free(admin, conn);
161
162
21.7k
  if(admin->multi) {
163
21.7k
    CURL_TRC_M(admin, "[SHUTDOWN] trigger multi connchanged");
164
21.7k
    Curl_multi_connchanged(admin->multi);
165
21.7k
  }
166
21.7k
}
167
168
static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn,
169
                                   const char *destination)
170
0
{
171
0
  struct Curl_llist_node *e;
172
0
  struct connectdata *conn;
173
174
0
  e = Curl_llist_head(&cshutdn->list);
175
0
  while(e) {
176
0
    conn = Curl_node_elem(e);
177
0
    if(!destination || !strcmp(destination, conn->destination))
178
0
      break;
179
0
    e = Curl_node_next(e);
180
0
  }
181
182
0
  if(e) {
183
0
    struct Curl_sigpipe_ctx sigpipe_ctx;
184
0
    conn = Curl_node_elem(e);
185
0
    Curl_node_remove(e);
186
0
    sigpipe_init(&sigpipe_ctx);
187
0
    sigpipe_apply(cshutdn->multi->admin, &sigpipe_ctx);
188
0
    Curl_conn_terminate(cshutdn->multi->admin, conn, FALSE);
189
0
    sigpipe_restore(&sigpipe_ctx);
190
0
    return TRUE;
191
0
  }
192
0
  return FALSE;
193
0
}
194
195
bool Curl_cshutdn_close_oldest(struct cshutdn *cshutdn,
196
                               const char *destination)
197
0
{
198
0
  if(cshutdn) {
199
0
    return cshutdn_destroy_oldest(cshutdn, destination);
200
0
  }
201
0
  return FALSE;
202
0
}
203
204
0
#define NUM_POLLS_ON_STACK 10
205
206
static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
207
                             int timeout_ms)
208
0
{
209
0
  struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
210
0
  struct curl_pollfds cpfds;
211
0
  CURLcode result;
212
213
0
  Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
214
215
0
  result = Curl_cshutdn_add_pollfds(cshutdn, &cpfds);
216
0
  if(result)
217
0
    goto out;
218
219
0
  Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
220
221
0
out:
222
0
  Curl_pollfds_cleanup(&cpfds);
223
0
  return result;
224
0
}
225
226
static void cshutdn_perform(struct cshutdn *cshutdn,
227
                            struct Curl_sigpipe_ctx *sigpipe_ctx)
228
31.9k
{
229
31.9k
  struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
230
31.9k
  struct Curl_llist_node *enext;
231
31.9k
  struct Curl_easy *admin = cshutdn->multi->admin;
232
31.9k
  struct connectdata *conn;
233
31.9k
  timediff_t next_expire_ms = 0, ms;
234
31.9k
  bool done;
235
236
31.9k
  if(!e)
237
31.8k
    return;
238
239
42
  CURL_TRC_M(admin, "[SHUTDOWN] perform on %zu connections",
240
42
             Curl_llist_count(&cshutdn->list));
241
42
  sigpipe_apply(admin, sigpipe_ctx);
242
85
  while(e) {
243
43
    enext = Curl_node_next(e);
244
43
    conn = Curl_node_elem(e);
245
43
    Curl_conn_shutdown_once(admin, conn, &done);
246
43
    if(done) {
247
9
      Curl_node_remove(e);
248
9
      Curl_conn_terminate(admin, conn, FALSE);
249
9
    }
250
34
    else {
251
      /* idata has one timer list, but maybe more than one connection.
252
       * Set EXPIRE_SHUTDOWN to the smallest time left for all. */
253
34
      ms = Curl_conn_shutdown_timeleft(admin, conn);
254
34
      if(ms && (!next_expire_ms || (ms < next_expire_ms)))
255
33
        next_expire_ms = ms;
256
34
    }
257
43
    e = enext;
258
43
  }
259
260
42
  if(next_expire_ms)
261
33
    Curl_expire(admin, next_expire_ms, EXPIRE_SHUTDOWN);
262
42
}
263
264
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
265
                                  int timeout_ms)
266
20.8k
{
267
20.8k
  struct Curl_easy *admin = cshutdn->multi->admin;
268
20.8k
  struct curltime started = *Curl_pgrs_now(admin);
269
20.8k
  struct Curl_llist_node *e;
270
20.8k
  struct Curl_sigpipe_ctx sigpipe_ctx;
271
272
20.8k
  CURL_TRC_M(admin, "[SHUTDOWN] shutdown all");
273
20.8k
  sigpipe_init(&sigpipe_ctx);
274
275
20.8k
  while(Curl_llist_head(&cshutdn->list)) {
276
22
    timediff_t spent_ms;
277
22
    int remain_ms;
278
279
22
    cshutdn_perform(cshutdn, &sigpipe_ctx);
280
281
22
    if(!Curl_llist_head(&cshutdn->list)) {
282
5
      CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished cleanly");
283
5
      break;
284
5
    }
285
286
    /* wait for activity, timeout or "nothing" */
287
17
    spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(admin), &started);
288
17
    if(spent_ms >= (timediff_t)timeout_ms) {
289
17
      CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, %s",
290
17
                 (timeout_ms > 0) ? "timeout" : "best effort done");
291
17
      break;
292
17
    }
293
294
0
    remain_ms = timeout_ms - (int)spent_ms;
295
0
    if(cshutdn_wait(cshutdn, remain_ms)) {
296
0
      CURL_TRC_M(admin, "[SHUTDOWN] shutdown finished, aborted");
297
0
      break;
298
0
    }
299
0
  }
300
301
  /* Terminate any remaining. */
302
20.8k
  e = Curl_llist_head(&cshutdn->list);
303
20.8k
  while(e) {
304
18
    struct connectdata *conn = Curl_node_elem(e);
305
18
    Curl_node_remove(e);
306
18
    Curl_conn_terminate(admin, conn, FALSE);
307
18
    e = Curl_llist_head(&cshutdn->list);
308
18
  }
309
20.8k
  DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
310
311
20.8k
  sigpipe_restore(&sigpipe_ctx);
312
20.8k
}
313
314
int Curl_cshutdn_init(struct cshutdn *cshutdn,
315
                      struct Curl_multi *multi)
316
20.8k
{
317
20.8k
  DEBUGASSERT(multi);
318
20.8k
  cshutdn->multi = multi;
319
20.8k
  Curl_llist_init(&cshutdn->list, NULL);
320
20.8k
  cshutdn->initialized = TRUE;
321
20.8k
  return 0; /* good */
322
20.8k
}
323
324
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
325
                          struct Curl_easy *admin)
326
20.8k
{
327
20.8k
  if(cshutdn->initialized && admin) {
328
20.8k
    int timeout_ms = 0;
329
    /* for testing, run graceful shutdown */
330
20.8k
#ifdef DEBUGBUILD
331
20.8k
    DEBUGASSERT(!admin->mid);
332
20.8k
    {
333
20.8k
      const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
334
20.8k
      if(p) {
335
0
        curl_off_t l;
336
0
        if(!curlx_str_number(&p, &l, INT_MAX))
337
0
          timeout_ms = (int)l;
338
0
      }
339
20.8k
    }
340
20.8k
#endif
341
342
20.8k
    CURL_TRC_M(admin, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
343
20.8k
               Curl_llist_count(&cshutdn->list), timeout_ms);
344
20.8k
    cshutdn_terminate_all(cshutdn, timeout_ms);
345
20.8k
  }
346
20.8k
  cshutdn->multi = NULL;
347
20.8k
  cshutdn->initialized = FALSE;
348
20.8k
}
349
350
size_t Curl_cshutdn_count(struct cshutdn *cshutdn)
351
0
{
352
0
  if(cshutdn) {
353
0
    return Curl_llist_count(&cshutdn->list);
354
0
  }
355
0
  return 0;
356
0
}
357
358
size_t Curl_cshutdn_dest_count(struct cshutdn *cshutdn,
359
                               const char *destination)
360
0
{
361
0
  if(cshutdn) {
362
0
    size_t n = 0;
363
0
    struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
364
0
    while(e) {
365
0
      struct connectdata *conn = Curl_node_elem(e);
366
0
      if(!strcmp(destination, conn->destination))
367
0
        ++n;
368
0
      e = Curl_node_next(e);
369
0
    }
370
0
    return n;
371
0
  }
372
0
  return 0;
373
0
}
374
375
static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn,
376
                                   struct connectdata *conn)
377
0
{
378
0
  struct Curl_easy *admin = cshutdn->multi->admin;
379
0
  CURLMcode mresult;
380
381
0
  DEBUGASSERT(cshutdn);
382
0
  DEBUGASSERT(cshutdn->multi->socket_cb);
383
384
0
  Curl_attach_connection(admin, conn, FALSE);
385
0
  mresult = Curl_multi_ev_assess_conn(cshutdn->multi, admin, conn);
386
0
  Curl_detach_connection(admin);
387
0
  return mresult;
388
0
}
389
390
void Curl_cshutdn_add(struct cshutdn *cshutdn,
391
                      struct connectdata *conn,
392
                      size_t conns_in_pool)
393
27
{
394
27
  struct Curl_easy *admin = cshutdn->multi->admin;
395
27
  size_t max_total = cshutdn->multi->max_total_connections;
396
397
  /* Add the connection to our shutdown list for non-blocking shutdown
398
   * during multi processing. */
399
27
  if(max_total > 0 &&
400
0
     (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
401
0
    CURL_TRC_M(admin, "[SHUTDOWN] discarding oldest shutdown connection "
402
0
               "due to connection limit of %zu", max_total);
403
0
    cshutdn_destroy_oldest(cshutdn, NULL);
404
0
  }
405
406
27
  if(cshutdn->multi->socket_cb && cshutdn_update_ev(cshutdn, conn)) {
407
0
    CURL_TRC_M(admin, "[SHUTDOWN] update events failed, discarding #%"
408
0
               FMT_OFF_T, conn->connection_id);
409
0
    Curl_conn_terminate(admin, conn, FALSE);
410
0
    return;
411
0
  }
412
413
27
  Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
414
27
  CURL_TRC_M(admin, "[SHUTDOWN] added #%" FMT_OFF_T
415
27
             " to shutdowns, now %zu conns in shutdown",
416
27
             conn->connection_id, Curl_llist_count(&cshutdn->list));
417
27
}
418
419
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
420
                          struct Curl_sigpipe_ctx *sigpipe_ctx)
421
31.8k
{
422
31.8k
  cshutdn_perform(cshutdn, sigpipe_ctx);
423
31.8k
}
424
425
/* return fd_set info about the shutdown connections */
426
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
427
                         fd_set *read_fd_set, fd_set *write_fd_set,
428
                         int *maxfd)
429
11.4k
{
430
11.4k
  if(Curl_llist_head(&cshutdn->list)) {
431
25
    struct Curl_easy *admin = cshutdn->multi->admin;
432
25
    struct Curl_llist_node *e;
433
25
    struct easy_pollset ps;
434
435
25
    Curl_pollset_init(&ps);
436
50
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
437
25
      unsigned int i;
438
25
      struct connectdata *conn = Curl_node_elem(e);
439
25
      CURLcode result;
440
441
25
      Curl_pollset_reset(&ps);
442
25
      Curl_attach_connection(admin, conn, FALSE);
443
25
      result = Curl_conn_adjust_pollset(admin, conn, &ps);
444
25
      Curl_detach_connection(admin);
445
446
25
      if(result)
447
0
        continue;
448
449
39
      for(i = 0; i < ps.n; i++) {
450
14
        curl_socket_t sock = ps.sockets[i];
451
14
        if(!FDSET_SOCK(sock))
452
0
          continue;
453
14
        if(ps.actions[i] & CURL_POLL_IN)
454
14
          FD_SET(sock, read_fd_set);
455
14
        if(ps.actions[i] & CURL_POLL_OUT)
456
14
          FD_SET(sock, write_fd_set);
457
14
        if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
458
14
           ((int)sock > *maxfd))
459
0
          *maxfd = (int)sock;
460
14
      }
461
25
    }
462
25
    Curl_pollset_cleanup(&ps);
463
25
  }
464
11.4k
}
465
466
/* return information about the shutdown connections */
467
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
468
                                      struct Curl_waitfds *cwfds)
469
0
{
470
0
  unsigned int need = 0;
471
472
0
  if(Curl_llist_head(&cshutdn->list)) {
473
0
    struct Curl_llist_node *e;
474
0
    struct Curl_easy *admin = cshutdn->multi->admin;
475
0
    struct easy_pollset ps;
476
0
    struct connectdata *conn;
477
0
    CURLcode result;
478
479
0
    Curl_pollset_init(&ps);
480
0
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
481
0
      conn = Curl_node_elem(e);
482
0
      Curl_pollset_reset(&ps);
483
0
      Curl_attach_connection(admin, conn, FALSE);
484
0
      result = Curl_conn_adjust_pollset(admin, conn, &ps);
485
0
      Curl_detach_connection(admin);
486
487
0
      if(!result)
488
0
        need += Curl_waitfds_add_ps(cwfds, &ps);
489
0
    }
490
0
    Curl_pollset_cleanup(&ps);
491
0
  }
492
0
  return need;
493
0
}
494
495
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
496
                                  struct curl_pollfds *cpfds)
497
0
{
498
0
  CURLcode result = CURLE_OK;
499
500
0
  if(Curl_llist_head(&cshutdn->list)) {
501
0
    struct Curl_llist_node *e;
502
0
    struct easy_pollset ps;
503
0
    struct Curl_easy *admin = cshutdn->multi->admin;
504
0
    struct connectdata *conn;
505
506
0
    Curl_pollset_init(&ps);
507
0
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
508
0
      conn = Curl_node_elem(e);
509
0
      Curl_pollset_reset(&ps);
510
0
      Curl_attach_connection(admin, conn, FALSE);
511
0
      result = Curl_conn_adjust_pollset(admin, conn, &ps);
512
0
      Curl_detach_connection(admin);
513
514
0
      if(!result)
515
0
        result = Curl_pollfds_add_ps(cpfds, &ps);
516
0
      if(result) {
517
0
        Curl_pollset_cleanup(&ps);
518
0
        Curl_pollfds_cleanup(cpfds);
519
0
        goto out;
520
0
      }
521
0
    }
522
0
    Curl_pollset_cleanup(&ps);
523
0
  }
524
0
out:
525
0
  return result;
526
0
}