terom@42: #include terom@42: #include terom@41: #include terom@41: terom@41: #include "command.h" terom@41: #include "../common.h" terom@41: terom@41: static char *memcache_cmd_names[MEMCACHE_CMD_MAX] = { terom@41: NULL, // MEMCACHE_CMD_INVALID terom@41: "get", // MEMCACHE_CMD_FETCH_GET terom@41: "set", // MEMCACHE_CMD_STORE_SET terom@41: "add", // MEMCACHE_CMD_STORE_ADD terom@41: "replace", // MEMCACHE_CMD_STORE_REPLACE terom@41: "append", // MEMCACHE_CMD_STORE_APPEND terom@41: "prepend" // MEMCACHE_CMD_STORE_PREPEND terom@41: }; terom@41: terom@41: static struct memcache_reply_info { terom@41: enum memcache_reply type; terom@41: char *name; terom@41: terom@42: } memcache_cmd_replies[MEMCACHE_RPL_MAX] = { terom@42: { MEMCACHE_RPL_INVALID, NULL }, terom@42: { MEMCACHE_RPL_ERROR, "ERROR" }, terom@42: { MEMCACHE_RPL_CLIENT_ERROR, "CLIENT_ERROR" }, terom@42: { MEMCACHE_RPL_SERVER_ERROR, "SERVER_ERROR" }, terom@41: terom@41: // MEMCACHE_CMD_FETCH_* terom@42: { MEMCACHE_RPL_VALUE, "VALUE" }, terom@42: { MEMCACHE_RPL_END, "END" }, terom@41: terom@41: // MEMCACHE_CMD_STORE_* terom@42: { MEMCACHE_RPL_STORED, "STORED" }, terom@42: { MEMCACHE_RPL_NOT_STORED, "NOT_STORED" }, terom@42: { MEMCACHE_RPL_EXISTS, "EXISTS" }, terom@42: { MEMCACHE_RPL_NOT_FOUND, "NOT_FOUND" } terom@41: }; terom@41: terom@42: terom@41: terom@41: int memcache_cmd_init (struct memcache_cmd *cmd, enum memcache_command cmd_type, struct memcache_key *key, struct memcache_obj *obj) { terom@41: // shouldn't already have a request header yet? terom@41: assert(cmd->req_header == NULL); terom@41: terom@41: // allocate the request header terom@41: if ((cmd->req_header = evbuffer_new()) == NULL) terom@41: ERROR("evbuffer_new"); terom@41: terom@41: // format the command terom@41: if (memcache_cmd_format_header(cmd->req_header, cmd_type, key, obj)) terom@41: goto error; terom@41: terom@41: // XXX: prepare the rest terom@41: terom@41: // success terom@41: return 0; terom@41: terom@41: error: terom@41: if (cmd->req_header) terom@41: evbuffer_free(cmd->req_header); terom@41: terom@41: return -1; terom@41: } terom@41: terom@41: int memcache_cmd_format_header (struct evbuffer *buf, enum memcache_command cmd_type, struct memcache_key *key, struct memcache_obj *obj) { terom@41: char *cmd_name; terom@41: terom@41: // valid command terom@41: assert(cmd_type < MEMCACHE_CMD_MAX); terom@41: terom@41: if (cmd_type == MEMCACHE_CMD_INVALID) terom@41: ERROR("invalid command"); terom@41: terom@41: // map the command to a string terom@41: cmd_name = memcache_cmd_names[cmd_type]; terom@41: terom@41: // format the request header terom@41: switch (cmd_type) { terom@41: case MEMCACHE_CMD_FETCH_GET: terom@41: assert(key != NULL && obj == NULL); terom@41: assert(key->len > 0 && key->buf != NULL); terom@41: terom@41: if (evbuffer_add_printf(buf, "%s %*s\r\n", cmd_name, (int) key->len, key->buf) == -1) terom@41: ERROR("evbuffer_add_printf"); terom@41: terom@41: break; terom@41: terom@41: case MEMCACHE_CMD_STORE_SET: terom@41: case MEMCACHE_CMD_STORE_ADD: terom@41: case MEMCACHE_CMD_STORE_REPLACE: terom@41: case MEMCACHE_CMD_STORE_APPEND: terom@41: case MEMCACHE_CMD_STORE_PREPEND: terom@41: assert(key != NULL && obj != NULL); terom@41: assert(key->len > 0 && key->buf != NULL); terom@41: assert(obj->bytes > 0); terom@41: terom@41: 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)) terom@41: ERROR("evbuffer_add_printf"); terom@41: terom@41: break; terom@41: terom@41: case MEMCACHE_CMD_STORE_CAS: terom@41: default: terom@41: // XXX: not supported yet/invalid terom@41: assert(0); terom@41: }; terom@41: terom@41: // success terom@41: return 0; terom@41: terom@41: error: terom@41: return -1; terom@41: terom@41: } terom@41: terom@41: 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) { terom@42: size_t line_length, buf_size; terom@42: char *line = NULL, *token_cursor, *token, *invalid; terom@42: int i; terom@42: terom@42: // take note of how long the buffer is terom@42: buf_size = evbuffer_get_length(buf); terom@42: terom@42: // first, try and read a full line terom@42: if ((line = evbuffer_readln(buf, &line_length, EVBUFFER_EOL_CRLF_STRICT)) == NULL) { terom@42: // check if any data was consumed terom@42: if (evbuffer_get_length(buf) != buf_size) { terom@42: // faaaail! terom@42: return -1; terom@42: terom@42: } else { terom@42: // no complete line found terom@42: return 0; terom@42: } terom@42: } terom@42: terom@42: // just check to make sure that it really is null-delimited terom@42: assert(line[line_length - 1] == '\0'); terom@42: terom@42: // empty lines? terom@42: if (line_length == 0) { terom@42: PWARNING("empty reply line !?!"); terom@42: return 0; terom@42: } terom@42: terom@42: // use strsep terom@42: token_cursor = line; terom@42: terom@42: // the first token should be the reply name terom@42: if ((token = strsep(&token_cursor, " ")) == NULL) terom@42: ERROR("no reply name in response line: %s", line); terom@42: terom@42: // figure out the reply type terom@42: for (i = MEMCACHE_RPL_INVALID + 1; i < MEMCACHE_RPL_MAX; i++) { terom@42: if (strcmp(token, memcache_cmd_replies[i].name) == 0) terom@42: break; terom@42: } terom@42: terom@42: // did we figure out what this reply means? terom@42: if (i == MEMCACHE_RPL_MAX) terom@42: ERROR("unrecognized reply: %s", line); terom@42: terom@42: // we found the type terom@42: *reply_type = memcache_cmd_replies[i].type; terom@42: terom@42: // default to no data terom@42: *has_data = 0; terom@42: terom@42: switch (*reply_type) { terom@42: case MEMCACHE_RPL_ERROR: terom@42: // no additional data terom@42: terom@42: break; terom@42: terom@42: case MEMCACHE_RPL_CLIENT_ERROR: terom@42: case MEMCACHE_RPL_SERVER_ERROR: terom@42: // the rest of the line is a human-readable error message terom@42: WARNING("received a %s reply: %s", token, token_cursor); terom@42: terom@42: break; terom@42: terom@42: case MEMCACHE_RPL_VALUE: terom@42: // [] terom@42: terom@42: // the key field terom@42: if ((key->buf = strsep(&token_cursor, " ")) == NULL) terom@42: ERROR("missing key in VALUE reply"); terom@42: terom@42: if ((key->len = strlen(key->buf)) == 0) terom@42: ERROR("zero-length key in VALUE reply"); terom@42: terom@42: // the flags field terom@42: if ((token = strsep(&token_cursor, " ")) == NULL) terom@42: ERROR("missing flags in VALUE reply"); terom@42: terom@42: obj->flags = (u_int32_t) strtol(token, &invalid, 10); terom@42: terom@42: if (*invalid != '\0') terom@42: ERROR("invalid flags in VALUE reply: %s (%s)", token, invalid); terom@42: terom@42: // the bytes field terom@42: if ((token = strsep(&token_cursor, " ")) == NULL) terom@42: ERROR("missing bytes in VALUE reply"); terom@42: terom@42: obj->bytes = (u_int32_t) strtol(token, &invalid, 10); terom@42: terom@42: if (*invalid != '\0') terom@42: ERROR("invalid bytes in VALUE reply: %s (%s)", token, invalid); terom@42: terom@42: // the optional cas field terom@42: if ((token = strsep(&token_cursor, " ")) != NULL) { terom@42: // there is a cas value present terom@42: obj->cas = (u_int64_t) strtoll(token, &invalid, 10); terom@42: terom@42: if (*invalid != '\0') terom@42: ERROR("invalid cas value in VALUE reply: %s (%s)", token, invalid); terom@42: terom@42: } else { terom@42: obj->cas = 0; terom@42: terom@42: } terom@42: terom@42: // and we do have data following this terom@42: *has_data = 1; terom@42: terom@42: break; terom@42: terom@42: case MEMCACHE_RPL_END: terom@42: // no additional data terom@42: terom@42: break; terom@42: terom@42: case MEMCACHE_RPL_STORED: terom@42: case MEMCACHE_RPL_NOT_STORED: terom@42: case MEMCACHE_RPL_EXISTS: terom@42: case MEMCACHE_RPL_NOT_FOUND: terom@42: // no additional data terom@42: terom@42: break; terom@42: terom@42: default: terom@42: assert(0); terom@42: }; terom@42: terom@42: // success terom@42: *header_data = line; terom@42: terom@42: return 0; terom@42: terom@42: error: terom@42: free(line); terom@42: terom@42: return -1; terom@41: } terom@41: