1 /*
2 * Copyright (C) 2011 Christos Tsantilas
3 * email: christos@chtsanti.net
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include "common.h"
21 #include "array.h"
22 #include "cache.h"
23 #include "ci_threads.h"
24 #include "debug.h"
25 #include "md5.h"
26 #include "mem.h"
27 #include "module.h"
28
29 #include <libmemcached/memcached.h>
30
31 /*
32 Set it to 1 if you want to use c-icap memory pools and
33 custom c-icap memory allocators.
34 Currently libmemcached looks that does not handle well
35 foreign mem allocators, so for nos it is disabled.
36 */
37 #define USE_CI_BUFFERS 0
38
39 #if defined(LIBMEMCACHED_VERSION_HEX)
40 #if LIBMEMCACHED_VERSION_HEX > 0x01000000
41 #include <libmemcached/util.h>
42 #else
43 #include <libmemcached/util/pool.h>
44 #endif /* LIBMEMCACHED_VERSION_HEX > 0x01000000*/
45 #else
46 /* And older version of libmemcached*/
47 #include <libmemcached/memcached_pool.h>
48 #endif
49
50 /* #include <crypt.h> */
51
52 int USE_MD5_SUM_KEYS = 1;
53
54 int mc_cfg_servers_set(const char *directive, const char **argv, void *setdata);
55 /*Configuration Table .....*/
56 static struct ci_conf_entry mc_conf_variables[] = {
57 {"servers", NULL, mc_cfg_servers_set, NULL},
58 {"use_md5_keys", &USE_MD5_SUM_KEYS, ci_cfg_onoff, NULL},
59 {NULL, NULL, NULL, NULL}
60 };
61
62 static int mc_module_init(struct ci_server_conf *server_conf);
63 static int mc_module_post_init(struct ci_server_conf *server_conf);
64 static void mc_module_release();
65 CI_DECLARE_MOD_DATA common_module_t module = {
66 "memcached",
67 mc_module_init,
68 mc_module_post_init,
69 mc_module_release,
70 mc_conf_variables,
71 };
72
73 #define MC_DOMAINLEN 32
74 #define MC_MAXKEYLEN 250
75 #define HOSTNAME_LEN 256
76 typedef struct mc_server {
77 char hostname[HOSTNAME_LEN];
78 int port;
79 } mc_server_t;
80
81 /*Vector of mc_server_t elements*/
82 static ci_list_t *servers_list = NULL;
83 /*A general mutex used in various configuration steps*/
84 static ci_thread_mutex_t mc_mtx;
85
86 struct mc_cache_data {
87 char domain[MC_DOMAINLEN + 1];
88 };
89 /*The list of mc caches. Objects of type mc_cache_data.*/
90 static ci_list_t *mc_caches_list = NULL;
91
92 static struct ci_cache_type mc_cache;
93
94 memcached_st *MC = NULL;
95 memcached_pool_st *MC_POOL = NULL;
96
97 #if USE_CI_BUFFERS
98 #if defined(LIBMEMCACHED_VERSION_HEX)
99 void *mc_mem_malloc(const memcached_st *ptr, const size_t size, void *context);
100 void mc_mem_free(const memcached_st *ptr, void *mem, void *context);
101 void *mc_mem_realloc(const memcached_st *ptr, void *mem, const size_t size, void *context);
102 void *mc_mem_calloc(const memcached_st *ptr, size_t nelem, const size_t elsize, void *context);
103 #else
104 void *mc_mem_malloc(memcached_st *ptr, const size_t size);
105 void mc_mem_free(memcached_st *ptr, void *mem);
106 void *mc_mem_realloc(memcached_st *ptr, void *mem, const size_t size);
107 void *mc_mem_calloc(memcached_st *ptr, size_t nelem, const size_t elsize);
108 #endif
109 #endif
110 static int computekey(char *mckey, const char *key, const char *search_domain);
111
mc_module_init(struct ci_server_conf * server_conf)112 int mc_module_init(struct ci_server_conf *server_conf)
113 {
114 if (ci_thread_mutex_init(&mc_mtx) != 0) {
115 ci_debug_printf(1, "Can not intialize mutex!\n");
116 return 0;
117 }
118
119 /*mc_caches_list should store pointers to memcached caches data*/
120 if ((mc_caches_list = ci_list_create(1024, 0)) == NULL) {
121 ci_debug_printf(1, "Can not allocate memory for list storing mc domains!\n");
122 return 0;
123 }
124
125 ci_cache_type_register(&mc_cache);
126 ci_debug_printf(3, "Memcached cache sucessfully initialized!\n");
127 return 1;
128 }
129
mc_module_post_init(struct ci_server_conf * server_conf)130 int mc_module_post_init(struct ci_server_conf *server_conf)
131 {
132 #if USE_CI_BUFFERS
133 memcached_return rc;
134 #endif
135 const mc_server_t *srv;
136 const char *default_servers[] = {
137 "127.0.0.1",
138 NULL
139 };
140
141 if (servers_list == NULL) {
142 mc_cfg_servers_set("server", default_servers, NULL);
143 if (servers_list == NULL)
144 return 0;
145 }
146
147 #if USE_CI_BUFFERS
148 MC = (memcached_st *)mc_mem_calloc(NULL, 1, sizeof(memcached_st)
149 #if defined(LIBMEMCACHED_VERSION_HEX)
150 ,(void *)0x1
151 #endif
152 );
153 #else
154 MC = calloc(1, sizeof(memcached_st));
155 #endif
156
157 MC = memcached_create(MC);
158 if (MC == NULL) {
159 ci_debug_printf(1, "Failed to create memcached instance\n");
160 return 0;
161 }
162 ci_debug_printf(1, "memcached instance created\n");
163
164 #if USE_CI_BUFFERS
165 rc = memcached_set_memory_allocators(MC,
166 mc_mem_malloc,
167 mc_mem_free,
168 mc_mem_realloc,
169 mc_mem_calloc
170 #if defined(LIBMEMCACHED_VERSION_HEX)
171 , (void *)0x1
172 #endif
173 );
174
175 if (rc != MEMCACHED_SUCCESS) {
176 ci_debug_printf(1, "Failed to set ci-icap membuf memory allocators\n");
177 memcached_free(MC);
178 MC = NULL;
179 return 0;
180 }
181 #endif
182
183 memcached_behavior_set(MC, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1);
184
185 for (srv = (const mc_server_t *)ci_list_first(servers_list); srv != NULL ; srv = (const mc_server_t *)ci_list_next(servers_list)) {
186 if (srv->hostname[0] == '/') {
187 if (memcached_server_add_unix_socket(MC, srv->hostname) != MEMCACHED_SUCCESS) {
188 ci_debug_printf(1, "Failed to add socket path to the server pool\n");
189 memcached_free(MC);
190 MC = NULL;
191 return 0;
192 }
193 } else if (memcached_server_add(MC, srv->hostname, srv->port) !=
194 MEMCACHED_SUCCESS) {
195 ci_debug_printf(1, "Failed to add localhost to the server pool\n");
196 memcached_free(MC);
197 MC = NULL;
198 return 0;
199 }
200 }
201
202 MC_POOL = memcached_pool_create(MC, 5, 500);
203 if (MC_POOL == NULL) {
204 ci_debug_printf(1, "Failed to create connection pool\n");
205 memcached_free(MC);
206 MC = NULL;
207 return 0;
208 }
209 return 1;
210 }
211
mc_module_release()212 void mc_module_release()
213 {
214 memcached_pool_destroy(MC_POOL);
215 memcached_free(MC);
216 ci_list_destroy(servers_list);
217 ci_list_destroy(mc_caches_list);
218 servers_list = NULL;
219 }
220
221
222 /*******************************************/
223 /* memcached cache implementation */
224 static int mc_cache_init(struct ci_cache *cache, const char *name);
225 static const void *mc_cache_search(struct ci_cache *cache, const void *key, void **val, void *data, void *(*dup_from_cache)(const void *stored_val, size_t stored_val_size, void *data));
226 static int mc_cache_update(struct ci_cache *cache, const void *key, const void *val, size_t val_size, void *(*copy_to_cache)(void *buf, const void *val, size_t buf_size));
227 static void mc_cache_destroy(struct ci_cache *cache);
228
229 static struct ci_cache_type mc_cache = {
230 mc_cache_init,
231 mc_cache_search,
232 mc_cache_update,
233 mc_cache_destroy,
234 "memcached"
235 };
236
mc_cache_cmp(const void * obj,const void * user_data,size_t user_data_size)237 int mc_cache_cmp(const void *obj, const void *user_data, size_t user_data_size)
238 {
239 struct mc_cache_data *mcObj = (struct mc_cache_data *)obj;
240 const char *domain = (const char *)user_data;
241 return strcmp(mcObj->domain, domain);
242 }
243
mc_cache_init(struct ci_cache * cache,const char * domain)244 int mc_cache_init(struct ci_cache *cache, const char *domain)
245 {
246 int i;
247 #define USE_EXCESS_BYTES 3 // Just to avoid gcc warnings
248 char useDomain[MC_DOMAINLEN + 1 + USE_EXCESS_BYTES];
249 strncpy(useDomain, domain, MC_DOMAINLEN);
250 useDomain[MC_DOMAINLEN] = '\0';
251 i = 0;
252 ci_thread_mutex_lock(&mc_mtx);
253 while (i < 1000 && ci_list_search2(mc_caches_list, useDomain, mc_cache_cmp)) {
254 const int istrSize = i < 10 ? 1 : (i < 100 ? 2 : 3);
255 snprintf(useDomain, sizeof(useDomain), "%.*s~%d",
256 (int)(MC_DOMAINLEN - 1 - istrSize),
257 domain,
258 i);
259 i++;
260 }
261 ci_thread_mutex_unlock(&mc_mtx);
262
263 if (i > 999) /*????*/
264 return 0;
265
266 struct mc_cache_data *mc_data = malloc(sizeof(struct mc_cache_data));
267 strncpy(mc_data->domain, useDomain, MC_DOMAINLEN);
268 mc_data->domain[MC_DOMAINLEN] = '\0';
269 cache->cache_data = mc_data;
270 ci_thread_mutex_lock(&mc_mtx);
271 ci_list_push_back(mc_caches_list, mc_data);
272 ci_thread_mutex_unlock(&mc_mtx);
273 ci_debug_printf(3, "memcached cache for domain: '%s' created\n", useDomain);
274 return 1;
275 }
276
mc_cache_destroy(struct ci_cache * cache)277 void mc_cache_destroy(struct ci_cache *cache)
278 {
279 ci_thread_mutex_lock(&mc_mtx);
280 ci_list_remove(mc_caches_list, cache->cache_data);
281 ci_thread_mutex_unlock(&mc_mtx);
282 free(cache->cache_data);
283 }
284
mc_cache_search(struct ci_cache * cache,const void * key,void ** val,void * data,void * (* dup_from_cache)(const void * stored_val,size_t stored_val_size,void * data))285 const void *mc_cache_search(struct ci_cache *cache, const void *key, void **val, void *data, void *(*dup_from_cache)(const void *stored_val, size_t stored_val_size, void *data))
286 {
287 memcached_return rc;
288 memcached_st *mlocal;
289 uint32_t flags;
290 char mckey[MC_MAXKEYLEN+1];
291 int mckeylen = 0;
292 void *value;
293 size_t value_len;
294 int found = 0;
295 struct mc_cache_data *mc_data = (struct mc_cache_data *)cache->cache_data;
296
297 mckeylen = computekey(mckey, key, mc_data->domain);
298 if (mckeylen == 0)
299 return NULL;
300
301 mlocal = memcached_pool_pop(MC_POOL, true, &rc);
302 if (!mlocal) {
303 ci_debug_printf(1, "Error getting memcached_st object from pool: %s\n", memcached_strerror(MC, rc));
304 return NULL;
305 }
306
307 value = memcached_get(mlocal, mckey, mckeylen, &value_len, &flags, &rc);
308
309 if ( rc != MEMCACHED_SUCCESS) {
310 ci_debug_printf(5, "Failed to retrieve %s object from cache: %s\n",
311 mckey,
312 memcached_strerror(mlocal, rc));
313 } else {
314 ci_debug_printf(5, "The %s object retrieved from cache has size %d\n", mckey, (int)value_len);
315 found = 1;
316 }
317
318 if ((rc = memcached_pool_push(MC_POOL, mlocal)) != MEMCACHED_SUCCESS) {
319 ci_debug_printf(1, "Failed to release memcached_st object (%s)!\n", memcached_strerror(MC, rc));
320 }
321
322 if (!found)
323 return NULL;
324
325 if (dup_from_cache && value) {
326 *val = dup_from_cache(value, value_len, data);
327 ci_buffer_free(value);
328 value = NULL;
329 } else {
330 #if USE_CI_BUFFERS
331 *val = value;
332 #else
333 if (value && value_len) {
334 *val = ci_buffer_alloc(value_len);
335 if (!*val) {
336 free(value);
337 return NULL;
338 }
339 memcpy(*val, value, value_len);
340 free(value);
341 } else
342 *val = NULL;
343 #endif
344 }
345 return key;
346 }
347
mc_cache_update(struct ci_cache * cache,const void * key,const void * val,size_t val_size,void * (* copy_to_cache)(void * buf,const void * val,size_t buf_size))348 int mc_cache_update(struct ci_cache *cache, const void *key, const void *val, size_t val_size, void *(*copy_to_cache)(void *buf, const void *val, size_t buf_size))
349 {
350 void *value = NULL;
351 memcached_return rc;
352 char mckey[MC_MAXKEYLEN+1];
353 int mckeylen = 0;
354 struct mc_cache_data *mc_data = (struct mc_cache_data *)cache->cache_data;
355 memcached_st *mlocal;
356 mckeylen = computekey(mckey, key, mc_data->domain);
357 if (mckeylen == 0)
358 return 0;
359
360 if (copy_to_cache && val_size) {
361 if ((value = ci_buffer_alloc(val_size)) == NULL)
362 return 0; /*debug message?*/
363
364 if (!copy_to_cache(value, val, val_size))
365 return 0; /*debug message?*/
366 }
367
368 mlocal = memcached_pool_pop(MC_POOL, true, &rc);
369 if (!mlocal) {
370 ci_debug_printf(1, "Error getting memcached_st object from pool: %s\n",
371 memcached_strerror(MC, rc));
372 return 0;
373 }
374
375 rc = memcached_set(mlocal, mckey, mckeylen, value != NULL ? (const char *)value : (const char *)val, val_size, cache->ttl, (uint32_t)0);
376
377 if (value)
378 ci_buffer_free(value);
379
380 if (rc != MEMCACHED_SUCCESS)
381 ci_debug_printf(5, "failed to set key: %s in memcached: %s\n",
382 mckey,
383 memcached_strerror(mlocal, rc));
384
385 if (memcached_pool_push(MC_POOL, mlocal) != MEMCACHED_SUCCESS) {
386 ci_debug_printf(1, "Failed to release memcached_st object:%s\n",
387 memcached_strerror(MC, rc));
388 }
389
390 ci_debug_printf(5, "mc_cache_update: successfully update key '%s'\n", mckey);
391 return 1;
392 }
393
mc_cache_delete(const char * key,const char * search_domain)394 int mc_cache_delete(const char *key, const char *search_domain)
395 {
396 memcached_return rc;
397 memcached_st *mlocal = memcached_pool_pop(MC_POOL, true, &rc);
398
399 if (!mlocal) {
400 ci_debug_printf(1, "Error getting memcached_st object from pool: %s\n",
401 memcached_strerror(MC, rc));
402 return 0;
403 }
404
405 char mckey[MC_MAXKEYLEN+1];
406 int mckeylen = 0;
407 mckeylen = computekey(mckey,key,search_domain);
408 if (mckeylen == 0)
409 return 0;
410
411 rc = memcached_delete(mlocal, mckey, mckeylen, (time_t)0);
412 if (rc != MEMCACHED_SUCCESS)
413 ci_debug_printf(5, "failed to set key: %s in memcached: %s\n",
414 mckey,
415 memcached_strerror(mlocal, rc));
416
417 return 1;
418 }
419
mc_cfg_servers_set(const char * directive,const char ** argv,void * setdata)420 int mc_cfg_servers_set(const char *directive, const char **argv, void *setdata)
421 {
422 int argc;
423 char *s;
424 mc_server_t srv;
425
426 if (!servers_list) {
427 servers_list = ci_list_create(4096, sizeof(mc_server_t));
428 if (!servers_list) {
429 ci_debug_printf(1, "Error allocating memory for mc_servers list!\n");
430 return 0;
431 }
432 }
433
434 for (argc = 0; argv[argc] != NULL; argc++) {
435
436 strncpy(srv.hostname, argv[argc], HOSTNAME_LEN);
437 srv.hostname[HOSTNAME_LEN - 1] = '\0';
438 if (srv.hostname[0] != '/' && (s = strchr(srv.hostname, ':')) != NULL) {
439 *s = '\0';
440 s++;
441 srv.port = atoi(s);
442 if (!srv.port)
443 srv.port = 11211;
444 } else
445 srv.port = 11211;
446 ci_debug_printf(2, "Setup memcached server %s:%d\n", srv.hostname, srv.port);
447 }
448 ci_list_push_back(servers_list, &srv);
449
450 return argc;
451 }
452
453 #if USE_CI_BUFFERS
454 /*Memory managment functions*/
455 #if defined(LIBMEMCACHED_VERSION_HEX)
mc_mem_malloc(const memcached_st * ptr,const size_t size,void * context)456 void *mc_mem_malloc(const memcached_st *ptr, const size_t size, void *context)
457 #else
458 void *mc_mem_malloc(memcached_st *ptr, const size_t size)
459 #endif
460 {
461
462 void *p = ci_buffer_alloc(size);
463 ci_debug_printf(5, "mc_mem_malloc: %p of size %u\n", p, (unsigned int)size);
464 return p;
465 }
466
467 #if defined(LIBMEMCACHED_VERSION_HEX)
mc_mem_free(const memcached_st * ptr,void * mem,void * context)468 void mc_mem_free(const memcached_st *ptr, void *mem, void *context)
469 #else
470 void mc_mem_free(memcached_st *ptr, void *mem)
471 #endif
472 {
473 #if defined(LIBMEMCACHED_VERSION_HEX)
474 ci_debug_printf(5, "mc_mem_free: %p/%p\n", mem, context);
475 #else
476 ci_debug_printf(5, "mc_mem_free: %p\n", mem);
477 #endif
478 if (mem)
479 ci_buffer_free(mem);
480 }
481
482 #if defined(LIBMEMCACHED_VERSION_HEX)
mc_mem_realloc(const memcached_st * ptr,void * mem,const size_t size,void * context)483 void *mc_mem_realloc(const memcached_st *ptr, void *mem, const size_t size, void *context)
484 #else
485 void *mc_mem_realloc(memcached_st *ptr, void *mem, const size_t size)
486 #endif
487 {
488 void *p = ci_buffer_realloc(mem, size);
489 ci_debug_printf(5, "mc_mem_realloc: %p of size %u\n", p, (unsigned int)size);
490 return p;
491 }
492
493 #if defined(LIBMEMCACHED_VERSION_HEX)
mc_mem_calloc(const memcached_st * ptr,size_t nelem,const size_t elsize,void * context)494 void *mc_mem_calloc(const memcached_st *ptr, size_t nelem, const size_t elsize, void *context)
495 #else
496 void *mc_mem_calloc(memcached_st *ptr, size_t nelem, const size_t elsize)
497 #endif
498 {
499 void *p;
500 p = ci_buffer_alloc(nelem*elsize);
501 if (!p)
502 return NULL;
503 memset(p, 0, nelem*elsize);
504 ci_debug_printf(5, "mc_mem_calloc: %p of size %u\n", p, (unsigned int)(nelem*elsize));
505 return p;
506 }
507 #endif
508
computekey(char * mckey,const char * key,const char * search_domain)509 int computekey(char *mckey, const char *key, const char *search_domain)
510 {
511 ci_MD5_CTX md5;
512 unsigned char digest[16];
513 int mckeylen;
514 /*we need to use keys in the form "search_domain:key"
515 We can not use keys bigger than MC_MAXKEYLEN
516 */
517 if (strlen(key)+strlen(search_domain)+2 < MC_MAXKEYLEN) {
518 mckeylen = sprintf(mckey, "v%s:%s", search_domain, key);
519 } else if (USE_MD5_SUM_KEYS) {
520 ci_MD5Init(&md5);
521 ci_MD5Update(&md5, (const unsigned char *)key, strlen(key));
522 ci_MD5Final(digest, &md5);
523
524 mckeylen = sprintf(mckey, "v%s:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
525 search_domain,
526 digest[0], digest[1], digest[2], digest[3],
527 digest[4], digest[5], digest[6], digest[7],
528 digest[8], digest[9], digest[10], digest[11],
529 digest[12], digest[13], digest[14], digest[15]);
530 } else {
531 mckeylen = 0;
532 }
533
534 return mckeylen;
535 }
536