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