Coverage Report

Created: 2025-12-14 06:39

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