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