Coverage Report

Created: 2026-04-29 07:01

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