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