1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "config.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 
7 #include <memcached/engine.h>
8 #include "genhash.h"
9 
10 #include "bucket_engine.h"
11 
12 #define MAGIC 0xbeefcafe
13 
14 #define ITEM_LINKED 1
15 #define ITEM_WITH_CAS 2
16 
17 struct mock_stats {
18     int get_reqs;
19     int set_reqs;
20     int current;
21 };
22 
23 struct mock_engine {
24     ENGINE_HANDLE_V1 engine;
25     uint64_t magic;
26     SERVER_HANDLE_V1 *server;
27     bool initialized;
28     genhash_t *hashtbl;
29     struct mock_stats stats;
30     int disconnects;
31     uint64_t magic2;
32 
33     union {
34       engine_info engine_info;
35       char buffer[sizeof(engine_info) +
36                   (sizeof(feature_info) * LAST_REGISTERED_ENGINE_FEATURE)];
37     } info;
38 };
39 
40 typedef struct mock_item {
41     uint64_t cas;
42     rel_time_t exptime; /**< When the item will expire (relative to process
43                              * startup) */
44     uint32_t nbytes; /**< The total size of the data (in bytes) */
45     uint32_t flags; /**< Flags associated with the item (in network byte order)*/
46     uint8_t datatype;
47     uint8_t clsid; /** class id for the object */
48     uint16_t nkey; /**< The total length of the key (in bytes) */
49 } mock_item;
50 
51 MEMCACHED_PUBLIC_API
52 ENGINE_ERROR_CODE create_instance(uint64_t interface,
53                                   GET_SERVER_API gsapi,
54                                   ENGINE_HANDLE **handle);
55 
56 static const engine_info* mock_get_info(ENGINE_HANDLE* handle);
57 static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
58                                          const char* config_str);
59 static void mock_destroy(ENGINE_HANDLE* handle,
60                          const bool force);
61 static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
62                                             const void* cookie,
63                                             item **item,
64                                             const void* key,
65                                             const size_t nkey,
66                                             const size_t nbytes,
67                                             const int flags,
68                                             const rel_time_t exptime,
69                                             uint8_t datatype);
70 static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
71                                           const void* cookie,
72                                           const void* key,
73                                           const size_t nkey,
74                                           uint64_t* cas,
75                                           uint16_t vbucket);
76 static void mock_item_release(ENGINE_HANDLE* handle,
77                               const void *cookie, item* item);
78 static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
79                                   const void* cookie,
80                                   item** item,
81                                   const void* key,
82                                   const int nkey,
83                                   uint16_t vbucket);
84 static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
85                                         const void *cookie,
86                                         const char *stat_key,
87                                         int nkey,
88                                         ADD_STAT add_stat);
89 static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie);
90 static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
91                                     const void *cookie,
92                                     item* item,
93                                     uint64_t *cas,
94                                     ENGINE_STORE_OPERATION operation,
95                                     uint16_t vbucket);
96 static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
97                                          const void* cookie,
98                                          const void* key,
99                                          const int nkey,
100                                          const bool increment,
101                                          const bool create,
102                                          const uint64_t delta,
103                                          const uint64_t initial,
104                                          const rel_time_t exptime,
105                                          uint64_t *cas,
106                                          uint8_t datatype,
107                                          uint64_t *result,
108                                          uint16_t vbucket);
109 static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
110                                     const void* cookie, time_t when);
111 static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
112                                               const void* cookie,
113                                               protocol_binary_request_header *request,
114                                               ADD_RESPONSE response);
115 static char* item_get_data(const item* item);
116 static const char* item_get_key(const item* item);
117 static void item_set_cas(ENGINE_HANDLE* handle, const void *cookie,
118                          item* item, uint64_t val);
119 static uint64_t item_get_cas(const item* item);
120 
121 static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
122                           const item* item, item_info *item_info);
123 
handle_disconnect(const void *cookie, ENGINE_EVENT_TYPE type, const void *event_data, const void *cb_data)124 static void handle_disconnect(const void *cookie,
125                               ENGINE_EVENT_TYPE type,
126                               const void *event_data,
127                               const void *cb_data) {
128     struct mock_engine *h = (struct mock_engine*)cb_data;
129     (void)cookie;
130     (void)event_data;
131     cb_assert(type == ON_DISCONNECT);
132     ++h->disconnects;
133 }
134 
mock_tap_iterator(ENGINE_HANDLE* handle, const void *cookie, item **it, void **engine_specific, uint16_t *nengine_specific, uint8_t *ttl, uint16_t *flags, uint32_t *seqno, uint16_t *vbucket)135 static tap_event_t mock_tap_iterator(ENGINE_HANDLE* handle,
136                                      const void *cookie,
137                                      item **it,
138                                      void **engine_specific,
139                                      uint16_t *nengine_specific,
140                                      uint8_t *ttl,
141                                      uint16_t *flags,
142                                      uint32_t *seqno,
143                                      uint16_t *vbucket)
144 {
145     struct mock_engine *e = (struct mock_engine*)handle;
146     e->server->cookie->release(cookie);
147     (void)it; (void)engine_specific; (void)nengine_specific;
148     (void)ttl;(void)flags; (void)seqno; (void)vbucket;
149     return TAP_DISCONNECT;
150 }
151 
152 
153 
mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie, const void* client, size_t nclient, uint32_t flags, const void* userdata, size_t nuserdata)154 static TAP_ITERATOR mock_get_tap_iterator(ENGINE_HANDLE* handle, const void* cookie,
155                                           const void* client, size_t nclient,
156                                           uint32_t flags,
157                                           const void* userdata, size_t nuserdata) {
158     struct mock_engine *e = (struct mock_engine*)handle;
159     (void)cookie;
160     (void)client;
161     (void)nclient;
162     (void)flags;
163     (void)userdata;
164     (void)nuserdata;
165     cb_assert(e->magic == MAGIC);
166     cb_assert(e->magic2 == MAGIC);
167 
168     e->server->cookie->reserve(cookie);
169     return mock_tap_iterator;
170 }
171 
mock_tap_notify(ENGINE_HANDLE* handle, const void *cookie, void *engine_specific, uint16_t nengine, uint8_t ttl, uint16_t tap_flags, tap_event_t tap_event, uint32_t tap_seqno, const void *key, size_t nkey, uint32_t flags, uint32_t exptime, uint64_t cas, uint8_t datatype, const void *data, size_t ndata, uint16_t vbucket)172 static ENGINE_ERROR_CODE mock_tap_notify(ENGINE_HANDLE* handle,
173                                          const void *cookie,
174                                          void *engine_specific,
175                                          uint16_t nengine,
176                                          uint8_t ttl,
177                                          uint16_t tap_flags,
178                                          tap_event_t tap_event,
179                                          uint32_t tap_seqno,
180                                          const void *key,
181                                          size_t nkey,
182                                          uint32_t flags,
183                                          uint32_t exptime,
184                                          uint64_t cas,
185                                          uint8_t datatype,
186                                          const void *data,
187                                          size_t ndata,
188                                          uint16_t vbucket) {
189     struct mock_engine *e = (struct mock_engine*)handle;
190     (void)e;
191     (void)cookie;
192     (void)engine_specific;
193     (void)nengine;
194     (void)ttl;
195     (void)tap_flags;
196     (void)tap_event;
197     (void)tap_seqno;
198     (void)key;
199     (void)nkey;
200     (void)flags;
201     (void)exptime;
202     (void)cas;
203     (void)datatype;
204     (void)data;
205     (void)ndata;
206     (void)vbucket;
207     cb_assert(e->magic == MAGIC);
208     cb_assert(e->magic2 == MAGIC);
209     return ENGINE_SUCCESS;
210 }
211 
212 MEMCACHED_PUBLIC_API
create_instance(uint64_t interface, GET_SERVER_API gsapi, ENGINE_HANDLE **handle)213 ENGINE_ERROR_CODE create_instance(uint64_t interface,
214                                   GET_SERVER_API gsapi,
215                                   ENGINE_HANDLE **handle) {
216     struct mock_engine *h;
217     if (interface != 1) {
218         return ENGINE_ENOTSUP;
219     }
220 
221     h = calloc(sizeof(struct mock_engine), 1);
222     cb_assert(h);
223     h->engine.interface.interface = 1;
224     h->engine.get_info = mock_get_info;
225     h->engine.initialize = mock_initialize;
226     h->engine.destroy = mock_destroy;
227     h->engine.allocate = mock_item_allocate;
228     h->engine.remove = mock_item_delete;
229     h->engine.release = mock_item_release;
230     h->engine.get = mock_get;
231     h->engine.get_stats = mock_get_stats;
232     h->engine.reset_stats = mock_reset_stats;
233     h->engine.store = mock_store;
234     h->engine.arithmetic = mock_arithmetic;
235     h->engine.flush = mock_flush;
236     h->engine.unknown_command = mock_unknown_command;
237     h->engine.item_set_cas = item_set_cas;
238     h->engine.get_item_info = get_item_info;
239     h->engine.get_tap_iterator = mock_get_tap_iterator;
240     h->engine.tap_notify = mock_tap_notify;
241 
242     h->server = gsapi();
243 
244     h->magic = MAGIC;
245     h->magic2 = MAGIC;
246 
247     h->info.engine_info.description = "Mock engine v0.2";
248     h->info.engine_info.num_features = 0;
249 
250     *handle = (ENGINE_HANDLE *)h;
251 
252     return ENGINE_SUCCESS;
253 }
254 
get_handle(ENGINE_HANDLE* handle)255 static struct mock_engine* get_handle(ENGINE_HANDLE* handle) {
256     struct mock_engine *e = (struct mock_engine*)handle;
257     cb_assert(e->magic == MAGIC);
258     cb_assert(e->magic2 == MAGIC);
259     return e;
260 }
261 
mock_get_info(ENGINE_HANDLE* handle)262 static const engine_info* mock_get_info(ENGINE_HANDLE* handle) {
263     return &get_handle(handle)->info.engine_info;
264 }
265 
my_hash_eq(const void *k1, size_t nkey1, const void *k2, size_t nkey2)266 static int my_hash_eq(const void *k1, size_t nkey1,
267                       const void *k2, size_t nkey2) {
268     return nkey1 == nkey2 && memcmp(k1, k2, nkey1) == 0;
269 }
270 
hash_strdup(const void *k, size_t nkey)271 static void* hash_strdup(const void *k, size_t nkey) {
272     void *rv = calloc(nkey, 1);
273     cb_assert(rv);
274     memcpy(rv, k, nkey);
275     return rv;
276 }
277 
noop_dup(const void* ob, size_t vlen)278 static void* noop_dup(const void* ob, size_t vlen) {
279     (void)vlen;
280     return (void*)ob;
281 }
282 
noop_free(void* ob)283 static void noop_free(void* ob) {
284     (void)ob;
285     /* Nothing */
286 }
287 
288 static struct hash_ops my_hash_ops;
289 
mock_initialize(ENGINE_HANDLE* handle, const char* config_str)290 static ENGINE_ERROR_CODE mock_initialize(ENGINE_HANDLE* handle,
291                                          const char* config_str) {
292     struct mock_engine* se = get_handle(handle);
293     cb_assert(!se->initialized);
294 
295     my_hash_ops.hashfunc = genhash_string_hash;
296     my_hash_ops.hasheq = my_hash_eq;
297     my_hash_ops.dupKey = hash_strdup;
298     my_hash_ops.dupValue = noop_dup;
299     my_hash_ops.freeKey = free;
300     my_hash_ops.freeValue = noop_free;
301 
302     cb_assert(my_hash_ops.dupKey);
303 
304     if (strcmp(config_str, "no_alloc") != 0) {
305         se->hashtbl = genhash_init(1, my_hash_ops);
306         cb_assert(se->hashtbl);
307     }
308 
309     se->server->callback->register_callback((ENGINE_HANDLE*)se, ON_DISCONNECT,
310                                             handle_disconnect, se);
311 
312     se->initialized = true;
313 
314     return ENGINE_SUCCESS;
315 }
316 
mock_destroy(ENGINE_HANDLE* handle, const bool force)317 static void mock_destroy(ENGINE_HANDLE* handle,
318                          const bool force) {
319     struct mock_engine* se = get_handle(handle);
320     (void)force;
321 
322     if (se->initialized) {
323         se->initialized = false;
324         genhash_free(se->hashtbl);
325         free(se);
326     }
327 }
328 
get_ht(ENGINE_HANDLE *handle)329 static genhash_t *get_ht(ENGINE_HANDLE *handle) {
330     struct mock_engine* se = get_handle(handle);
331     cb_assert(se->initialized);
332     return se->hashtbl;
333 }
334 
mock_item_allocate(ENGINE_HANDLE* handle, const void* cookie, item **it, const void* key, const size_t nkey, const size_t nbytes, const int flags, const rel_time_t exptime, uint8_t datatype)335 static ENGINE_ERROR_CODE mock_item_allocate(ENGINE_HANDLE* handle,
336                                             const void* cookie,
337                                             item **it,
338                                             const void* key,
339                                             const size_t nkey,
340                                             const size_t nbytes,
341                                             const int flags,
342                                             const rel_time_t exptime,
343                                             uint8_t datatype) {
344     (void)cookie;
345     /* Only perform allocations if there's a hashtable. */
346     if (get_ht(handle) != NULL) {
347         size_t to_alloc = sizeof(mock_item) + nkey + nbytes;
348         *it = calloc(to_alloc, 1);
349     } else {
350         *it = NULL;
351     }
352     /* If an allocation was requested *and* worked, fill and report success */
353     if (*it) {
354         mock_item* i = (mock_item*) *it;
355         i->datatype = datatype;
356         i->exptime = exptime;
357         i->nbytes = (uint32_t)nbytes;
358         i->flags = flags;
359         i->nkey = (uint16_t)nkey;
360         memcpy((char*)item_get_key(i), key, nkey);
361         return ENGINE_SUCCESS;
362     } else {
363         return ENGINE_ENOMEM;
364     }
365 }
366 
mock_item_delete(ENGINE_HANDLE* handle, const void* cookie, const void* key, const size_t nkey, uint64_t* cas, uint16_t vbucket)367 static ENGINE_ERROR_CODE mock_item_delete(ENGINE_HANDLE* handle,
368                                           const void* cookie,
369                                           const void* key,
370                                           const size_t nkey,
371                                           uint64_t* cas,
372                                           uint16_t vbucket) {
373     int r = genhash_delete_all(get_ht(handle), key, nkey);
374     (void)cookie;
375     (void)cas;
376     (void)vbucket;
377     return r > 0 ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
378 }
379 
mock_item_release(ENGINE_HANDLE* handle, const void *cookie, item* itm)380 static void mock_item_release(ENGINE_HANDLE* handle,
381                               const void *cookie, item* itm) {
382     (void)handle;
383     (void)cookie;
384     free(itm);
385 }
386 
mock_get(ENGINE_HANDLE* handle, const void* cookie, item** itm, const void* key, const int nkey, uint16_t vbucket)387 static ENGINE_ERROR_CODE mock_get(ENGINE_HANDLE* handle,
388                                   const void* cookie,
389                                   item** itm,
390                                   const void* key,
391                                   const int nkey,
392                                   uint16_t vbucket) {
393     (void)cookie;
394     (void)vbucket;
395     *itm = genhash_find(get_ht(handle), key, nkey);
396 
397     return *itm ? ENGINE_SUCCESS : ENGINE_KEY_ENOENT;
398 }
399 
mock_get_stats(ENGINE_HANDLE* handle, const void* cookie, const char* stat_key, int nkey, ADD_STAT add_stat)400 static ENGINE_ERROR_CODE mock_get_stats(ENGINE_HANDLE* handle,
401                                         const void* cookie,
402                                         const char* stat_key,
403                                         int nkey,
404                                         ADD_STAT add_stat)
405 {
406     (void)handle;
407     (void)cookie;
408     (void)stat_key;
409     (void)nkey;
410     (void)add_stat;
411     /* TODO:  Implement */
412     return ENGINE_SUCCESS;
413 }
414 
mock_store(ENGINE_HANDLE* handle, const void *cookie, item* itm, uint64_t *cas, ENGINE_STORE_OPERATION operation, uint16_t vbucket)415 static ENGINE_ERROR_CODE mock_store(ENGINE_HANDLE* handle,
416                                     const void *cookie,
417                                     item* itm,
418                                     uint64_t *cas,
419                                     ENGINE_STORE_OPERATION operation,
420                                     uint16_t vbucket) {
421     mock_item* it = (mock_item*)itm;
422     (void)cookie;
423     (void)cas;
424     (void)vbucket;
425     (void)operation;
426     genhash_update(get_ht(handle), item_get_key(itm), it->nkey, itm, 0);
427     return ENGINE_SUCCESS;
428 }
429 
mock_arithmetic(ENGINE_HANDLE* handle, const void* cookie, const void* key, const int nkey, const bool increment, const bool create, const uint64_t delta, const uint64_t initial, const rel_time_t exptime, uint64_t *cas, uint8_t datatype, uint64_t *result, uint16_t vbucket)430 static ENGINE_ERROR_CODE mock_arithmetic(ENGINE_HANDLE* handle,
431                                          const void* cookie,
432                                          const void* key,
433                                          const int nkey,
434                                          const bool increment,
435                                          const bool create,
436                                          const uint64_t delta,
437                                          const uint64_t initial,
438                                          const rel_time_t exptime,
439                                          uint64_t *cas,
440                                          uint8_t datatype,
441                                          uint64_t *result,
442                                          uint16_t vbucket) {
443     item *item_in = NULL, *item_out = NULL;
444     int flags = 0;
445     char buf[32];
446     ENGINE_ERROR_CODE rv;
447     (void)increment;
448     (void)vbucket;
449     *cas = 0;
450 
451     if (mock_get(handle, cookie, &item_in, key, nkey, 0) == ENGINE_SUCCESS) {
452         /* Found, just do the math. */
453         /* This is all int stuff, just to make it easy. */
454         *result = atoi(item_get_data(item_in));
455         *result += delta;
456         flags = ((mock_item*) item_in)->flags;
457     } else if (create) {
458         /* Not found, do the initialization */
459         *result = initial;
460     } else {
461         /* Reject. */
462         return ENGINE_KEY_ENOENT;
463     }
464 
465     snprintf(buf, sizeof(buf), "%"PRIu64, *result);
466     if((rv = mock_item_allocate(handle, cookie, &item_out,
467                                 key, nkey,
468                                 strlen(buf) + 1,
469                                 flags, exptime,
470                                 datatype)) != ENGINE_SUCCESS) {
471         return rv;
472     }
473     memcpy(item_get_data(item_out), buf, strlen(buf) + 1);
474     mock_store(handle, cookie, item_out, 0, OPERATION_SET, 0);
475     return ENGINE_SUCCESS;
476 }
477 
mock_flush(ENGINE_HANDLE* handle, const void* cookie, time_t when)478 static ENGINE_ERROR_CODE mock_flush(ENGINE_HANDLE* handle,
479                                     const void* cookie, time_t when) {
480     (void)cookie;
481     (void)when;
482     genhash_clear(get_ht(handle));
483     return ENGINE_SUCCESS;
484 }
485 
mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie)486 static void mock_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {
487     (void)handle;
488     (void)cookie;
489     /* TODO:  Implement */
490 }
491 
mock_unknown_command(ENGINE_HANDLE* handle, const void* cookie, protocol_binary_request_header *request, ADD_RESPONSE response)492 static ENGINE_ERROR_CODE mock_unknown_command(ENGINE_HANDLE* handle,
493                                               const void* cookie,
494                                               protocol_binary_request_header *request,
495                                               ADD_RESPONSE response)
496 {
497     switch (request->request.opcode) {
498     case PROTOCOL_BINARY_CMD_GET_REPLICA:
499     case PROTOCOL_BINARY_CMD_EVICT_KEY:
500     case PROTOCOL_BINARY_CMD_GET_LOCKED:
501     case PROTOCOL_BINARY_CMD_UNLOCK_KEY:
502     case PROTOCOL_BINARY_CMD_GET_META:
503     case PROTOCOL_BINARY_CMD_GETQ_META:
504     case PROTOCOL_BINARY_CMD_SET_WITH_META:
505     case PROTOCOL_BINARY_CMD_SETQ_WITH_META:
506     case PROTOCOL_BINARY_CMD_DEL_WITH_META:
507     case PROTOCOL_BINARY_CMD_DELQ_WITH_META:
508         return ENGINE_SUCCESS;
509     }
510 
511     (void)handle; (void)cookie; (void)response;
512     return ENGINE_ENOTSUP;
513 }
514 
item_get_cas(const item* itm)515 static uint64_t item_get_cas(const item* itm)
516 {
517     const mock_item* it = (mock_item*)itm;
518     return it->cas;
519 }
520 
item_set_cas(ENGINE_HANDLE *handle, const void *cookie, item* itm, uint64_t val)521 static void item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
522                          item* itm, uint64_t val)
523 {
524     mock_item* it = (mock_item*)itm;
525     (void)handle;
526     (void)cookie;
527     it->cas = val;
528 }
529 
item_get_key(const item* itm)530 static const char* item_get_key(const item* itm)
531 {
532     const mock_item* it = (mock_item*)itm;
533     char *ret = (void*)(it + 1);
534     return ret;
535 }
536 
item_get_data(const item* itm)537 static char* item_get_data(const item* itm)
538 {
539     const mock_item* it = (mock_item*)itm;
540     return ((char*)item_get_key(itm)) + it->nkey;
541 }
542 
get_item_info(ENGINE_HANDLE *handle, const void *cookie, const item* itm, item_info *itm_info)543 static bool get_item_info(ENGINE_HANDLE *handle, const void *cookie,
544                           const item* itm, item_info *itm_info)
545 {
546     mock_item* it = (mock_item*)itm;
547     (void)handle;
548     (void)cookie;
549     if (itm_info->nvalue < 1) {
550         return false;
551     }
552     itm_info->cas = item_get_cas(it);
553     itm_info->exptime = it->exptime;
554     itm_info->nbytes = it->nbytes;
555     itm_info->flags = it->flags;
556     itm_info->clsid = it->clsid;
557     itm_info->nkey = it->nkey;
558     itm_info->nvalue = 1;
559     itm_info->key = item_get_key(it);
560     itm_info->value[0].iov_base = item_get_data(it);
561     itm_info->value[0].iov_len = it->nbytes;
562     return true;
563 }
564