Coverage Report

Created: 2025-10-10 06:31

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