| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 "urldata.h" |
| #include "strerror.h" |
| #include "cfilters.h" |
| #include "connect.h" |
| #include "url.h" /* for Curl_safefree() */ |
| #include "sendf.h" |
| #include "sockaddr.h" /* required for Curl_sockaddr_storage */ |
| #include "multiif.h" |
| #include "progress.h" |
| #include "select.h" |
| #include "warnless.h" |
| |
| /* The last 3 #include files should be in this order */ |
| #include "curl_printf.h" |
| #include "curl_memory.h" |
| #include "memdebug.h" |
| |
| #ifndef ARRAYSIZE |
| #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) |
| #endif |
| |
| #ifdef DEBUGBUILD |
| /* used by unit2600.c */ |
| void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data) |
| { |
| cf->connected = FALSE; |
| if(cf->next) |
| cf->next->cft->do_close(cf->next, data); |
| } |
| #endif |
| |
| static void conn_report_connect_stats(struct Curl_easy *data, |
| struct connectdata *conn); |
| |
| void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data, |
| const char **phost, const char **pdisplay_host, |
| int *pport) |
| { |
| if(cf->next) |
| cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); |
| else { |
| *phost = cf->conn->host.name; |
| *pdisplay_host = cf->conn->host.dispname; |
| *pport = cf->conn->primary.remote_port; |
| } |
| } |
| |
| void Curl_cf_def_adjust_pollset(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct easy_pollset *ps) |
| { |
| /* NOP */ |
| (void)cf; |
| (void)data; |
| (void)ps; |
| } |
| |
| bool Curl_cf_def_data_pending(struct Curl_cfilter *cf, |
| const struct Curl_easy *data) |
| { |
| return cf->next? |
| cf->next->cft->has_data_pending(cf->next, data) : FALSE; |
| } |
| |
| ssize_t Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data, |
| const void *buf, size_t len, CURLcode *err) |
| { |
| return cf->next? |
| cf->next->cft->do_send(cf->next, data, buf, len, err) : |
| CURLE_RECV_ERROR; |
| } |
| |
| ssize_t Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data, |
| char *buf, size_t len, CURLcode *err) |
| { |
| return cf->next? |
| cf->next->cft->do_recv(cf->next, data, buf, len, err) : |
| CURLE_SEND_ERROR; |
| } |
| |
| bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| bool *input_pending) |
| { |
| return cf->next? |
| cf->next->cft->is_alive(cf->next, data, input_pending) : |
| FALSE; /* pessimistic in absence of data */ |
| } |
| |
| CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| return cf->next? |
| cf->next->cft->keep_alive(cf->next, data) : |
| CURLE_OK; |
| } |
| |
| CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| int query, int *pres1, void *pres2) |
| { |
| return cf->next? |
| cf->next->cft->query(cf->next, data, query, pres1, pres2) : |
| CURLE_UNKNOWN_OPTION; |
| } |
| |
| void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf, |
| struct Curl_easy *data) |
| { |
| struct Curl_cfilter *cfn, *cf = *pcf; |
| |
| if(cf) { |
| *pcf = NULL; |
| while(cf) { |
| cfn = cf->next; |
| /* prevent destroying filter to mess with its sub-chain, since |
| * we have the reference now and will call destroy on it. |
| */ |
| cf->next = NULL; |
| cf->cft->destroy(cf, data); |
| free(cf); |
| cf = cfn; |
| } |
| } |
| } |
| |
| void Curl_conn_cf_discard_all(struct Curl_easy *data, |
| struct connectdata *conn, int index) |
| { |
| Curl_conn_cf_discard_chain(&conn->cfilter[index], data); |
| } |
| |
| void Curl_conn_close(struct Curl_easy *data, int index) |
| { |
| struct Curl_cfilter *cf; |
| |
| DEBUGASSERT(data->conn); |
| /* it is valid to call that without filters being present */ |
| cf = data->conn->cfilter[index]; |
| if(cf) { |
| cf->cft->do_close(cf, data); |
| } |
| } |
| |
| ssize_t Curl_cf_recv(struct Curl_easy *data, int num, char *buf, |
| size_t len, CURLcode *code) |
| { |
| struct Curl_cfilter *cf; |
| |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| *code = CURLE_OK; |
| cf = data->conn->cfilter[num]; |
| while(cf && !cf->connected) { |
| cf = cf->next; |
| } |
| if(cf) { |
| ssize_t nread = cf->cft->do_recv(cf, data, buf, len, code); |
| DEBUGASSERT(nread >= 0 || *code); |
| DEBUGASSERT(nread < 0 || !*code); |
| return nread; |
| } |
| failf(data, "recv: no filter connected"); |
| *code = CURLE_FAILED_INIT; |
| return -1; |
| } |
| |
| ssize_t Curl_cf_send(struct Curl_easy *data, int num, |
| const void *mem, size_t len, CURLcode *code) |
| { |
| struct Curl_cfilter *cf; |
| |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| *code = CURLE_OK; |
| cf = data->conn->cfilter[num]; |
| while(cf && !cf->connected) { |
| cf = cf->next; |
| } |
| if(cf) { |
| ssize_t nwritten = cf->cft->do_send(cf, data, mem, len, code); |
| DEBUGASSERT(nwritten >= 0 || *code); |
| DEBUGASSERT(nwritten < 0 || !*code || !len); |
| return nwritten; |
| } |
| failf(data, "send: no filter connected"); |
| DEBUGASSERT(0); |
| *code = CURLE_FAILED_INIT; |
| return -1; |
| } |
| |
| CURLcode Curl_cf_create(struct Curl_cfilter **pcf, |
| const struct Curl_cftype *cft, |
| void *ctx) |
| { |
| struct Curl_cfilter *cf; |
| CURLcode result = CURLE_OUT_OF_MEMORY; |
| |
| DEBUGASSERT(cft); |
| cf = calloc(1, sizeof(*cf)); |
| if(!cf) |
| goto out; |
| |
| cf->cft = cft; |
| cf->ctx = ctx; |
| result = CURLE_OK; |
| out: |
| *pcf = cf; |
| return result; |
| } |
| |
| void Curl_conn_cf_add(struct Curl_easy *data, |
| struct connectdata *conn, |
| int index, |
| struct Curl_cfilter *cf) |
| { |
| (void)data; |
| DEBUGASSERT(conn); |
| DEBUGASSERT(!cf->conn); |
| DEBUGASSERT(!cf->next); |
| |
| cf->next = conn->cfilter[index]; |
| cf->conn = conn; |
| cf->sockindex = index; |
| conn->cfilter[index] = cf; |
| CURL_TRC_CF(data, cf, "added"); |
| } |
| |
| void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at, |
| struct Curl_cfilter *cf_new) |
| { |
| struct Curl_cfilter *tail, **pnext; |
| |
| DEBUGASSERT(cf_at); |
| DEBUGASSERT(cf_new); |
| DEBUGASSERT(!cf_new->conn); |
| |
| tail = cf_at->next; |
| cf_at->next = cf_new; |
| do { |
| cf_new->conn = cf_at->conn; |
| cf_new->sockindex = cf_at->sockindex; |
| pnext = &cf_new->next; |
| cf_new = cf_new->next; |
| } while(cf_new); |
| *pnext = tail; |
| } |
| |
| bool Curl_conn_cf_discard_sub(struct Curl_cfilter *cf, |
| struct Curl_cfilter *discard, |
| struct Curl_easy *data, |
| bool destroy_always) |
| { |
| struct Curl_cfilter **pprev = &cf->next; |
| bool found = FALSE; |
| |
| /* remove from sub-chain and destroy */ |
| DEBUGASSERT(cf); |
| while(*pprev) { |
| if(*pprev == cf) { |
| *pprev = discard->next; |
| discard->next = NULL; |
| found = TRUE; |
| break; |
| } |
| pprev = &((*pprev)->next); |
| } |
| if(found || destroy_always) { |
| discard->next = NULL; |
| discard->cft->destroy(discard, data); |
| free(discard); |
| } |
| return found; |
| } |
| |
| CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| bool blocking, bool *done) |
| { |
| if(cf) |
| return cf->cft->do_connect(cf, data, blocking, done); |
| return CURLE_FAILED_INIT; |
| } |
| |
| void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) |
| { |
| if(cf) |
| cf->cft->do_close(cf, data); |
| } |
| |
| ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data, |
| const void *buf, size_t len, CURLcode *err) |
| { |
| if(cf) |
| return cf->cft->do_send(cf, data, buf, len, err); |
| *err = CURLE_SEND_ERROR; |
| return -1; |
| } |
| |
| ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data, |
| char *buf, size_t len, CURLcode *err) |
| { |
| if(cf) |
| return cf->cft->do_recv(cf, data, buf, len, err); |
| *err = CURLE_RECV_ERROR; |
| return -1; |
| } |
| |
| CURLcode Curl_conn_connect(struct Curl_easy *data, |
| int sockindex, |
| bool blocking, |
| bool *done) |
| { |
| struct Curl_cfilter *cf; |
| CURLcode result = CURLE_OK; |
| |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| |
| cf = data->conn->cfilter[sockindex]; |
| DEBUGASSERT(cf); |
| if(!cf) |
| return CURLE_FAILED_INIT; |
| |
| *done = cf->connected; |
| if(!*done) { |
| result = cf->cft->do_connect(cf, data, blocking, done); |
| if(!result && *done) { |
| Curl_conn_ev_update_info(data, data->conn); |
| conn_report_connect_stats(data, data->conn); |
| data->conn->keepalive = Curl_now(); |
| } |
| else if(result) { |
| conn_report_connect_stats(data, data->conn); |
| } |
| } |
| |
| return result; |
| } |
| |
| bool Curl_conn_is_connected(struct connectdata *conn, int sockindex) |
| { |
| struct Curl_cfilter *cf; |
| |
| cf = conn->cfilter[sockindex]; |
| return cf && cf->connected; |
| } |
| |
| bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex) |
| { |
| struct Curl_cfilter *cf; |
| |
| cf = data->conn->cfilter[sockindex]; |
| while(cf) { |
| if(cf->connected) |
| return TRUE; |
| if(cf->cft->flags & CF_TYPE_IP_CONNECT) |
| return FALSE; |
| cf = cf->next; |
| } |
| return FALSE; |
| } |
| |
| bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf) |
| { |
| for(; cf; cf = cf->next) { |
| if(cf->cft->flags & CF_TYPE_SSL) |
| return TRUE; |
| if(cf->cft->flags & CF_TYPE_IP_CONNECT) |
| return FALSE; |
| } |
| return FALSE; |
| } |
| |
| bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex) |
| { |
| return conn? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE; |
| } |
| |
| bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex) |
| { |
| struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL; |
| |
| for(; cf; cf = cf->next) { |
| if(cf->cft->flags & CF_TYPE_MULTIPLEX) |
| return TRUE; |
| if(cf->cft->flags & CF_TYPE_IP_CONNECT |
| || cf->cft->flags & CF_TYPE_SSL) |
| return FALSE; |
| } |
| return FALSE; |
| } |
| |
| bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex) |
| { |
| struct Curl_cfilter *cf; |
| |
| (void)data; |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| |
| cf = data->conn->cfilter[sockindex]; |
| while(cf && !cf->connected) { |
| cf = cf->next; |
| } |
| if(cf) { |
| return cf->cft->has_data_pending(cf, data); |
| } |
| return FALSE; |
| } |
| |
| void Curl_conn_cf_adjust_pollset(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct easy_pollset *ps) |
| { |
| /* Get the lowest not-connected filter, if there are any */ |
| while(cf && !cf->connected && cf->next && !cf->next->connected) |
| cf = cf->next; |
| /* From there on, give all filters a chance to adjust the pollset. |
| * Lower filters are called later, so they may override */ |
| while(cf) { |
| cf->cft->adjust_pollset(cf, data, ps); |
| cf = cf->next; |
| } |
| } |
| |
| void Curl_conn_adjust_pollset(struct Curl_easy *data, |
| struct easy_pollset *ps) |
| { |
| int i; |
| |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| for(i = 0; i < 2; ++i) { |
| Curl_conn_cf_adjust_pollset(data->conn->cfilter[i], data, ps); |
| } |
| } |
| |
| void Curl_conn_get_host(struct Curl_easy *data, int sockindex, |
| const char **phost, const char **pdisplay_host, |
| int *pport) |
| { |
| struct Curl_cfilter *cf; |
| |
| DEBUGASSERT(data->conn); |
| cf = data->conn->cfilter[sockindex]; |
| if(cf) { |
| cf->cft->get_host(cf, data, phost, pdisplay_host, pport); |
| } |
| else { |
| /* Some filter ask during shutdown for this, mainly for debugging |
| * purposes. We hand out the defaults, however this is not always |
| * accurate, as the connection might be tunneled, etc. But all that |
| * state is already gone here. */ |
| *phost = data->conn->host.name; |
| *pdisplay_host = data->conn->host.dispname; |
| *pport = data->conn->remote_port; |
| } |
| } |
| |
| CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| int event, int arg1, void *arg2) |
| { |
| (void)cf; |
| (void)data; |
| (void)event; |
| (void)arg1; |
| (void)arg2; |
| return CURLE_OK; |
| } |
| |
| CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| bool ignore_result, |
| int event, int arg1, void *arg2) |
| { |
| CURLcode result = CURLE_OK; |
| |
| for(; cf; cf = cf->next) { |
| if(Curl_cf_def_cntrl == cf->cft->cntrl) |
| continue; |
| result = cf->cft->cntrl(cf, data, event, arg1, arg2); |
| if(!ignore_result && result) |
| break; |
| } |
| return result; |
| } |
| |
| curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| curl_socket_t sock; |
| if(cf && !cf->cft->query(cf, data, CF_QUERY_SOCKET, NULL, &sock)) |
| return sock; |
| return CURL_SOCKET_BAD; |
| } |
| |
| curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex) |
| { |
| struct Curl_cfilter *cf; |
| |
| cf = data->conn? data->conn->cfilter[sockindex] : NULL; |
| /* if the top filter has not connected, ask it (and its sub-filters) |
| * for the socket. Otherwise conn->sock[sockindex] should have it. |
| */ |
| if(cf && !cf->connected) |
| return Curl_conn_cf_get_socket(cf, data); |
| return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD; |
| } |
| |
| void Curl_conn_forget_socket(struct Curl_easy *data, int sockindex) |
| { |
| if(data->conn) { |
| struct Curl_cfilter *cf = data->conn->cfilter[sockindex]; |
| if(cf) |
| (void)Curl_conn_cf_cntrl(cf, data, TRUE, |
| CF_CTRL_FORGET_SOCKET, 0, NULL); |
| fake_sclose(data->conn->sock[sockindex]); |
| data->conn->sock[sockindex] = CURL_SOCKET_BAD; |
| } |
| } |
| |
| static CURLcode cf_cntrl_all(struct connectdata *conn, |
| struct Curl_easy *data, |
| bool ignore_result, |
| int event, int arg1, void *arg2) |
| { |
| CURLcode result = CURLE_OK; |
| size_t i; |
| |
| for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { |
| result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result, |
| event, arg1, arg2); |
| if(!ignore_result && result) |
| break; |
| } |
| return result; |
| } |
| |
| void Curl_conn_ev_data_attach(struct connectdata *conn, |
| struct Curl_easy *data) |
| { |
| cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL); |
| } |
| |
| void Curl_conn_ev_data_detach(struct connectdata *conn, |
| struct Curl_easy *data) |
| { |
| cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL); |
| } |
| |
| CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data) |
| { |
| return cf_cntrl_all(data->conn, data, FALSE, |
| CF_CTRL_DATA_SETUP, 0, NULL); |
| } |
| |
| CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data) |
| { |
| return cf_cntrl_all(data->conn, data, FALSE, |
| CF_CTRL_DATA_IDLE, 0, NULL); |
| } |
| |
| /** |
| * Notify connection filters that the transfer represented by `data` |
| * is donw with sending data (e.g. has uploaded everything). |
| */ |
| void Curl_conn_ev_data_done_send(struct Curl_easy *data) |
| { |
| cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL); |
| } |
| |
| /** |
| * Notify connection filters that the transfer represented by `data` |
| * is finished - eventually premature, e.g. before being complete. |
| */ |
| void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature) |
| { |
| cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL); |
| } |
| |
| CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause) |
| { |
| return cf_cntrl_all(data->conn, data, FALSE, |
| CF_CTRL_DATA_PAUSE, do_pause, NULL); |
| } |
| |
| void Curl_conn_ev_update_info(struct Curl_easy *data, |
| struct connectdata *conn) |
| { |
| cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL); |
| } |
| |
| /** |
| * Update connection statistics |
| */ |
| static void conn_report_connect_stats(struct Curl_easy *data, |
| struct connectdata *conn) |
| { |
| struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET]; |
| if(cf) { |
| struct curltime connected; |
| struct curltime appconnected; |
| |
| memset(&connected, 0, sizeof(connected)); |
| cf->cft->query(cf, data, CF_QUERY_TIMER_CONNECT, NULL, &connected); |
| if(connected.tv_sec || connected.tv_usec) |
| Curl_pgrsTimeWas(data, TIMER_CONNECT, connected); |
| |
| memset(&appconnected, 0, sizeof(appconnected)); |
| cf->cft->query(cf, data, CF_QUERY_TIMER_APPCONNECT, NULL, &appconnected); |
| if(appconnected.tv_sec || appconnected.tv_usec) |
| Curl_pgrsTimeWas(data, TIMER_APPCONNECT, appconnected); |
| } |
| } |
| |
| bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn, |
| bool *input_pending) |
| { |
| struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET]; |
| return cf && !cf->conn->bits.close && |
| cf->cft->is_alive(cf, data, input_pending); |
| } |
| |
| CURLcode Curl_conn_keep_alive(struct Curl_easy *data, |
| struct connectdata *conn, |
| int sockindex) |
| { |
| struct Curl_cfilter *cf = conn->cfilter[sockindex]; |
| return cf? cf->cft->keep_alive(cf, data) : CURLE_OK; |
| } |
| |
| size_t Curl_conn_get_max_concurrent(struct Curl_easy *data, |
| struct connectdata *conn, |
| int sockindex) |
| { |
| CURLcode result; |
| int n = 0; |
| |
| struct Curl_cfilter *cf = conn->cfilter[sockindex]; |
| result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT, |
| &n, NULL) : CURLE_UNKNOWN_OPTION; |
| return (result || n <= 0)? 1 : (size_t)n; |
| } |
| |
| int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) |
| { |
| if(data && data->conn && |
| sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) |
| return SECONDARYSOCKET; |
| return FIRSTSOCKET; |
| } |
| |
| CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, |
| char *buf, size_t blen, ssize_t *n) |
| { |
| CURLcode result = CURLE_OK; |
| ssize_t nread; |
| |
| DEBUGASSERT(data->conn); |
| nread = data->conn->recv[sockindex](data, sockindex, buf, blen, &result); |
| DEBUGASSERT(nread >= 0 || result); |
| DEBUGASSERT(nread < 0 || !result); |
| *n = (nread >= 0)? (size_t)nread : 0; |
| return result; |
| } |
| |
| CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, |
| const void *buf, size_t blen, |
| size_t *pnwritten) |
| { |
| ssize_t nwritten; |
| CURLcode result = CURLE_OK; |
| struct connectdata *conn; |
| |
| DEBUGASSERT(sockindex >= 0 && sockindex < 2); |
| DEBUGASSERT(pnwritten); |
| DEBUGASSERT(data); |
| DEBUGASSERT(data->conn); |
| conn = data->conn; |
| #ifdef CURLDEBUG |
| { |
| /* Allow debug builds to override this logic to force short sends |
| */ |
| char *p = getenv("CURL_SMALLSENDS"); |
| if(p) { |
| size_t altsize = (size_t)strtoul(p, NULL, 10); |
| if(altsize) |
| blen = CURLMIN(blen, altsize); |
| } |
| } |
| #endif |
| nwritten = conn->send[sockindex](data, sockindex, buf, blen, &result); |
| DEBUGASSERT((nwritten >= 0) || result); |
| *pnwritten = (nwritten < 0)? 0 : (size_t)nwritten; |
| return result; |
| } |
| |
| void Curl_pollset_reset(struct Curl_easy *data, |
| struct easy_pollset *ps) |
| { |
| size_t i; |
| (void)data; |
| memset(ps, 0, sizeof(*ps)); |
| for(i = 0; i< MAX_SOCKSPEREASYHANDLE; i++) |
| ps->sockets[i] = CURL_SOCKET_BAD; |
| } |
| |
| /** |
| * |
| */ |
| void Curl_pollset_change(struct Curl_easy *data, |
| struct easy_pollset *ps, curl_socket_t sock, |
| int add_flags, int remove_flags) |
| { |
| unsigned int i; |
| |
| (void)data; |
| DEBUGASSERT(VALID_SOCK(sock)); |
| if(!VALID_SOCK(sock)) |
| return; |
| |
| DEBUGASSERT(add_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); |
| DEBUGASSERT(remove_flags <= (CURL_POLL_IN|CURL_POLL_OUT)); |
| DEBUGASSERT((add_flags&remove_flags) == 0); /* no overlap */ |
| for(i = 0; i < ps->num; ++i) { |
| if(ps->sockets[i] == sock) { |
| ps->actions[i] &= (unsigned char)(~remove_flags); |
| ps->actions[i] |= (unsigned char)add_flags; |
| /* all gone? remove socket */ |
| if(!ps->actions[i]) { |
| if((i + 1) < ps->num) { |
| memmove(&ps->sockets[i], &ps->sockets[i + 1], |
| (ps->num - (i + 1)) * sizeof(ps->sockets[0])); |
| memmove(&ps->actions[i], &ps->actions[i + 1], |
| (ps->num - (i + 1)) * sizeof(ps->actions[0])); |
| } |
| --ps->num; |
| } |
| return; |
| } |
| } |
| /* not present */ |
| if(add_flags) { |
| /* Having more SOCKETS per easy handle than what is defined |
| * is a programming error. This indicates that we need |
| * to raise this limit, making easy_pollset larger. |
| * Since we use this in tight loops, we do not want to make |
| * the pollset dynamic unnecessarily. |
| * The current maximum in practise is HTTP/3 eyeballing where |
| * we have up to 4 sockets involved in connection setup. |
| */ |
| DEBUGASSERT(i < MAX_SOCKSPEREASYHANDLE); |
| if(i < MAX_SOCKSPEREASYHANDLE) { |
| ps->sockets[i] = sock; |
| ps->actions[i] = (unsigned char)add_flags; |
| ps->num = i + 1; |
| } |
| } |
| } |
| |
| void Curl_pollset_set(struct Curl_easy *data, |
| struct easy_pollset *ps, curl_socket_t sock, |
| bool do_in, bool do_out) |
| { |
| Curl_pollset_change(data, ps, sock, |
| (do_in?CURL_POLL_IN:0)|(do_out?CURL_POLL_OUT:0), |
| (!do_in?CURL_POLL_IN:0)|(!do_out?CURL_POLL_OUT:0)); |
| } |
| |
| static void ps_add(struct Curl_easy *data, struct easy_pollset *ps, |
| int bitmap, curl_socket_t *socks) |
| { |
| if(bitmap) { |
| int i; |
| for(i = 0; i < MAX_SOCKSPEREASYHANDLE; ++i) { |
| if(!(bitmap & GETSOCK_MASK_RW(i)) || !VALID_SOCK((socks[i]))) { |
| break; |
| } |
| if(bitmap & GETSOCK_READSOCK(i)) { |
| if(bitmap & GETSOCK_WRITESOCK(i)) |
| Curl_pollset_add_inout(data, ps, socks[i]); |
| else |
| /* is READ, since we checked MASK_RW above */ |
| Curl_pollset_add_in(data, ps, socks[i]); |
| } |
| else |
| Curl_pollset_add_out(data, ps, socks[i]); |
| } |
| } |
| } |
| |
| void Curl_pollset_add_socks(struct Curl_easy *data, |
| struct easy_pollset *ps, |
| int (*get_socks_cb)(struct Curl_easy *data, |
| curl_socket_t *socks)) |
| { |
| curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; |
| int bitmap; |
| |
| bitmap = get_socks_cb(data, socks); |
| ps_add(data, ps, bitmap, socks); |
| } |
| |
| void Curl_pollset_check(struct Curl_easy *data, |
| struct easy_pollset *ps, curl_socket_t sock, |
| bool *pwant_read, bool *pwant_write) |
| { |
| unsigned int i; |
| |
| (void)data; |
| DEBUGASSERT(VALID_SOCK(sock)); |
| for(i = 0; i < ps->num; ++i) { |
| if(ps->sockets[i] == sock) { |
| *pwant_read = !!(ps->actions[i] & CURL_POLL_IN); |
| *pwant_write = !!(ps->actions[i] & CURL_POLL_OUT); |
| return; |
| } |
| } |
| *pwant_read = *pwant_write = FALSE; |
| } |