proto_parser.c C 1,875 lines View on github.com → Search inside
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}

Code quality findings 4

Warning: Excessive nesting depth (>= 5 levels) detected. Deeply nested code is hard to read and maintain. Consider refactoring into separate functions.
warning maintainability deep-nesting
while (len > done && dch) {
Info: Function has many parameters (>= 6). Consider using a struct to group related parameters for better readability.
info maintainability long-parameter-list
static int _meta_flag_preparse(mcp_parser_t *pr, const size_t start,
Info: Magic number detected in condition or control flow. Use named constants via #define or const for better readability and maintainability.
info maintainability magic-number
if (o >= 127 || seen[o] != 0) {
Info: Function has many parameters (>= 6). Consider using a struct to group related parameters for better readability.
info maintainability long-parameter-list
int 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) {

Get this view in your editor

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