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