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