Merge Trunk
[m6w6/libmemcached] / libmemcached / protocol / 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 "config.h"
38
39 #include <libmemcached/protocol/common.h>
40 #include <libmemcached/byteorder.h>
41
42 #include <ctype.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46
47 /**
48 * Try to parse a key from the string.
49 * @pointer start pointer to a pointer to the string (IN and OUT)
50 * @return length of the string of -1 if this was an illegal key (invalid
51 * characters or invalid length)
52 * @todo add length!
53 */
54 static uint16_t parse_ascii_key(char **start)
55 {
56 uint16_t len= 0;
57 char *c= *start;
58 /* Strip leading whitespaces */
59 while (isspace(*c))
60 {
61 ++c;
62 }
63
64 *start= c;
65
66 while (*c != '\0' && !isspace(*c) && !iscntrl(*c))
67 {
68 ++c;
69 ++len;
70 }
71
72
73 if (len == 0 || len > 240 || (*c != '\0' && *c != '\r' && iscntrl(*c)))
74 {
75 return 0;
76 }
77
78 return len;
79 }
80
81 /**
82 * Spool a zero-terminated string
83 * @param client destination
84 * @param text the text to spool
85 * @return status of the spool operation
86 */
87 static protocol_binary_response_status
88 spool_string(memcached_protocol_client_st *client, const char *text)
89 {
90 return client->root->spool(client, text, strlen(text));
91 }
92
93 /**
94 * Send a "CLIENT_ERROR" message back to the client with the correct
95 * format of the command being sent
96 * @param client the client to send the message to
97 */
98 static void send_command_usage(memcached_protocol_client_st *client)
99 {
100 const char *errmsg[]= {
101 [GET_CMD]= "CLIENT_ERROR: Syntax error: get <key>*\r\n",
102 [GETS_CMD]= "CLIENT_ERROR: Syntax error: gets <key>*\r\n",
103 [SET_CMD]= "CLIENT_ERROR: Syntax error: set <key> <flags> <exptime> <bytes> [noreply]\r\n",
104 [ADD_CMD]= "CLIENT_ERROR: Syntax error: add <key> <flags> <exptime> <bytes> [noreply]\r\n",
105 [REPLACE_CMD]= "CLIENT_ERROR: Syntax error: replace <key> <flags> <exptime> <bytes> [noreply]\r\n",
106 [CAS_CMD]= "CLIENT_ERROR: Syntax error: cas <key> <flags> <exptime> <bytes> <casid> [noreply]\r\n",
107 [APPEND_CMD]= "CLIENT_ERROR: Syntax error: append <key> <flags> <exptime> <bytes> [noreply]\r\n",
108 [PREPEND_CMD]= "CLIENT_ERROR: Syntax error: prepend <key> <flags> <exptime> <bytes> [noreply]\r\n",
109 [DELETE_CMD]= "CLIENT_ERROR: Syntax error: delete <key> [noreply]\r\n",
110 [INCR_CMD]= "CLIENT_ERROR: Syntax error: incr <key> <value> [noreply]\r\n",
111 [DECR_CMD]= "CLIENT_ERROR: Syntax error: decr <key> <value> [noreply]\r\n",
112 [STATS_CMD]= "CLIENT_ERROR: Syntax error: stats [key]\r\n",
113 [FLUSH_ALL_CMD]= "CLIENT_ERROR: Syntax error: flush_all [timeout] [noreply]\r\n",
114 [VERSION_CMD]= "CLIENT_ERROR: Syntax error: version\r\n",
115 [QUIT_CMD]="CLIENT_ERROR: Syntax error: quit\r\n",
116
117 [VERBOSITY_CMD]= "CLIENT_ERROR: Syntax error: verbosity <num>\r\n",
118 [UNKNOWN_CMD]= "CLIENT_ERROR: Unknown command\r\n",
119 };
120
121 client->mute = false;
122 spool_string(client, errmsg[client->ascii_command]);
123 }
124
125 /**
126 * Callback for the VERSION responses
127 * @param cookie client identifier
128 * @param text the length of the body
129 * @param textlen the length of the body
130 */
131 static protocol_binary_response_status
132 ascii_version_response_handler(const void *cookie,
133 const void *text,
134 uint32_t textlen)
135 {
136 memcached_protocol_client_st *client= (memcached_protocol_client_st*)cookie;
137 spool_string(client, "VERSION ");
138 client->root->spool(client, text, textlen);
139 spool_string(client, "\r\n");
140 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
141 }
142
143 /**
144 * Callback for the GET/GETQ/GETK and GETKQ responses
145 * @param cookie client identifier
146 * @param key the key for the item
147 * @param keylen the length of the key
148 * @param body the length of the body
149 * @param bodylen the length of the body
150 * @param flags the flags for the item
151 * @param cas the CAS id for the item
152 */
153 static protocol_binary_response_status
154 ascii_get_response_handler(const void *cookie,
155 const void *key,
156 uint16_t keylen,
157 const void *body,
158 uint32_t bodylen,
159 uint32_t flags,
160 uint64_t cas)
161 {
162 memcached_protocol_client_st *client= (void*)cookie;
163 char buffer[300];
164 strcpy(buffer, "VALUE ");
165 const char *source= key;
166 char *dest= buffer + 6;
167
168 for (int x= 0; x < keylen; ++x)
169 {
170 if (*source != '\0' && !isspace(*source) && !iscntrl(*source))
171 {
172 *dest= *source;
173 }
174 else
175 {
176 return PROTOCOL_BINARY_RESPONSE_EINVAL; /* key constraints in ascii */
177 }
178
179 ++dest;
180 ++source;
181 }
182
183 size_t used= (size_t)(dest - buffer);
184
185 if (client->ascii_command == GETS_CMD)
186 {
187 snprintf(dest, sizeof(buffer) - used, " %u %u %" PRIu64 "\r\n", flags,
188 bodylen, cas);
189 }
190 else
191 {
192 snprintf(dest, sizeof(buffer) - used, " %u %u\r\n", flags, bodylen);
193 }
194
195 client->root->spool(client, buffer, strlen(buffer));
196 client->root->spool(client, body, bodylen);
197 client->root->spool(client, "\r\n", 2);
198
199 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
200 }
201
202 /**
203 * Callback for the STAT responses
204 * @param cookie client identifier
205 * @param key the key for the item
206 * @param keylen the length of the key
207 * @param body the length of the body
208 * @param bodylen the length of the body
209 */
210 static protocol_binary_response_status
211 ascii_stat_response_handler(const void *cookie,
212 const void *key,
213 uint16_t keylen,
214 const void *body,
215 uint32_t bodylen)
216 {
217
218 memcached_protocol_client_st *client= (void*)cookie;
219
220 if (key != NULL)
221 {
222 spool_string(client, "STAT ");
223 client->root->spool(client, key, keylen);
224 spool_string(client, " ");
225 client->root->spool(client, body, bodylen);
226 spool_string(client, "\r\n");
227 }
228 else
229 {
230 spool_string(client, "END\r\n");
231 }
232
233 return PROTOCOL_BINARY_RESPONSE_SUCCESS;
234 }
235
236 /**
237 * Process a get or a gets request.
238 * @param client the client handle
239 * @param buffer the complete get(s) command
240 * @param end the last character in the command
241 */
242 static void ascii_process_gets(memcached_protocol_client_st *client,
243 char *buffer, char *end)
244 {
245 char *key= buffer;
246
247 /* Skip command */
248 key += (client->ascii_command == GETS_CMD) ? 5 : 4;
249
250 int num_keys= 0;
251 while (key < end)
252 {
253 uint16_t nkey= parse_ascii_key(&key);
254 if (nkey == 0) /* Invalid key... stop processing this line */
255 {
256 break;
257 }
258
259 (void)client->root->callback->interface.v1.get(client, key, nkey,
260 ascii_get_response_handler);
261 key += nkey;
262 ++num_keys;
263 }
264
265 if (num_keys == 0)
266 {
267 send_command_usage(client);
268 }
269 else
270 client->root->spool(client, "END\r\n", 5);
271 }
272
273 /**
274 * Try to split up the command line "asdf asdf asdf asdf\n" into an
275 * argument vector for easier parsing.
276 * @param start the first character in the command line
277 * @param end the last character in the command line ("\n")
278 * @param vec the vector to insert the pointers into
279 * @size the number of elements in the vector
280 * @return the number of tokens in the vector
281 */
282 static int ascii_tokenize_command(char *str, char *end, char **vec, int size)
283 {
284 int elem= 0;
285
286 while (str < end)
287 {
288 /* Skip leading blanks */
289 while (str < end && isspace(*str))
290 {
291 ++str;
292 }
293
294 if (str == end)
295 {
296 return elem;
297 }
298
299 vec[elem++]= str;
300 /* find the next non-blank field */
301 while (str < end && !isspace(*str))
302 {
303 ++str;
304 }
305
306 /* zero-terminate it for easier parsing later on */
307 *str= '\0';
308 ++str;
309
310 /* Is the vector full? */
311 if (elem == size)
312 {
313 break;
314 }
315 }
316
317 return elem;
318 }
319
320 /**
321 * If we for some reasons needs to push the line back to read more
322 * data we have to reverse the tokenization. Just do the brain-dead replace
323 * of all '\0' to ' ' and set the last character to '\n'. We could have used
324 * the vector we created, but then we would have to search for all of the
325 * spaces we ignored...
326 * @param start pointer to the first character in the buffer to recover
327 * @param end pointer to the last character in the buffer to recover
328 */
329 static void recover_tokenize_command(char *start, char *end)
330 {
331 while (start < end)
332 {
333 if (*start == '\0')
334 *start= ' ';
335 ++start;
336 }
337
338 *end= '\n';
339 }
340
341 /**
342 * Convert the textual command into a comcode
343 */
344 static enum ascii_cmd ascii_to_cmd(char *start, size_t length)
345 {
346 struct {
347 const char *cmd;
348 size_t len;
349 enum ascii_cmd cc;
350 } commands[]= {
351 { .cmd= "get", .len= 3, .cc= GET_CMD },
352 { .cmd= "gets", .len= 4, .cc= GETS_CMD },
353 { .cmd= "set", .len= 3, .cc= SET_CMD },
354 { .cmd= "add", .len= 3, .cc= ADD_CMD },
355 { .cmd= "replace", .len= 7, .cc= REPLACE_CMD },
356 { .cmd= "cas", .len= 3, .cc= CAS_CMD },
357 { .cmd= "append", .len= 6, .cc= APPEND_CMD },
358 { .cmd= "prepend", .len= 7, .cc= PREPEND_CMD },
359 { .cmd= "delete", .len= 6, .cc= DELETE_CMD },
360 { .cmd= "incr", .len= 4, .cc= INCR_CMD },
361 { .cmd= "decr", .len= 4, .cc= DECR_CMD },
362 { .cmd= "stats", .len= 5, .cc= STATS_CMD },
363 { .cmd= "flush_all", .len= 9, .cc= FLUSH_ALL_CMD },
364 { .cmd= "version", .len= 7, .cc= VERSION_CMD },
365 { .cmd= "quit", .len= 4, .cc= QUIT_CMD },
366 { .cmd= "verbosity", .len= 9, .cc= VERBOSITY_CMD },
367 { .cmd= NULL, .len= 0, .cc= UNKNOWN_CMD }};
368
369 int x= 0;
370 while (commands[x].len > 0) {
371 if (length >= commands[x].len)
372 {
373 if (strncmp(start, commands[x].cmd, commands[x].len) == 0)
374 {
375 /* Potential hit */
376 if (length == commands[x].len || isspace(*(start + commands[x].len)))
377 {
378 return commands[x].cc;
379 }
380 }
381 }
382 ++x;
383 }
384
385 return UNKNOWN_CMD;
386 }
387
388 /**
389 * Perform a delete operation.
390 *
391 * @param client client requesting the deletion
392 * @param tokens the command as a vector
393 * @param ntokens the number of items in the vector
394 */
395 static void process_delete(memcached_protocol_client_st *client,
396 char **tokens, int ntokens)
397 {
398 char *key= tokens[1];
399 uint16_t nkey;
400
401 if (ntokens != 2 || (nkey= parse_ascii_key(&key)) == 0)
402 {
403 send_command_usage(client);
404 return;
405 }
406
407 if (client->root->callback->interface.v1.delete == NULL)
408 {
409 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
410 return;
411 }
412
413 protocol_binary_response_status rval;
414 rval= client->root->callback->interface.v1.delete(client, key, nkey, 0);
415
416 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
417 {
418 spool_string(client, "DELETED\r\n");
419 }
420 else if (rval == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT)
421 {
422 spool_string(client, "NOT_FOUND\r\n");
423 }
424 else
425 {
426 char msg[80];
427 snprintf(msg, sizeof(msg), "SERVER_ERROR: delete failed %u\r\n",(uint32_t)rval);
428 spool_string(client, msg);
429 }
430 }
431
432 static void process_arithmetic(memcached_protocol_client_st *client,
433 char **tokens, int ntokens)
434 {
435 char *key= tokens[1];
436 uint16_t nkey;
437
438 if (ntokens != 3 || (nkey= parse_ascii_key(&key)) == 0)
439 {
440 send_command_usage(client);
441 return;
442 }
443
444 uint64_t cas;
445 uint64_t result;
446 uint64_t delta= strtoull(tokens[2], NULL, 10);
447
448 protocol_binary_response_status rval;
449 if (client->ascii_command == INCR_CMD)
450 {
451 if (client->root->callback->interface.v1.increment == NULL)
452 {
453 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
454 return;
455 }
456 rval= client->root->callback->interface.v1.increment(client,
457 key, nkey,
458 delta, 0,
459 0,
460 &result,
461 &cas);
462 }
463 else
464 {
465 if (client->root->callback->interface.v1.decrement == NULL)
466 {
467 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
468 return;
469 }
470 rval= client->root->callback->interface.v1.decrement(client,
471 key, nkey,
472 delta, 0,
473 0,
474 &result,
475 &cas);
476 }
477
478 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
479 {
480 char buffer[80];
481 snprintf(buffer, sizeof(buffer), "%"PRIu64"\r\n", result);
482 spool_string(client, buffer);
483 }
484 else
485 {
486 spool_string(client, "NOT_FOUND\r\n");
487 }
488 }
489
490 /**
491 * Process the stats command (with or without a key specified)
492 * @param key pointer to the first character after "stats"
493 * @param end pointer to the "\n"
494 */
495 static void process_stats(memcached_protocol_client_st *client,
496 char *key, char *end)
497 {
498 if (client->root->callback->interface.v1.stat == NULL)
499 {
500 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
501 return;
502 }
503
504 while (isspace(*key))
505 key++;
506
507 uint16_t nkey= (uint16_t)(end - key);
508 (void)client->root->callback->interface.v1.stat(client, key, nkey,
509 ascii_stat_response_handler);
510 }
511
512 static void process_version(memcached_protocol_client_st *client,
513 char **tokens, int ntokens)
514 {
515 (void)tokens;
516 if (ntokens != 1)
517 {
518 send_command_usage(client);
519 return;
520 }
521
522 if (client->root->callback->interface.v1.version == NULL)
523 {
524 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
525 return;
526 }
527
528 client->root->callback->interface.v1.version(client,
529 ascii_version_response_handler);
530 }
531
532 static void process_flush(memcached_protocol_client_st *client,
533 char **tokens, int ntokens)
534 {
535 if (ntokens > 2)
536 {
537 send_command_usage(client);
538 return;
539 }
540
541 if (client->root->callback->interface.v1.flush == NULL)
542 {
543 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
544 return;
545 }
546
547 uint32_t timeout= 0;
548 if (ntokens == 2)
549 {
550 timeout= (uint32_t)strtoul(tokens[1], NULL, 10);
551 }
552
553 protocol_binary_response_status rval;
554 rval= client->root->callback->interface.v1.flush(client, timeout);
555 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
556 spool_string(client, "OK\r\n");
557 else
558 spool_string(client, "SERVER_ERROR: internal error\r\n");
559 }
560
561 /**
562 * Process one of the storage commands
563 * @param client the client performing the operation
564 * @param tokens the command tokens
565 * @param ntokens the number of tokens
566 * @param start pointer to the first character in the line
567 * @param end pointer to the pointer where the last character of this
568 * command is (IN and OUT)
569 * @param length the number of bytes available
570 * @return -1 if an error occurs (and we should just terminate the connection
571 * because we are out of sync)
572 * 0 storage command completed, continue processing
573 * 1 We need more data, so just go ahead and wait for more!
574 */
575 static inline int process_storage_command(memcached_protocol_client_st *client,
576 char **tokens, int ntokens, char *start,
577 char **end, ssize_t length)
578 {
579 (void)ntokens; /* already checked */
580 char *key= tokens[1];
581 uint16_t nkey= parse_ascii_key(&key);
582 if (nkey == 0)
583 {
584 /* return error */
585 spool_string(client, "CLIENT_ERROR: bad key\r\n");
586 return -1;
587 }
588
589 uint32_t flags= (uint32_t)strtoul(tokens[2], NULL, 10);
590 uint32_t timeout= (uint32_t)strtoul(tokens[3], NULL, 10);
591 unsigned long nbytes= strtoul(tokens[4], NULL, 10);
592
593 /* Do we have all data? */
594 unsigned long need= nbytes + (unsigned long)((*end - start) + 1) + 2; /* \n\r\n */
595 if ((ssize_t)need > length)
596 {
597 /* Keep on reading */
598 recover_tokenize_command(start, *end);
599 return 1;
600 }
601
602 void *data= (*end) + 1;
603 uint64_t cas= 0;
604 uint64_t result_cas;
605 protocol_binary_response_status rval;
606 switch (client->ascii_command)
607 {
608 case SET_CMD:
609 rval= client->root->callback->interface.v1.set(client, key,
610 (uint16_t)nkey,
611 data,
612 (uint32_t)nbytes,
613 flags,
614 timeout, cas,
615 &result_cas);
616 break;
617 case ADD_CMD:
618 rval= client->root->callback->interface.v1.add(client, key,
619 (uint16_t)nkey,
620 data,
621 (uint32_t)nbytes,
622 flags,
623 timeout, &result_cas);
624 break;
625 case CAS_CMD:
626 cas= strtoull(tokens[5], NULL, 10);
627 /* FALLTHROUGH */
628 case REPLACE_CMD:
629 rval= client->root->callback->interface.v1.replace(client, key,
630 (uint16_t)nkey,
631 data,
632 (uint32_t)nbytes,
633 flags,
634 timeout, cas,
635 &result_cas);
636 break;
637 case APPEND_CMD:
638 rval= client->root->callback->interface.v1.append(client, key,
639 (uint16_t)nkey,
640 data,
641 (uint32_t)nbytes,
642 cas,
643 &result_cas);
644 break;
645 case PREPEND_CMD:
646 rval= client->root->callback->interface.v1.prepend(client, key,
647 (uint16_t)nkey,
648 data,
649 (uint32_t)nbytes,
650 cas,
651 &result_cas);
652 break;
653
654 /* gcc complains if I don't put all of the enums in here.. */
655 case GET_CMD:
656 case GETS_CMD:
657 case DELETE_CMD:
658 case DECR_CMD:
659 case INCR_CMD:
660 case STATS_CMD:
661 case FLUSH_ALL_CMD:
662 case VERSION_CMD:
663 case QUIT_CMD:
664 case VERBOSITY_CMD:
665 case UNKNOWN_CMD:
666 default:
667 abort(); /* impossible */
668 }
669
670 if (rval == PROTOCOL_BINARY_RESPONSE_SUCCESS)
671 {
672 spool_string(client, "STORED\r\n");
673 }
674 else
675 {
676 if (client->ascii_command == CAS_CMD)
677 {
678 if (rval == PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS)
679 {
680 spool_string(client, "EXISTS\r\n");
681 }
682 else if (rval == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT)
683 {
684 spool_string(client, "NOT_FOUND\r\n");
685 }
686 else
687 {
688 spool_string(client, "NOT_STORED\r\n");
689 }
690 }
691 else
692 {
693 spool_string(client, "NOT_STORED\r\n");
694 }
695 }
696
697 *end += nbytes + 2;
698
699 return 0;
700 }
701
702 static int process_cas_command(memcached_protocol_client_st *client,
703 char **tokens, int ntokens, char *start,
704 char **end, ssize_t length)
705 {
706 if (ntokens != 6)
707 {
708 send_command_usage(client);
709 return false;
710 }
711
712 if (client->root->callback->interface.v1.replace == NULL)
713 {
714 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
715 return false;
716 }
717
718 return process_storage_command(client, tokens, ntokens, start, end, length);
719 }
720
721 static int process_set_command(memcached_protocol_client_st *client,
722 char **tokens, int ntokens, char *start,
723 char **end, ssize_t length)
724 {
725 if (ntokens != 5)
726 {
727 send_command_usage(client);
728 return false;
729 }
730
731 if (client->root->callback->interface.v1.set == NULL)
732 {
733 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
734 return false;
735 }
736
737 return process_storage_command(client, tokens, ntokens, start, end, length);
738 }
739
740 static int process_add_command(memcached_protocol_client_st *client,
741 char **tokens, int ntokens, char *start,
742 char **end, ssize_t length)
743 {
744 if (ntokens != 5)
745 {
746 send_command_usage(client);
747 return false;
748 }
749
750 if (client->root->callback->interface.v1.add == NULL)
751 {
752 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
753 return false;
754 }
755
756 return process_storage_command(client, tokens, ntokens, start, end, length);
757 }
758
759 static int process_replace_command(memcached_protocol_client_st *client,
760 char **tokens, int ntokens, char *start,
761 char **end, ssize_t length)
762 {
763 if (ntokens != 5)
764 {
765 send_command_usage(client);
766 return false;
767 }
768
769 if (client->root->callback->interface.v1.replace == NULL)
770 {
771 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
772 return false;
773 }
774
775 return process_storage_command(client, tokens, ntokens, start, end, length);
776 }
777
778 static int process_append_command(memcached_protocol_client_st *client,
779 char **tokens, int ntokens, char *start,
780 char **end, ssize_t length)
781 {
782 if (ntokens != 5)
783 {
784 send_command_usage(client);
785 return false;
786 }
787
788 if (client->root->callback->interface.v1.append == NULL)
789 {
790 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
791 return false;
792 }
793
794 return process_storage_command(client, tokens, ntokens, start, end, length);
795 }
796
797 static int process_prepend_command(memcached_protocol_client_st *client,
798 char **tokens, int ntokens, char *start,
799 char **end, ssize_t length)
800 {
801 if (ntokens != 5)
802 {
803 send_command_usage(client);
804 return false;
805 }
806
807 if (client->root->callback->interface.v1.prepend == NULL)
808 {
809 spool_string(client, "SERVER_ERROR: callback not implemented\r\n");
810 return false;
811 }
812
813 return process_storage_command(client, tokens, ntokens, start, end, length);
814 }
815
816 /**
817 * The ASCII protocol support is just one giant big hack. Instead of adding
818 * a optimal ascii support, I just convert the ASCII commands to the binary
819 * protocol and calls back into the command handlers for the binary protocol ;)
820 */
821 memcached_protocol_event_t memcached_ascii_protocol_process_data(memcached_protocol_client_st *client, ssize_t *length, void **endptr)
822 {
823 char *ptr= (char*)client->root->input_buffer;
824 *endptr= ptr;
825
826 do {
827 /* Do we have \n (indicating the command preamble)*/
828 char *end= memchr(ptr, '\n', (size_t)*length);
829 if (end == NULL)
830 {
831 *endptr= ptr;
832 return MEMCACHED_PROTOCOL_READ_EVENT;
833 }
834
835 client->ascii_command= ascii_to_cmd(ptr, (size_t)(*length));
836
837 /* A multiget lists all of the keys, and I don't want to have an
838 * avector of let's say 512 pointers to tokenize all of them, so let's
839 * just handle them immediately
840 */
841 if (client->ascii_command == GET_CMD ||
842 client->ascii_command == GETS_CMD) {
843 if (client->root->callback->interface.v1.get != NULL)
844 ascii_process_gets(client, ptr, end);
845 else
846 spool_string(client, "SERVER_ERROR: Command not implemented\n");
847 } else {
848 /* None of the defined commands takes 10 parameters, so lets just use
849 * that as a maximum limit.
850 */
851 char *tokens[10];
852 int ntokens= ascii_tokenize_command(ptr, end, tokens, 10);
853
854 if (ntokens < 10)
855 {
856 client->mute= strcmp(tokens[ntokens - 1], "noreply") == 0;
857 if (client->mute)
858 --ntokens; /* processed noreply token*/
859 }
860
861 int error= 0;
862
863 switch (client->ascii_command) {
864 case SET_CMD:
865 error= process_set_command(client, tokens, ntokens, ptr, &end, *length);
866 break;
867 case ADD_CMD:
868 error= process_add_command(client, tokens, ntokens, ptr, &end, *length);
869 break;
870 case REPLACE_CMD:
871 error= process_replace_command(client, tokens, ntokens,
872 ptr, &end, *length);
873 break;
874 case CAS_CMD:
875 error= process_cas_command(client, tokens, ntokens, ptr, &end, *length);
876 break;
877 case APPEND_CMD:
878 error= process_append_command(client, tokens, ntokens,
879 ptr, &end, *length);
880 break;
881 case PREPEND_CMD:
882 error= process_prepend_command(client, tokens, ntokens,
883 ptr, &end, *length);
884 break;
885 case DELETE_CMD:
886 process_delete(client, tokens, ntokens);
887 break;
888
889 case INCR_CMD: /* FALLTHROUGH */
890 case DECR_CMD:
891 process_arithmetic(client, tokens, ntokens);
892 break;
893 case STATS_CMD:
894 if (client->mute)
895 {
896 send_command_usage(client);
897 }
898 else
899 {
900 recover_tokenize_command(ptr, end);
901 process_stats(client, ptr + 6, end);
902 }
903 break;
904 case FLUSH_ALL_CMD:
905 process_flush(client, tokens, ntokens);
906 break;
907 case VERSION_CMD:
908 if (client->mute)
909 {
910 send_command_usage(client);
911 }
912 else
913 {
914 process_version(client, tokens, ntokens);
915 }
916 break;
917 case QUIT_CMD:
918 if (ntokens != 1 || client->mute)
919 {
920 send_command_usage(client);
921 }
922 else
923 {
924 if (client->root->callback->interface.v1.quit != NULL)
925 client->root->callback->interface.v1.quit(client);
926
927 return MEMCACHED_PROTOCOL_ERROR_EVENT;
928 }
929 break;
930
931 case VERBOSITY_CMD:
932 if (ntokens != 2)
933 send_command_usage(client);
934 else
935 spool_string(client, "OK\r\n");
936 break;
937
938 case UNKNOWN_CMD:
939 send_command_usage(client);
940 break;
941
942 case GET_CMD:
943 case GETS_CMD:
944 default:
945 /* Should already be handled */
946 abort();
947 }
948
949 if (error == -1)
950 return MEMCACHED_PROTOCOL_ERROR_EVENT;
951 else if (error == 1)
952 return MEMCACHED_PROTOCOL_READ_EVENT;
953 }
954
955 /* Move past \n */
956 ++end;
957 *length -= end - ptr;
958 ptr= end;
959 } while (*length > 0);
960
961 *endptr= ptr;
962 return MEMCACHED_PROTOCOL_READ_EVENT;
963 }