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",
83 spool_string(client
, errmsg
[client
->ascii_command
]);
87 * Callback for the VERSION responses
88 * @param cookie client identifier
89 * @param text the length of the body
90 * @param textlen the length of the body
92 static protocol_binary_response_status
93 ascii_version_response_handler(const void *cookie
,
97 memcached_protocol_client_st
*client
= (void*)cookie
;
98 spool_string(client
, "VERSION ");
99 client
->root
->spool(client
, text
, textlen
);
100 spool_string(client
, "\r\n");
101 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
105 * Callback for the GET/GETQ/GETK and GETKQ responses
106 * @param cookie client identifier
107 * @param key the key for the item
108 * @param keylen the length of the key
109 * @param body the length of the body
110 * @param bodylen the length of the body
111 * @param flags the flags for the item
112 * @param cas the CAS id for the item
114 static protocol_binary_response_status
115 ascii_get_response_handler(const void *cookie
,
123 memcached_protocol_client_st
*client
= (void*)cookie
;
125 strcpy(buffer
, "VALUE ");
126 const char *source
= key
;
127 char *dest
= buffer
+ 6;
129 for (int x
= 0; x
< keylen
; ++x
)
131 if (*source
!= '\0' && !isspace(*source
) && !iscntrl(*source
))
137 return PROTOCOL_BINARY_RESPONSE_EINVAL
; /* key constraints in ascii */
144 size_t used
= (size_t)(dest
- buffer
);
146 if (client
->ascii_command
== GETS_CMD
)
148 snprintf(dest
, sizeof(buffer
) - used
, " %u %u %llu\r\n", flags
,
149 flags
, (unsigned long long)cas
);
153 snprintf(dest
, sizeof(buffer
) - used
, " %u %u\r\n", flags
, flags
);
156 client
->root
->spool(client
, buffer
, strlen(buffer
));
157 client
->root
->spool(client
, body
, bodylen
);
158 client
->root
->spool(client
, "\r\n", 2);
160 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
164 * Callback for the STAT responses
165 * @param cookie client identifier
166 * @param key the key for the item
167 * @param keylen the length of the key
168 * @param body the length of the body
169 * @param bodylen the length of the body
171 static protocol_binary_response_status
172 ascii_stat_response_handler(const void *cookie
,
179 memcached_protocol_client_st
*client
= (void*)cookie
;
183 spool_string(client
, "STAT ");
184 client
->root
->spool(client
, key
, keylen
);
185 spool_string(client
, " ");
186 client
->root
->spool(client
, body
, bodylen
);
187 spool_string(client
, "\r\n");
191 spool_string(client
, "END\r\n");
194 return PROTOCOL_BINARY_RESPONSE_SUCCESS
;
198 * Process a get or a gets request.
199 * @param client the client handle
200 * @param buffer the complete get(s) command
201 * @param end the last character in the command
203 static void ascii_process_gets(memcached_protocol_client_st
*client
,
204 char *buffer
, char *end
)
209 key
+= (client
->ascii_command
== GETS_CMD
) ? 5 : 4;
214 uint16_t nkey
= parse_ascii_key(&key
);
215 if (nkey
== 0) /* Invalid key... stop processing this line */
220 (void)client
->root
->callback
->interface
.v1
.get(client
, key
, nkey
,
221 ascii_get_response_handler
);
228 send_command_usage(client
);
231 client
->root
->spool(client
, "END\r\n", 5);
235 * Try to split up the command line "asdf asdf asdf asdf\n" into an
236 * argument vector for easier parsing.
237 * @param start the first character in the command line
238 * @param end the last character in the command line ("\n")
239 * @param vec the vector to insert the pointers into
240 * @size the number of elements in the vector
241 * @return the number of tokens in the vector
243 static int ascii_tokenize_command(char *str
, char *end
, char **vec
, int size
)
249 /* Skip leading blanks */
250 while (str
< end
&& isspace(*str
))
261 /* find the next non-blank field */
262 while (str
< end
&& !isspace(*str
))
267 /* zero-terminate it for easier parsing later on */
271 /* Is the vector full? */
282 * If we for some reasons needs to push the line back to read more
283 * data we have to reverse the tokenization. Just do the brain-dead replace
284 * of all '\0' to ' ' and set the last character to '\n'. We could have used
285 * the vector we created, but then we would have to search for all of the
286 * spaces we ignored...
287 * @param start pointer to the first character in the buffer to recover
288 * @param end pointer to the last character in the buffer to recover
290 static void recover_tokenize_command(char *start
, char *end
)
303 * Convert the textual command into a comcode
305 static enum ascii_cmd
ascii_to_cmd(char *start
, size_t length
)
312 { .cmd
= "get", .len
= 3, .cc
= GET_CMD
},
313 { .cmd
= "gets", .len
= 4, .cc
= GETS_CMD
},
314 { .cmd
= "set", .len
= 3, .cc
= SET_CMD
},
315 { .cmd
= "add", .len
= 3, .cc
= ADD_CMD
},
316 { .cmd
= "replace", .len
= 7, .cc
= REPLACE_CMD
},
317 { .cmd
= "cas", .len
= 3, .cc
= CAS_CMD
},
318 { .cmd
= "append", .len
= 6, .cc
= APPEND_CMD
},
319 { .cmd
= "prepend", .len
= 7, .cc
= PREPEND_CMD
},
320 { .cmd
= "delete", .len
= 6, .cc
= DELETE_CMD
},
321 { .cmd
= "incr", .len
= 4, .cc
= INCR_CMD
},
322 { .cmd
= "decr", .len
= 4, .cc
= DECR_CMD
},
323 { .cmd
= "stats", .len
= 5, .cc
= STATS_CMD
},
324 { .cmd
= "flush_all", .len
= 9, .cc
= FLUSH_ALL_CMD
},
325 { .cmd
= "version", .len
= 7, .cc
= VERSION_CMD
},
326 { .cmd
= "quit", .len
= 4, .cc
= QUIT_CMD
},
327 { .cmd
= "verbosity", .len
= 9, .cc
= VERBOSITY_CMD
},
328 { .cmd
= NULL
, .len
= 0, .cc
= UNKNOWN_CMD
}};
331 while (commands
[x
].len
> 0) {
332 if (length
>= commands
[x
].len
)
334 if (strncmp(start
, commands
[x
].cmd
, commands
[x
].len
) == 0)
337 if (length
== commands
[x
].len
|| isspace(*(start
+ commands
[x
].len
)))
339 return commands
[x
].cc
;
350 * Perform a delete operation.
352 * @param client client requesting the deletion
353 * @param tokens the command as a vector
354 * @param ntokens the number of items in the vector
356 static void process_delete(memcached_protocol_client_st
*client
,
357 char **tokens
, int ntokens
)
359 char *key
= tokens
[1];
362 if (ntokens
!= 2 || (nkey
= parse_ascii_key(&key
)) == 0)
364 send_command_usage(client
);
368 if (client
->root
->callback
->interface
.v1
.delete == NULL
)
370 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
374 protocol_binary_response_status rval
;
375 rval
= client
->root
->callback
->interface
.v1
.delete(client
, key
, nkey
, 0);
377 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
379 spool_string(client
, "DELETED\r\n");
381 else if (rval
== PROTOCOL_BINARY_RESPONSE_KEY_ENOENT
)
383 spool_string(client
, "NOT_FOUND\r\n");
388 snprintf(msg
, sizeof(msg
), "SERVER_ERROR: delete failed %u\r\n",(int)rval
);
389 spool_string(client
, msg
);
393 static void process_arithmetic(memcached_protocol_client_st
*client
,
394 char **tokens
, int ntokens
)
396 char *key
= tokens
[1];
399 if (ntokens
!= 3 || (nkey
= parse_ascii_key(&key
)) == 0)
401 send_command_usage(client
);
407 uint64_t delta
= strtoull(tokens
[2], NULL
, 10);
409 protocol_binary_response_status rval
;
410 if (client
->ascii_command
== INCR_CMD
)
412 if (client
->root
->callback
->interface
.v1
.increment
== NULL
)
414 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
417 rval
= client
->root
->callback
->interface
.v1
.increment(client
,
426 if (client
->root
->callback
->interface
.v1
.decrement
== NULL
)
428 spool_string(client
, "SERVER_ERROR: callback not implemented\r\n");
431 rval
= client
->root
->callback
->interface
.v1
.decrement(client
,
439 if (rval
== PROTOCOL_BINARY_RESPONSE_SUCCESS
)
442 snprintf(buffer
, sizeof(buffer
), "%llu\r\n",
443 (unsigned long long)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
,
836 error
= process_cas_command(client
, tokens
, ntokens
, ptr
, &end
, *length
);
839 error
= process_append_command(client
, tokens
, ntokens
,
843 error
= process_prepend_command(client
, tokens
, ntokens
,
847 process_delete(client
, tokens
, ntokens
);
850 case INCR_CMD
: /* FALLTHROUGH */
852 process_arithmetic(client
, tokens
, ntokens
);
855 recover_tokenize_command(ptr
, end
);
856 process_stats(client
, ptr
+ 6, end
);
859 process_flush(client
, tokens
, ntokens
);
862 process_version(client
, tokens
, ntokens
);
866 send_command_usage(client
);
869 if (client
->root
->callback
->interface
.v1
.quit
!= NULL
)
870 client
->root
->callback
->interface
.v1
.quit(client
);
872 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
878 send_command_usage(client
);
880 spool_string(client
, "OK\r\n");
884 send_command_usage(client
);
890 /* Should already be handled */
895 return MEMCACHED_PROTOCOL_ERROR_EVENT
;
897 return MEMCACHED_PROTOCOL_READ_EVENT
;
902 *length
-= end
- ptr
;
904 } while (*length
> 0);
907 return MEMCACHED_PROTOCOL_READ_EVENT
;