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