fix doc tpyos, rename some enums, fix printf format len for non-zero terminated strings (hg status), pass args to memcache_cmd_format_header via memcache_req_*, handle zero-length STORE requests, memcache_req is_buf_ours + free, other function name typos (keymemcache_req_key), fix req state behaviour re *_DATA_* for STORE requests and FETCH/END, better memcache_server connpool events/management, modular memcache_test with a working benchmark. This is a long commit message.
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "command.h"
#include "../common.h"
static char *memcache_cmd_names[MEMCACHE_CMD_MAX] = {
NULL, // MEMCACHE_CMD_INVALID
"get", // MEMCACHE_CMD_FETCH_GET
"set", // MEMCACHE_CMD_STORE_SET
"add", // MEMCACHE_CMD_STORE_ADD
"replace", // MEMCACHE_CMD_STORE_REPLACE
"append", // MEMCACHE_CMD_STORE_APPEND
"prepend" // MEMCACHE_CMD_STORE_PREPEND
};
static struct memcache_reply_info {
enum memcache_reply type;
char *name;
} memcache_cmd_replies[MEMCACHE_RPL_MAX] = {
{ MEMCACHE_RPL_INVALID, NULL },
{ MEMCACHE_RPL_ERROR, "ERROR" },
{ MEMCACHE_RPL_CLIENT_ERROR, "CLIENT_ERROR" },
{ MEMCACHE_RPL_SERVER_ERROR, "SERVER_ERROR" },
// MEMCACHE_CMD_FETCH_*
{ MEMCACHE_RPL_VALUE, "VALUE" },
{ MEMCACHE_RPL_END, "END" },
// MEMCACHE_CMD_STORE_*
{ MEMCACHE_RPL_STORED, "STORED" },
{ MEMCACHE_RPL_NOT_STORED, "NOT_STORED" },
{ MEMCACHE_RPL_EXISTS, "EXISTS" },
{ MEMCACHE_RPL_NOT_FOUND, "NOT_FOUND" }
};
int memcache_cmd_init (struct memcache_cmd *cmd, enum memcache_command cmd_type, struct memcache_key *key, struct memcache_obj *obj) {
// shouldn't already have a request header yet?
assert(cmd->req_header == NULL);
// allocate the request header
if ((cmd->req_header = evbuffer_new()) == NULL)
ERROR("evbuffer_new");
// format the command
if (memcache_cmd_format_header(cmd->req_header, cmd_type, key, obj))
goto error;
// XXX: prepare the rest
// success
return 0;
error:
if (cmd->req_header)
evbuffer_free(cmd->req_header);
return -1;
}
int memcache_cmd_format_header (struct evbuffer *buf, enum memcache_command cmd_type, const struct memcache_key *key, const struct memcache_obj *obj) {
char *cmd_name;
// valid command
assert(cmd_type < MEMCACHE_CMD_MAX);
if (cmd_type == MEMCACHE_CMD_INVALID)
ERROR("invalid command");
// map the command to a string
cmd_name = memcache_cmd_names[cmd_type];
// format the request header
switch (cmd_type) {
case MEMCACHE_CMD_FETCH_GET:
assert(key != NULL && obj == NULL);
assert(key->len > 0 && key->buf != NULL);
if (evbuffer_add_printf(buf, "%s %.*s\r\n", cmd_name, (int) key->len, key->buf) == -1)
ERROR("evbuffer_add_printf");
break;
case MEMCACHE_CMD_STORE_SET:
case MEMCACHE_CMD_STORE_ADD:
case MEMCACHE_CMD_STORE_REPLACE:
case MEMCACHE_CMD_STORE_APPEND:
case MEMCACHE_CMD_STORE_PREPEND:
assert(key != NULL && obj != NULL);
assert(key->len > 0 && key->buf != NULL);
// XXX: ensure that we have a valid buf
if (evbuffer_add_printf(buf, "%s %.*s %u %lu %zu\r\n", cmd_name, (int) key->len, key->buf, obj->flags, obj->exptime, obj->bytes) == -1)
ERROR("evbuffer_add_printf");
break;
case MEMCACHE_CMD_STORE_CAS:
default:
// XXX: not supported yet/invalid
assert(0);
};
// success
return 0;
error:
return -1;
}
int memcache_cmd_parse_header (struct evbuffer *buf, char **header_data, enum memcache_reply *reply_type, struct memcache_key *key, struct memcache_obj *obj, int *has_data) {
size_t line_length, buf_size;
char *line = NULL, *token_cursor, *token, *invalid;
int i;
// read lines until we find one that isn't empty
do {
// take note of how long the buffer is
buf_size = evbuffer_get_length(buf);
// free the prvious line in case it was empty
free(line);
// try and read a line
if ((line = evbuffer_readln(buf, &line_length, EVBUFFER_EOL_CRLF_STRICT)) == NULL) {
// check if any data was consumed
if (evbuffer_get_length(buf) != buf_size) {
// faaaail!
return -1;
} else {
// no complete line found
return 0;
}
}
} while (line_length == 0);
// just check to make sure that it really is null-delimited
assert(line[line_length] == '\0');
// use strsep
token_cursor = line;
// the first token should be the reply name
if ((token = strsep(&token_cursor, " ")) == NULL)
ERROR("no reply name in response line: %s", line);
// figure out the reply type
for (i = MEMCACHE_RPL_INVALID + 1; i < MEMCACHE_RPL_MAX; i++) {
if (strcmp(token, memcache_cmd_replies[i].name) == 0)
break;
}
// did we figure out what this reply means?
if (i == MEMCACHE_RPL_MAX)
ERROR("unrecognized reply: %s", line);
// we found the type
*reply_type = memcache_cmd_replies[i].type;
// default to no data
*has_data = 0;
switch (*reply_type) {
case MEMCACHE_RPL_ERROR:
// no additional data
break;
case MEMCACHE_RPL_CLIENT_ERROR:
case MEMCACHE_RPL_SERVER_ERROR:
// the rest of the line is a human-readable error message
WARNING("received a %s reply: %s", token, token_cursor);
break;
case MEMCACHE_RPL_VALUE:
// <key> <flags> <bytes> [<cas unique>]
// the key field
if ((key->buf = strsep(&token_cursor, " ")) == NULL)
ERROR("missing key in VALUE reply");
if ((key->len = strlen(key->buf)) == 0)
ERROR("zero-length key in VALUE reply");
// the flags field
if ((token = strsep(&token_cursor, " ")) == NULL)
ERROR("missing flags in VALUE reply");
obj->flags = (u_int32_t) strtol(token, &invalid, 10);
if (*invalid != '\0')
ERROR("invalid flags in VALUE reply: %s (%s)", token, invalid);
// the bytes field
if ((token = strsep(&token_cursor, " ")) == NULL)
ERROR("missing bytes in VALUE reply");
obj->bytes = (u_int32_t) strtol(token, &invalid, 10);
if (*invalid != '\0')
ERROR("invalid bytes in VALUE reply: %s (%s)", token, invalid);
// the optional cas field
if ((token = strsep(&token_cursor, " ")) != NULL) {
// there is a cas value present
obj->cas = (u_int64_t) strtoll(token, &invalid, 10);
if (*invalid != '\0')
ERROR("invalid cas value in VALUE reply: %s (%s)", token, invalid);
} else {
obj->cas = 0;
}
// and we do have data following this
*has_data = 1;
break;
case MEMCACHE_RPL_END:
// no additional data
break;
case MEMCACHE_RPL_STORED:
case MEMCACHE_RPL_NOT_STORED:
case MEMCACHE_RPL_EXISTS:
case MEMCACHE_RPL_NOT_FOUND:
// no additional data
break;
default:
assert(0);
};
// success
*header_data = line;
return 0;
error:
free(line);
return -1;
}