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