/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Linus Nielsen Feltzing, * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * SPDX-License-Identifier: curl * ***************************************************************************/ #include "curl_setup.h" #include #include "urldata.h" #include "url.h" #include "cfilters.h" #include "progress.h" #include "multiif.h" #include "multi_ev.h" #include "sendf.h" #include "cshutdn.h" #include "http_negotiate.h" #include "http_ntlm.h" #include "sigpipe.h" #include "connect.h" #include "select.h" #include "strcase.h" #include "strparse.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" static void cshutdn_run_conn_handler(struct Curl_easy *data, struct connectdata *conn) { if(!conn->bits.shutdown_handler) { if(conn->dns_entry) Curl_resolv_unlink(data, &conn->dns_entry); /* Cleanup NTLM connection-related data */ Curl_http_auth_cleanup_ntlm(conn); /* Cleanup NEGOTIATE connection-related data */ Curl_http_auth_cleanup_negotiate(conn); if(conn->handler && conn->handler->disconnect) { /* Some disconnect handlers do a blocking wait on server responses. * FTP/IMAP/SMTP and SFTP are among them. When using the internal * handle, set an overall short timeout so we do not hang for the * default 120 seconds. */ if(data->state.internal) { data->set.timeout = DEFAULT_SHUTDOWN_TIMEOUT_MS; (void)Curl_pgrsTime(data, TIMER_STARTOP); } /* This is set if protocol-specific cleanups should be made */ DEBUGF(infof(data, "connection #%" FMT_OFF_T ", shutdown protocol handler (aborted=%d)", conn->connection_id, conn->bits.aborted)); /* There are protocol handlers that block on retrieving * server responses here (FTP). Set a short timeout. */ conn->handler->disconnect(data, conn, conn->bits.aborted); } /* possible left-overs from the async name resolvers */ Curl_resolver_cancel(data); conn->bits.shutdown_handler = TRUE; } } static void cshutdn_run_once(struct Curl_easy *data, struct connectdata *conn, bool *done) { CURLcode r1, r2; bool done1, done2; /* We expect to be attached when called */ DEBUGASSERT(data->conn == conn); cshutdn_run_conn_handler(data, conn); if(conn->bits.shutdown_filters) { *done = TRUE; return; } if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); else { r1 = CURLE_OK; done1 = TRUE; } if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); else { r2 = CURLE_OK; done2 = TRUE; } /* we are done when any failed or both report success */ *done = (r1 || r2 || (done1 && done2)); if(*done) conn->bits.shutdown_filters = TRUE; } void Curl_cshutdn_run_once(struct Curl_easy *data, struct connectdata *conn, bool *done) { DEBUGASSERT(!data->conn); Curl_attach_connection(data, conn); cshutdn_run_once(data, conn, done); CURL_TRC_M(data, "[SHUTDOWN] shutdown, done=%d", *done); Curl_detach_connection(data); } void Curl_cshutdn_terminate(struct Curl_easy *data, struct connectdata *conn, bool do_shutdown) { struct Curl_easy *admin = data; bool done; /* there must be a connection to close */ DEBUGASSERT(conn); /* it must be removed from the connection pool */ DEBUGASSERT(!conn->bits.in_cpool); /* the transfer must be detached from the connection */ DEBUGASSERT(data && !data->conn); /* If we can obtain an internal admin handle, use that to attach * and terminate the connection. Some protocol will try to mess with * `data` during shutdown and we do not want that with a `data` from * the application. */ if(data->multi && data->multi->admin) admin = data->multi->admin; Curl_attach_connection(admin, conn); cshutdn_run_conn_handler(admin, conn); if(do_shutdown) { /* Make a last attempt to shutdown handlers and filters, if * not done so already. */ cshutdn_run_once(admin, conn, &done); } CURL_TRC_M(admin, "[SHUTDOWN] closing connection"); Curl_conn_close(admin, SECONDARYSOCKET); Curl_conn_close(admin, FIRSTSOCKET); Curl_detach_connection(admin); if(data->multi) Curl_multi_ev_conn_done(data->multi, data, conn); Curl_conn_free(admin, conn); if(data->multi) { CURL_TRC_M(data, "[SHUTDOWN] trigger multi connchanged"); Curl_multi_connchanged(data->multi); } } static void cshutdn_destroy_oldest(struct cshutdn *cshutdn, struct Curl_easy *data) { struct Curl_llist_node *e; struct connectdata *conn; e = Curl_llist_head(&cshutdn->list); if(e) { SIGPIPE_VARIABLE(pipe_st); conn = Curl_node_elem(e); Curl_node_remove(e); sigpipe_init(&pipe_st); sigpipe_apply(data, &pipe_st); Curl_cshutdn_terminate(data, conn, FALSE); sigpipe_restore(&pipe_st); } } #define NUM_POLLS_ON_STACK 10 static CURLcode cshutdn_wait(struct cshutdn *cshutdn, struct Curl_easy *data, int timeout_ms) { struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; struct curl_pollfds cpfds; CURLcode result; Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); result = Curl_cshutdn_add_pollfds(cshutdn, data, &cpfds); if(result) goto out; Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); out: Curl_pollfds_cleanup(&cpfds); return result; } static void cshutdn_perform(struct cshutdn *cshutdn, struct Curl_easy *data) { struct Curl_llist_node *e = Curl_llist_head(&cshutdn->list); struct Curl_llist_node *enext; struct connectdata *conn; struct curltime *nowp = NULL; struct curltime now; timediff_t next_expire_ms = 0, ms; bool done; if(!e) return; CURL_TRC_M(data, "[SHUTDOWN] perform on %zu connections", Curl_llist_count(&cshutdn->list)); while(e) { enext = Curl_node_next(e); conn = Curl_node_elem(e); Curl_cshutdn_run_once(data, conn, &done); if(done) { Curl_node_remove(e); Curl_cshutdn_terminate(data, conn, FALSE); } else { /* idata has one timer list, but maybe more than one connection. * Set EXPIRE_SHUTDOWN to the smallest time left for all. */ if(!nowp) { now = Curl_now(); nowp = &now; } ms = Curl_conn_shutdown_timeleft(conn, nowp); if(ms && ms < next_expire_ms) next_expire_ms = ms; } e = enext; } if(next_expire_ms) Curl_expire_ex(data, nowp, next_expire_ms, EXPIRE_SHUTDOWN); } static void cshutdn_terminate_all(struct cshutdn *cshutdn, struct Curl_easy *data, int timeout_ms) { struct curltime started = Curl_now(); struct Curl_llist_node *e; SIGPIPE_VARIABLE(pipe_st); DEBUGASSERT(cshutdn); DEBUGASSERT(data); CURL_TRC_M(data, "[SHUTDOWN] shutdown all"); sigpipe_init(&pipe_st); sigpipe_apply(data, &pipe_st); while(Curl_llist_head(&cshutdn->list)) { timediff_t timespent; int remain_ms; cshutdn_perform(cshutdn, data); if(!Curl_llist_head(&cshutdn->list)) { CURL_TRC_M(data, "[SHUTDOWN] shutdown finished cleanly"); break; } /* wait for activity, timeout or "nothing" */ timespent = Curl_timediff(Curl_now(), started); if(timespent >= (timediff_t)timeout_ms) { CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, %s", (timeout_ms > 0) ? "timeout" : "best effort done"); break; } remain_ms = timeout_ms - (int)timespent; if(cshutdn_wait(cshutdn, data, remain_ms)) { CURL_TRC_M(data, "[SHUTDOWN] shutdown finished, aborted"); break; } } /* Terminate any remaining. */ e = Curl_llist_head(&cshutdn->list); while(e) { struct connectdata *conn = Curl_node_elem(e); Curl_node_remove(e); Curl_cshutdn_terminate(data, conn, FALSE); e = Curl_llist_head(&cshutdn->list); } DEBUGASSERT(!Curl_llist_count(&cshutdn->list)); Curl_hostcache_clean(data, data->dns.hostcache); sigpipe_restore(&pipe_st); } int Curl_cshutdn_init(struct cshutdn *cshutdn, struct Curl_multi *multi) { DEBUGASSERT(multi); cshutdn->multi = multi; Curl_llist_init(&cshutdn->list, NULL); cshutdn->initialised = TRUE; return 0; /* good */ } void Curl_cshutdn_destroy(struct cshutdn *cshutdn, struct Curl_easy *data) { if(cshutdn->initialised && data) { int timeout_ms = 0; /* Just for testing, run graceful shutdown */ #ifdef DEBUGBUILD { const char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); if(p) { curl_off_t l; if(!Curl_str_number(&p, &l, INT_MAX)) timeout_ms = (int)l; } } #endif CURL_TRC_M(data, "[SHUTDOWN] destroy, %zu connections, timeout=%dms", Curl_llist_count(&cshutdn->list), timeout_ms); cshutdn_terminate_all(cshutdn, data, timeout_ms); } cshutdn->multi = NULL; } size_t Curl_cshutdn_count(struct Curl_easy *data) { if(data && data->multi) { struct cshutdn *csd = &data->multi->cshutdn; return Curl_llist_count(&csd->list); } return 0; } size_t Curl_cshutdn_dest_count(struct Curl_easy *data, const char *destination) { if(data && data->multi) { struct cshutdn *csd = &data->multi->cshutdn; size_t n = 0; struct Curl_llist_node *e = Curl_llist_head(&csd->list); while(e) { struct connectdata *conn = Curl_node_elem(e); if(!strcmp(destination, conn->destination)) ++n; e = Curl_node_next(e); } return n; } return 0; } static CURLMcode cshutdn_update_ev(struct cshutdn *cshutdn, struct Curl_easy *data, struct connectdata *conn) { CURLMcode mresult; DEBUGASSERT(cshutdn); DEBUGASSERT(cshutdn->multi->socket_cb); Curl_attach_connection(data, conn); mresult = Curl_multi_ev_assess_conn(cshutdn->multi, data, conn); Curl_detach_connection(data); return mresult; } void Curl_cshutdn_add(struct cshutdn *cshutdn, struct connectdata *conn, size_t conns_in_pool) { struct Curl_easy *data = cshutdn->multi->admin; size_t max_total = (cshutdn->multi->max_total_connections > 0) ? (size_t)cshutdn->multi->max_total_connections : 0; /* Add the connection to our shutdown list for non-blocking shutdown * during multi processing. */ if(max_total > 0 && (max_total <= (conns_in_pool + Curl_llist_count(&cshutdn->list)))) { CURL_TRC_M(data, "[SHUTDOWN] discarding oldest shutdown connection " "due to connection limit of %zu", max_total); cshutdn_destroy_oldest(cshutdn, data); } if(cshutdn->multi->socket_cb) { if(cshutdn_update_ev(cshutdn, data, conn)) { CURL_TRC_M(data, "[SHUTDOWN] update events failed, discarding #%" FMT_OFF_T, conn->connection_id); Curl_cshutdn_terminate(data, conn, FALSE); return; } } Curl_llist_append(&cshutdn->list, conn, &conn->cshutdn_node); CURL_TRC_M(data, "[SHUTDOWN] added #%" FMT_OFF_T " to shutdowns, now %zu conns in shutdown", conn->connection_id, Curl_llist_count(&cshutdn->list)); } static void cshutdn_multi_socket(struct cshutdn *cshutdn, struct Curl_easy *data, curl_socket_t s) { struct Curl_llist_node *e; struct connectdata *conn; bool done; DEBUGASSERT(cshutdn->multi->socket_cb); e = Curl_llist_head(&cshutdn->list); while(e) { conn = Curl_node_elem(e); if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { Curl_cshutdn_run_once(data, conn, &done); if(done || cshutdn_update_ev(cshutdn, data, conn)) { Curl_node_remove(e); Curl_cshutdn_terminate(data, conn, FALSE); } break; } e = Curl_node_next(e); } } void Curl_cshutdn_perform(struct cshutdn *cshutdn, struct Curl_easy *data, curl_socket_t s) { if((s == CURL_SOCKET_TIMEOUT) || (!cshutdn->multi->socket_cb)) cshutdn_perform(cshutdn, data); else cshutdn_multi_socket(cshutdn, data, s); } /* return fd_set info about the shutdown connections */ void Curl_cshutdn_setfds(struct cshutdn *cshutdn, struct Curl_easy *data, fd_set *read_fd_set, fd_set *write_fd_set, int *maxfd) { if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { struct easy_pollset ps; unsigned int i; struct connectdata *conn = Curl_node_elem(e); memset(&ps, 0, sizeof(ps)); Curl_attach_connection(data, conn); Curl_conn_adjust_pollset(data, conn, &ps); Curl_detach_connection(data); for(i = 0; i < ps.num; i++) { #if defined(__DJGPP__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Warith-conversion" #endif if(ps.actions[i] & CURL_POLL_IN) FD_SET(ps.sockets[i], read_fd_set); if(ps.actions[i] & CURL_POLL_OUT) FD_SET(ps.sockets[i], write_fd_set); #if defined(__DJGPP__) #pragma GCC diagnostic pop #endif if((ps.actions[i] & (CURL_POLL_OUT | CURL_POLL_IN)) && ((int)ps.sockets[i] > *maxfd)) *maxfd = (int)ps.sockets[i]; } } } } /* return information about the shutdown connections */ unsigned int Curl_cshutdn_add_waitfds(struct cshutdn *cshutdn, struct Curl_easy *data, struct Curl_waitfds *cwfds) { unsigned int need = 0; if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; struct easy_pollset ps; struct connectdata *conn; for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); memset(&ps, 0, sizeof(ps)); Curl_attach_connection(data, conn); Curl_conn_adjust_pollset(data, conn, &ps); Curl_detach_connection(data); need += Curl_waitfds_add_ps(cwfds, &ps); } } return need; } CURLcode Curl_cshutdn_add_pollfds(struct cshutdn *cshutdn, struct Curl_easy *data, struct curl_pollfds *cpfds) { CURLcode result = CURLE_OK; if(Curl_llist_head(&cshutdn->list)) { struct Curl_llist_node *e; struct easy_pollset ps; struct connectdata *conn; for(e = Curl_llist_head(&cshutdn->list); e; e = Curl_node_next(e)) { conn = Curl_node_elem(e); memset(&ps, 0, sizeof(ps)); Curl_attach_connection(data, conn); Curl_conn_adjust_pollset(data, conn, &ps); Curl_detach_connection(data); result = Curl_pollfds_add_ps(cpfds, &ps); if(result) { Curl_pollfds_cleanup(cpfds); goto out; } } } out: return result; }