Coverage Report

Created: 2025-07-11 07:03

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