1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012, Linus Nielsen Feltzing, <linus@haxx.se>
9 * Copyright (C) 2012 - 2014, 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 http://curl.haxx.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 ***************************************************************************/
23
24#include "curl_setup.h"
25
26#include <curl/curl.h>
27
28#include "urldata.h"
29#include "url.h"
30#include "progress.h"
31#include "multiif.h"
32#include "sendf.h"
33#include "rawstr.h"
34#include "bundles.h"
35#include "conncache.h"
36
37#include "curl_memory.h"
38/* The last #include file should be: */
39#include "memdebug.h"
40
41static void free_bundle_hash_entry(void *freethis)
42{
43  struct connectbundle *b = (struct connectbundle *) freethis;
44
45  Curl_bundle_destroy(b);
46}
47
48struct conncache *Curl_conncache_init(int size)
49{
50  struct conncache *connc;
51
52  connc = calloc(1, sizeof(struct conncache));
53  if(!connc)
54    return NULL;
55
56  connc->hash = Curl_hash_alloc(size, Curl_hash_str,
57                                Curl_str_key_compare, free_bundle_hash_entry);
58
59  if(!connc->hash) {
60    free(connc);
61    return NULL;
62  }
63
64  return connc;
65}
66
67void Curl_conncache_destroy(struct conncache *connc)
68{
69  if(connc) {
70    Curl_hash_destroy(connc->hash);
71    connc->hash = NULL;
72    free(connc);
73  }
74}
75
76struct connectbundle *Curl_conncache_find_bundle(struct conncache *connc,
77                                                 char *hostname)
78{
79  struct connectbundle *bundle = NULL;
80
81  if(connc)
82    bundle = Curl_hash_pick(connc->hash, hostname, strlen(hostname)+1);
83
84  return bundle;
85}
86
87static bool conncache_add_bundle(struct conncache *connc,
88                                 char *hostname,
89                                 struct connectbundle *bundle)
90{
91  void *p;
92
93  p = Curl_hash_add(connc->hash, hostname, strlen(hostname)+1, bundle);
94
95  return p?TRUE:FALSE;
96}
97
98static void conncache_remove_bundle(struct conncache *connc,
99                                    struct connectbundle *bundle)
100{
101  struct curl_hash_iterator iter;
102  struct curl_hash_element *he;
103
104  if(!connc)
105    return;
106
107  Curl_hash_start_iterate(connc->hash, &iter);
108
109  he = Curl_hash_next_element(&iter);
110  while(he) {
111    if(he->ptr == bundle) {
112      /* The bundle is destroyed by the hash destructor function,
113         free_bundle_hash_entry() */
114      Curl_hash_delete(connc->hash, he->key, he->key_len);
115      return;
116    }
117
118    he = Curl_hash_next_element(&iter);
119  }
120}
121
122CURLcode Curl_conncache_add_conn(struct conncache *connc,
123                                 struct connectdata *conn)
124{
125  CURLcode result;
126  struct connectbundle *bundle;
127  struct connectbundle *new_bundle = NULL;
128  struct SessionHandle *data = conn->data;
129
130  bundle = Curl_conncache_find_bundle(data->state.conn_cache,
131                                      conn->host.name);
132  if(!bundle) {
133    result = Curl_bundle_create(data, &new_bundle);
134    if(result != CURLE_OK)
135      return result;
136
137    if(!conncache_add_bundle(data->state.conn_cache,
138                             conn->host.name, new_bundle)) {
139      Curl_bundle_destroy(new_bundle);
140      return CURLE_OUT_OF_MEMORY;
141    }
142    bundle = new_bundle;
143  }
144
145  result = Curl_bundle_add_conn(bundle, conn);
146  if(result != CURLE_OK) {
147    if(new_bundle)
148      conncache_remove_bundle(data->state.conn_cache, new_bundle);
149    return result;
150  }
151
152  conn->connection_id = connc->next_connection_id++;
153  connc->num_connections++;
154
155  return CURLE_OK;
156}
157
158void Curl_conncache_remove_conn(struct conncache *connc,
159                                struct connectdata *conn)
160{
161  struct connectbundle *bundle = conn->bundle;
162
163  /* The bundle pointer can be NULL, since this function can be called
164     due to a failed connection attempt, before being added to a bundle */
165  if(bundle) {
166    Curl_bundle_remove_conn(bundle, conn);
167    if(bundle->num_connections == 0) {
168      conncache_remove_bundle(connc, bundle);
169    }
170
171    if(connc) {
172      connc->num_connections--;
173
174      DEBUGF(infof(conn->data, "The cache now contains %d members\n",
175                   connc->num_connections));
176    }
177  }
178}
179
180/* This function iterates the entire connection cache and calls the
181   function func() with the connection pointer as the first argument
182   and the supplied 'param' argument as the other,
183
184   Return 0 from func() to continue the loop, return 1 to abort it.
185 */
186void Curl_conncache_foreach(struct conncache *connc,
187                            void *param,
188                            int (*func)(struct connectdata *conn, void *param))
189{
190  struct curl_hash_iterator iter;
191  struct curl_llist_element *curr;
192  struct curl_hash_element *he;
193
194  if(!connc)
195    return;
196
197  Curl_hash_start_iterate(connc->hash, &iter);
198
199  he = Curl_hash_next_element(&iter);
200  while(he) {
201    struct connectbundle *bundle;
202    struct connectdata *conn;
203
204    bundle = he->ptr;
205
206    curr = bundle->conn_list->head;
207    while(curr) {
208      /* Yes, we need to update curr before calling func(), because func()
209         might decide to remove the connection */
210      conn = curr->ptr;
211      curr = curr->next;
212
213      if(1 == func(conn, param))
214        return;
215    }
216
217    he = Curl_hash_next_element(&iter);
218  }
219}
220
221/* Return the first connection found in the cache. Used when closing all
222   connections */
223struct connectdata *
224Curl_conncache_find_first_connection(struct conncache *connc)
225{
226  struct curl_hash_iterator iter;
227  struct curl_llist_element *curr;
228  struct curl_hash_element *he;
229  struct connectbundle *bundle;
230
231  Curl_hash_start_iterate(connc->hash, &iter);
232
233  he = Curl_hash_next_element(&iter);
234  while(he) {
235    bundle = he->ptr;
236
237    curr = bundle->conn_list->head;
238    if(curr) {
239      return curr->ptr;
240    }
241
242    he = Curl_hash_next_element(&iter);
243  }
244
245  return NULL;
246}
247
248
249#if 0
250/* Useful for debugging the connection cache */
251void Curl_conncache_print(struct conncache *connc)
252{
253  struct curl_hash_iterator iter;
254  struct curl_llist_element *curr;
255  struct curl_hash_element *he;
256
257  if(!connc)
258    return;
259
260  fprintf(stderr, "=Bundle cache=\n");
261
262  Curl_hash_start_iterate(connc->hash, &iter);
263
264  he = Curl_hash_next_element(&iter);
265  while(he) {
266    struct connectbundle *bundle;
267    struct connectdata *conn;
268
269    bundle = he->ptr;
270
271    fprintf(stderr, "%s -", he->key);
272    curr = bundle->conn_list->head;
273    while(curr) {
274      conn = curr->ptr;
275
276      fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse);
277      curr = curr->next;
278    }
279    fprintf(stderr, "\n");
280
281    he = Curl_hash_next_element(&iter);
282  }
283}
284#endif
285