1 /* -*- Mode: C; tab-width: 2; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2 #include "libmemcached/protocol/common.h"
10 * Try to parse a key from the string.
11 * @pointer start pointer to a pointer to the string (IN and OUT)
12 * @return length of the string of -1 if this was an illegal key (invalid
13 * characters or invalid length)
16 static uint16_t parse_ascii_key(char **start
)
20 /* Strip leading whitespaces */
28 while (*c
!= '\0' && !isspace(*c
) && !iscntrl(*c
))
35 if (len
== 0 || len
> 240 || (*c
!= '\0' && *c
!= '\r' && iscntrl(*c
)))
44 * Spool a zero-terminated string
45 * @param client destination
46 * @param text the text to spool
47 * @return status of the spool operation
49 static protocol_binary_response_status
50 spool_string(memcached_protocol_client_st
*client
, const char *text
)
52 return client
->root
->spool(client
, text
, strlen(text
));
56 * Send a "CLIENT_ERROR" message back to the client with the correct
57 * format of the command being sent
58 * @param client the client to send the message to
60 static void send_command_usage(memcached_protocol_client_st
*client
)
62 const char *errmsg
[]= {
63 [GET_CMD
]= "CLIENT_ERROR: Syntax error: get <key>*\r\n",
64 [GETS_CMD
]= "CLIENT_ERROR: Syntax error: gets <key>*\r\n",
65 [SET_CMD
]= "CLIENT_ERROR: Syntax error: set <key> <flags> <exptime> <bytes> [noreply]\r\n",
66 [ADD_CMD
]= "CLIENT_ERROR: Syntax error: add <key> <flags> <exptime> <bytes> [noreply]\r\n",
67 [REPLACE_CMD
]= "CLIENT_ERROR: Syntax error: replace <key> <flags> <exptime> <bytes> [noreply]\r\n",
68 [CAS_CMD
]= "CLIENT_ERROR: Syntax error: cas <key> <flags> <exptime> <bytes> <casid> [noreply]\r\n",
69 [APPEND_CMD
]= "CLIENT_ERROR: Syntax error: append <key> <flags> <exptime> <bytes> [noreply]\r\n",
70 [PREPEND_CMD
]= "CLIENT_ERROR: Syntax error: prepend <key> <flags> <exptime> <bytes> [noreply]\r\n",
71 [DELETE_CMD
]= "CLIENT_ERROR: Syntax error: delete <key> [noreply]\r\n",
72 [INCR_CMD
]= "CLIENT_ERROR: Syntax error: incr <key> <value> [noreply]\r\n",
73 [DECR_CMD
]= "CLIENT_ERROR: Syntax error: decr <key> <value> [noreply]\r\n",
74 [STATS_CMD
]= "CLIENT_ERROR: Syntax error: stats [key]\r\n",
75 [FLUSH_ALL_CMD
]= "CLIENT_ERROR: Syntax error: flush_all [timeout] [noreply]\r\n",
76 [VERSION_CMD
]= "CLIENT_ERROR: Syntax error: version\r\n",
77 [QUIT_CMD
]="CLIENT_ERROR: Syntax error: quit\r\n",
79 [VERBOSITY_CMD
]= "CLIENT_ERROR: Syntax error: verbosity <num>\r\n",
80 [UNKNOWN_CMD
]= "CLIENT_ERROR: Unknown command\r\n",
84 spool_string(client
, errmsg
[client
->ascii_command
]);
88 * Callback for the VERSION responses
89 * @param cookie client identifier
90 * @param text the length of the body
91 * @param textlen the length of the body
93 static protocol_binary_response_status
94 ascii_version_response_handler(const void *cookie
,
98 memcached_protocol_client_st
*client
= (void*)cookie
;
99 spool_string(client
, "VERSION ");
100 client
->root
->spool(client
, text
, textlen
);
101 spool_string(client
, "\r\n");
102 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
106 * Callback for the GET/GETQ/GETK and GETKQ responses
107 * @param cookie client identifier
108 * @param key the key for the item
109 * @param keylen the length of the key
110 * @param body the length of the body
111 * @param bodylen the length of the body
112 * @param flags the flags for the item
113 * @param cas the CAS id for the item
115 static protocol_binary_response_status
116 ascii_get_response_handler(const void *cookie
,
124 memcached_protocol_client_st
*client
= (void*)cookie
;
126 strcpy(buffer
, "VALUE ");
127 const char *source
= key
;
128 char *dest
= buffer
+ 6;
130 for (int x
= 0; x
< keylen
; ++x
)
132 if (*source
!= '\0' && !isspace(*source
) && !iscntrl(*source
))
138 return PROTOCOL_BINARY_RESPONSE_EINVAL
; /* key constraints in ascii */
145 size_t used
= (size_t)(dest
- buffer
);
147 if (client
->ascii_command
== GETS_CMD
)
149 snprintf(dest
, sizeof(buffer
) - used
, " %u %u %llu\r\n", flags
,
150 bodylen
, (unsigned long long)cas
);
154 snprintf(dest
, sizeof(buffer
) - used
, " %u %u\r\n", flags
, bodylen
);
157 client
->root
->spool(client
, buffer
, strlen(buffer
));
158 client
->root
->spool(client
, body
, bodylen
);
159 client
->root
->spool(client
, "\r\n", 2);
161 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
165 * Callback for the STAT responses
166 * @param cookie client identifier
167 * @param key the key for the item
168 * @param keylen the length of the key
169 * @param body the length of the body
170 * @param bodylen the length of the body
172 static protocol_binary_response_status
173 ascii_stat_response_handler(const void *cookie
,
180 memcached_protocol_client_st
*client
= (void*)cookie
;
184 spool_string(client
, "STAT ");
185 client
->root
->spool(client
, key
, keylen
);
186 spool_string(client
, " ");
187 client
->root
->spool(client
, body
, bodylen
);
188 spool_string(client
, "\r\n");
192 spool_string(client
, "END\r\n");
195 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
199 * Process a get or a gets request.
200 * @param client the client handle
201 * @param buffer the complete get(s) command
202 * @param end the last character in the command
204 static void ascii_process_gets(memcached_protocol_client_st
*client
,
205 char *buffer
, char *end
)
210 key
+= (client
->ascii_command
== GETS_CMD
) ? 5 : 4;
215 uint16_t nkey
= parse_ascii_key(&key
);
216 if (nkey
== 0) /* Invalid key... stop processing this line */
221 (void)client
->root
->callback
->interface
.v1
.get(client
, key
, nkey
,
222 ascii_get_response_handler
);
229 send_command_usage(client
);
232 client
->root
->spool(client
, "END\r\n", 5);
236 * Try to split up the command line "asdf asdf asdf asdf\n" into an
237 * argument vector for easier parsing.
238 * @param start the first character in the command line
239 * @param end the last character in the command line ("\n")
240 * @param vec the vector to insert the pointers into
241 * @size the number of elements in the vector
242 * @return the number of tokens in the vector
244 static int ascii_tokenize_command(char *str
, char *end
, char **vec
, int size
)
250 /* Skip leading blanks */
251 while (str
< end
&& isspace(*str
))
262 /* find the next non-blank field */
263 while (str
< end
&& !isspace(*str
))
268 /* zero-terminate it for easier parsing later on */
272 /* Is the vector full? */
283 * If we for some reasons needs to push the line back to read more
284 * data we have to reverse the tokenization. Just do the brain-dead replace
285 * of all '\0' to ' ' and set the last character to '\n'. We could have used
286 * the vector we created, but then we would have to search for all of the
287 * spaces we ignored...
288 * @param start pointer to the first character in the buffer to recover
289 * @param end pointer to the last character in the buffer to recover
291 static void recover_tokenize_command(char *start
, char *end
)
304 * Convert the textual command into a comcode
306 static enum ascii_cmd
ascii_to_cmd(char *start
, size_t length
)
313 { .cmd
= "get", .len
= 3, .cc
= GET_CMD
},
314 { .cmd
= "gets", .len
= 4, .cc
= GETS_CMD
},
315 { .cmd
= "set", .len
= 3, .cc
= SET_CMD
},
316 { .cmd
= "add", .len
= 3, .cc
= ADD_CMD
},
317 { .cmd
= "replace", .len
= 7, .cc
= REPLACE_CMD
},
318 { .cmd
= "cas", .len
= 3, .cc
= CAS_CMD
},
319 { .cmd
= "append", .len
= 6, .cc
= APPEND_CMD
},
320 { .cmd
= "prepend", .len
= 7, .cc
= PREPEND_CMD
},
321 { .cmd
= "delete", .len
= 6, .cc
= DELETE_CMD
},
322 { .cmd
= "incr", .len
= 4, .cc
= INCR_CMD
},
323 { .cmd
= "decr", .len
= 4, .cc
= DECR_CMD
},
324 { .cmd
= "stats", .len
= 5, .cc
= STATS_CMD
},
325 { .cmd
= "flush_all", .len
= 9, .cc
= FLUSH_ALL_CMD
},
326 { .cmd
= "version", .len
= 7, .cc
= VERSION_CMD
},
327 { .cmd
= "quit", .len
= 4, .cc
= QUIT_CMD
},
328 { .cmd
= "verbosity", .len
= 9, .cc
= VERBOSITY_CMD
},
329 { .cmd
= NULL
, .len
= 0, .cc
= UNKNOWN_CMD
}};
332 while (commands
[x
].len
> 0) {
333 if (length
>= commands
[x
].len
)
335 if (strncmp(start
, commands
[x
].cmd
, commands
[x
].len
) == 0)
338 if (length
== commands
[x
].len
|| isspace(*(start
+ commands
[x
].len
)))
340 return commands
[x
].cc
;
351 * Perform a delete operation.
353 * @param client client requesting the deletion
354 * @param tokens the command as a vector
355 * @param ntokens the number of items in the vector
357 static void process_delete(memcached_protocol_client_st
*client
,
358 char **tokens
, int ntokens
)
360 char *key
= tokens
[1];
363 if (ntokens
!= 2 || (nkey
= parse_ascii_key(&key
)) == 0)
365 send_command_usage(client
);
369 if (client
->root
->callback
->interface
.v1
.delete == NULL
)
371 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
375 protocol_binary_response_status rval
;
376 rval
= client
->root
->callback
->interface
.v1
.delete(client
, key
, nkey
, 0);
378 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
380 spool_string(client
, "DELETED\r\n");
382 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
384 spool_string(client
, "NOT_FOUND\r\n");
389 snprintf(msg
, sizeof(msg
), "SERVER_ERROR: delete failed %u\r\n",(uint32_t)rval
);
390 spool_string(client
, msg
);
394 static void process_arithmetic(memcached_protocol_client_st
*client
,
395 char **tokens
, int ntokens
)
397 char *key
= tokens
[1];
400 if (ntokens
!= 3 || (nkey
= parse_ascii_key(&key
)) == 0)
402 send_command_usage(client
);
408 uint64_t delta
= strtoull(tokens
[2], NULL
, 10);
410 protocol_binary_response_status rval
;
411 if (client
->ascii_command
== INCR_CMD
)
413 if (client
->root
->callback
->interface
.v1
.increment
== NULL
)
415 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
418 rval
= client
->root
->callback
->interface
.v1
.increment(client
,
427 if (client
->root
->callback
->interface
.v1
.decrement
== NULL
)
429 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
432 rval
= client
->root
->callback
->interface
.v1
.decrement(client
,
440 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
443 snprintf(buffer
, sizeof(buffer
), "%llu\r\n",
444 (unsigned long long)result
);
445 spool_string(client
, buffer
);
449 spool_string(client
, "NOT_FOUND\r\n");
454 * Process the stats command (with or without a key specified)
455 * @param key pointer to the first character after "stats"
456 * @param end pointer to the "\n"
458 static void process_stats(memcached_protocol_client_st
*client
,
459 char *key
, char *end
)
461 if (client
->root
->callback
->interface
.v1
.stat
== NULL
)
463 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
467 while (isspace(*key
))
470 uint16_t nkey
= (uint16_t)(end
- key
);
471 (void)client
->root
->callback
->interface
.v1
.stat(client
, key
, nkey
,
472 ascii_stat_response_handler
);
475 static void process_version(memcached_protocol_client_st
*client
,
476 char **tokens
, int ntokens
)
481 send_command_usage(client
);
485 if (client
->root
->callback
->interface
.v1
.version
== NULL
)
487 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
491 client
->root
->callback
->interface
.v1
.version(client
,
492 ascii_version_response_handler
);
495 static void process_flush(memcached_protocol_client_st
*client
,
496 char **tokens
, int ntokens
)
500 send_command_usage(client
);
504 if (client
->root
->callback
->interface
.v1
.flush
== NULL
)
506 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
513 timeout
= (uint32_t)strtoul(tokens
[1], NULL
, 10);
516 protocol_binary_response_status rval
;
517 rval
= client
->root
->callback
->interface
.v1
.flush(client
, timeout
);
518 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
519 spool_string(client
, "OK\r\n");
521 spool_string(client
, "SERVER_ERROR: internal error\r\n");
525 * Process one of the storage commands
526 * @param client the client performing the operation
527 * @param tokens the command tokens
528 * @param ntokens the number of tokens
529 * @param start pointer to the first character in the line
530 * @param end pointer to the pointer where the last character of this
531 * command is (IN and OUT)
532 * @param length the number of bytes available
533 * @return -1 if an error occurs (and we should just terminate the connection
534 * because we are out of sync)
535 * 0 storage command completed, continue processing
536 * 1 We need more data, so just go ahead and wait for more!
538 static inline int process_storage_command(memcached_protocol_client_st
*client
,
539 char **tokens
, int ntokens
, char *start
,
540 char **end
, ssize_t length
)
542 (void)ntokens
; /* already checked */
543 char *key
= tokens
[1];
544 uint16_t nkey
= parse_ascii_key(&key
);
548 spool_string(client
, "CLIENT_ERROR: bad key\r\n");
552 uint32_t flags
= (uint32_t)strtoul(tokens
[2], NULL
, 10);
553 uint32_t timeout
= (uint32_t)strtoul(tokens
[3], NULL
, 10);
554 unsigned long nbytes
= strtoul(tokens
[4], NULL
, 10);
556 /* Do we have all data? */
557 unsigned long need
= nbytes
+ (unsigned long)((*end
- start
) + 1) + 2; /* \n\r\n */
558 if ((ssize_t
)need
> length
)
560 /* Keep on reading */
561 recover_tokenize_command(start
, *end
);
565 void *data
= (*end
) + 1;
568 protocol_binary_response_status rval
;
569 switch (client
->ascii_command
)
572 rval
= client
->root
->callback
->interface
.v1
.set(client
, key
,
581 rval
= client
->root
->callback
->interface
.v1
.add(client
, key
,
586 timeout
, &result_cas
);
589 cas
= strtoull(tokens
[5], NULL
, 10);
592 rval
= client
->root
->callback
->interface
.v1
.replace(client
, key
,
601 rval
= client
->root
->callback
->interface
.v1
.append(client
, key
,
609 rval
= client
->root
->callback
->interface
.v1
.prepend(client
, key
,
617 /* gcc complains if I don't put all of the enums in here.. */
630 abort(); /* impossible */
633 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
635 spool_string(client
, "STORED\r\n");
639 if (client
->ascii_command
== CAS_CMD
)
641 if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS
)
643 spool_string(client
, "EXISTS\r\n");
645 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
647 spool_string(client
, "NOT_FOUND\r\n");
651 spool_string(client
, "NOT_STORED\r\n");
656 spool_string(client
, "NOT_STORED\r\n");
665 static int process_cas_command(memcached_protocol_client_st
*client
,
666 char **tokens
, int ntokens
, char *start
,
667 char **end
, ssize_t length
)
671 send_command_usage(client
);
675 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
677 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
681 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
684 static int process_set_command(memcached_protocol_client_st
*client
,
685 char **tokens
, int ntokens
, char *start
,
686 char **end
, ssize_t length
)
690 send_command_usage(client
);
694 if (client
->root
->callback
->interface
.v1
.set
== NULL
)
696 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
700 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
703 static int process_add_command(memcached_protocol_client_st
*client
,
704 char **tokens
, int ntokens
, char *start
,
705 char **end
, ssize_t length
)
709 send_command_usage(client
);
713 if (client
->root
->callback
->interface
.v1
.add
== NULL
)
715 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
719 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
722 static int process_replace_command(memcached_protocol_client_st
*client
,
723 char **tokens
, int ntokens
, char *start
,
724 char **end
, ssize_t length
)
728 send_command_usage(client
);
732 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
734 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
738 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
741 static int process_append_command(memcached_protocol_client_st
*client
,
742 char **tokens
, int ntokens
, char *start
,
743 char **end
, ssize_t length
)
747 send_command_usage(client
);
751 if (client
->root
->callback
->interface
.v1
.append
== NULL
)
753 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
757 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
760 static int process_prepend_command(memcached_protocol_client_st
*client
,
761 char **tokens
, int ntokens
, char *start
,
762 char **end
, ssize_t length
)
766 send_command_usage(client
);
770 if (client
->root
->callback
->interface
.v1
.prepend
== NULL
)
772 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
776 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
780 * The ASCII protocol support is just one giant big hack. Instead of adding
781 * a optimal ascii support, I just convert the ASCII commands to the binary
782 * protocol and calls back into the command handlers for the binary protocol ;)
784 memcached_protocol_event_t
memcached_ascii_protocol_process_data(memcached_protocol_client_st
*client
, ssize_t
*length
, void **endptr
)
786 char *ptr
= (char*)client
->root
->input_buffer
;
790 /* Do we have \n (indicating the command preamble)*/
791 char *end
= memchr(ptr
, '\n', (size_t)*length
);
795 return MEMCACHED_PROTOCOL_READ_EVENT
;
798 client
->ascii_command
= ascii_to_cmd(ptr
, (size_t)(*length
));
800 /* A multiget lists all of the keys, and I don't want to have an
801 * avector of let's say 512 pointers to tokenize all of them, so let's
802 * just handle them immediately
804 if (client
->ascii_command
== GET_CMD
||
805 client
->ascii_command
== GETS_CMD
) {
806 if (client
->root
->callback
->interface
.v1
.get
!= NULL
)
807 ascii_process_gets(client
, ptr
, end
);
809 spool_string(client
, "SERVER_ERROR: Command not implemented\n");
811 /* None of the defined commands takes 10 parameters, so lets just use
812 * that as a maximum limit.
815 int ntokens
= ascii_tokenize_command(ptr
, end
, tokens
, 10);
819 client
->mute
= strcmp(tokens
[ntokens
- 1], "noreply") == 0;
821 --ntokens
; /* processed noreply token*/
826 switch (client
->ascii_command
) {
828 error
= process_set_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
831 error
= process_add_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
834 error
= process_replace_command(client
, tokens
, ntokens
,
838 error
= process_cas_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
841 error
= process_append_command(client
, tokens
, ntokens
,
845 error
= process_prepend_command(client
, tokens
, ntokens
,
849 process_delete(client
, tokens
, ntokens
);
852 case INCR_CMD
: /* FALLTHROUGH */
854 process_arithmetic(client
, tokens
, ntokens
);
859 send_command_usage(client
);
863 recover_tokenize_command(ptr
, end
);
864 process_stats(client
, ptr
+ 6, end
);
868 process_flush(client
, tokens
, ntokens
);
873 send_command_usage(client
);
877 process_version(client
, tokens
, ntokens
);
881 if (ntokens
!= 1 || client
->mute
)
883 send_command_usage(client
);
887 if (client
->root
->callback
->interface
.v1
.quit
!= NULL
)
888 client
->root
->callback
->interface
.v1
.quit(client
);
890 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
896 send_command_usage(client
);
898 spool_string(client
, "OK\r\n");
902 send_command_usage(client
);
908 /* Should already be handled */
913 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
915 return MEMCACHED_PROTOCOL_READ_EVENT
;
920 *length
-= end
- ptr
;
922 } while (*length
> 0);
925 return MEMCACHED_PROTOCOL_READ_EVENT
;