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 %" PRIu64
"\r\n", flags
,
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
), "%"PRIu64
"\r\n", result
);
444 spool_string(client
, buffer
);
448 spool_string(client
, "NOT_FOUND\r\n");
453 * Process the stats command (with or without a key specified)
454 * @param key pointer to the first character after "stats"
455 * @param end pointer to the "\n"
457 static void process_stats(memcached_protocol_client_st
*client
,
458 char *key
, char *end
)
460 if (client
->root
->callback
->interface
.v1
.stat
== NULL
)
462 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
466 while (isspace(*key
))
469 uint16_t nkey
= (uint16_t)(end
- key
);
470 (void)client
->root
->callback
->interface
.v1
.stat(client
, key
, nkey
,
471 ascii_stat_response_handler
);
474 static void process_version(memcached_protocol_client_st
*client
,
475 char **tokens
, int ntokens
)
480 send_command_usage(client
);
484 if (client
->root
->callback
->interface
.v1
.version
== NULL
)
486 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
490 client
->root
->callback
->interface
.v1
.version(client
,
491 ascii_version_response_handler
);
494 static void process_flush(memcached_protocol_client_st
*client
,
495 char **tokens
, int ntokens
)
499 send_command_usage(client
);
503 if (client
->root
->callback
->interface
.v1
.flush
== NULL
)
505 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
512 timeout
= (uint32_t)strtoul(tokens
[1], NULL
, 10);
515 protocol_binary_response_status rval
;
516 rval
= client
->root
->callback
->interface
.v1
.flush(client
, timeout
);
517 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
518 spool_string(client
, "OK\r\n");
520 spool_string(client
, "SERVER_ERROR: internal error\r\n");
524 * Process one of the storage commands
525 * @param client the client performing the operation
526 * @param tokens the command tokens
527 * @param ntokens the number of tokens
528 * @param start pointer to the first character in the line
529 * @param end pointer to the pointer where the last character of this
530 * command is (IN and OUT)
531 * @param length the number of bytes available
532 * @return -1 if an error occurs (and we should just terminate the connection
533 * because we are out of sync)
534 * 0 storage command completed, continue processing
535 * 1 We need more data, so just go ahead and wait for more!
537 static inline int process_storage_command(memcached_protocol_client_st
*client
,
538 char **tokens
, int ntokens
, char *start
,
539 char **end
, ssize_t length
)
541 (void)ntokens
; /* already checked */
542 char *key
= tokens
[1];
543 uint16_t nkey
= parse_ascii_key(&key
);
547 spool_string(client
, "CLIENT_ERROR: bad key\r\n");
551 uint32_t flags
= (uint32_t)strtoul(tokens
[2], NULL
, 10);
552 uint32_t timeout
= (uint32_t)strtoul(tokens
[3], NULL
, 10);
553 unsigned long nbytes
= strtoul(tokens
[4], NULL
, 10);
555 /* Do we have all data? */
556 unsigned long need
= nbytes
+ (unsigned long)((*end
- start
) + 1) + 2; /* \n\r\n */
557 if ((ssize_t
)need
> length
)
559 /* Keep on reading */
560 recover_tokenize_command(start
, *end
);
564 void *data
= (*end
) + 1;
567 protocol_binary_response_status rval
;
568 switch (client
->ascii_command
)
571 rval
= client
->root
->callback
->interface
.v1
.set(client
, key
,
580 rval
= client
->root
->callback
->interface
.v1
.add(client
, key
,
585 timeout
, &result_cas
);
588 cas
= strtoull(tokens
[5], NULL
, 10);
591 rval
= client
->root
->callback
->interface
.v1
.replace(client
, key
,
600 rval
= client
->root
->callback
->interface
.v1
.append(client
, key
,
608 rval
= client
->root
->callback
->interface
.v1
.prepend(client
, key
,
616 /* gcc complains if I don't put all of the enums in here.. */
629 abort(); /* impossible */
632 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
634 spool_string(client
, "STORED\r\n");
638 if (client
->ascii_command
== CAS_CMD
)
640 if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_EEXISTS
)
642 spool_string(client
, "EXISTS\r\n");
644 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
646 spool_string(client
, "NOT_FOUND\r\n");
650 spool_string(client
, "NOT_STORED\r\n");
655 spool_string(client
, "NOT_STORED\r\n");
664 static int process_cas_command(memcached_protocol_client_st
*client
,
665 char **tokens
, int ntokens
, char *start
,
666 char **end
, ssize_t length
)
670 send_command_usage(client
);
674 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
676 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
680 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
683 static int process_set_command(memcached_protocol_client_st
*client
,
684 char **tokens
, int ntokens
, char *start
,
685 char **end
, ssize_t length
)
689 send_command_usage(client
);
693 if (client
->root
->callback
->interface
.v1
.set
== NULL
)
695 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
699 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
702 static int process_add_command(memcached_protocol_client_st
*client
,
703 char **tokens
, int ntokens
, char *start
,
704 char **end
, ssize_t length
)
708 send_command_usage(client
);
712 if (client
->root
->callback
->interface
.v1
.add
== NULL
)
714 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
718 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
721 static int process_replace_command(memcached_protocol_client_st
*client
,
722 char **tokens
, int ntokens
, char *start
,
723 char **end
, ssize_t length
)
727 send_command_usage(client
);
731 if (client
->root
->callback
->interface
.v1
.replace
== NULL
)
733 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
737 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
740 static int process_append_command(memcached_protocol_client_st
*client
,
741 char **tokens
, int ntokens
, char *start
,
742 char **end
, ssize_t length
)
746 send_command_usage(client
);
750 if (client
->root
->callback
->interface
.v1
.append
== NULL
)
752 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
756 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
759 static int process_prepend_command(memcached_protocol_client_st
*client
,
760 char **tokens
, int ntokens
, char *start
,
761 char **end
, ssize_t length
)
765 send_command_usage(client
);
769 if (client
->root
->callback
->interface
.v1
.prepend
== NULL
)
771 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
775 return process_storage_command(client
, tokens
, ntokens
, start
, end
, length
);
779 * The ASCII protocol support is just one giant big hack. Instead of adding
780 * a optimal ascii support, I just convert the ASCII commands to the binary
781 * protocol and calls back into the command handlers for the binary protocol ;)
783 memcached_protocol_event_t
memcached_ascii_protocol_process_data(memcached_protocol_client_st
*client
, ssize_t
*length
, void **endptr
)
785 char *ptr
= (char*)client
->root
->input_buffer
;
789 /* Do we have \n (indicating the command preamble)*/
790 char *end
= memchr(ptr
, '\n', (size_t)*length
);
794 return MEMCACHED_PROTOCOL_READ_EVENT
;
797 client
->ascii_command
= ascii_to_cmd(ptr
, (size_t)(*length
));
799 /* A multiget lists all of the keys, and I don't want to have an
800 * avector of let's say 512 pointers to tokenize all of them, so let's
801 * just handle them immediately
803 if (client
->ascii_command
== GET_CMD
||
804 client
->ascii_command
== GETS_CMD
) {
805 if (client
->root
->callback
->interface
.v1
.get
!= NULL
)
806 ascii_process_gets(client
, ptr
, end
);
808 spool_string(client
, "SERVER_ERROR: Command not implemented\n");
810 /* None of the defined commands takes 10 parameters, so lets just use
811 * that as a maximum limit.
814 int ntokens
= ascii_tokenize_command(ptr
, end
, tokens
, 10);
818 client
->mute
= strcmp(tokens
[ntokens
- 1], "noreply") == 0;
820 --ntokens
; /* processed noreply token*/
825 switch (client
->ascii_command
) {
827 error
= process_set_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
830 error
= process_add_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
833 error
= process_replace_command(client
, tokens
, ntokens
,
837 error
= process_cas_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
840 error
= process_append_command(client
, tokens
, ntokens
,
844 error
= process_prepend_command(client
, tokens
, ntokens
,
848 process_delete(client
, tokens
, ntokens
);
851 case INCR_CMD
: /* FALLTHROUGH */
853 process_arithmetic(client
, tokens
, ntokens
);
858 send_command_usage(client
);
862 recover_tokenize_command(ptr
, end
);
863 process_stats(client
, ptr
+ 6, end
);
867 process_flush(client
, tokens
, ntokens
);
872 send_command_usage(client
);
876 process_version(client
, tokens
, ntokens
);
880 if (ntokens
!= 1 || client
->mute
)
882 send_command_usage(client
);
886 if (client
->root
->callback
->interface
.v1
.quit
!= NULL
)
887 client
->root
->callback
->interface
.v1
.quit(client
);
889 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
895 send_command_usage(client
);
897 spool_string(client
, "OK\r\n");
901 send_command_usage(client
);
907 /* Should already be handled */
912 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
914 return MEMCACHED_PROTOCOL_READ_EVENT
;
919 *length
-= end
- ptr
;
921 } while (*length
> 0);
924 return MEMCACHED_PROTOCOL_READ_EVENT
;