Coverage Report

Created: 2026-03-12 06:35

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->handler && conn->handler->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
        (void)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->handler->disconnect(data, conn, 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
  cshutdn_run_conn_handler(data, conn);
80
81
0
  if(conn->bits.shutdown_filters) {
82
0
    *done = TRUE;
83
0
    return;
84
0
  }
85
86
0
  if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET))
87
0
    r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1);
88
0
  else {
89
0
    r1 = CURLE_OK;
90
0
    done1 = TRUE;
91
0
  }
92
93
0
  if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET))
94
0
    r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2);
95
0
  else {
96
0
    r2 = CURLE_OK;
97
0
    done2 = TRUE;
98
0
  }
99
100
  /* we are done when any failed or both report success */
101
0
  *done = (r1 || r2 || (done1 && done2));
102
0
  if(*done)
103
0
    conn->bits.shutdown_filters = TRUE;
104
0
}
105
106
void Curl_cshutdn_run_once(struct Curl_easy *data,
107
                           struct connectdata *conn,
108
                           bool *done)
109
0
{
110
0
  DEBUGASSERT(!data->conn);
111
0
  Curl_attach_connection(data, conn);
112
0
  cshutdn_run_once(data, conn, done);
113
0
  CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done);
114
0
  Curl_detach_connection(data);
115
0
}
116
117
void Curl_cshutdn_terminate(struct Curl_easy *data,
118
                            struct connectdata *conn,
119
                            bool do_shutdown)
120
0
{
121
0
  struct Curl_easy *admin = data;
122
0
  bool done;
123
124
  /* there must be a connection to close */
125
0
  DEBUGASSERT(conn);
126
  /* it must be removed from the connection pool */
127
0
  DEBUGASSERT(!conn->bits.in_cpool);
128
  /* the transfer must be detached from the connection */
129
0
  DEBUGASSERT(data && !data->conn);
130
131
  /* If we can obtain an internal admin handle, use that to attach
132
   * and terminate the connection. Some protocol will try to mess with
133
   * `data` during shutdown and we do not want that with a `data` from
134
   * the application. */
135
0
  if(data->multi && data->multi->admin)
136
0
    admin = data->multi->admin;
137
138
0
  Curl_attach_connection(admin, conn);
139
140
0
  cshutdn_run_conn_handler(admin, conn);
141
0
  if(do_shutdown) {
142
    /* Make a last attempt to shutdown handlers and filters, if
143
     * not done so already. */
144
0
    cshutdn_run_once(admin, conn, &done);
145
0
  }
146
0
  CURL_TRC_M(admin, "[SHUTDOWN] %sclosing connection #%" FMT_OFF_T,
147
0
             conn->bits.shutdown_filters ? "" : "force ",
148
0
             conn->connection_id);
149
0
  Curl_conn_close(admin, SECONDARYSOCKET);
150
0
  Curl_conn_close(admin, FIRSTSOCKET);
151
0
  Curl_detach_connection(admin);
152
153
0
  if(data->multi)
154
0
    Curl_multi_ev_conn_done(data->multi, data, conn);
155
0
  Curl_conn_free(admin, conn);
156
157
0
  if(data->multi) {
158
0
    CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged");
159
0
    Curl_multi_connchanged(data->multi);
160
0
  }
161
0
}
162
163
static bool cshutdn_destroy_oldest(struct cshutdn *cshutdn,
164
                                   struct Curl_easy *data,
165
                                   const char *destination)
166
0
{
167
0
  struct Curl_llist_node *e;
168
0
  struct connectdata *conn;
169
170
0
  e = Curl_llist_head(&cshutdn->list);
171
0
  while(e) {
172
0
    conn = Curl_node_elem(e);
173
0
    if(!destination || !strcmp(destination, conn->destination))
174
0
      break;
175
0
    e = Curl_node_next(e);
176
0
  }
177
178
0
  if(e) {
179
0
    SIGPIPE_VARIABLE(pipe_st);
180
0
    conn = Curl_node_elem(e);
181
0
    Curl_node_remove(e);
182
0
    sigpipe_init(&pipe_st);
183
0
    sigpipe_apply(data, &pipe_st);
184
0
    Curl_cshutdn_terminate(data, conn, FALSE);
185
0
    sigpipe_restore(&pipe_st);
186
0
    return TRUE;
187
0
  }
188
0
  return FALSE;
189
0
}
190
191
bool Curl_cshutdn_close_oldest(struct Curl_easy *data,
192
                               const char *destination)
193
0
{
194
0
  if(data && data->multi) {
195
0
    struct cshutdn *csd = &data->multi->cshutdn;
196
0
    return cshutdn_destroy_oldest(csd, data, destination);
197
0
  }
198
0
  return FALSE;
199
0
}
200
201
0
#define NUM_POLLS_ON_STACK 10
202
203
static CURLcode cshutdn_wait(struct cshutdn *cshutdn,
204
                             struct Curl_easy *data,
205
                             int timeout_ms)
206
0
{
207
0
  struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK];
208
0
  struct curl_pollfds cpfds;
209
0
  CURLcode result;
210
211
0
  Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK);
212
213
0
  result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds);
214
0
  if(result)
215
0
    goto out;
216
217
0
  Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000));
218
219
0
out:
220
0
  Curl_pollfds_cleanup(&cpfds);
221
0
  return result;
222
0
}
223
224
static void cshutdn_perform(struct cshutdn *cshutdn,
225
                            struct Curl_easy *data)
226
0
{
227
0
  struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list);
228
0
  struct Curl_llist_node *enext;
229
0
  struct connectdata *conn;
230
0
  timediff_t next_expire_ms = 0, ms;
231
0
  bool done;
232
233
0
  if(!e)
234
0
    return;
235
236
0
  CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections",
237
0
             Curl_llist_count(&cshutdn->list));
238
0
  while(e) {
239
0
    enext = Curl_node_next(e);
240
0
    conn = Curl_node_elem(e);
241
0
    Curl_cshutdn_run_once(data, conn, &done);
242
0
    if(done) {
243
0
      Curl_node_remove(e);
244
0
      Curl_cshutdn_terminate(data, conn, FALSE);
245
0
    }
246
0
    else {
247
      /* idata has one timer list, but maybe more than one connection.
248
       * Set EXPIRE_SHUTDOWN to the smallest time left for all. */
249
0
      ms = Curl_conn_shutdown_timeleft(data, conn);
250
0
      if(ms && ms < next_expire_ms)
251
0
        next_expire_ms = ms;
252
0
    }
253
0
    e = enext;
254
0
  }
255
256
0
  if(next_expire_ms)
257
0
    Curl_expire_ex(data, next_expire_ms, EXPIRE_SHUTDOWN);
258
0
}
259
260
static void cshutdn_terminate_all(struct cshutdn *cshutdn,
261
                                  struct Curl_easy *data,
262
                                  int timeout_ms)
263
0
{
264
0
  struct curltime started = *Curl_pgrs_now(data);
265
0
  struct Curl_llist_node *e;
266
0
  SIGPIPE_VARIABLE(pipe_st);
267
268
0
  DEBUGASSERT(cshutdn);
269
0
  DEBUGASSERT(data);
270
271
0
  CURL_TRC_M(data, "[SHUTDOWN] shutdown all");
272
0
  sigpipe_init(&pipe_st);
273
0
  sigpipe_apply(data, &pipe_st);
274
275
0
  while(Curl_llist_head(&cshutdn->list)) {
276
0
    timediff_t spent_ms;
277
0
    int remain_ms;
278
279
0
    cshutdn_perform(cshutdn, data);
280
281
0
    if(!Curl_llist_head(&cshutdn->list)) {
282
0
      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly");
283
0
      break;
284
0
    }
285
286
    /* wait for activity, timeout or "nothing" */
287
0
    spent_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &started);
288
0
    if(spent_ms >= (timediff_t)timeout_ms) {
289
0
      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s",
290
0
                 (timeout_ms > 0) ? "timeout" : "best effort done");
291
0
      break;
292
0
    }
293
294
0
    remain_ms = timeout_ms - (int)spent_ms;
295
0
    if(cshutdn_wait(cshutdn, data, remain_ms)) {
296
0
      CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted");
297
0
      break;
298
0
    }
299
0
  }
300
301
  /* Terminate any remaining. */
302
0
  e = Curl_llist_head(&cshutdn->list);
303
0
  while(e) {
304
0
    struct connectdata *conn = Curl_node_elem(e);
305
0
    Curl_node_remove(e);
306
0
    Curl_cshutdn_terminate(data, conn, FALSE);
307
0
    e = Curl_llist_head(&cshutdn->list);
308
0
  }
309
0
  DEBUGASSERT(!Curl_llist_count(&cshutdn->list));
310
311
0
  sigpipe_restore(&pipe_st);
312
0
}
313
314
int Curl_cshutdn_init(struct cshutdn *cshutdn,
315
                      struct Curl_multi *multi)
316
0
{
317
0
  DEBUGASSERT(multi);
318
0
  cshutdn->multi = multi;
319
0
  Curl_llist_init(&cshutdn->list, NULL);
320
0
  cshutdn->initialised = TRUE;
321
0
  return 0; /* good */
322
0
}
323
324
void Curl_cshutdn_destroy(struct cshutdn *cshutdn,
325
                          struct Curl_easy *data)
326
0
{
327
0
  if(cshutdn->initialised && data) {
328
0
    int timeout_ms = 0;
329
    /* Just for testing, run graceful shutdown */
330
#ifdef DEBUGBUILD
331
    {
332
      const char *p = getenv("CURL_GRACEFUL_SHUTDOWN");
333
      if(p) {
334
        curl_off_t l;
335
        if(!curlx_str_number(&p, &l, INT_MAX))
336
          timeout_ms = (int)l;
337
      }
338
    }
339
#endif
340
341
0
    CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms",
342
0
               Curl_llist_count(&cshutdn->list), timeout_ms);
343
0
    cshutdn_terminate_all(cshutdn, data, timeout_ms);
344
0
  }
345
0
  cshutdn->multi = NULL;
346
0
}
347
348
size_t Curl_cshutdn_count(struct Curl_easy *data)
349
0
{
350
0
  if(data && data->multi) {
351
0
    struct cshutdn *csd = &data->multi->cshutdn;
352
0
    return Curl_llist_count(&csd->list);
353
0
  }
354
0
  return 0;
355
0
}
356
357
size_t Curl_cshutdn_dest_count(struct Curl_easy *data,
358
                               const char *destination)
359
0
{
360
0
  if(data && data->multi) {
361
0
    struct cshutdn *csd = &data->multi->cshutdn;
362
0
    size_t n = 0;
363
0
    struct Curl_llist_node *e = Curl_llist_head(&csd->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 Curl_easy *data,
377
                                   struct connectdata *conn)
378
0
{
379
0
  CURLMcode mresult;
380
381
0
  DEBUGASSERT(cshutdn);
382
0
  DEBUGASSERT(cshutdn->multi->socket_cb);
383
384
0
  Curl_attach_connection(data, conn);
385
0
  mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn);
386
0
  Curl_detach_connection(data);
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
0
{
394
0
  struct Curl_easy *data = cshutdn->multi->admin;
395
0
  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
0
  if(max_total > 0 && (max_total <=
400
0
        (conns_in_pool + Curl_llist_count(&cshutdn->list)))) {
401
0
    CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection "
402
0
               "due to connection limit of %zu", max_total);
403
0
    cshutdn_destroy_oldest(cshutdn, data, NULL);
404
0
  }
405
406
0
  if(cshutdn->multi->socket_cb) {
407
0
    if(cshutdn_update_ev(cshutdn, data, conn)) {
408
0
      CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%"
409
0
                 FMT_OFF_T, conn->connection_id);
410
0
      Curl_cshutdn_terminate(data, conn, FALSE);
411
0
      return;
412
0
    }
413
0
  }
414
415
0
  Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node);
416
0
  CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T
417
0
             " to shutdowns, now %zu conns in shutdown",
418
0
             conn->connection_id, Curl_llist_count(&cshutdn->list));
419
0
}
420
421
static void cshutdn_multi_socket(struct cshutdn *cshutdn,
422
                                 struct Curl_easy *data,
423
                                 curl_socket_t s)
424
0
{
425
0
  struct Curl_llist_node *e;
426
0
  struct connectdata *conn;
427
0
  bool done;
428
429
0
  DEBUGASSERT(cshutdn->multi->socket_cb);
430
0
  e = Curl_llist_head(&cshutdn->list);
431
0
  while(e) {
432
0
    conn = Curl_node_elem(e);
433
0
    if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) {
434
0
      Curl_cshutdn_run_once(data, conn, &done);
435
0
      if(done || cshutdn_update_ev(cshutdn, data, conn)) {
436
0
        Curl_node_remove(e);
437
0
        Curl_cshutdn_terminate(data, conn, FALSE);
438
0
      }
439
0
      break;
440
0
    }
441
0
    e = Curl_node_next(e);
442
0
  }
443
0
}
444
445
void Curl_cshutdn_perform(struct cshutdn *cshutdn,
446
                          struct Curl_easy *data,
447
                          curl_socket_t s)
448
0
{
449
0
  if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb))
450
0
    cshutdn_perform(cshutdn, data);
451
0
  else
452
0
    cshutdn_multi_socket(cshutdn, data, s);
453
0
}
454
455
/* return fd_set info about the shutdown connections */
456
void Curl_cshutdn_setfds(struct cshutdn *cshutdn,
457
                         struct Curl_easy *data,
458
                         fd_set *read_fd_set, fd_set *write_fd_set,
459
                         int *maxfd)
460
0
{
461
0
  if(Curl_llist_head(&cshutdn->list)) {
462
0
    struct Curl_llist_node *e;
463
0
    struct easy_pollset ps;
464
465
0
    Curl_pollset_init(&ps);
466
0
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
467
0
      unsigned int i;
468
0
      struct connectdata *conn = Curl_node_elem(e);
469
0
      CURLcode result;
470
471
0
      Curl_pollset_reset(&ps);
472
0
      Curl_attach_connection(data, conn);
473
0
      result = Curl_conn_adjust_pollset(data, conn, &ps);
474
0
      Curl_detach_connection(data);
475
476
0
      if(result)
477
0
        continue;
478
479
0
      for(i = 0; i < ps.n; i++) {
480
0
        curl_socket_t sock = ps.sockets[i];
481
0
        if(!FDSET_SOCK(sock))
482
0
          continue;
483
#ifdef __DJGPP__
484
#pragma GCC diagnostic push
485
#pragma GCC diagnostic ignored "-Warith-conversion"
486
#endif
487
0
        if(ps.actions[i] & CURL_POLL_IN)
488
0
          FD_SET(sock, read_fd_set);
489
0
        if(ps.actions[i] & CURL_POLL_OUT)
490
0
          FD_SET(sock, write_fd_set);
491
#ifdef __DJGPP__
492
#pragma GCC diagnostic pop
493
#endif
494
0
        if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) &&
495
0
           ((int)sock > *maxfd))
496
0
          *maxfd = (int)sock;
497
0
      }
498
0
    }
499
0
    Curl_pollset_cleanup(&ps);
500
0
  }
501
0
}
502
503
/* return information about the shutdown connections */
504
unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn,
505
                                      struct Curl_easy *data,
506
                                      struct Curl_waitfds *cwfds)
507
0
{
508
0
  unsigned int need = 0;
509
510
0
  if(Curl_llist_head(&cshutdn->list)) {
511
0
    struct Curl_llist_node *e;
512
0
    struct easy_pollset ps;
513
0
    struct connectdata *conn;
514
0
    CURLcode result;
515
516
0
    Curl_pollset_init(&ps);
517
0
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
518
0
      conn = Curl_node_elem(e);
519
0
      Curl_pollset_reset(&ps);
520
0
      Curl_attach_connection(data, conn);
521
0
      result = Curl_conn_adjust_pollset(data, conn, &ps);
522
0
      Curl_detach_connection(data);
523
524
0
      if(!result)
525
0
        need += Curl_waitfds_add_ps(cwfds, &ps);
526
0
    }
527
0
    Curl_pollset_cleanup(&ps);
528
0
  }
529
0
  return need;
530
0
}
531
532
CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn,
533
                                  struct Curl_easy *data,
534
                                  struct curl_pollfds *cpfds)
535
0
{
536
0
  CURLcode result = CURLE_OK;
537
538
0
  if(Curl_llist_head(&cshutdn->list)) {
539
0
    struct Curl_llist_node *e;
540
0
    struct easy_pollset ps;
541
0
    struct connectdata *conn;
542
543
0
    Curl_pollset_init(&ps);
544
0
    for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) {
545
0
      conn = Curl_node_elem(e);
546
0
      Curl_pollset_reset(&ps);
547
0
      Curl_attach_connection(data, conn);
548
0
      result = Curl_conn_adjust_pollset(data, conn, &ps);
549
0
      Curl_detach_connection(data);
550
551
0
      if(!result)
552
0
        result = Curl_pollfds_add_ps(cpfds, &ps);
553
0
      if(result) {
554
0
        Curl_pollset_cleanup(&ps);
555
0
        Curl_pollfds_cleanup(cpfds);
556
0
        goto out;
557
0
      }
558
0
    }
559
0
    Curl_pollset_cleanup(&ps);
560
0
  }
561
0
out:
562
0
  return result;
563
0
}