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