Merge lp:~tangent-org/libmemcached/1.0-build/ Build: jenkins-Libmemcached-187
[m6w6/libmemcached] / libmemcachedprotocol / ascii_handler.c
1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 *
3 * Libmemcached library
4 *
5 * Copyright (C) 2011 Data Differential, http://datadifferential.com/
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following disclaimer
16 * in the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * * The names of its contributors may not be used to endorse or
20 * promote products derived from this software without specific prior
21 * written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
31 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
33 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 */
36
37 #include <libmemcachedprotocol/common.h>
38
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <errno.h>
44
45
46 static void print_ascii_command(memcached_protocol_client_st *client)
47 {
48 if (client->is_verbose)
49 {
50 switch (client->ascii_command)
51 {
52 case SET_CMD:
53 fprintf(stderr, "%s:%d SET_CMD\n", __FILE__, __LINE__);
54 break;
55
56 case ADD_CMD:
57 fprintf(stderr, "%s:%d ADD_CMD\n", __FILE__, __LINE__);
58 break;
59
60 case REPLACE_CMD:
61 fprintf(stderr, "%s:%d REPLACE_CMD\n", __FILE__, __LINE__);
62 break;
63
64 case CAS_CMD:
65 fprintf(stderr, "%s:%d CAS_CMD\n", __FILE__, __LINE__);
66 break;
67
68 case APPEND_CMD:
69 fprintf(stderr, "%s:%d APPEND_CMD\n", __FILE__, __LINE__);
70 break;
71
72 case PREPEND_CMD:
73 fprintf(stderr, "%s:%d PREPEND_CMD\n", __FILE__, __LINE__);
74 break;
75
76 case DELETE_CMD:
77 fprintf(stderr, "%s:%d DELETE_CMD\n", __FILE__, __LINE__);
78 break;
79
80 case INCR_CMD: /* FALLTHROUGH */
81 fprintf(stderr, "%s:%d INCR_CMD\n", __FILE__, __LINE__);
82 break;
83
84 case DECR_CMD:
85 fprintf(stderr, "%s:%d DECR_CMD\n", __FILE__, __LINE__);
86 break;
87
88 case STATS_CMD:
89 fprintf(stderr, "%s:%d STATS_CMD\n", __FILE__, __LINE__);
90 break;
91
92 case FLUSH_ALL_CMD:
93 fprintf(stderr, "%s:%d FLUSH_ALL_CMD\n", __FILE__, __LINE__);
94 break;
95
96 case VERSION_CMD:
97 fprintf(stderr, "%s:%d VERSION_CMD\n", __FILE__, __LINE__);
98 break;
99
100 case QUIT_CMD:
101 fprintf(stderr, "%s:%d QUIT_CMD\n", __FILE__, __LINE__);
102 break;
103
104 case VERBOSITY_CMD:
105 fprintf(stderr, "%s:%d VERBOSITY_CMD\n", __FILE__, __LINE__);
106 break;
107
108 case GET_CMD:
109 fprintf(stderr, "%s:%d GET_CMD\n", __FILE__, __LINE__);
110 break;
111
112 case GETS_CMD:
113 fprintf(stderr, "%s:%d GETS_CMD\n", __FILE__, __LINE__);
114 break;
115
116 default:
117 case UNKNOWN_CMD:
118 fprintf(stderr, "%s:%d UNKNOWN_CMD\n", __FILE__, __LINE__);
119 break;
120
121 }
122 }
123 }
124
125 /**
126 * Try to parse a key from the string.
127 * @pointer start pointer to a pointer to the string (IN and OUT)
128 * @return length of the string of -1 if this was an illegal key (invalid
129 * characters or invalid length)
130 * @todo add length!
131 */
132 static uint16_t parse_ascii_key(char **start)
133 {
134 uint16_t len= 0;
135 char *c= *start;
136 /* Strip leading whitespaces */
137 while (isspace(*c))
138 {
139 ++c;
140 }
141
142 *start= c;
143
144 while (*c != '\0' && !isspace(*c) && !iscntrl(*c))
145 {
146 ++c;
147 ++len;
148 }
149
150
151 if (len == 0 || len > 240 || (*c != '\0' && *c != '\r' && iscntrl(*c)))
152 {
153 return 0;
154 }
155
156 return len;
157 }
158
159 /**
160 * Spool a zero-terminated string
161 * @param client destination
162 * @param text the text to spool
163 * @return status of the spool operation
164 */
165 static protocol_binary_response_status raw_response_handler(memcached_protocol_client_st *client, const char *text)
166 {
167 if (client->is_verbose)
168 {
169 fprintf(stderr, "%s:%d %s\n", __FILE__, __LINE__, text);
170 }
171
172 if (client->root->drain(client) == false)
173 {
174 return PROTOCOL_BINARY_RESPONSE_EINTERNAL;
175 }
176
177 assert(client->output != NULL);
178 #if 0
179 if (client->output == NULL)
180 {
181 /* I can write directly to the socket.... */
182 do
183 {
184 size_t num_bytes= len -offset;
185 ssize_t nw= client->root->send(client,
186 client->sock,
187 ptr + offset,
188 num_bytes);
189 if (nw == -1)
190 {
191 if (get_socket_errno() == EWOULDBLOCK)
192 {
193 break;
194 }
195 else if (get_socket_errno() != EINTR)
196 {
197 client->error= errno;
198 return PROTOCOL_BINARY_RESPONSE_EINTERNAL;
199 }
200 }
201 else
202 {
203 offset += (size_t)nw;
204 }
205 } while (offset < len);
206 }
207 #endif
208
209 return client->root->spool(client, text, strlen(text));
210 }
211
212 /**
213 * Send a "CLIENT_ERROR" message back to the client with the correct
214 * format of the command being sent
215 * @param client the client to send the message to
216 */
217 static void send_command_usage(memcached_protocol_client_st *client)
218 {
219 const char *errmsg[]= {
220 [GET_CMD]= "CLIENT_ERROR: Syntax error: get <key>*\r\n",
221 [GETS_CMD]= "CLIENT_ERROR: Syntax error: gets <key>*\r\n",
222 [SET_CMD]= "CLIENT_ERROR: Syntax error: set <key> <flags> <exptime> <bytes> [noreply]\r\n",
223 [ADD_CMD]= "CLIENT_ERROR: Syntax error: add <key> <flags> <exptime> <bytes> [noreply]\r\n",
224 [REPLACE_CMD]= "CLIENT_ERROR: Syntax error: replace <key> <flags> <exptime> <bytes> [noreply]\r\n",
225 [CAS_CMD]= "CLIENT_ERROR: Syntax error: cas <key> <flags> <exptime> <bytes> <casid> [noreply]\r\n",
226 [APPEND_CMD]= "CLIENT_ERROR: Syntax error: append <key> <flags> <exptime> <bytes> [noreply]\r\n",
227 [PREPEND_CMD]= "CLIENT_ERROR: Syntax error: prepend <key> <flags> <exptime> <bytes> [noreply]\r\n",
228 [DELETE_CMD]= "CLIENT_ERROR: Syntax error: delete_object <key> [noreply]\r\n",
229 [INCR_CMD]= "CLIENT_ERROR: Syntax error: incr <key> <value> [noreply]\r\n",
230 [DECR_CMD]= "CLIENT_ERROR: Syntax error: decr <key> <value> [noreply]\r\n",
231 [STATS_CMD]= "CLIENT_ERROR: Syntax error: stats [key]\r\n",
232 [FLUSH_ALL_CMD]= "CLIENT_ERROR: Syntax error: flush_all [timeout] [noreply]\r\n",
233 [VERSION_CMD]= "CLIENT_ERROR: Syntax error: version\r\n",
234 [QUIT_CMD]="CLIENT_ERROR: Syntax error: quit\r\n",
235
236 [VERBOSITY_CMD]= "CLIENT_ERROR: Syntax error: verbosity <num>\r\n",
237 [UNKNOWN_CMD]= "CLIENT_ERROR: Unknown command\r\n",
238 };
239
240 client->mute = false;
241 raw_response_handler(client, errmsg[client->ascii_command]);
242 }
243
244 /**
245 * Callback for the VERSION responses
246 * @param cookie client identifier
247 * @param text the length of the body
248 * @param textlen the length of the body
249 */
250 static protocol_binary_response_status ascii_version_response_handler(const void *cookie,
251 const void *text,
252 uint32_t textlen)
253 {
254 memcached_protocol_client_st *client= (memcached_protocol_client_st*)cookie;
255 raw_response_handler(client, "VERSION ");
256 client->root->spool(client, text, textlen);
257 raw_response_handler(client, "\r\n");
258 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
259 }
260
261 /**
262 * Callback for the GET/GETQ/GETK and GETKQ responses
263 * @param cookie client identifier
264 * @param key the key for the item
265 * @param keylen the length of the key
266 * @param body the length of the body
267 * @param bodylen the length of the body
268 * @param flags the flags for the item
269 * @param cas the CAS id for the item
270 */
271 static protocol_binary_response_status
272 ascii_get_response_handler(const void *cookie,
273 const void *key,
274 uint16_t keylen,
275 const void *body,
276 uint32_t bodylen,
277 uint32_t flags,
278 uint64_t cas)
279 {
280 memcached_protocol_client_st *client= (void*)cookie;
281 char buffer[300];
282 strcpy(buffer, "VALUE ");
283 const char *source= key;
284 char *dest= buffer + 6;
285
286 for (int x= 0; x < keylen; ++x)
287 {
288 if (*source != '\0' && !isspace(*source) && !iscntrl(*source))
289 {
290 *dest= *source;
291 }
292 else
293 {
294 return PROTOCOL_BINARY_RESPONSE_EINVAL; /* key constraints in ascii */
295 }
296
297 ++dest;
298 ++source;
299 }
300
301 size_t used= (size_t)(dest - buffer);
302
303 if (client->ascii_command == GETS_CMD)
304 {
305 snprintf(dest, sizeof(buffer) - used, " %u %u %" PRIu64 "\r\n", flags,
306 bodylen, cas);
307 }
308 else
309 {
310 snprintf(dest, sizeof(buffer) - used, " %u %u\r\n", flags, bodylen);
311 }
312
313 client->root->spool(client, buffer, strlen(buffer));
314 client->root->spool(client, body, bodylen);
315 client->root->spool(client, "\r\n", 2);
316
317 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
318 }
319
320 /**
321 * Callback for the STAT responses
322 * @param cookie client identifier
323 * @param key the key for the item
324 * @param keylen the length of the key
325 * @param body the length of the body
326 * @param bodylen the length of the body
327 */
328 static protocol_binary_response_status ascii_stat_response_handler(const void *cookie,
329 const void *key,
330 uint16_t keylen,
331 const void *body,
332 uint32_t bodylen)
333 {
334
335 memcached_protocol_client_st *client= (void*)cookie;
336
337 if (key != NULL)
338 {
339 raw_response_handler(client, "STAT ");
340 client->root->spool(client, key, keylen);
341 raw_response_handler(client, " ");
342 client->root->spool(client, body, bodylen);
343 raw_response_handler(client, "\r\n");
344 }
345 else
346 {
347 raw_response_handler(client, "END\r\n");
348 }
349
350 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
351 }
352
353 /**
354 * Process a get or a gets request.
355 * @param client the client handle
356 * @param buffer the complete get(s) command
357 * @param end the last character in the command
358 */
359 static void ascii_process_gets(memcached_protocol_client_st *client,
360 char *buffer, char *end)
361 {
362 char *key= buffer;
363
364 /* Skip command */
365 key += (client->ascii_command == GETS_CMD) ? 5 : 4;
366
367 int num_keys= 0;
368 while (key < end)
369 {
370 uint16_t nkey= parse_ascii_key(&key);
371 if (nkey == 0) /* Invalid key... stop processing this line */
372 {
373 break;
374 }
375
376 (void)client->root->callback->interface.v1.get(client, key, nkey,
377 ascii_get_response_handler);
378 key += nkey;
379 ++num_keys;
380 }
381
382 if (num_keys == 0)
383 {
384 send_command_usage(client);
385 }
386 else
387 {
388 client->root->spool(client, "END\r\n", 5);
389 }
390 }
391
392 /**
393 * Try to split up the command line "asdf asdf asdf asdf\n" into an
394 * argument vector for easier parsing.
395 * @param start the first character in the command line
396 * @param end the last character in the command line ("\n")
397 * @param vec the vector to insert the pointers into
398 * @size the number of elements in the vector
399 * @return the number of tokens in the vector
400 */
401 static int ascii_tokenize_command(char *str, char *end, char **vec, int size)
402 {
403 int elem= 0;
404
405 while (str < end)
406 {
407 /* Skip leading blanks */
408 while (str < end && isspace(*str))
409 {
410 ++str;
411 }
412
413 if (str == end)
414 {
415 return elem;
416 }
417
418 vec[elem++]= str;
419 /* find the next non-blank field */
420 while (str < end && !isspace(*str))
421 {
422 ++str;
423 }
424
425 /* zero-terminate it for easier parsing later on */
426 *str= '\0';
427 ++str;
428
429 /* Is the vector full? */
430 if (elem == size)
431 {
432 break;
433 }
434 }
435
436 return elem;
437 }
438
439 /**
440 * If we for some reasons needs to push the line back to read more
441 * data we have to reverse the tokenization. Just do the brain-dead replace
442 * of all '\0' to ' ' and set the last character to '\n'. We could have used
443 * the vector we created, but then we would have to search for all of the
444 * spaces we ignored...
445 * @param start pointer to the first character in the buffer to recover
446 * @param end pointer to the last character in the buffer to recover
447 */
448 static void recover_tokenize_command(char *start, char *end)
449 {
450 while (start < end)
451 {
452 if (*start == '\0')
453 *start= ' ';
454 ++start;
455 }
456
457 *end= '\n';
458 }
459
460 /**
461 * Convert the textual command into a comcode
462 */
463 static enum ascii_cmd ascii_to_cmd(char *start, size_t length)
464 {
465 struct {
466 const char *cmd;
467 size_t len;
468 enum ascii_cmd cc;
469 } commands[]= {
470 { .cmd= "get", .len= 3, .cc= GET_CMD },
471 { .cmd= "gets", .len= 4, .cc= GETS_CMD },
472 { .cmd= "set", .len= 3, .cc= SET_CMD },
473 { .cmd= "add", .len= 3, .cc= ADD_CMD },
474 { .cmd= "replace", .len= 7, .cc= REPLACE_CMD },
475 { .cmd= "cas", .len= 3, .cc= CAS_CMD },
476 { .cmd= "append", .len= 6, .cc= APPEND_CMD },
477 { .cmd= "prepend", .len= 7, .cc= PREPEND_CMD },
478 { .cmd= "delete_object", .len= 6, .cc= DELETE_CMD },
479 { .cmd= "incr", .len= 4, .cc= INCR_CMD },
480 { .cmd= "decr", .len= 4, .cc= DECR_CMD },
481 { .cmd= "stats", .len= 5, .cc= STATS_CMD },
482 { .cmd= "flush_all", .len= 9, .cc= FLUSH_ALL_CMD },
483 { .cmd= "version", .len= 7, .cc= VERSION_CMD },
484 { .cmd= "quit", .len= 4, .cc= QUIT_CMD },
485 { .cmd= "verbosity", .len= 9, .cc= VERBOSITY_CMD },
486 { .cmd= NULL, .len= 0, .cc= UNKNOWN_CMD }};
487
488 int x= 0;
489 while (commands[x].len > 0) {
490 if (length >= commands[x].len)
491 {
492 if (strncmp(start, commands[x].cmd, commands[x].len) == 0)
493 {
494 /* Potential hit */
495 if (length == commands[x].len || isspace(*(start + commands[x].len)))
496 {
497 return commands[x].cc;
498 }
499 }
500 }
501 ++x;
502 }
503
504 return UNKNOWN_CMD;
505 }
506
507 /**
508 * Perform a delete_object operation.
509 *
510 * @param client client requesting the deletion
511 * @param tokens the command as a vector
512 * @param ntokens the number of items in the vector
513 */
514 static void process_delete(memcached_protocol_client_st *client,
515 char **tokens, int ntokens)
516 {
517 char *key= tokens[1];
518 uint16_t nkey;
519
520 if (ntokens != 2 || (nkey= parse_ascii_key(&key)) == 0)
521 {
522 send_command_usage(client);
523 return;
524 }
525
526 if (client->root->callback->interface.v1.delete_object == NULL)
527 {
528 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
529 return;
530 }
531
532 protocol_binary_response_status rval= client->root->callback->interface.v1.delete_object(client, key, nkey, 0);
533
534 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
535 {
536 raw_response_handler(client, "DELETED\r\n");
537 }
538 else if (rval == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT)
539 {
540 raw_response_handler(client, "NOT_FOUND\r\n");
541 }
542 else
543 {
544 char msg[80];
545 snprintf(msg, sizeof(msg), "SERVER_ERROR: delete_object failed %u\r\n",(uint32_t)rval);
546 raw_response_handler(client, msg);
547 }
548 }
549
550 static void process_arithmetic(memcached_protocol_client_st *client,
551 char **tokens, int ntokens)
552 {
553 char *key= tokens[1];
554 uint16_t nkey;
555
556 if (ntokens != 3 || (nkey= parse_ascii_key(&key)) == 0)
557 {
558 send_command_usage(client);
559 return;
560 }
561
562 uint64_t cas;
563 uint64_t result;
564 uint64_t delta= strtoull(tokens[2], NULL, 10);
565
566 protocol_binary_response_status rval;
567 if (client->ascii_command == INCR_CMD)
568 {
569 if (client->root->callback->interface.v1.increment == NULL)
570 {
571 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
572 return;
573 }
574 rval= client->root->callback->interface.v1.increment(client,
575 key, nkey,
576 delta, 0,
577 0,
578 &result,
579 &cas);
580 }
581 else
582 {
583 if (client->root->callback->interface.v1.decrement == NULL)
584 {
585 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
586 return;
587 }
588 rval= client->root->callback->interface.v1.decrement(client,
589 key, nkey,
590 delta, 0,
591 0,
592 &result,
593 &cas);
594 }
595
596 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
597 {
598 char buffer[80];
599 snprintf(buffer, sizeof(buffer), "%"PRIu64"\r\n", result);
600 raw_response_handler(client, buffer);
601 }
602 else
603 {
604 raw_response_handler(client, "NOT_FOUND\r\n");
605 }
606 }
607
608 /**
609 * Process the stats command (with or without a key specified)
610 * @param key pointer to the first character after "stats"
611 * @param end pointer to the "\n"
612 */
613 static void process_stats(memcached_protocol_client_st *client,
614 char *key, char *end)
615 {
616 if (client->root->callback->interface.v1.stat == NULL)
617 {
618 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
619 return;
620 }
621
622 while (isspace(*key))
623 {
624 key++;
625 }
626
627 uint16_t nkey= (uint16_t)(end - key);
628 (void)client->root->callback->interface.v1.stat(client, key, nkey,
629 ascii_stat_response_handler);
630 }
631
632 static void process_version(memcached_protocol_client_st *client,
633 char **tokens, int ntokens)
634 {
635 (void)tokens;
636 if (ntokens != 1)
637 {
638 send_command_usage(client);
639 return;
640 }
641
642 if (client->root->callback->interface.v1.version == NULL)
643 {
644 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
645 return;
646 }
647
648 client->root->callback->interface.v1.version(client,
649 ascii_version_response_handler);
650 }
651
652 static void process_flush(memcached_protocol_client_st *client,
653 char **tokens, int ntokens)
654 {
655 if (ntokens > 2)
656 {
657 send_command_usage(client);
658 return;
659 }
660
661 if (client->root->callback->interface.v1.flush_object == NULL)
662 {
663 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
664 return;
665 }
666
667 uint32_t timeout= 0;
668 if (ntokens == 2)
669 {
670 timeout= (uint32_t)strtoul(tokens[1], NULL, 10);
671 }
672
673 protocol_binary_response_status rval;
674 rval= client->root->callback->interface.v1.flush_object(client, timeout);
675 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
676 raw_response_handler(client, "OK\r\n");
677 else
678 raw_response_handler(client, "SERVER_ERROR: internal error\r\n");
679 }
680
681 /**
682 * Process one of the storage commands
683 * @param client the client performing the operation
684 * @param tokens the command tokens
685 * @param ntokens the number of tokens
686 * @param start pointer to the first character in the line
687 * @param end pointer to the pointer where the last character of this
688 * command is (IN and OUT)
689 * @param length the number of bytes available
690 * @return -1 if an error occurs (and we should just terminate the connection
691 * because we are out of sync)
692 * 0 storage command completed, continue processing
693 * 1 We need more data, so just go ahead and wait for more!
694 */
695 static inline int process_storage_command(memcached_protocol_client_st *client,
696 char **tokens, int ntokens, char *start,
697 char **end, ssize_t length)
698 {
699 (void)ntokens; /* already checked */
700 char *key= tokens[1];
701 uint16_t nkey= parse_ascii_key(&key);
702 if (nkey == 0)
703 {
704 /* return error */
705 raw_response_handler(client, "CLIENT_ERROR: bad key\r\n");
706 return -1;
707 }
708
709 uint32_t flags= (uint32_t)strtoul(tokens[2], NULL, 10);
710 uint32_t timeout= (uint32_t)strtoul(tokens[3], NULL, 10);
711 unsigned long nbytes= strtoul(tokens[4], NULL, 10);
712
713 /* Do we have all data? */
714 unsigned long need= nbytes + (unsigned long)((*end - start) + 1) + 2; /* \n\r\n */
715 if ((ssize_t)need > length)
716 {
717 /* Keep on reading */
718 recover_tokenize_command(start, *end);
719 return 1;
720 }
721
722 void *data= (*end) + 1;
723 uint64_t cas= 0;
724 uint64_t result_cas;
725 protocol_binary_response_status rval;
726 switch (client->ascii_command)
727 {
728 case SET_CMD:
729 rval= client->root->callback->interface.v1.set(client, key,
730 (uint16_t)nkey,
731 data,
732 (uint32_t)nbytes,
733 flags,
734 timeout, cas,
735 &result_cas);
736 break;
737 case ADD_CMD:
738 rval= client->root->callback->interface.v1.add(client, key,
739 (uint16_t)nkey,
740 data,
741 (uint32_t)nbytes,
742 flags,
743 timeout, &result_cas);
744 break;
745 case CAS_CMD:
746 cas= strtoull(tokens[5], NULL, 10);
747 /* FALLTHROUGH */
748 case REPLACE_CMD:
749 rval= client->root->callback->interface.v1.replace(client, key,
750 (uint16_t)nkey,
751 data,
752 (uint32_t)nbytes,
753 flags,
754 timeout, cas,
755 &result_cas);
756 break;
757 case APPEND_CMD:
758 rval= client->root->callback->interface.v1.append(client, key,
759 (uint16_t)nkey,
760 data,
761 (uint32_t)nbytes,
762 cas,
763 &result_cas);
764 break;
765 case PREPEND_CMD:
766 rval= client->root->callback->interface.v1.prepend(client, key,
767 (uint16_t)nkey,
768 data,
769 (uint32_t)nbytes,
770 cas,
771 &result_cas);
772 break;
773
774 /* gcc complains if I don't put all of the enums in here.. */
775 case GET_CMD:
776 case GETS_CMD:
777 case DELETE_CMD:
778 case DECR_CMD:
779 case INCR_CMD:
780 case STATS_CMD:
781 case FLUSH_ALL_CMD:
782 case VERSION_CMD:
783 case QUIT_CMD:
784 case VERBOSITY_CMD:
785 case UNKNOWN_CMD:
786 default:
787 abort(); /* impossible */
788 }
789
790 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
791 {
792 raw_response_handler(client, "STORED\r\n");
793 }
794 else
795 {
796 if (client->ascii_command == CAS_CMD)
797 {
798 if (rval == PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS)
799 {
800 raw_response_handler(client, "EXISTS\r\n");
801 }
802 else if (rval == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT)
803 {
804 raw_response_handler(client, "NOT_FOUND\r\n");
805 }
806 else
807 {
808 raw_response_handler(client, "NOT_STORED\r\n");
809 }
810 }
811 else
812 {
813 raw_response_handler(client, "NOT_STORED\r\n");
814 }
815 }
816
817 *end += nbytes + 2;
818
819 return 0;
820 }
821
822 static int process_cas_command(memcached_protocol_client_st *client,
823 char **tokens, int ntokens, char *start,
824 char **end, ssize_t length)
825 {
826 if (ntokens != 6)
827 {
828 send_command_usage(client);
829 return false;
830 }
831
832 if (client->root->callback->interface.v1.replace == NULL)
833 {
834 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
835 return false;
836 }
837
838 return process_storage_command(client, tokens, ntokens, start, end, length);
839 }
840
841 static int process_set_command(memcached_protocol_client_st *client,
842 char **tokens, int ntokens, char *start,
843 char **end, ssize_t length)
844 {
845 if (ntokens != 5)
846 {
847 send_command_usage(client);
848 return false;
849 }
850
851 if (client->root->callback->interface.v1.set == NULL)
852 {
853 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
854 return false;
855 }
856
857 return process_storage_command(client, tokens, ntokens, start, end, length);
858 }
859
860 static int process_add_command(memcached_protocol_client_st *client,
861 char **tokens, int ntokens, char *start,
862 char **end, ssize_t length)
863 {
864 if (ntokens != 5)
865 {
866 send_command_usage(client);
867 return false;
868 }
869
870 if (client->root->callback->interface.v1.add == NULL)
871 {
872 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
873 return false;
874 }
875
876 return process_storage_command(client, tokens, ntokens, start, end, length);
877 }
878
879 static int process_replace_command(memcached_protocol_client_st *client,
880 char **tokens, int ntokens, char *start,
881 char **end, ssize_t length)
882 {
883 if (ntokens != 5)
884 {
885 send_command_usage(client);
886 return false;
887 }
888
889 if (client->root->callback->interface.v1.replace == NULL)
890 {
891 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
892 return false;
893 }
894
895 return process_storage_command(client, tokens, ntokens, start, end, length);
896 }
897
898 static int process_append_command(memcached_protocol_client_st *client,
899 char **tokens, int ntokens, char *start,
900 char **end, ssize_t length)
901 {
902 if (ntokens != 5)
903 {
904 send_command_usage(client);
905 return false;
906 }
907
908 if (client->root->callback->interface.v1.append == NULL)
909 {
910 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
911 return false;
912 }
913
914 return process_storage_command(client, tokens, ntokens, start, end, length);
915 }
916
917 static int process_prepend_command(memcached_protocol_client_st *client,
918 char **tokens, int ntokens, char *start,
919 char **end, ssize_t length)
920 {
921 if (ntokens != 5)
922 {
923 send_command_usage(client);
924 return false;
925 }
926
927 if (client->root->callback->interface.v1.prepend == NULL)
928 {
929 raw_response_handler(client, "SERVER_ERROR: callback not implemented\r\n");
930 return false;
931 }
932
933 return process_storage_command(client, tokens, ntokens, start, end, length);
934 }
935
936 /**
937 * The ASCII protocol support is just one giant big hack. Instead of adding
938 * a optimal ascii support, I just convert the ASCII commands to the binary
939 * protocol and calls back into the command handlers for the binary protocol ;)
940 */
941 memcached_protocol_event_t memcached_ascii_protocol_process_data(memcached_protocol_client_st *client, ssize_t *length, void **endptr)
942 {
943 char *ptr= (char*)client->root->input_buffer;
944 *endptr= ptr;
945
946 do {
947 /* Do we have \n (indicating the command preamble)*/
948 char *end= memchr(ptr, '\n', (size_t)*length);
949 if (end == NULL)
950 {
951 *endptr= ptr;
952 return MEMCACHED_PROTOCOL_READ_EVENT;
953 }
954
955 client->ascii_command= ascii_to_cmd(ptr, (size_t)(*length));
956
957 /* we got all data available, execute the callback! */
958 if (client->root->callback->pre_execute != NULL)
959 {
960 client->root->callback->pre_execute(client, NULL);
961 }
962
963
964 /* A multiget lists all of the keys, and I don't want to have an
965 * avector of let's say 512 pointers to tokenize all of them, so let's
966 * just handle them immediately
967 */
968 if (client->ascii_command == GET_CMD ||
969 client->ascii_command == GETS_CMD)
970 {
971 if (client->root->callback->interface.v1.get != NULL)
972 {
973 ascii_process_gets(client, ptr, end);
974 }
975 else
976 {
977 raw_response_handler(client, "SERVER_ERROR: Command not implemented\n");
978 }
979 }
980 else
981 {
982 /* None of the defined commands takes 10 parameters, so lets just use
983 * that as a maximum limit.
984 */
985 char *tokens[10];
986 int ntokens= ascii_tokenize_command(ptr, end, tokens, 10);
987
988 if (ntokens < 10)
989 {
990 client->mute= strcmp(tokens[ntokens - 1], "noreply") == 0;
991 if (client->mute)
992 {
993 --ntokens; /* processed noreply token*/
994 }
995 }
996
997 int error= 0;
998
999 print_ascii_command(client);
1000 switch (client->ascii_command)
1001 {
1002 case SET_CMD:
1003 error= process_set_command(client, tokens, ntokens, ptr, &end, *length);
1004 break;
1005
1006 case ADD_CMD:
1007 error= process_add_command(client, tokens, ntokens, ptr, &end, *length);
1008 break;
1009
1010 case REPLACE_CMD:
1011 error= process_replace_command(client, tokens, ntokens, ptr, &end, *length);
1012 break;
1013
1014 case CAS_CMD:
1015 error= process_cas_command(client, tokens, ntokens, ptr, &end, *length);
1016 break;
1017
1018 case APPEND_CMD:
1019 error= process_append_command(client, tokens, ntokens, ptr, &end, *length);
1020 break;
1021
1022 case PREPEND_CMD:
1023 error= process_prepend_command(client, tokens, ntokens, ptr, &end, *length);
1024 break;
1025
1026 case DELETE_CMD:
1027 process_delete(client, tokens, ntokens);
1028 break;
1029
1030 case INCR_CMD: /* FALLTHROUGH */
1031 case DECR_CMD:
1032 process_arithmetic(client, tokens, ntokens);
1033 break;
1034
1035 case STATS_CMD:
1036 if (client->mute)
1037 {
1038 send_command_usage(client);
1039 }
1040 else
1041 {
1042 recover_tokenize_command(ptr, end);
1043 process_stats(client, ptr + 6, end);
1044 }
1045 break;
1046
1047 case FLUSH_ALL_CMD:
1048 process_flush(client, tokens, ntokens);
1049 break;
1050
1051 case VERSION_CMD:
1052 if (client->mute)
1053 {
1054 send_command_usage(client);
1055 }
1056 else
1057 {
1058 process_version(client, tokens, ntokens);
1059 }
1060 break;
1061
1062 case QUIT_CMD:
1063 if (ntokens != 1 || client->mute)
1064 {
1065 send_command_usage(client);
1066 }
1067 else
1068 {
1069 if (client->root->callback->interface.v1.quit != NULL)
1070 {
1071 client->root->callback->interface.v1.quit(client);
1072 }
1073
1074 return MEMCACHED_PROTOCOL_ERROR_EVENT;
1075 }
1076 break;
1077
1078 case VERBOSITY_CMD:
1079 if (ntokens != 2)
1080 {
1081 send_command_usage(client);
1082 }
1083 else
1084 {
1085 raw_response_handler(client, "OK\r\n");
1086 }
1087 break;
1088
1089 case UNKNOWN_CMD:
1090 send_command_usage(client);
1091 break;
1092
1093 case GET_CMD:
1094 case GETS_CMD:
1095 default:
1096 /* Should already be handled */
1097 abort();
1098 }
1099
1100 if (error == -1)
1101 {
1102 return MEMCACHED_PROTOCOL_ERROR_EVENT;
1103 }
1104 else if (error == 1)
1105 {
1106 return MEMCACHED_PROTOCOL_READ_EVENT;
1107 }
1108 }
1109
1110 if (client->root->callback->post_execute != NULL)
1111 {
1112 client->root->callback->post_execute(client, NULL);
1113 }
1114
1115 /* Move past \n */
1116 ++end;
1117 *length -= end - ptr;
1118 ptr= end;
1119 } while (*length > 0);
1120
1121 *endptr= ptr;
1122 return MEMCACHED_PROTOCOL_READ_EVENT;
1123 }