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