Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
while (len > done && dch) {
1// TODO: fix the include header nightmare.2#include "memcached.h"3#include "proto_parser.h"4#include "storage.h"5#include "base64.h"67#include <limits.h>8#include <string.h>910#define PARSER_MAXLEN USHRT_MAX-11112struct _meta_flags {13 unsigned int has_error :1; // flipped if we found an error during parsing.14 unsigned int no_update :1;15 unsigned int locked :1;16 unsigned int vivify :1;17 unsigned int la :1;18 unsigned int hit :1;19 unsigned int value :1;20 unsigned int set_stale :1;21 unsigned int no_reply :1;22 unsigned int has_cas :1;23 unsigned int has_cas_in :1;24 unsigned int new_ttl :1;25 unsigned int key_binary:1;26 unsigned int remove_val:1;27 char mode; // single character mode switch, common to ms/ma28 uint8_t key_len; // decoded binary key length29 rel_time_t exptime;30 rel_time_t autoviv_exptime;31 rel_time_t recache_time;32 client_flags_t client_flags;33 const char *key;34 uint64_t req_cas_id;35 uint64_t cas_id_in; // client supplied next-CAS36 uint64_t delta; // ma37 uint64_t initial; // ma38};3940static int _meta_flag_preparse(mcp_parser_t *pr, const size_t start,41 struct _meta_flags *of, char *binkey, char **errstr);4243/*44 * BEGIN TOKENIZER CODE45 */4647// TODO: func to just get length of token48static int _process_request_key(mcp_parser_t *pr) {49 // TODO: can klen be int?50 int len = 0;51 mcmc_token_get(pr->request, &pr->tok, pr->keytoken, &len);52 pr->klen = len;5354 return 0;55}5657// All meta commands are of form: "cm key f l a g S100"58static int _process_request_meta(mcp_parser_t *pr) {59 // TODO: can check result60 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 2);61 if (pr->tok.ntokens < 2) {62 return -1;63 }64 pr->keytoken = 1;65 _process_request_key(pr);6667 return 0;68}6970// ms <key> <datalen> <flags>*\r\n71static int _process_request_mset(mcp_parser_t *pr) {72 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 3);73 if (pr->tok.ntokens < 3) {74 return -1;75 }76 pr->keytoken = 1;77 _process_request_key(pr);7879 int vlen = 0;80 if (mcmc_token_get_32(pr->request, &pr->tok, 2, &vlen) != MCMC_OK) {81 return -1;82 }8384 if (vlen < 0 || vlen > (INT_MAX - 2)) {85 return -1;86 }87 vlen += 2;8889 pr->vlen = vlen;9091 return 0;92}9394// gat[s] <exptime> <key>*\r\n95static int _process_request_gat(mcp_parser_t *pr) {96 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);97 if (pr->tok.ntokens < 3) {98 return -1;99 }100101 pr->keytoken = 2;102 _process_request_key(pr);103 return 0;104}105106#define NOREPLYSTR "noreply"107#define NOREPLYLEN sizeof(NOREPLYSTR)-1108// given a tokenized parser for a normal ASCII command, checks for noreply109// mode.110static int _process_request_noreply(mcp_parser_t *pr) {111 int len = 0;112 const char *tok = mcmc_token_get(pr->request, &pr->tok, pr->tok.ntokens-1, &len);113 if (len == NOREPLYLEN && strncmp(NOREPLYSTR, tok, NOREPLYLEN) == 0) {114 pr->noreply = true;115 }116 return 0;117}118119// we need t find the bytes supplied immediately so we can read the request120// from the client properly.121// set <key> <flags> <exptime> <bytes> [noreply]\r\n122static int _process_request_storage(mcp_parser_t *pr) {123 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);124 if (pr->tok.ntokens < 5) {125 return -1;126 }127 pr->keytoken = 1;128 _process_request_key(pr);129130 int vlen = 0;131 if (mcmc_token_get_32(pr->request, &pr->tok, 4, &vlen) != MCMC_OK) {132 return -1;133 }134135 if (vlen < 0 || vlen > (INT_MAX - 2)) {136 return -1;137 }138 vlen += 2;139140 pr->vlen = vlen;141142 return _process_request_noreply(pr);143}144145// common request with key: <cmd> <key> <args>146static int _process_request_simple(mcp_parser_t *pr, const int min) {147 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);148 if (pr->tok.ntokens < min) {149 return -1;150 }151 pr->keytoken = 1; // second token is usually the key... stupid GAT.152153 _process_request_key(pr);154 return _process_request_noreply(pr);155}156157static int _process_request_unknown(mcp_parser_t *pr) {158 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);159 return _process_request_noreply(pr);160}161162// TODO: return code ENUM with error types.163int process_request(mcp_parser_t *pr, const char *command, size_t cmdlen) {164 // we want to "parse in place" as much as possible, which allows us to165 // forward an unmodified request without having to rebuild it.166167 const char *cm = command;168 size_t cl = 0;169 // min command length is 2, plus the "\r\n"170 if (cmdlen < 4) {171 return -1;172 }173174 // Commands can end with bare '\n's. Depressingly I intended to be strict175 // with a \r\n requirement but never did this and need backcompat.176 // In this case we _know_ \n is at cmdlen because we can't enter this177 // function otherwise.178 size_t endlen = 0;179 assert(cm[cmdlen-1] == '\n');180 if (cm[cmdlen-2] == '\r') {181 endlen = cmdlen - 2;182 } else {183 endlen = cmdlen - 1;184 }185186 const char *s = memchr(command, ' ', endlen);187 if (s != NULL) {188 cl = s - command;189 } else {190 cl = endlen;191 }192 pr->keytoken = 0;193 pr->request = command;194 pr->reqlen = cmdlen;195196 int cmd = -1;197 int type = CMD_TYPE_GENERIC;198 int ret = 0;199200 switch (cl) {201 case 0:202 case 1:203 // falls through with cmd as -1. should error.204 break;205 case 2:206 if (cm[0] == 'm') {207 type = CMD_TYPE_META;208 switch (cm[1]) {209 case 'g':210 cmd = CMD_MG;211 ret = _process_request_meta(pr);212 break;213 case 's':214 cmd = CMD_MS;215 ret = _process_request_mset(pr);216 break;217 case 'd':218 cmd = CMD_MD;219 ret = _process_request_meta(pr);220 break;221 case 'n':222 // TODO: do we route/handle NOP's at all?223 // they should simply reflect to the client.224 cmd = CMD_MN;225 break;226 case 'a':227 cmd = CMD_MA;228 ret = _process_request_meta(pr);229 break;230 case 'e':231 cmd = CMD_ME;232 // TODO: not much special processing here; binary keys233 ret = _process_request_meta(pr);234 break;235 }236 }237 break;238 case 3:239 if (cm[0] == 'g') {240 if (cm[1] == 'e' && cm[2] == 't') {241 cmd = CMD_GET;242 type = CMD_TYPE_GET;243 ret = _process_request_simple(pr, 2);244 }245 if (cm[1] == 'a' && cm[2] == 't') {246 type = CMD_TYPE_GET;247 cmd = CMD_GAT;248 ret = _process_request_gat(pr);249 }250 } else if (cm[0] == 's' && cm[1] == 'e' && cm[2] == 't') {251 cmd = CMD_SET;252 ret = _process_request_storage(pr);253 } else if (cm[0] == 'a' && cm[1] == 'd' && cm[2] == 'd') {254 cmd = CMD_ADD;255 ret = _process_request_storage(pr);256 } else if (cm[0] == 'c' && cm[1] == 'a' && cm[2] == 's') {257 cmd = CMD_CAS;258 ret = _process_request_storage(pr);259 }260 break;261 case 4:262 if (strncmp(cm, "gets", 4) == 0) {263 cmd = CMD_GETS;264 type = CMD_TYPE_GET;265 ret = _process_request_simple(pr, 2);266 } else if (strncmp(cm, "incr", 4) == 0) {267 cmd = CMD_INCR;268 ret = _process_request_simple(pr, 3);269 } else if (strncmp(cm, "decr", 4) == 0) {270 cmd = CMD_DECR;271 ret = _process_request_simple(pr, 3);272 } else if (strncmp(cm, "gats", 4) == 0) {273 cmd = CMD_GATS;274 type = CMD_TYPE_GET;275 ret = _process_request_gat(pr);276 } else if (strncmp(cm, "quit", 4) == 0) {277 cmd = CMD_QUIT;278 }279 break;280 case 5:281 if (strncmp(cm, "touch", 5) == 0) {282 cmd = CMD_TOUCH;283 ret = _process_request_simple(pr, 3);284 } else if (strncmp(cm, "stats", 5) == 0) {285 cmd = CMD_STATS;286 // Don't process a key; fetch via arguments.287 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);288 } else if (strncmp(cm, "watch", 5) == 0) {289 cmd = CMD_WATCH;290 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);291 }292 break;293 case 6:294 if (strncmp(cm, "delete", 6) == 0) {295 cmd = CMD_DELETE;296 ret = _process_request_simple(pr, 2);297 } else if (strncmp(cm, "append", 6) == 0) {298 cmd = CMD_APPEND;299 ret = _process_request_storage(pr);300 }301 break;302 case 7:303 if (strncmp(cm, "replace", 7) == 0) {304 cmd = CMD_REPLACE;305 ret = _process_request_storage(pr);306 } else if (strncmp(cm, "prepend", 7) == 0) {307 cmd = CMD_PREPEND;308 ret = _process_request_storage(pr);309 } else if (strncmp(cm, "version", 7) == 0) {310 cmd = CMD_VERSION;311 mcmc_tokenize(pr->request, pr->reqlen, &pr->tok, 255);312 }313 break;314 }315316 // TODO: log more specific error code.317 if (cmd == -1) {318 _process_request_unknown(pr);319 return PROCESS_REQUEST_CMD_NOT_FOUND;320 }321322 pr->command = cmd;323 pr->cmd_type = type;324325 return ret;326}327328/*329 * END TOKENIZER CODE330 */331332/*333 * PARSER UTIL CODE334 */335336int mc_prcmp(mcp_parser_t *pr, int token, const char *s) {337 int len = 0;338 const char *t = mcmc_token_get(pr->request, &pr->tok, token, &len);339 return strncmp(t, s, len);340}341342bool mc_toktou32(mcp_parser_t *pr, int token, uint32_t *val) {343 if (mcmc_token_get_u32(pr->request, &pr->tok, token, val) != MCMC_OK) {344 return false;345 }346 return true;347}348349bool mc_tokto32(mcp_parser_t *pr, int token, int32_t *val) {350 if (mcmc_token_get_32(pr->request, &pr->tok, token, val) != MCMC_OK) {351 return false;352 }353 return true;354}355356bool mc_toktod(mcp_parser_t *pr, int token, double *val) {357 char buffer[32];358 int len = 0;359 const char *t = mcmc_token_get(pr->request, &pr->tok, token, &len);360 if (len > sizeof(buffer)-1) {361 return false;362 }363 memcpy(buffer, t, len);364 buffer[len] = '\0';365366 return safe_strtod(buffer, val);367}368369/*370 * BEGIN PROTOCOL HANDLER CODE371 */372373// FIXME: REDUNDANT374static void pout_string(mc_resp *resp, const char *str) {375 size_t len;376 bool skip = resp->skip;377 assert(resp != NULL);378379 // if response was originally filled with something, but we're now writing380 // out an error or similar, have to reset the object first.381 resp_reset(resp);382383 // We blank the response "just in case", but if we're not intending on384 // sending it lets not rewrite it.385 if (skip || resp->noreply) {386 resp->skip = true;387 return;388 }389390 // Fill response object with static string.391392 len = strlen(str);393 if ((len + 2) > WRITE_BUFFER_SIZE) {394 /* ought to be always enough. just fail for simplicity */395 str = "SERVER_ERROR output line too long";396 len = strlen(str);397 }398399 memcpy(resp->wbuf, str, len);400 memcpy(resp->wbuf + len, "\r\n", 2);401 resp_add_iov(resp, resp->wbuf, len + 2);402403 return;404}405406// FIXME: REDUNDANT407// For meta commands error strings override the quiet flag.408static void pout_errstring(mc_resp *resp, const char *str) {409 resp->skip = false;410 pout_string(resp, str);411}412413bool mc_parse_exptime(mc_resp *resp, mcp_parser_t *pr, int token, rel_time_t *exptime) {414 int32_t exptime_int = 0;415 if (mcmc_token_get_32(pr->request, &pr->tok, token, &exptime_int) != MCMC_OK) {416 pout_string(resp, "CLIENT_ERROR invalid exptime argument");417 return false;418 }419 *exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));420 return true;421}422423// FIXME: use mcmc func!424static int _process_token_len(mcp_parser_t *pr, size_t token) {425 const char *s = pr->request + pr->tok.tokens[token];426 const char *e = pr->request + pr->tok.tokens[token+1];427 // start of next token is after any space delimiters, so back those out.428 while (*(e-1) == ' ') {429 e--;430 }431 return e - s;432}433434static int _store_item_copy_from_buf(item *d_it, char *buf, const int len) {435 if (d_it->it_flags & ITEM_CHUNKED) {436 item_chunk *dch = (item_chunk *) ITEM_schunk(d_it);437 int done = 0;438 // Fill dch's via a flat data buffer439 while (len > done && dch) {440 int todo = (dch->size - dch->used < len - done)441 ? dch->size - dch->used : len - done;442 memcpy(dch->data + dch->used, buf + done, todo);443 done += todo;444 dch->used += todo;445 assert(dch->used <= dch->size);446447 if (dch->size == dch->used) {448 item_chunk *tch = do_item_alloc_chunk(dch, len - done);449 if (tch) {450 dch = tch;451 } else {452 return -1;453 }454 }455 }456 assert(len == done);457 } else {458 memcpy(ITEM_data(d_it), buf, len);459 }460461 return 0;462}463464// FIXME: rename from _meta to pr_meta465static int _meta_flag_preparse(mcp_parser_t *pr, const size_t start,466 struct _meta_flags *of, char *binkey, char **errstr) {467 unsigned int i;468 size_t ret;469 int32_t tmp_int;470 uint8_t seen[127] = {0};471 of->key = MCP_PARSER_KEY(pr);472 of->key_len = pr->klen;473474 if (pr->klen > KEY_MAX_LENGTH) {475 *errstr = "CLIENT_ERROR bad command line format";476 return -1;477 }478479 if (pr->tok.ntokens > MFLAG_MAX_OPT_LENGTH) {480 // TODO: ensure the command tokenizer gives us at least this many481 *errstr = "CLIENT_ERROR options flags are too long";482 return -1;483 }484485 // Start just past the key token. Look at first character of each token.486 for (i = start; i < pr->tok.ntokens; i++) {487 uint8_t o = (uint8_t)pr->request[pr->tok.tokens[i]];488 // zero out repeat flags so we don't over-parse for return data.489 if (o >= 127 || seen[o] != 0) {490 *errstr = "CLIENT_ERROR duplicate flag";491 return -1;492 }493 seen[o] = 1;494 switch (o) {495 // base64 decode the key in stack memory, as the binary should always be496 // shorter and the conversion code buffers bytes.497 case 'b':498 ret = base64_decode((unsigned char *)MCP_PARSER_KEY(pr), pr->klen,499 (unsigned char *)binkey, pr->klen);500 if (ret == 0) {501 // Failed to decode502 *errstr = "CLIENT_ERROR error decoding key";503 of->has_error = 1;504 }505 of->key = binkey;506 of->key_len = ret;507 of->key_binary = 1;508 break;509 /* Negative exptimes can underflow and end up immortal. realtime() will510 immediately expire values that are greater than REALTIME_MAXDELTA, but less511 than process_started, so lets aim for that. */512 case 'N':513 of->locked = 1;514 of->vivify = 1;515 if (mcmc_token_get_flag_arg_32(pr->request, &pr->tok, i, &tmp_int) == MCMC_OK) {516 of->autoviv_exptime = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));517 } else {518 *errstr = "CLIENT_ERROR bad token in command line format";519 of->has_error = 1;520 }521 break;522 case 'T':523 of->locked = 1;524 if (mcmc_token_get_flag_arg_32(pr->request, &pr->tok, i, &tmp_int) == MCMC_OK) {525 of->exptime = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));526 of->new_ttl = true;527 } else {528 *errstr = "CLIENT_ERROR bad token in command line format";529 of->has_error = 1;530 }531 break;532 case 'R':533 of->locked = 1;534 if (mcmc_token_get_flag_arg_32(pr->request, &pr->tok, i, &tmp_int) == MCMC_OK) {535 of->recache_time = realtime(EXPTIME_TO_POSITIVE_TIME(tmp_int));536 } else {537 *errstr = "CLIENT_ERROR bad token in command line format";538 of->has_error = 1;539 }540 break;541 case 'l':542 of->la = 1;543 of->locked = 1; // need locked to delay LRU bump544 break;545 case 'O':546 case 'P':547 case 'L':548 break;549 case 'k': // known but no special handling550 case 's':551 case 't':552 case 'c':553 case 'f':554 break;555 case 'v':556 of->value = 1;557 break;558 case 'h':559 of->locked = 1; // need locked to delay LRU bump560 break;561 case 'u':562 of->no_update = 1;563 break;564 case 'q':565 of->no_reply = 1;566 break;567 case 'x':568 of->remove_val = 1;569 break;570 // mset-related.571 case 'F':572#ifdef LARGE_CLIENT_FLAGS573 if (mcmc_token_get_flag_arg_u64(pr->request, &pr->tok, i, &of->client_flags) != MCMC_OK) {574#else575 if (mcmc_token_get_flag_arg_u32(pr->request, &pr->tok, i, &of->client_flags) != MCMC_OK) {576#endif577 of->has_error = true;578 }579 break;580 case 'C': // mset, mdelete, marithmetic581 if (mcmc_token_get_flag_arg_u64(pr->request, &pr->tok, i, &of->req_cas_id) == MCMC_OK) {582 of->has_cas = true;583 } else {584 *errstr = "CLIENT_ERROR bad token in command line format";585 of->has_error = 1;586 }587 break;588 case 'E': // ms, md, ma589 if (mcmc_token_get_flag_arg_u64(pr->request, &pr->tok, i, &of->cas_id_in) == MCMC_OK) {590 of->has_cas_in = true;591 } else {592 *errstr = "CLIENT_ERROR bad token in command line format";593 of->has_error = 1;594 }595 break;596 case 'M': // mset and marithmetic mode switch597 // FIXME: this used to error if the token isn't a single byte.598 // It probably should still?599 of->mode = pr->request[pr->tok.tokens[i]+1];600 break;601 case 'J': // marithmetic initial value602 if (mcmc_token_get_flag_arg_u64(pr->request, &pr->tok, i, &of->initial) != MCMC_OK) {603 *errstr = "CLIENT_ERROR invalid numeric initial value";604 of->has_error = 1;605 }606 break;607 case 'D': // marithmetic delta value608 if (mcmc_token_get_flag_arg_u64(pr->request, &pr->tok, i, &of->delta) != MCMC_OK) {609 *errstr = "CLIENT_ERROR invalid numeric delta value";610 of->has_error = 1;611 }612 break;613 case 'I':614 of->set_stale = 1;615 break;616 default: // unknown flag, bail.617 *errstr = "CLIENT_ERROR invalid flag";618 return -1;619 }620 }621622 return of->has_error ? -1 : 0;623}624625/* client flags == 0 means use no storage for client flags */626static inline int make_ascii_get_suffix(char *suffix, item *it, bool return_cas, int nbytes) {627 char *p = suffix;628 *p = ' ';629 p++;630 if (FLAGS_SIZE(it) == 0) {631 *p = '0';632 p++;633 } else {634 client_flags_t flags;635 FLAGS_CONV(it, flags);636 p = itoa_u64(flags, p);637 }638 *p = ' ';639 p = itoa_u32(nbytes-2, p+1);640641 if (return_cas) {642 *p = ' ';643 p = itoa_u64(ITEM_get_cas(it), p+1);644 }645646 *p = '\r';647 *(p+1) = '\n';648 *(p+2) = '\0';649 return (p - suffix) + 2;650}651652int process_get_cmd(LIBEVENT_THREAD *t, const char *key, const int nkey, mc_resp *resp, parser_storage_get_cb storage_cb, rel_time_t exptime, bool return_cas, bool should_touch) {653 bool overflow = false; // unused.654655 if (nkey > KEY_MAX_LENGTH) {656 pout_string(resp, "CLIENT_ERROR bad command line format");657 return -1;658 }659660 item *it = limited_get(key, nkey, t, exptime, should_touch, DO_UPDATE, &overflow);661 if (settings.detail_enabled) {662 stats_prefix_record_get(key, nkey, NULL != it);663 }664 if (it) {665 int nbytes = it->nbytes;;666 nbytes = it->nbytes;667 char *p = resp->wbuf;668 memcpy(p, "VALUE ", 6);669 p += 6;670 memcpy(p, ITEM_key(it), it->nkey);671 p += it->nkey;672 p += make_ascii_get_suffix(p, it, return_cas, nbytes);673 resp_add_iov(resp, resp->wbuf, p - resp->wbuf);674675#ifdef EXTSTORE676 if (it->it_flags & ITEM_HDR) {677 if (storage_cb(t, it, resp) != 0) {678 pthread_mutex_lock(&t->stats.mutex);679 t->stats.get_oom_extstore++;680 pthread_mutex_unlock(&t->stats.mutex);681682 item_remove(it);683 pout_errstring(resp, "SERVER_ERROR out of memory writing get response");684 return -1;685 }686 } else if ((it->it_flags & ITEM_CHUNKED) == 0) {687 resp_add_iov(resp, ITEM_data(it), it->nbytes);688 } else {689 resp_add_chunked_iov(resp, it, it->nbytes);690 }691#else692 if ((it->it_flags & ITEM_CHUNKED) == 0) {693 resp_add_iov(resp, ITEM_data(it), it->nbytes);694 } else {695 resp_add_chunked_iov(resp, it, it->nbytes);696 }697#endif698699 /* item_get() has incremented it->refcount for us */700 pthread_mutex_lock(&t->stats.mutex);701 if (should_touch) {702 t->stats.touch_cmds++;703 t->stats.slab_stats[ITEM_clsid(it)].touch_hits++;704 } else {705 t->stats.lru_hits[it->slabs_clsid]++;706 t->stats.get_cmds++;707 }708 pthread_mutex_unlock(&t->stats.mutex);709#ifdef EXTSTORE710 /* If ITEM_HDR, an io_wrap owns the reference. */711 if ((it->it_flags & ITEM_HDR) == 0) {712 resp->item = it;713 }714#else715 resp->item = it;716#endif717718 if (settings.verbose > 1) {719 int ii;720 fprintf(stderr, ">%d sending key ", t->cur_sfd);721 for (ii = 0; ii < it->nkey; ++ii) {722 fprintf(stderr, "%c", key[ii]);723 }724 fprintf(stderr, "\n");725 }726 } else {727 pthread_mutex_lock(&t->stats.mutex);728 if (should_touch) {729 t->stats.touch_cmds++;730 t->stats.touch_misses++;731 } else {732 t->stats.get_misses++;733 t->stats.get_cmds++;734 }735 pthread_mutex_unlock(&t->stats.mutex);736 }737738 return 0;739}740741item *process_update_cmd_start(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp, int comm, bool handle_cas) {742 const char *key = MCP_PARSER_KEY(pr);743 size_t nkey = pr->klen;744 client_flags_t flags;745 int32_t exptime_int = 0;746 rel_time_t exptime = 0;747 uint64_t req_cas_id = 0;748 item *it;749750 assert(resp != NULL);751752 if (nkey > KEY_MAX_LENGTH) {753 pout_string(resp, "CLIENT_ERROR bad command line format");754 return NULL;755 }756757 if (! (safe_strtoflags(&pr->request[pr->tok.tokens[2]], &flags)758 && safe_strtol(&pr->request[pr->tok.tokens[3]], &exptime_int))) {759 pout_string(resp, "CLIENT_ERROR bad command line format");760 return NULL;761 }762763 exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));764765 // does cas value exist?766 if (handle_cas) {767 if (mcmc_token_get_u64(pr->request, &pr->tok, 5, &req_cas_id) != MCMC_OK) {768 pout_string(resp, "CLIENT_ERROR bad command line format");769 return NULL;770 }771 }772773 // vlen is validated from the main parser.774775 if (settings.detail_enabled) {776 stats_prefix_record_set(key, nkey);777 }778779 it = item_alloc(key, nkey, flags, exptime, pr->vlen);780781 if (it == 0) {782 enum store_item_type status;783 if (! item_size_ok(nkey, flags, pr->vlen)) {784 pout_string(resp, "SERVER_ERROR object too large for cache");785 status = TOO_LARGE;786 pthread_mutex_lock(&t->stats.mutex);787 t->stats.store_too_large++;788 pthread_mutex_unlock(&t->stats.mutex);789 } else {790 pout_string(resp, "SERVER_ERROR out of memory storing object");791 status = NO_MEMORY;792 pthread_mutex_lock(&t->stats.mutex);793 t->stats.store_no_memory++;794 pthread_mutex_unlock(&t->stats.mutex);795 }796 LOGGER_LOG(t->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,797 NULL, status, comm, key, nkey, 0, 0, t->cur_sfd);798799 /* Avoid stale data persisting in cache because we failed alloc.800 * Unacceptable for SET. Anywhere else too? */801 if (comm == NREAD_SET) {802 it = item_get(key, nkey, t, DONT_UPDATE);803 if (it) {804 item_unlink(it);805 STORAGE_delete(t->storage, it);806 item_remove(it);807 }808 }809810 return NULL;811 }812 ITEM_set_cas(it, req_cas_id);813814 pthread_mutex_lock(&t->stats.mutex);815 t->stats.slab_stats[ITEM_clsid(it)].set_cmds++;816 pthread_mutex_unlock(&t->stats.mutex);817818 return it;819}820821void process_update_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp, int comm, bool handle_cas) {822 item *it = process_update_cmd_start(t, pr, resp, comm, handle_cas);823 if (it == NULL) {824 return;825 }826 // complete_nread_proxy() does the data chunk check so all we need to do827 // is copy the data.828 if (_store_item_copy_from_buf(it, pr->vbuf, it->nbytes) != 0) {829 pout_string(resp, "SERVER_ERROR out of memory storing object");830 item_remove(it);831 return;832 }833834 int ret = store_item(it, comm, t, NULL, NULL, (settings.use_cas) ? get_cas_id() : 0, CAS_NO_STALE);835 switch (ret) {836 case STORED:837 pout_string(resp, "STORED");838 break;839 case EXISTS:840 pout_string(resp, "EXISTS");841 break;842 case NOT_FOUND:843 pout_string(resp, "NOT_FOUND");844 break;845 case NOT_STORED:846 pout_string(resp, "NOT_STORED");847 break;848 default:849 pout_string(resp, "SERVER_ERROR Unhandled storage type.");850 }851852 // We don't need to hold a reference since the item was fully read.853 item_remove(it);854}855856void process_arithmetic_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp, const bool incr) {857 char temp[INCR_MAX_STORAGE_LEN];858 uint64_t delta;859 const char *key = MCP_PARSER_KEY(pr);860 size_t nkey = pr->klen;861862 assert(t != NULL);863864 if (nkey > KEY_MAX_LENGTH) {865 pout_string(resp, "CLIENT_ERROR bad command line format");866 return;867 }868869 if (mcmc_token_get_u64(pr->request, &pr->tok, 2, &delta) != MCMC_OK) {870 pout_string(resp, "CLIENT_ERROR invalid numeric delta argument");871 return;872 }873874 switch(add_delta(t, key, nkey, incr, delta, temp, NULL)) {875 case OK:876 pout_string(resp, temp);877 break;878 case NON_NUMERIC:879 pout_string(resp, "CLIENT_ERROR cannot increment or decrement non-numeric value");880 break;881 case EOM:882 pout_string(resp, "SERVER_ERROR out of memory");883 break;884 case DELTA_ITEM_NOT_FOUND:885 pthread_mutex_lock(&t->stats.mutex);886 if (incr) {887 t->stats.incr_misses++;888 } else {889 t->stats.decr_misses++;890 }891 pthread_mutex_unlock(&t->stats.mutex);892893 pout_string(resp, "NOT_FOUND");894 break;895 case DELTA_ITEM_CAS_MISMATCH:896 break; /* Should never get here */897 }898}899900void process_delete_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp) {901 const char *key = MCP_PARSER_KEY(pr);902 size_t nkey = pr->klen;903 item *it;904 uint32_t hv;905906 assert(t != NULL);907908 // NOTE: removed a compatibility bodge from a decade ago.909 // delete used to take a "delay" argument, which was removed, but some910 // ancient php clients always sent a 0 argument, which would then fail.911 // It's been long enough that I don't want to carry this forward into the912 // new parser.913914 if (nkey > KEY_MAX_LENGTH) {915 pout_string(resp, "CLIENT_ERROR bad command line format");916 return;917 }918919 if (settings.detail_enabled) {920 stats_prefix_record_delete(key, nkey);921 }922923 it = item_get_locked(key, nkey, t, DONT_UPDATE, &hv);924 if (it) {925 //MEMCACHED_COMMAND_DELETE(c->sfd, ITEM_key(it), it->nkey);926927 pthread_mutex_lock(&t->stats.mutex);928 t->stats.slab_stats[ITEM_clsid(it)].delete_hits++;929 pthread_mutex_unlock(&t->stats.mutex);930 LOGGER_LOG(t->l, LOG_DELETIONS, LOGGER_DELETIONS, it, LOG_TYPE_DELETE);931 do_item_unlink(it, hv);932 STORAGE_delete(t->storage, it);933 do_item_remove(it); /* release our reference */934 pout_string(resp, "DELETED");935 } else {936 pthread_mutex_lock(&t->stats.mutex);937 t->stats.delete_misses++;938 pthread_mutex_unlock(&t->stats.mutex);939940 pout_string(resp, "NOT_FOUND");941 }942 item_unlock(hv);943}944945void process_touch_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp) {946 const char *key = MCP_PARSER_KEY(pr);947 size_t nkey = pr->klen;948 int32_t exptime_int = 0;949 rel_time_t exptime = 0;950 item *it;951952 assert(t != NULL);953954 if (nkey > KEY_MAX_LENGTH) {955 pout_string(resp, "CLIENT_ERROR bad command line format");956 return;957 }958959 if (mcmc_token_get_32(pr->request, &pr->tok, 2, &exptime_int) != MCMC_OK) {960 pout_string(resp, "CLIENT_ERROR invalid exptime argument");961 return;962 }963964 exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));965 it = item_touch(key, nkey, exptime, t);966 if (it) {967 pthread_mutex_lock(&t->stats.mutex);968 t->stats.touch_cmds++;969 t->stats.slab_stats[ITEM_clsid(it)].touch_hits++;970 pthread_mutex_unlock(&t->stats.mutex);971972 pout_string(resp, "TOUCHED");973 item_remove(it);974 } else {975 pthread_mutex_lock(&t->stats.mutex);976 t->stats.touch_cmds++;977 t->stats.touch_misses++;978 pthread_mutex_unlock(&t->stats.mutex);979980 pout_string(resp, "NOT_FOUND");981 }982}983984void process_mget_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp,985 parser_storage_get_cb storage_cb) {986 char binkey[KEY_MAX_LENGTH]; // if we decode a binary key, put it here.987 item *it;988 unsigned int i = 0;989 struct _meta_flags of = {0}; // option bitflags.990 uint32_t hv; // cached hash value for unlocking an item.991 bool failed = false;992 bool item_created = false;993 bool won_token = false;994 bool ttl_set = false;995 char *errstr = "CLIENT_ERROR bad command line format";996 assert(t != NULL);997 char *p = resp->wbuf;998 int tlen = 0;9991000 // scrubs duplicated options and sets flags for how to load the item.1001 // we pass in the first token that should be a flag.1002 if (_meta_flag_preparse(pr, 2, &of, binkey, &errstr) != 0) {1003 pout_errstring(resp, errstr);1004 return;1005 }10061007 // indirect key access, in case of binary key.1008 const char *key = of.key;1009 size_t nkey = of.key_len;10101011 bool overflow = false;1012 if (!of.locked) {1013 it = limited_get(key, nkey, t, 0, false, !of.no_update, &overflow);1014 } else {1015 // If we had to lock the item, we're doing our own bump later.1016 it = limited_get_locked(key, nkey, t, DONT_UPDATE, &hv, &overflow);1017 }10181019 // Since we're a new protocol, we can actually inform users that refcount1020 // overflow is happening by straight up throwing an error.1021 // We definitely don't want to re-autovivify by accident.1022 if (overflow) {1023 assert(it == NULL);1024 pout_errstring(resp, "SERVER_ERROR refcount overflow during fetch");1025 return;1026 }10271028 if (it == NULL && of.vivify) {1029 // Fill in the exptime during parsing later.1030 it = item_alloc(key, nkey, 0, realtime(0), 2);1031 // We don't actually need any of do_store_item's logic:1032 // - already fetched and missed an existing item.1033 // - lock is still held.1034 // - not append/prepend/replace1035 // - not testing CAS1036 if (it != NULL) {1037 // I look forward to the day I get rid of this :)1038 memcpy(ITEM_data(it), "\r\n", 2);1039 // NOTE: This initializes the CAS value.1040 do_item_link(it, hv, of.has_cas_in ? of.cas_id_in : get_cas_id());1041 item_created = true;1042 }1043 }10441045 // don't have to check result of add_iov() since the iov size defaults are1046 // enough.1047 if (it) {1048 if (of.has_cas && ITEM_get_cas(it) == of.req_cas_id) {1049 of.value = false;1050 }10511052 if (of.value) {1053 memcpy(p, "VA ", 3);1054 p = itoa_u32(it->nbytes-2, p+3);1055 } else {1056 memcpy(p, "HD", 2);1057 p += 2;1058 }10591060 for (i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1061 switch (pr->request[pr->tok.tokens[i]]) {1062 case 'T':1063 ttl_set = true;1064 it->exptime = of.exptime;1065 break;1066 case 'N':1067 if (item_created) {1068 it->exptime = of.autoviv_exptime;1069 won_token = true;1070 }1071 break;1072 case 'R':1073 // If we haven't autovivified and supplied token is less1074 // than current TTL, mark a win.1075 if ((it->it_flags & ITEM_TOKEN_SENT) == 01076 && !item_created1077 && it->exptime != 01078 && it->exptime < of.recache_time) {1079 won_token = true;1080 }1081 break;1082 case 's':1083 META_CHAR(p, 's');1084 p = itoa_u32(it->nbytes-2, p);1085 break;1086 case 't':1087 // TTL remaining as of this request.1088 // needs to be relative because server clocks may not be in sync.1089 META_CHAR(p, 't');1090 if (it->exptime == 0) {1091 *p = '-';1092 *(p+1) = '1';1093 p += 2;1094 } else {1095 p = itoa_u32(it->exptime - current_time, p);1096 }1097 break;1098 case 'c':1099 META_CHAR(p, 'c');1100 p = itoa_u64(ITEM_get_cas(it), p);1101 break;1102 case 'f':1103 META_CHAR(p, 'f');1104 if (FLAGS_SIZE(it) == 0) {1105 *p = '0';1106 p++;1107 } else {1108 client_flags_t flags;1109 FLAGS_CONV(it, flags);1110 p = itoa_u64(flags, p);1111 }1112 break;1113 case 'l':1114 META_CHAR(p, 'l');1115 p = itoa_u32(current_time - it->time, p);1116 break;1117 case 'h':1118 META_CHAR(p, 'h');1119 if (it->it_flags & ITEM_FETCHED) {1120 *p = '1';1121 } else {1122 *p = '0';1123 }1124 p++;1125 break;1126 case 'O':1127 tlen = _process_token_len(pr, i);1128 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1129 errstr = "CLIENT_ERROR opaque token too long";1130 goto error;1131 }1132 META_SPACE(p);1133 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1134 p += tlen;1135 break;1136 case 'k':1137 META_KEY(p, ITEM_key(it), it->nkey, (it->it_flags & ITEM_KEY_BINARY));1138 break;1139 }1140 }11411142 // Has this item already sent a token?1143 // Important to do this here so we don't send W with Z.1144 // Isn't critical, but easier for client authors to understand.1145 if (it->it_flags & ITEM_TOKEN_SENT) {1146 META_CHAR(p, 'Z');1147 }1148 if (it->it_flags & ITEM_STALE) {1149 META_CHAR(p, 'X');1150 // FIXME: think hard about this. is this a default, or a flag?1151 if ((it->it_flags & ITEM_TOKEN_SENT) == 0) {1152 // If we're stale but no token already sent, now send one.1153 won_token = true;1154 }1155 }11561157 if (won_token) {1158 // Mark a win into the flag buffer.1159 META_CHAR(p, 'W');1160 it->it_flags |= ITEM_TOKEN_SENT;1161 }11621163 *p = '\r';1164 *(p+1) = '\n';1165 *(p+2) = '\0';1166 p += 2;1167 // finally, chain in the buffer.1168 resp_add_iov(resp, resp->wbuf, p - resp->wbuf);11691170 if (of.value) {1171#ifdef EXTSTORE1172 if (it->it_flags & ITEM_HDR) {1173 if (storage_cb(t, it, resp) != 0) {1174 pthread_mutex_lock(&t->stats.mutex);1175 t->stats.get_oom_extstore++;1176 pthread_mutex_unlock(&t->stats.mutex);11771178 failed = true;1179 }1180 } else if ((it->it_flags & ITEM_CHUNKED) == 0) {1181 resp_add_iov(resp, ITEM_data(it), it->nbytes);1182 } else {1183 resp_add_chunked_iov(resp, it, it->nbytes);1184 }1185#else1186 if ((it->it_flags & ITEM_CHUNKED) == 0) {1187 resp_add_iov(resp, ITEM_data(it), it->nbytes);1188 } else {1189 resp_add_chunked_iov(resp, it, it->nbytes);1190 }1191#endif1192 }11931194 // need to hold the ref at least because of the key above.1195#ifdef EXTSTORE1196 if (!failed) {1197 if ((it->it_flags & ITEM_HDR) != 0 && of.value) {1198 // Only have extstore clean if header and returning value.1199 resp->item = NULL;1200 } else {1201 resp->item = it;1202 }1203 } else {1204 // Failed to set up extstore fetch.1205 if (of.locked) {1206 do_item_remove(it);1207 } else {1208 item_remove(it);1209 }1210 }1211#else1212 resp->item = it;1213#endif1214 } else {1215 failed = true;1216 }12171218 if (of.locked) {1219 // Delayed bump so we could get fetched/last access time pre-update.1220 if (!of.no_update && it != NULL) {1221 do_item_bump(t, it, hv);1222 }1223 item_unlock(hv);1224 }12251226 // we count this command as a normal one if we've gotten this far.1227 // TODO: for autovivify case, miss never happens. Is this okay?1228 if (!failed) {1229 pthread_mutex_lock(&t->stats.mutex);1230 if (ttl_set) {1231 t->stats.touch_cmds++;1232 t->stats.slab_stats[ITEM_clsid(it)].touch_hits++;1233 } else {1234 t->stats.lru_hits[it->slabs_clsid]++;1235 t->stats.get_cmds++;1236 }1237 pthread_mutex_unlock(&t->stats.mutex);1238 } else {1239 pthread_mutex_lock(&t->stats.mutex);1240 if (ttl_set) {1241 t->stats.touch_cmds++;1242 t->stats.touch_misses++;1243 } else {1244 t->stats.get_misses++;1245 t->stats.get_cmds++;1246 }1247 pthread_mutex_unlock(&t->stats.mutex);12481249 // This gets elided in noreply mode.1250 if (of.no_reply)1251 resp->skip = true;1252 memcpy(p, "EN", 2);1253 p += 2;1254 for (i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1255 switch (pr->request[pr->tok.tokens[i]]) {1256 // TODO: macro perhaps?1257 case 'O':1258 tlen = _process_token_len(pr, i);1259 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1260 errstr = "CLIENT_ERROR opaque token too long";1261 goto error;1262 }1263 META_SPACE(p);1264 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1265 p += tlen;1266 break;1267 case 'k':1268 META_KEY(p, key, nkey, of.key_binary);1269 break;1270 }1271 }1272 resp->wbytes = p - resp->wbuf;1273 memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);1274 resp->wbytes += 2;1275 resp_add_iov(resp, resp->wbuf, resp->wbytes);1276 }1277 return;1278error:1279 if (it) {1280 if (of.locked) {1281 do_item_remove(it);1282 item_unlock(hv);1283 } else {1284 item_remove(it);1285 }1286 }1287 pout_errstring(resp, errstr);1288}12891290item *process_mset_cmd_start(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp,1291 uint64_t *cas_in, bool *has_cas_in, short *comm) {1292 char binkey[KEY_MAX_LENGTH]; // if we decode a binary key, put it here.1293 item *it;1294 *comm = NREAD_SET;1295 struct _meta_flags of = {0}; // option bitflags.1296 char *errstr = "CLIENT_ERROR bad command line format";1297 uint32_t hv; // cached hash value.1298 int vlen = pr->vlen; // value from data line.1299 assert(t != NULL);13001301 if (pr->tok.ntokens < 3) {1302 pout_string(resp, "CLIENT_ERROR bad command line format");1303 return NULL;1304 }13051306 // We need to at least try to get the size to properly slurp bad bytes1307 // after an error.1308 // we pass in the first token that should be a flag.1309 if (_meta_flag_preparse(pr, 3, &of, binkey, &errstr) != 0) {1310 goto error;1311 }13121313 // indirect key access, in case of binary key.1314 const char *key = of.key;1315 size_t nkey = of.key_len;13161317 rel_time_t exptime = of.exptime;1318 // "mode switch" to alternative commands1319 switch (of.mode) {1320 case 0:1321 break; // no mode supplied.1322 case 'E': // Add...1323 *comm = NREAD_ADD;1324 break;1325 case 'A': // Append.1326 if (of.vivify) {1327 *comm = NREAD_APPENDVIV;1328 exptime = of.autoviv_exptime;1329 } else {1330 *comm = NREAD_APPEND;1331 }1332 break;1333 case 'P': // Prepend.1334 if (of.vivify) {1335 *comm = NREAD_PREPENDVIV;1336 exptime = of.autoviv_exptime;1337 } else {1338 *comm = NREAD_PREPEND;1339 }1340 break;1341 case 'R': // Replace.1342 *comm = NREAD_REPLACE;1343 break;1344 case 'S': // Set. Default.1345 *comm = NREAD_SET;1346 break;1347 default:1348 errstr = "CLIENT_ERROR invalid mode for ms M token";1349 goto error;1350 }13511352 // The item storage function doesn't exactly map to mset.1353 // If a CAS value is supplied, upgrade default SET mode to CAS mode.1354 // Also allows REPLACE to work, as REPLACE + CAS works the same as CAS.1355 // add-with-cas works the same as add; but could only LRU bump if match..1356 // APPEND/PREPEND allow a simplified CAS check.1357 if (of.has_cas && (*comm == NREAD_SET || *comm == NREAD_REPLACE)) {1358 *comm = NREAD_CAS;1359 }13601361 it = item_alloc(key, nkey, of.client_flags, exptime, vlen);13621363 if (it == 0) {1364 enum store_item_type status;1365 if (! item_size_ok(nkey, of.client_flags, vlen)) {1366 errstr = "SERVER_ERROR object too large for cache";1367 status = TOO_LARGE;1368 pthread_mutex_lock(&t->stats.mutex);1369 t->stats.store_too_large++;1370 pthread_mutex_unlock(&t->stats.mutex);1371 } else {1372 errstr = "SERVER_ERROR out of memory storing object";1373 status = NO_MEMORY;1374 pthread_mutex_lock(&t->stats.mutex);1375 t->stats.store_no_memory++;1376 pthread_mutex_unlock(&t->stats.mutex);1377 }1378 // FIXME: LOGGER_LOG specific to mset, include options.1379 LOGGER_LOG(t->l, LOG_MUTATIONS, LOGGER_ITEM_STORE,1380 NULL, status, comm, key, nkey, 0, t->cur_sfd);13811382 /* Avoid stale data persisting in cache because we failed alloc. */1383 // NOTE: only if SET mode?1384 it = item_get_locked(key, nkey, t, DONT_UPDATE, &hv);1385 if (it) {1386 do_item_unlink(it, hv);1387 STORAGE_delete(t->storage, it);1388 do_item_remove(it);1389 }1390 item_unlock(hv);13911392 goto error;1393 }1394 ITEM_set_cas(it, of.req_cas_id);13951396 // data should already be read into the request.13971398 // Prevent printing back the key in meta commands as garbage.1399 if (of.key_binary) {1400 it->it_flags |= ITEM_KEY_BINARY;1401 }14021403 resp->set_stale = CAS_NO_STALE;1404 if (of.set_stale && *comm == NREAD_CAS) {1405 resp->set_stale = CAS_ALLOW_STALE;1406 }1407 resp->noreply = of.no_reply;14081409 pthread_mutex_lock(&t->stats.mutex);1410 t->stats.slab_stats[ITEM_clsid(it)].set_cmds++;1411 pthread_mutex_unlock(&t->stats.mutex);14121413 if (of.has_cas_in) {1414 *cas_in = of.cas_id_in;1415 *has_cas_in = true;1416 }1417 return it;1418error:1419 // Note: no errors possible after the item was successfully allocated.1420 // So we're just looking at dumping error codes and returning.1421 pout_errstring(resp, errstr);1422 return NULL;1423}14241425void process_mset_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp) {1426 bool has_cas_in = false;1427 uint64_t cas_in = 0;1428 short comm = 0;1429 item *it = process_mset_cmd_start(t, pr, resp, &cas_in, &has_cas_in, &comm);1430 char *errstr = "CLIENT_ERROR bad command line format";14311432 if (it == NULL)1433 return;14341435 // complete_nread_proxy() does the data chunk check so all we need to do1436 // is copy the data.1437 if (_store_item_copy_from_buf(it, pr->vbuf, it->nbytes) != 0) {1438 pout_string(resp, "SERVER_ERROR out of memory storing object");1439 item_remove(it);1440 return;1441 }14421443 char *p = resp->wbuf;1444 uint64_t cas = 0;1445 int nbytes = 0;1446 int ret = store_item(it, comm, t, &nbytes, &cas, has_cas_in ? cas_in : get_cas_id(), resp->set_stale);1447 switch (ret) {1448 case STORED:1449 memcpy(p, "HD", 2);1450 // Only place noreply is used for meta cmds is a nominal response.1451 if (resp->noreply) {1452 resp->skip = true;1453 }1454 break;1455 case EXISTS:1456 memcpy(p, "EX", 2);1457 break;1458 case NOT_FOUND:1459 memcpy(p, "NF", 2);1460 break;1461 case NOT_STORED:1462 memcpy(p, "NS", 2);1463 break;1464 default:1465 pout_errstring(resp, "SERVER_ERROR Unhandled storage type.");1466 item_remove(it);1467 return;14681469 }1470 p += 2;14711472 for (int i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1473 int tlen;1474 switch (pr->request[pr->tok.tokens[i]]) {1475 case 'O':1476 tlen = _process_token_len(pr, i);1477 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1478 errstr = "CLIENT_ERROR opaque token too long";1479 goto error;1480 }1481 META_SPACE(p);1482 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1483 p += tlen;1484 break;1485 case 'k':1486 META_KEY(p, ITEM_key(it), it->nkey, (it->it_flags & ITEM_KEY_BINARY));1487 break;1488 case 'c':1489 META_CHAR(p, 'c');1490 p = itoa_u64(cas, p);1491 break;1492 case 's':1493 // Get final item size, ie from append/prepend1494 META_CHAR(p, 's');1495 // If the size changed during append/prepend1496 if (nbytes != 0) {1497 p = itoa_u32(nbytes-2, p);1498 } else {1499 p = itoa_u32(it->nbytes-2, p);1500 }1501 break;1502 }1503 }15041505 // We don't need to free pr->vbuf as that is owned by *rq1506 // either way, there's no c->item or resp->item reference right now.15071508 memcpy(p, "\r\n", 2);1509 p += 2;1510 // we're offset into wbuf, but good convention to track wbytes.1511 resp->wbytes = p - resp->wbuf;1512 resp_add_iov(resp, resp->wbuf, resp->wbytes);15131514 item_remove(it);15151516 return;1517error:1518 pout_errstring(resp, errstr);1519 item_remove(it);1520}15211522void process_mdelete_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp) {1523 char binkey[KEY_MAX_LENGTH]; // if we decode a binary key, put it here.1524 item *it = NULL;1525 int i;1526 uint32_t hv = 0;1527 struct _meta_flags of = {0}; // option bitflags.1528 char *errstr = "CLIENT_ERROR bad command line format";1529 assert(t != NULL);1530 // reserve bytes for status code1531 char *p = resp->wbuf + 2;1532 int tlen = 0;15331534 // scrubs duplicated options and sets flags for how to load the item.1535 // we pass in the first token that should be a flag.1536 if (_meta_flag_preparse(pr, 2, &of, binkey, &errstr) != 0) {1537 pout_errstring(resp, errstr);1538 return;1539 }15401541 // indirect key access, in case of binary key.1542 const char *key = of.key;1543 size_t nkey = of.key_len;15441545 for (i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1546 switch (pr->request[pr->tok.tokens[i]]) {1547 // TODO: macro perhaps?1548 case 'O':1549 tlen = _process_token_len(pr, i);1550 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1551 errstr = "CLIENT_ERROR opaque token too long";1552 goto error;1553 }1554 META_SPACE(p);1555 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1556 p += tlen;1557 break;1558 case 'k':1559 META_KEY(p, key, nkey, of.key_binary);1560 break;1561 }1562 }15631564 it = item_get_locked(key, nkey, t, DONT_UPDATE, &hv);1565 if (it) {1566 // allow only deleting/marking if a CAS value matches.1567 if (of.has_cas && ITEM_get_cas(it) != of.req_cas_id) {1568 pthread_mutex_lock(&t->stats.mutex);1569 t->stats.delete_misses++;1570 pthread_mutex_unlock(&t->stats.mutex);15711572 memcpy(resp->wbuf, "EX", 2);1573 goto cleanup;1574 }15751576 // If requested, create a new empty tombstone item.1577 if (of.remove_val) {1578 item *new_it = item_alloc(key, nkey, of.client_flags, of.exptime, 2);1579 if (new_it != NULL) {1580 memcpy(ITEM_data(new_it), "\r\n", 2);1581 if (do_store_item(new_it, NREAD_SET, t, hv, NULL, NULL,1582 of.has_cas_in ? of.cas_id_in : ITEM_get_cas(it), CAS_NO_STALE)) {1583 do_item_remove(it);1584 it = new_it;1585 } else {1586 do_item_remove(new_it);1587 memcpy(resp->wbuf, "NS", 2);1588 goto cleanup;1589 }1590 } else {1591 errstr = "SERVER_ERROR out of memory";1592 goto error;1593 }1594 }15951596 // If we're to set this item as stale, we don't actually want to1597 // delete it. We mark the stale bit, bump CAS, and update exptime if1598 // we were supplied a new TTL.1599 if (of.set_stale) {1600 if (of.new_ttl) {1601 it->exptime = of.exptime;1602 }1603 it->it_flags |= ITEM_STALE;1604 // Also need to remove TOKEN_SENT, so next client can win.1605 it->it_flags &= ~ITEM_TOKEN_SENT;16061607 ITEM_set_cas(it, of.has_cas_in ? of.cas_id_in : get_cas_id());1608 if (of.no_reply)1609 resp->skip = true;16101611 memcpy(resp->wbuf, "HD", 2);1612 } else {1613 pthread_mutex_lock(&t->stats.mutex);1614 t->stats.slab_stats[ITEM_clsid(it)].delete_hits++;1615 pthread_mutex_unlock(&t->stats.mutex);1616 LOGGER_LOG(t->l, LOG_DELETIONS, LOGGER_DELETIONS, it, LOG_TYPE_META_DELETE);16171618 if (!of.remove_val) {1619 do_item_unlink(it, hv);1620 STORAGE_delete(t->storage, it);1621 }1622 if (of.no_reply)1623 resp->skip = true;1624 memcpy(resp->wbuf, "HD", 2);1625 }1626 goto cleanup;1627 } else {1628 pthread_mutex_lock(&t->stats.mutex);1629 t->stats.delete_misses++;1630 pthread_mutex_unlock(&t->stats.mutex);16311632 memcpy(resp->wbuf, "NF", 2);1633 goto cleanup;1634 }1635cleanup:1636 if (it) {1637 do_item_remove(it);1638 }1639 // Item is always returned locked, even if missing.1640 item_unlock(hv);1641 resp->wbytes = p - resp->wbuf;1642 memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);1643 resp->wbytes += 2;1644 resp_add_iov(resp, resp->wbuf, resp->wbytes);1645 //conn_set_state(c, conn_new_cmd);1646 return;1647error:1648 // cleanup if an error happens after we fetched an item.1649 if (it) {1650 do_item_remove(it);1651 item_unlock(hv);1652 }1653 pout_errstring(resp, errstr);1654}16551656void process_marithmetic_cmd(LIBEVENT_THREAD *t, mcp_parser_t *pr, mc_resp *resp) {1657 char binkey[KEY_MAX_LENGTH]; // if we decode a binary key, put it here.1658 int i;1659 struct _meta_flags of = {0}; // option bitflags.1660 char *errstr = "CLIENT_ERROR bad command line format";1661 assert(t != NULL);1662 // no reservation (like del/set) since we post-process the status line.1663 char *p = resp->wbuf;1664 int tlen = 0;16651666 // If no argument supplied, incr or decr by one.1667 of.delta = 1;1668 of.initial = 0; // redundant, for clarity.1669 bool incr = true; // default mode is to increment.1670 bool locked = false;1671 uint32_t hv = 0;1672 item *it = NULL; // item returned by do_add_delta.16731674 // scrubs duplicated options and sets flags for how to load the item.1675 // we pass in the first token that should be a flag.1676 if (_meta_flag_preparse(pr, 2, &of, binkey, &errstr) != 0) {1677 pout_errstring(resp, "CLIENT_ERROR invalid or duplicate flag");1678 return;1679 }16801681 // indirect key access, in case of binary key.1682 const char *key = of.key;1683 size_t nkey = of.key_len;16841685 // "mode switch" to alternative commands1686 switch (of.mode) {1687 case 0: // no switch supplied.1688 break;1689 case 'I': // Incr (default)1690 case '+':1691 incr = true;1692 break;1693 case 'D': // Decr.1694 case '-':1695 incr = false;1696 break;1697 default:1698 errstr = "CLIENT_ERROR invalid mode for ma M token";1699 goto error;1700 break;1701 }17021703 // take hash value and manually lock item... hold lock during store phase1704 // on miss and avoid recalculating the hash multiple times.1705 hv = hash(key, nkey);1706 item_lock(hv);1707 locked = true;1708 char tmpbuf[INCR_MAX_STORAGE_LEN];17091710 // return a referenced item if it exists, so we can modify it here, rather1711 // than adding even more parameters to do_add_delta.1712 bool item_created = false;1713 uint64_t cas = 0;1714 switch(do_add_delta(t, key, nkey, incr, of.delta, tmpbuf, &of.req_cas_id, hv, &it)) {1715 case OK:1716 if (of.no_reply)1717 resp->skip = true;1718 // *it was filled, set the status below.1719 if (of.has_cas_in) {1720 // override the CAS. slightly inefficient but fixing that can wait1721 // until the next time do_add_delta is changed.1722 ITEM_set_cas(it, of.cas_id_in);1723 }1724 cas = ITEM_get_cas(it);1725 break;1726 case NON_NUMERIC:1727 errstr = "CLIENT_ERROR cannot increment or decrement non-numeric value";1728 goto error;1729 break;1730 case EOM:1731 errstr = "SERVER_ERROR out of memory";1732 goto error;1733 break;1734 case DELTA_ITEM_NOT_FOUND:1735 if (of.vivify) {1736 itoa_u64(of.initial, tmpbuf);1737 int vlen = strlen(tmpbuf);17381739 it = item_alloc(key, nkey, 0, 0, vlen+2);1740 if (it != NULL) {1741 memcpy(ITEM_data(it), tmpbuf, vlen);1742 memcpy(ITEM_data(it) + vlen, "\r\n", 2);1743 if (do_store_item(it, NREAD_ADD, t, hv, NULL, &cas,1744 of.has_cas_in ? of.cas_id_in : get_cas_id(), CAS_NO_STALE)) {1745 item_created = true;1746 if (of.no_reply)1747 resp->skip = true;1748 } else {1749 // Not sure how we can get here if we're holding the lock.1750 memcpy(resp->wbuf, "NS", 2);1751 }1752 } else {1753 errstr = "SERVER_ERROR Out of memory allocating new item";1754 goto error;1755 }1756 } else {1757 pthread_mutex_lock(&t->stats.mutex);1758 if (incr) {1759 t->stats.incr_misses++;1760 } else {1761 t->stats.decr_misses++;1762 }1763 pthread_mutex_unlock(&t->stats.mutex);1764 // won't have a valid it here.1765 memcpy(p, "NF", 2);1766 p += 2;1767 }1768 break;1769 case DELTA_ITEM_CAS_MISMATCH:1770 // also returns without a valid it.1771 memcpy(p, "EX", 2);1772 p += 2;1773 break;1774 }17751776 // final loop1777 // allows building the response with information after vivifying from a1778 // miss, or returning a new CAS value after add_delta().1779 if (it) {1780 size_t vlen = strlen(tmpbuf);1781 if (of.value) {1782 memcpy(p, "VA ", 3);1783 p = itoa_u32(vlen, p+3);1784 } else {1785 memcpy(p, "HD", 2);1786 p += 2;1787 }17881789 for (i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1790 switch (pr->request[pr->tok.tokens[i]]) {1791 case 'c':1792 META_CHAR(p, 'c');1793 p = itoa_u64(cas, p);1794 break;1795 case 't':1796 META_CHAR(p, 't');1797 if (it->exptime == 0) {1798 *p = '-';1799 *(p+1) = '1';1800 p += 2;1801 } else {1802 p = itoa_u32(it->exptime - current_time, p);1803 }1804 break;1805 case 'T':1806 it->exptime = of.exptime;1807 break;1808 case 'N':1809 if (item_created) {1810 it->exptime = of.autoviv_exptime;1811 }1812 break;1813 case 'O':1814 tlen = _process_token_len(pr, i);1815 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1816 errstr = "CLIENT_ERROR opaque token too long";1817 goto error;1818 }1819 META_SPACE(p);1820 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1821 p += tlen;1822 break;1823 case 'k':1824 META_KEY(p, key, nkey, of.key_binary);1825 break;1826 }1827 }18281829 if (of.value) {1830 *p = '\r';1831 *(p+1) = '\n';1832 p += 2;1833 memcpy(p, tmpbuf, vlen);1834 p += vlen;1835 }18361837 do_item_remove(it);1838 } else {1839 // No item to handle. still need to return opaque/key tokens1840 for (i = pr->keytoken+1; i < pr->tok.ntokens; i++) {1841 switch (pr->request[pr->tok.tokens[i]]) {1842 case 'O':1843 tlen = _process_token_len(pr, i);1844 if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {1845 errstr = "CLIENT_ERROR opaque token too long";1846 goto error;1847 }1848 META_SPACE(p);1849 memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);1850 p += tlen;1851 break;1852 case 'k':1853 META_KEY(p, key, nkey, of.key_binary);1854 break;1855 }1856 }1857 }18581859 item_unlock(hv);18601861 resp->wbytes = p - resp->wbuf;1862 memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);1863 resp->wbytes += 2;1864 resp_add_iov(resp, resp->wbuf, resp->wbytes);1865 return;1866error:1867 if (it != NULL)1868 do_item_remove(it);1869 if (locked)1870 item_unlock(hv);1871 pout_errstring(resp, errstr);1872}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings