proto_text.c C 1,669 lines View on github.com → Search inside
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */2/*3 * Functions for handling the text related protocols, original and meta.4 */56#include "memcached.h"7// FIXME: only for process_proxy_stats()8// - some better/different structure for stats subcommands9// would remove this abstraction leak.10#include "proto_proxy.h"11#include "proto_parser.h"12#include "proto_text.h"13#include "authfile.h"14#include "storage.h"15#include "base64.h"16#ifdef TLS17#include "tls.h"18#endif19#include <string.h>20#include <stdlib.h>2122#define _DO_CAS true23#define _NO_CAS false24#define _DO_TOUCH true25#define _NO_TOUCH false2627// FIXME: use mcmc func!28static int _process_token_len(mcp_parser_t *pr, size_t token) {29  const char *s = pr->request + pr->tok.tokens[token];30  const char *e = pr->request + pr->tok.tokens[token+1];31  // start of next token is after any space delimiters, so back those out.32  while (*(e-1) == ' ') {33      e--;34  }35  return e - s;36}3738static void _finalize_mset(conn *c, int nbytes, enum store_item_type ret, uint64_t cas) {39    mc_resp *resp = c->resp;40    item *it = c->item;41    conn_set_state(c, conn_new_cmd);4243    // information about the response line has been stashed in wbuf.44    char *p = resp->wbuf + resp->wbytes;45    char *end = p; // end of the stashed data portion.4647    switch (ret) {48    case STORED:49      memcpy(p, "HD", 2);50      // Only place noreply is used for meta cmds is a nominal response.51      if (c->resp->noreply) {52          resp->skip = true;53      }54      break;55    case EXISTS:56      memcpy(p, "EX", 2);57      break;58    case NOT_FOUND:59      memcpy(p, "NF", 2);60      break;61    case NOT_STORED:62      memcpy(p, "NS", 2);63      break;64    default:65      out_errstring(c, "SERVER_ERROR Unhandled storage type.");66      return;67    }68    p += 2;6970    for (char *fp = resp->wbuf; fp < end; fp++) {71        switch (*fp) {72            case 'O':73                // Copy stashed opaque.74                META_SPACE(p);75                while (fp < end && *fp != ' ') {76                    *p = *fp;77                    p++;78                    fp++;79                }80                break;81            case 'k':82                // Encode the key here instead of earlier to minimize copying.83                META_KEY(p, ITEM_key(it), it->nkey, (it->it_flags & ITEM_KEY_BINARY));84                break;85            case 'c':86                // We don't have the CAS until this point, which is why we87                // generate this line so late.88                META_CHAR(p, 'c');89                p = itoa_u64(cas, p);90                break;91            case 's':92                // Get final item size, ie from append/prepend93                META_CHAR(p, 's');94                // If the size changed during append/prepend95                if (nbytes != 0) {96                    p = itoa_u32(nbytes-2, p);97                } else {98                    p = itoa_u32(it->nbytes-2, p);99                }100                break;101            default:102                break;103        }104    }105106    memcpy(p, "\r\n", 2);107    p += 2;108    // we're offset into wbuf, but good convention to track wbytes.109    resp->wbytes = p - resp->wbuf;110    resp_add_iov(resp, end, p - end);111}112113/*114 * we get here after reading the value in set/add/replace commands. The command115 * has been stored in c->cmd, and the item is ready in c->item.116 */117void complete_nread_ascii(conn *c) {118    assert(c != NULL);119120    item *it = c->item;121    int comm = c->cmd;122    enum store_item_type ret;123    bool is_valid = false;124    int nbytes = 0;125126    if ((it->it_flags & ITEM_CHUNKED) == 0) {127        if (strncmp(ITEM_data(it) + it->nbytes - 2, "\r\n", 2) == 0) {128            is_valid = true;129        }130    } else {131        char buf[2];132        /* should point to the final item chunk */133        item_chunk *ch = (item_chunk *) c->ritem;134        assert(ch->used != 0);135        /* :( We need to look at the last two bytes. This could span two136         * chunks.137         */138        if (ch->used > 1) {139            buf[0] = ch->data[ch->used - 2];140            buf[1] = ch->data[ch->used - 1];141        } else {142            assert(ch->prev);143            assert(ch->used == 1);144            buf[0] = ch->prev->data[ch->prev->used - 1];145            buf[1] = ch->data[ch->used - 1];146        }147        if (strncmp(buf, "\r\n", 2) == 0) {148            is_valid = true;149        } else {150            assert(1 == 0);151        }152    }153154    if (!is_valid) {155        // metaset mode always returns errors.156        if (c->resp->mset_res) {157            c->resp->noreply = false;158        }159        out_string(c, "CLIENT_ERROR bad data chunk");160    } else {161      uint64_t cas = 0;162      c->thread->cur_sfd = c->sfd; // cuddle sfd for logging.163      ret = store_item(it, comm, c->thread, &nbytes, &cas, c->cas ? c->cas : get_cas_id(), c->resp->set_stale);164      c->cas = 0;165166#ifdef ENABLE_DTRACE167      switch (c->cmd) {168      case NREAD_ADD:169          MEMCACHED_COMMAND_ADD(c->sfd, ITEM_key(it), it->nkey,170                                (ret == 1) ? it->nbytes : -1, cas);171          break;172      case NREAD_REPLACE:173          MEMCACHED_COMMAND_REPLACE(c->sfd, ITEM_key(it), it->nkey,174                                    (ret == 1) ? it->nbytes : -1, cas);175          break;176      case NREAD_APPEND:177          MEMCACHED_COMMAND_APPEND(c->sfd, ITEM_key(it), it->nkey,178                                   (ret == 1) ? it->nbytes : -1, cas);179          break;180      case NREAD_PREPEND:181          MEMCACHED_COMMAND_PREPEND(c->sfd, ITEM_key(it), it->nkey,182                                    (ret == 1) ? it->nbytes : -1, cas);183          break;184      case NREAD_SET:185          MEMCACHED_COMMAND_SET(c->sfd, ITEM_key(it), it->nkey,186                                (ret == 1) ? it->nbytes : -1, cas);187          break;188      case NREAD_CAS:189          MEMCACHED_COMMAND_CAS(c->sfd, ITEM_key(it), it->nkey, it->nbytes,190                                cas);191          break;192      }193#endif194195      if (c->resp->mset_res) {196          _finalize_mset(c, nbytes, ret, cas);197      } else {198          switch (ret) {199          case STORED:200              out_string(c, "STORED");201              break;202          case EXISTS:203              out_string(c, "EXISTS");204              break;205          case NOT_FOUND:206              out_string(c, "NOT_FOUND");207              break;208          case NOT_STORED:209              out_string(c, "NOT_STORED");210              break;211          default:212              out_string(c, "SERVER_ERROR Unhandled storage type.");213          }214      }215216    }217218    item_remove(c->item);       /* release the c->item reference */219    c->item = 0;220}221222#define COMMAND_TOKEN 0223#define SUBCOMMAND_TOKEN 1224#define KEY_TOKEN 1225#define MAX_AUTH_REQ_LEN 16384226#define MIN_AUTH_REQ_LEN 6227228int try_read_command_asciiauth(conn *c) {229    mcmc_tokenizer_t tok;230    memset(&tok, 0, sizeof(tok));231    char *cont = NULL;232    char *mid = NULL;233234    // TODO: move to another function.235    if (!c->sasl_started) {236        char *el, *st;237        uint32_t size = 0;238239        // impossible for the auth command to be this short.240        if (c->rbytes < 3)241            return 0;242243        st = c->rcurr;244        el = memchr(c->rcurr, '\n', c->rbytes);245246        // If no newline after 1k, getting junk data, close out.247        if (!el) {248            if (c->rbytes > 2048) {249                conn_set_state(c, conn_closing);250                return 1;251            }252            return 0;253        }254255        if (el - st < MIN_AUTH_REQ_LEN) {256            // Can't possibly be shorter than this. Parser dislikes getting257            // strings < 2 in len.258            conn_set_state(c, conn_closing);259            return 1;260        }261        // Looking for: "set foo 0 0 N\r\nuser pass\r\n"262        // key, flags, and ttl are ignored. N is used to see if we have the rest.263264        if (mcmc_tokenize(st, el - st, &tok, 255) != MCMC_OK) {265            conn_set_state(c, conn_closing);266            return 1;267        }268        // ensure the buffer is consumed.269        c->rbytes -= (el - c->rcurr) + 1;270        c->rcurr += (el - c->rcurr) + 1;271272        // final token is a NULL ender, so we have one more than expected.273        if (tok.ntokens < 5274                || strncmp(st, "set", 3) != 0275                || mcmc_token_get_u32(st, &tok, 4, &size) != MCMC_OK) {276            if (!c->resp) {277                if (!resp_start(c)) {278                    conn_set_state(c, conn_closing);279                    return 1;280                }281            }282            out_string(c, "CLIENT_ERROR unauthenticated");283            return 1;284        }285286        if (size > MAX_AUTH_REQ_LEN) {287            if (!c->resp) {288                if (!resp_start(c)) {289                    conn_set_state(c, conn_closing);290                    return 1;291                }292            }293            out_string(c, "CLIENT_ERROR auth token too long");294            return 1;295        }296297        // we don't actually care about the key at all; it can be anything.298        // we do care about the size of the remaining read.299        c->rlbytes = size + 2;300301        c->sasl_started = true; // reuse from binprot sasl, but not sasl :)302    }303304    if (c->rbytes < c->rlbytes) {305        // need more bytes.306        return 0;307    }308309    // Going to respond at this point, so attach a response object.310    if (!c->resp) {311        if (!resp_start(c)) {312            conn_set_state(c, conn_closing);313            return 1;314        }315    }316317    cont = c->rcurr;318    // advance buffer. no matter what we're stopping.319    c->rbytes -= c->rlbytes;320    c->rcurr += c->rlbytes;321    c->sasl_started = false;322323    // must end with \r\n324    // NB: I thought ASCII sets also worked with just \n, but according to325    // complete_nread_ascii only \r\n is valid.326    if (strncmp(cont + c->rlbytes - 2, "\r\n", 2) != 0) {327        out_string(c, "CLIENT_ERROR bad command line termination");328        return 1;329    }330331    // payload should be "user pass", so we can use the tokenizer.332    mid = memchr(cont, ' ', c->rlbytes);333334    if (!mid) {335        out_string(c, "CLIENT_ERROR bad authentication token format");336        return 1;337    }338339    if (authfile_check(cont, mid-cont, mid+1, c->rlbytes-2 - (mid+1-cont)) == 1) {340        out_string(c, "STORED");341        c->authenticated = true;342        c->try_read_command = try_read_command_ascii;343        pthread_mutex_lock(&c->thread->stats.mutex);344        c->thread->stats.auth_cmds++;345        pthread_mutex_unlock(&c->thread->stats.mutex);346    } else {347        out_string(c, "CLIENT_ERROR authentication failure");348        pthread_mutex_lock(&c->thread->stats.mutex);349        c->thread->stats.auth_cmds++;350        c->thread->stats.auth_errors++;351        pthread_mutex_unlock(&c->thread->stats.mutex);352    }353354    return 1;355}356357int try_read_command_ascii(conn *c) {358    char *el, *cont;359360    if (c->rbytes == 0)361        return 0;362363    el = memchr(c->rcurr, '\n', c->rbytes);364    if (!el) {365        if (c->rbytes > 2048) {366            /*367             * We didn't have a '\n' in the first few k. This _has_ to be a368             * large multiget, if not we should just nuke the connection.369             */370            char *ptr = c->rcurr;371            char *end = c->rcurr + c->rbytes-6;372            while (*ptr == ' ' && ptr != end) { /* ignore leading whitespaces */373                ++ptr;374            }375376            if (ptr - c->rcurr > 100 ||377                (strncmp(ptr, "get ", 4) && strncmp(ptr, "gets ", 5))) {378379                conn_set_state(c, conn_closing);380                return 1;381            }382383            // ASCII multigets are unbound, so our fixed size rbuf may not384            // work for this particular workload... For backcompat we'll use a385            // malloc/realloc/free routine just for this.386            if (!c->rbuf_malloced) {387                if (!rbuf_switch_to_malloc(c)) {388                    conn_set_state(c, conn_closing);389                    return 1;390                }391            }392        }393394        return 0;395    }396    cont = el + 1;397398    assert(cont <= (c->rcurr + c->rbytes));399400    c->last_cmd_time = current_time;401    process_command_ascii(c, c->rcurr, cont - c->rcurr);402403    c->rbytes -= (cont - c->rcurr);404    c->rcurr = cont;405406    assert(c->rcurr <= (c->rbuf + c->rsize));407408    return 1;409}410411static void process_get_command_err(conn *c, const char *errstr) {412    // Use passed in error or rescue the last error while processing.413    char wbuf[WRITE_BUFFER_SIZE];414    if (errstr) {415        memcpy(wbuf, errstr, strlen(errstr));416    } else {417        size_t l = c->resp->iov[0].iov_len;418        memcpy(wbuf, c->resp->wbuf, l);419        wbuf[l] = '\0';420    }421    conn_release_items(c);422    if (!resp_start(c)) {423        conn_set_state(c, conn_closing);424        return;425    }426    out_string(c, wbuf);427}428429static void process_get_command(conn *c, LIBEVENT_THREAD *t, mcp_parser_t *pr, parser_storage_get_cb storage_cb, bool return_cas, bool should_touch) {430    uint32_t keyoff = pr->tok.tokens[pr->keytoken];431    const char *curkey = pr->request + keyoff;432    int klen = pr->klen;433    const char *kend = NULL;434    rel_time_t exptime = 0;435436    if (should_touch) {437        int32_t exptime_int = 0;438        if (!mc_tokto32(pr, 1, &exptime_int)) {439            out_string(c, "CLIENT_ERROR invalid exptime argument");440            return;441        }442        exptime = realtime(EXPTIME_TO_POSITIVE_TIME(exptime_int));443    }444445    if (pr->request[pr->reqlen-2] == '\r') {446        kend = pr->request + pr->reqlen - 2;447    } else {448        kend = pr->request + pr->reqlen - 1;449    }450451    while (klen != 0) {452        mc_resp *resp = c->resp;453        if (process_get_cmd(t, curkey, klen, c->resp, storage_get_item, exptime, return_cas, should_touch) != 0) {454            process_get_command_err(c, NULL);455            return;456        }457        if (resp->io_pending) {458            resp->io_pending->c = c;459            conn_resp_suspend(c, resp);460        }461        curkey += klen;462        klen = 0;463        while (curkey != kend) {464            if (*curkey == ' ') {465                curkey++;466            } else {467                const char *s = memchr(curkey, ' ', kend - curkey);468                if (s != NULL) {469                    klen = s - curkey;470                } else {471                    klen = kend - curkey;472                }473                break;474            }475        }476477        if (klen && !resp_start(c)) {478            // This may succeed because it first frees existing resp objects.479            process_get_command_err(c, "SERVER_ERROR out of memory writing get response");480            return;481        }482    }483    if (settings.verbose > 1)484        fprintf(stderr, ">%d END\n", t->cur_sfd);485486    resp_add_iov(c->resp, "END\r\n", 5);487    conn_set_state(c, conn_new_cmd);488    return;489}490491inline static void process_stats_detail(conn *c, mcp_parser_t *pr, int token) {492    assert(c != NULL);493    int len = 0;494    const char *command = mcmc_token_get(pr->request, &pr->tok, token, &len);495496    if (strncmp(command, "on", len) == 0) {497        settings.detail_enabled = 1;498        out_string(c, "OK");499    }500    else if (strncmp(command, "off", len) == 0) {501        settings.detail_enabled = 0;502        out_string(c, "OK");503    }504    else if (strncmp(command, "dump", len) == 0) {505        int l;506        char *stats = stats_prefix_dump(&l);507        write_and_free(c, stats, l);508    }509    else {510        out_string(c, "CLIENT_ERROR usage: stats detail on|off|dump");511    }512}513514static void process_stat(conn *c, mcp_parser_t *pr) {515    int len = 0;516    int ntokens = pr->tok.ntokens;517    const char *subcommand = mcmc_token_get(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &len);518    assert(c != NULL);519520    if (ntokens < 1) {521        out_string(c, "CLIENT_ERROR bad command line");522        return;523    }524525    if (ntokens == 1) {526        server_stats(&append_stats, c);527        (void)get_stats(NULL, 0, &append_stats, c);528    } else if (strncmp(subcommand, "reset", len) == 0) {529        stats_reset();530        out_string(c, "RESET");531        return;532    } else if (strncmp(subcommand, "detail", len) == 0) {533        if (!settings.dump_enabled) {534            out_string(c, "CLIENT_ERROR stats detail not allowed");535            return;536        }537538        /* NOTE: how to tackle detail with binary? */539        if (ntokens < 3)540            process_stats_detail(c, pr, 0);  /* outputs the error message */541        else542            process_stats_detail(c, pr, 2);543        /* Output already generated */544        return;545    } else if (strncmp(subcommand, "settings", len) == 0) {546        process_stat_settings(&append_stats, c);547    } else if (strncmp(subcommand, "cachedump", len) == 0) {548        char *buf;549        unsigned int bytes, id, limit = 0;550551        if (!settings.dump_enabled) {552            out_string(c, "CLIENT_ERROR stats cachedump not allowed");553            return;554        }555556        if (ntokens < 4) {557            out_string(c, "CLIENT_ERROR bad command line");558            return;559        }560561        if ((mcmc_token_get_u32(pr->request, &pr->tok, 2, &id) != MCMC_OK) ||562            (mcmc_token_get_u32(pr->request, &pr->tok, 3, &limit) != MCMC_OK)) {563            out_string(c, "CLIENT_ERROR bad command line format");564            return;565        }566567        if (id >= MAX_NUMBER_OF_SLAB_CLASSES) {568            out_string(c, "CLIENT_ERROR Illegal slab id");569            return;570        }571572        buf = item_cachedump(id, limit, &bytes);573        write_and_free(c, buf, bytes);574        return;575    } else if (strncmp(subcommand, "conns", len) == 0) {576        process_stats_conns(&append_stats, c);577#ifdef EXTSTORE578    } else if (strncmp(subcommand, "extstore", len) == 0) {579        process_extstore_stats(&append_stats, c);580#endif581#ifdef PROXY582    } else if (strncmp(subcommand, "proxy", len) == 0) {583        process_proxy_stats(settings.proxy_ctx, &append_stats, c);584    } else if (strncmp(subcommand, "proxyfuncs", len) == 0) {585        process_proxy_funcstats(settings.proxy_ctx, &append_stats, c);586    } else if (strncmp(subcommand, "proxybe", len) == 0) {587        process_proxy_bestats(settings.proxy_ctx, &append_stats, c);588#endif589    } else {590        /* getting here means that the subcommand is either engine specific or591           is invalid. query the engine and see. */592        if (get_stats(subcommand, len, &append_stats, c)) {593            if (c->stats.buffer == NULL) {594                out_of_memory(c, "SERVER_ERROR out of memory writing stats");595            } else {596                write_and_free(c, c->stats.buffer, c->stats.offset);597                c->stats.buffer = NULL;598            }599        } else {600            out_string(c, "ERROR");601        }602        return;603    }604605    /* append terminator and start the transfer */606    append_stats(NULL, 0, NULL, 0, c);607608    if (c->stats.buffer == NULL) {609        out_of_memory(c, "SERVER_ERROR out of memory writing stats");610    } else {611        write_and_free(c, c->stats.buffer, c->stats.offset);612        c->stats.buffer = NULL;613    }614}615616// slow snprintf for debugging purposes.617static void process_meta_command(conn *c, mcp_parser_t *pr) {618    assert(c != NULL);619620    int ntokens = pr->tok.ntokens;621    if (ntokens < 2 || pr->klen > KEY_MAX_LENGTH) {622        out_string(c, "CLIENT_ERROR bad command line format");623        return;624    }625626    const char *key = MCP_PARSER_KEY(pr);627    size_t nkey = pr->klen;628    int tlen = 0;629    const char *flag = mcmc_token_get(pr->request, &pr->tok, 2, &tlen);630631    if (ntokens >= 4 && tlen == 1 && flag[0] == 'b') {632        size_t ret = base64_decode((unsigned char *)key, nkey,633                    (unsigned char *)key, nkey);634        if (ret == 0) {635            // failed to decode.636            out_string(c, "CLIENT_ERROR bad command line format");637            return;638        }639        nkey = ret;640    }641642    bool overflow; // not used here.643    item *it = limited_get(key, nkey, c->thread, 0, false, DONT_UPDATE, &overflow);644    if (it) {645        mc_resp *resp = c->resp;646        size_t total = 0;647        size_t ret;648        // similar to out_string().649        memcpy(resp->wbuf, "ME ", 3);650        total += 3;651        if (it->it_flags & ITEM_KEY_BINARY) {652            // re-encode from memory rather than copy the original key;653            // to help give confidence that what in memory is what we asked654            // for.655            total += base64_encode((unsigned char *) ITEM_key(it), it->nkey, (unsigned char *)resp->wbuf + total, WRITE_BUFFER_SIZE - total);656        } else {657            memcpy(resp->wbuf + total, ITEM_key(it), it->nkey);658            total += it->nkey;659        }660        resp->wbuf[total] = ' ';661        total++;662663        ret = snprintf(resp->wbuf + total, WRITE_BUFFER_SIZE - (it->nkey + 12),664                "exp=%d la=%llu cas=%llu fetch=%s cls=%u size=%lu\r\n",665                (it->exptime == 0) ? -1 : (it->exptime - current_time),666                (unsigned long long)(current_time - it->time),667                (unsigned long long)ITEM_get_cas(it),668                (it->it_flags & ITEM_FETCHED) ? "yes" : "no",669                ITEM_clsid(it),670                (unsigned long) ITEM_ntotal(it));671672        item_remove(it);673        resp->wbytes = total + ret;674        resp_add_iov(resp, resp->wbuf, resp->wbytes);675        conn_set_state(c, conn_new_cmd);676    } else {677        out_string(c, "EN");678    }679    pthread_mutex_lock(&c->thread->stats.mutex);680    c->thread->stats.meta_cmds++;681    pthread_mutex_unlock(&c->thread->stats.mutex);682}683684// Text handler requires some custom code around the update code: we directly685// load buffer data into the allocated item, meaning we can drop back to the686// event system for a network read, meaning we lose the request context687// inbetween now and when a store command is finalized.688//689// The meta parsing code was abstracted out into common code.690static void process_mset_command(conn *c, mcp_parser_t *pr, mc_resp *resp) {691    bool has_cas_in = false;692    uint64_t cas_in = 0;693    short comm = 0;694    item *it;695    char *errstr = "CLIENT_ERROR bad command line format";696    c->item = it = process_mset_cmd_start(c->thread, pr, resp, &cas_in, &has_cas_in, &comm);697    if (it == NULL) {698        c->sbytes = pr->vlen;699        conn_set_state(c, conn_swallow);700        return;701    }702    c->cas = has_cas_in ? cas_in : get_cas_id();703    c->cmd = comm;704#ifdef NEED_ALIGN705    if (it->it_flags & ITEM_CHUNKED) {706        c->ritem = ITEM_schunk(it);707    } else {708        c->ritem = ITEM_data(it);709    }710#else711    c->ritem = ITEM_data(it);712#endif713    c->rlbytes = it->nbytes;714    c->cmd = comm;715716    // We note tokens into the front of the write buffer, so we can create the717    // final buffer in complete_nread_ascii.718    // FIXME: maybe move this to proto_parser? it's pretty specialized...719    char *p = resp->wbuf;720    for (int i = pr->keytoken+1; i < pr->tok.ntokens; i++) {721        int tlen;722        switch (pr->request[pr->tok.tokens[i]]) {723            case 'O':724                tlen = _process_token_len(pr, i);725                if (tlen > MFLAG_MAX_OPAQUE_LENGTH) {726                    errstr = "CLIENT_ERROR opaque token too long";727                    goto error;728                }729                META_SPACE(p);730                memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);731                p += tlen;732                break;733            case 'k':734                META_CHAR(p, 'k');735                break;736            case 'c':737                // need to set the cas value post-assignment.738                META_CHAR(p, 'c');739                break;740            case 's':741                // get the final size post-fill742                META_CHAR(p, 's');743                break;744        }745    }746747    resp->wbytes = p - resp->wbuf;748    // we don't set up the iov here, instead after complete_nread_ascii when749    // we have the full status code and item data.750    resp->mset_res = true;751    conn_set_state(c, conn_nread);752    return;753error:754    /* swallow the data line */755    c->sbytes = pr->vlen;756757    // Note: no errors possible after the item was successfully allocated.758    // So we're just looking at dumping error codes and returning.759    out_errstring(c, errstr);760    // TODO: pass state in? else switching twice meh.761    conn_set_state(c, conn_swallow);762}763764static void process_update_command(conn *c, mcp_parser_t *pr, mc_resp *resp, int comm, bool handle_cas) {765    item *it = process_update_cmd_start(c->thread, pr, resp, comm, handle_cas);766    if (it == NULL) {767        conn_set_state(c, conn_swallow);768        c->sbytes = pr->vlen;769        return;770    }771    c->item = it;772#ifdef NEED_ALIGN773    if (it->it_flags & ITEM_CHUNKED) {774        c->ritem = ITEM_schunk(it);775    } else {776        c->ritem = ITEM_data(it);777    }778#else779    c->ritem = ITEM_data(it);780#endif781    c->rlbytes = it->nbytes;782    c->cmd = comm;783    conn_set_state(c, conn_nread);784}785786static void process_verbosity_command(conn *c, mcp_parser_t *pr) {787    assert(c != NULL);788789    uint32_t level;790    if (mcmc_token_get_u32(pr->request, &pr->tok, 1, &level) != MCMC_OK) {791        out_string(c, "CLIENT_ERROR bad command line format");792        return;793    }794    settings.verbose = level > MAX_VERBOSITY_LEVEL ? MAX_VERBOSITY_LEVEL : level;795    out_string(c, "OK");796    return;797}798799#ifdef MEMCACHED_DEBUG800static void process_misbehave_command(conn *c, mcp_parser_t *pr) {801    int allowed = 0;802803    // try opening new TCP socket804    int i = socket(AF_INET, SOCK_STREAM, 0);805    if (i != -1) {806        allowed++;807        close(i);808    }809810    // try executing new commands811    i = system("sleep 0");812    if (i != -1) {813        allowed++;814    }815816    if (allowed) {817        out_string(c, "ERROR");818    } else {819        out_string(c, "OK");820    }821}822823static void process_debugtime_command(conn *c, mcp_parser_t *pr) {824    int len = 0;825    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &len);826    if (subcmd == NULL) {827        out_string(c, "ERROR");828        return;829    }830831    if (strncmp(subcmd, "p", len) == 0) {832        if (!is_paused) {833            is_paused = true;834        }835    } else if (strncmp(subcmd, "r", len) == 0) {836        if (is_paused) {837            is_paused = false;838        }839    } else {840        int64_t time_delta = 0;841        if (mcmc_token_get_64(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &time_delta) != MCMC_OK) {842            out_string(c, "ERROR");843            return;844        }845        delta += time_delta;846        current_time += delta;847    }848    out_string(c, "OK");849}850851static void process_debugitem_command(conn *c, mcp_parser_t *pr) {852    int len = 0;853    int klen = 0;854    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, 1, &len);855    const char *key = mcmc_token_get(pr->request, &pr->tok, 2, &klen);856    if (subcmd == NULL || key == NULL) {857        out_string(c, "ERROR");858        return;859    }860861    if (strncmp(subcmd, "lock", len) == 0) {862        uint32_t hv = hash(key, klen);863        item_lock(hv);864    } else if (strncmp(subcmd, "unlock", len) == 0) {865        uint32_t hv = hash(key, klen);866        item_unlock(hv);867    } else if (strncmp(subcmd, "ref", len) == 0) {868        // intentionally leak a reference.869        item *it = item_get(key, klen, c->thread, DONT_UPDATE);870        if (it == NULL) {871            out_string(c, "MISS");872            return;873        }874        c->debug_item_reffed = it;875    } else if (strncmp(subcmd, "unref", len) == 0) {876        if (klen == 6 && strncmp(key, "reffed", klen) == 0) {877            // Item memory held in slot so we can test replacing reffed items878            // without leaking.879            item *reffed = c->debug_item_reffed;880            do_item_remove(reffed);881            c->debug_item_reffed = NULL;882        } else {883            // double unlink. debugger must have already ref'ed it or this884            // underflows.885            item *it = item_get(key, klen, c->thread, DONT_UPDATE);886            if (it == NULL) {887                out_string(c, "MISS");888                return;889            }890            do_item_remove(it);891            do_item_remove(it);892        }893    } else {894        out_string(c, "ERROR");895        return;896    }897    out_string(c, "OK");898}899#endif900901static void process_slabs_automove_command(conn *c, mcp_parser_t *pr) {902    unsigned int level;903    double ratio;904    int len = 0;905    int ntokens = pr->tok.ntokens;906907    assert(c != NULL);908909    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, 2, &len);910    if (subcmd == NULL) {911        out_string(c, "CLIENT_ERROR bad command line format");912        return;913    }914915    if (strncmp(subcmd, "ratio", len) == 0) {916        if (ntokens < 4 || !mc_toktod(pr, 3, &ratio)) {917            out_string(c, "ERROR");918            return;919        }920        // TODO: settings needs an overhaul... no locks/etc.921        settings.slab_automove_ratio = ratio;922        settings.slab_automove_version++;923    } else if (strncmp(subcmd, "freeratio", len) == 0) {924        if (ntokens < 4 || !mc_toktod(pr, 3, &ratio)) {925            out_string(c, "ERROR");926            return;927        }928        settings.slab_automove_freeratio = ratio;929        settings.slab_automove_version++;930    } else if (strncmp(subcmd, "window", len) == 0) {931        if (ntokens < 4 || mcmc_token_get_u32(pr->request, &pr->tok, 3, &level) != MCMC_OK) {932            out_string(c, "CLIENT_ERROR bad command line format");933            return;934        }935        if (level < 3 || level > 1800) {936            out_string(c, "CLIENT_ERROR automove window too low or too high");937            return;938        }939940        settings.slab_automove_window = level;941        settings.slab_automove_version++;942    } else {943        if (mcmc_token_get_u32(pr->request, &pr->tok, 2, &level) != MCMC_OK) {944            out_string(c, "CLIENT_ERROR bad command line format");945            return;946        }947        if (level == 0) {948            settings.slab_automove = 0;949        } else if (level == 1 || level == 2) {950            settings.slab_automove = level;951        } else {952            out_string(c, "ERROR");953            return;954        }955    }956    out_string(c, "OK");957    return;958}959960/* TODO: decide on syntax for sampling? */961static void process_watch_command(conn *c, mcp_parser_t *pr) {962    uint16_t f = 0;963    int x;964    int ntokens = pr->tok.ntokens;965    assert(c != NULL);966967    if (!settings.watch_enabled) {968        out_string(c, "CLIENT_ERROR watch commands not allowed");969        return;970    }971972    if (resp_has_stack(c)) {973        out_string(c, "ERROR cannot pipeline other commands before watch");974        return;975    }976977    if (ntokens > 1) {978        for (x = COMMAND_TOKEN + 1; x < ntokens; x++) {979            int len = 0;980            const char *t = mcmc_token_get(pr->request, &pr->tok, x, &len);981            if ((strncmp(t, "rawcmds", len) == 0)) {982                f |= LOG_RAWCMDS;983            } else if ((strncmp(t, "evictions", len) == 0)) {984                f |= LOG_EVICTIONS;985            } else if ((strncmp(t, "fetchers", len) == 0)) {986                f |= LOG_FETCHERS;987            } else if ((strncmp(t, "mutations", len) == 0)) {988                f |= LOG_MUTATIONS;989            } else if ((strncmp(t, "sysevents", len) == 0)) {990                f |= LOG_SYSEVENTS;991            } else if ((strncmp(t, "connevents", len) == 0)) {992                f |= LOG_CONNEVENTS;993            } else if ((strncmp(t, "proxyreqs", len) == 0)) {994                f |= LOG_PROXYREQS;995            } else if ((strncmp(t, "proxyevents", len) == 0)) {996                f |= LOG_PROXYEVENTS;997            } else if ((strncmp(t, "proxyuser", len) == 0)) {998                f |= LOG_PROXYUSER;999            } else if ((strncmp(t, "deletions", len) == 0)) {1000                f |= LOG_DELETIONS;1001            } else {1002                out_string(c, "ERROR");1003                return;1004            }1005        }1006    } else {1007        f |= LOG_FETCHERS;1008    }10091010    switch(logger_add_watcher(c, c->sfd, f)) {1011        case LOGGER_ADD_WATCHER_TOO_MANY:1012            out_string(c, "WATCHER_TOO_MANY log watcher limit reached");1013            break;1014        case LOGGER_ADD_WATCHER_FAILED:1015            out_string(c, "WATCHER_FAILED failed to add log watcher");1016            break;1017        case LOGGER_ADD_WATCHER_OK:1018            conn_set_state(c, conn_watch);1019            event_del(&c->event);1020            break;1021    }1022}10231024static void process_memlimit_command(conn *c, mcp_parser_t *pr) {1025    uint32_t memlimit;1026    assert(c != NULL);10271028    if (mcmc_token_get_u32(pr->request, &pr->tok, 1, &memlimit) != MCMC_OK) {1029        out_string(c, "ERROR");1030    } else {1031        if (memlimit < 8) {1032            out_string(c, "MEMLIMIT_TOO_SMALL cannot set maxbytes to less than 8m");1033        } else {1034            if (memlimit > 1000000000) {1035                out_string(c, "MEMLIMIT_ADJUST_FAILED input value is megabytes not bytes");1036            } else if (slabs_adjust_mem_limit((size_t) memlimit * 1024 * 1024)) {1037                if (settings.verbose > 0) {1038                    fprintf(stderr, "maxbytes adjusted to %llum\n", (unsigned long long)memlimit);1039                }10401041                out_string(c, "OK");1042            } else {1043                out_string(c, "MEMLIMIT_ADJUST_FAILED out of bounds or unable to adjust");1044            }1045        }1046    }1047}10481049static void process_lru_command(conn *c, mcp_parser_t *pr) {1050    uint32_t pct_hot;1051    uint32_t pct_warm;1052    double hot_factor;1053    int32_t ttl;1054    double factor;1055    int len = 0;1056    int ntokens = pr->tok.ntokens;1057    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &len);1058    if (subcmd == NULL) {1059        out_string(c, "ERROR");1060    }10611062    if (strncmp(subcmd, "tune", len) == 0 && ntokens >= 6) {1063        if (!mc_toktou32(pr, 2, &pct_hot) ||1064            !mc_toktou32(pr, 3, &pct_warm) ||1065            !mc_toktod(pr, 4, &hot_factor) ||1066            !mc_toktod(pr, 5, &factor)) {1067            out_string(c, "ERROR");1068        } else {1069            if (pct_hot + pct_warm > 80) {1070                out_string(c, "ERROR hot and warm pcts must not exceed 80");1071            } else if (factor <= 0 || hot_factor <= 0) {1072                out_string(c, "ERROR hot/warm age factors must be greater than 0");1073            } else {1074                settings.hot_lru_pct = pct_hot;1075                settings.warm_lru_pct = pct_warm;1076                settings.hot_max_factor = hot_factor;1077                settings.warm_max_factor = factor;1078                out_string(c, "OK");1079            }1080        }1081    } else if (strncmp(subcmd, "mode", len) == 0 && ntokens >= 3 &&1082               settings.lru_maintainer_thread) {1083        if (mc_prcmp(pr, 2, "flat") == 0) {1084            settings.lru_segmented = false;1085            out_string(c, "OK");1086        } else if (mc_prcmp(pr, 2, "segmented") == 0) {1087            settings.lru_segmented = true;1088            out_string(c, "OK");1089        } else {1090            out_string(c, "ERROR");1091        }1092    } else if (strncmp(subcmd, "temp_ttl", len) == 0 && ntokens >= 3 &&1093               settings.lru_maintainer_thread) {1094        if (!mc_tokto32(pr, 2, &ttl)) {1095            out_string(c, "ERROR");1096        } else {1097            if (ttl < 0) {1098                settings.temp_lru = false;1099            } else {1100                settings.temp_lru = true;1101                settings.temporary_ttl = ttl;1102            }1103            out_string(c, "OK");1104        }1105    } else {1106        out_string(c, "ERROR");1107    }1108}1109#ifdef EXTSTORE1110static void process_extstore_command(conn *c, mcp_parser_t *pr) {1111    bool ok = true;1112    int ntokens = pr->tok.ntokens;1113    int len = 0;1114    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &len);11151116    if (ntokens < 3 || subcmd == NULL) {1117        ok = false;1118    } else if (strncmp(subcmd, "free_memchunks", len) == 0 && ntokens > 3) {1119        // setting is deprecated and ignored, but accepted for backcompat1120        unsigned int clsid = 0;1121        unsigned int limit = 0;1122        if (!mc_toktou32(pr, 2, &clsid) ||1123                !mc_toktou32(pr, 3, &limit)) {1124            ok = false;1125        } else {1126            if (clsid < MAX_NUMBER_OF_SLAB_CLASSES) {1127                ok = true;1128            } else {1129                ok = false;1130            }1131        }1132    } else if (strncmp(subcmd, "item_size", len) == 0) {1133        if (mc_toktou32(pr, 2, &settings.ext_item_size)) {1134            settings.slab_automove_version++;1135        } else {1136            ok = false;1137        }1138    } else if (strncmp(subcmd, "item_age", len) == 0) {1139        if (!mc_toktou32(pr, 2, &settings.ext_item_age))1140            ok = false;1141    } else if (strncmp(subcmd, "low_ttl", len) == 0) {1142        if (!mc_toktou32(pr, 2, &settings.ext_low_ttl))1143            ok = false;1144    } else if (strncmp(subcmd, "recache_rate", len) == 0) {1145        if (!mc_toktou32(pr, 2, &settings.ext_recache_rate))1146            ok = false;1147    } else if (strncmp(subcmd, "compact_under", len) == 0) {1148        if (!mc_toktou32(pr, 2, &settings.ext_compact_under))1149            ok = false;1150    } else if (strncmp(subcmd, "drop_under", len) == 0) {1151        if (!mc_toktou32(pr, 2, &settings.ext_drop_under))1152            ok = false;1153    } else if (strncmp(subcmd, "max_sleep", len) == 0) {1154        if (!mc_toktou32(pr, 2, &settings.ext_max_sleep))1155            ok = false;1156    } else if (strncmp(subcmd, "max_frag", len) == 0) {1157        if (!mc_toktod(pr, 2, &settings.ext_max_frag))1158            ok = false;1159    } else if (strncmp(subcmd, "drop_unread", len) == 0) {1160        unsigned int v;1161        if (!mc_toktou32(pr, 2, &v)) {1162            ok = false;1163        } else {1164            settings.ext_drop_unread = v == 0 ? false : true;1165        }1166    } else {1167        ok = false;1168    }1169    if (!ok) {1170        out_string(c, "ERROR");1171    } else {1172        out_string(c, "OK");1173    }1174}1175#endif1176static void process_flush_all_command(conn *c, mcp_parser_t *pr) {1177    int32_t exptime = 0;1178    rel_time_t new_oldest = 0;11791180    pthread_mutex_lock(&c->thread->stats.mutex);1181    c->thread->stats.flush_cmds++;1182    pthread_mutex_unlock(&c->thread->stats.mutex);11831184    if (!settings.flush_enabled) {1185        // flush_all is not allowed but we log it on stats1186        out_string(c, "CLIENT_ERROR flush_all not allowed");1187        return;1188    }11891190    if (pr->tok.ntokens != (c->resp->noreply ? 2 : 1)) {1191        if (!mc_tokto32(pr, 1, &exptime)) {1192            out_string(c, "CLIENT_ERROR invalid exptime argument");1193            return;1194        }1195    }11961197    /*1198      If exptime is zero realtime() would return zero too, and1199      realtime(exptime) - 1 would overflow to the max unsigned1200      value.  So we process exptime == 0 the same way we do when1201      no delay is given at all.1202    */1203    if (exptime > 0) {1204        new_oldest = realtime(exptime) - 1;1205    } else { /* exptime == 0 */1206        new_oldest = current_time - 1;1207    }12081209    settings.oldest_live = new_oldest;1210    item_flush_expired();1211    out_string(c, "OK");1212}12131214static void process_version_command(conn *c, mcp_parser_t *pr) {1215    out_string(c, "VERSION " VERSION);1216}12171218static void process_quit_command(conn *c, mcp_parser_t *pr) {1219    conn_set_state(c, conn_mwrite);1220    c->close_after_write = true;1221    c->close_reason = NORMAL_CLOSE;1222}12231224static void process_shutdown_command(conn *c, mcp_parser_t *pr) {1225    if (!settings.shutdown_command) {1226        out_string(c, "SERVER_ERROR: shutdown not enabled");1227        return;1228    }12291230    if (pr->tok.ntokens == 1) {1231        c->close_reason = SHUTDOWN_CLOSE;1232        conn_set_state(c, conn_closing);1233        raise(SIGINT);1234    } else if (pr->tok.ntokens == 2 && mc_prcmp(pr, SUBCOMMAND_TOKEN, "graceful") == 0) {1235        c->close_reason = SHUTDOWN_CLOSE;1236        conn_set_state(c, conn_closing);1237        raise(SIGUSR1);1238    } else {1239        out_string(c, "CLIENT_ERROR invalid shutdown mode");1240    }1241}12421243static void process_slabs_command(conn *c, mcp_parser_t *pr) {1244    int ntokens = pr->tok.ntokens;1245    int len = 0;1246    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, SUBCOMMAND_TOKEN, &len);1247    if (ntokens == 4 && strncmp(subcmd, "reassign", len) == 0) {1248        int src, dst, rv;12491250        if (settings.slab_reassign == false) {1251            out_string(c, "CLIENT_ERROR slab reassignment disabled");1252            return;1253        }12541255        if (!mc_tokto32(pr, 2, &src) || !mc_tokto32(pr, 3, &dst)) {1256            out_string(c, "CLIENT_ERROR bad command line format");1257            return;1258        }12591260        rv = slabs_reassign(settings.slab_rebal, src, dst, SLABS_REASSIGN_ALLOW_EVICTIONS);1261        switch (rv) {1262        case REASSIGN_OK:1263            out_string(c, "OK");1264            break;1265        case REASSIGN_RUNNING:1266            out_string(c, "BUSY currently processing reassign request");1267            break;1268        case REASSIGN_BADCLASS:1269            out_string(c, "BADCLASS invalid src or dst class id");1270            break;1271        case REASSIGN_NOSPARE:1272            out_string(c, "NOSPARE source class has no spare pages");1273            break;1274        case REASSIGN_SRC_DST_SAME:1275            out_string(c, "SAME src and dst class are identical");1276            break;1277        }1278        return;1279    } else if (ntokens >= 3 &&1280        (strncmp(subcmd, "automove", len) == 0)) {1281        process_slabs_automove_command(c, pr);1282    } else {1283        out_string(c, "ERROR");1284    }1285}12861287static void process_lru_crawler_command(conn *c, mcp_parser_t *pr) {1288    int ntokens = pr->tok.ntokens;1289    int len = 0;1290    const char *subcmd = mcmc_token_get(pr->request, &pr->tok, 1, &len);1291    char sc_buf[512];12921293    if (ntokens == 3 && strncmp(subcmd, "crawl", len) == 0) {1294        int rv;1295        if (settings.lru_crawler == false) {1296            out_string(c, "CLIENT_ERROR lru crawler disabled");1297            return;1298        }12991300        const char *slabclass = mcmc_token_get(pr->request, &pr->tok, 2, &len);1301        len = len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len;1302        memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);1303        sc_buf[len] = '\0';13041305        rv = lru_crawler_crawl(sc_buf, CRAWLER_EXPIRED, NULL, 0,1306                settings.lru_crawler_tocrawl);13071308        switch(rv) {1309        case CRAWLER_OK:1310            out_string(c, "OK");1311            break;1312        case CRAWLER_RUNNING:1313            out_string(c, "BUSY currently processing crawler request");1314            break;1315        case CRAWLER_BADCLASS:1316            out_string(c, "BADCLASS invalid class id");1317            break;1318        case CRAWLER_NOTSTARTED:1319            out_string(c, "NOTSTARTED no items to crawl");1320            break;1321        case CRAWLER_ERROR:1322            out_string(c, "ERROR an unknown error happened");1323            break;1324        }1325        return;1326    } else if (ntokens == 3 && strncmp(subcmd, "metadump", len) == 0) {1327        if (settings.lru_crawler == false) {1328            out_string(c, "CLIENT_ERROR lru crawler disabled");1329            return;1330        }1331        if (!settings.dump_enabled) {1332            out_string(c, "ERROR metadump not allowed");1333            return;1334        }1335        if (resp_has_stack(c)) {1336            out_string(c, "ERROR cannot pipeline other commands before metadump");1337            return;1338        }13391340        const char *slabclass = mcmc_token_get(pr->request, &pr->tok, 2, &len);1341        len = len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len;1342        memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);1343        sc_buf[len] = '\0';13441345        int rv = lru_crawler_crawl(sc_buf, CRAWLER_METADUMP,1346                c, c->sfd, LRU_CRAWLER_CAP_REMAINING);1347        switch(rv) {1348            case CRAWLER_OK:1349                // TODO: documentation says this string is returned, but1350                // it never was before. We never switch to conn_write so1351                // this o_s call never worked. Need to talk to users and1352                // decide if removing the OK from docs is fine.1353                //out_string(c, "OK");1354                // TODO: Don't reuse conn_watch here.1355                conn_set_state(c, conn_watch);1356                event_del(&c->event);1357                break;1358            case CRAWLER_RUNNING:1359                out_string(c, "BUSY currently processing crawler request");1360                break;1361            case CRAWLER_BADCLASS:1362                out_string(c, "BADCLASS invalid class id");1363                break;1364            case CRAWLER_NOTSTARTED:1365                out_string(c, "NOTSTARTED no items to crawl");1366                break;1367            case CRAWLER_ERROR:1368                out_string(c, "ERROR an unknown error happened");1369                break;1370        }1371        return;1372    } else if (ntokens == 3 && strncmp(subcmd, "mgdump", len) == 0) {1373        if (settings.lru_crawler == false) {1374            out_string(c, "CLIENT_ERROR lru crawler disabled");1375            return;1376        }1377        if (!settings.dump_enabled) {1378            out_string(c, "ERROR key dump not allowed");1379            return;1380        }1381        if (resp_has_stack(c)) {1382            out_string(c, "ERROR cannot pipeline other commands before mgdump");1383            return;1384        }13851386        const char *slabclass = mcmc_token_get(pr->request, &pr->tok, 2, &len);1387        len = len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len;1388        memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);1389        sc_buf[len] = '\0';13901391        int rv = lru_crawler_crawl(sc_buf, CRAWLER_MGDUMP,1392                c, c->sfd, LRU_CRAWLER_CAP_REMAINING);1393        switch(rv) {1394            case CRAWLER_OK:1395                conn_set_state(c, conn_watch);1396                event_del(&c->event);1397                break;1398            case CRAWLER_RUNNING:1399                out_string(c, "BUSY currently processing crawler request");1400                break;1401            case CRAWLER_BADCLASS:1402                out_string(c, "BADCLASS invalid class id");1403                break;1404            case CRAWLER_NOTSTARTED:1405                out_string(c, "NOTSTARTED no items to crawl");1406                break;1407            case CRAWLER_ERROR:1408                out_string(c, "ERROR an unknown error happened");1409                break;1410        }1411        return;1412    } else if (ntokens == 3 && strncmp(subcmd, "tocrawl", len) == 0) {1413        uint32_t tocrawl;1414         if (!mc_toktou32(pr, 2, &tocrawl)) {1415            out_string(c, "CLIENT_ERROR bad command line format");1416            return;1417        }1418        settings.lru_crawler_tocrawl = tocrawl;1419        out_string(c, "OK");1420        return;1421    } else if (ntokens == 3 && strncmp(subcmd, "sleep", len) == 0) {1422        uint32_t tosleep;1423        if (!mc_toktou32(pr, 2, &tosleep)) {1424            out_string(c, "CLIENT_ERROR bad command line format");1425            return;1426        }1427        if (tosleep > 1000000) {1428            out_string(c, "CLIENT_ERROR sleep must be one second or less");1429            return;1430        }1431        settings.lru_crawler_sleep = tosleep;1432        out_string(c, "OK");1433        return;1434    } else if (ntokens == 2) {1435        if ((strncmp(subcmd, "enable", len) == 0)) {1436            if (start_item_crawler_thread() == 0) {1437                out_string(c, "OK");1438            } else {1439                out_string(c, "ERROR failed to start lru crawler thread");1440            }1441        } else if ((strncmp(subcmd, "disable", len) == 0)) {1442            if (stop_item_crawler_thread(CRAWLER_NOWAIT) == 0) {1443                out_string(c, "OK");1444            } else {1445                out_string(c, "ERROR failed to stop lru crawler thread");1446            }1447        } else {1448            out_string(c, "ERROR");1449        }1450        return;1451    } else {1452        out_string(c, "ERROR");1453    }1454}1455#ifdef TLS1456static void process_refresh_certs_command(conn *c, mcp_parser_t *pr) {1457    char *errmsg = NULL;1458    if (refresh_certs(&errmsg)) {1459        out_string(c, "OK");1460    } else {1461        write_and_free(c, errmsg, strlen(errmsg));1462    }1463    return;1464}1465#endif14661467// TODO: pipelined commands are incompatible with shifting connections to a1468// side thread. Given this only happens in two instances (watch and1469// lru_crawler metadump) it should be fine for things to bail. It _should_ be1470// unusual for these commands.1471// This was hard to fix since tokenize_command() mutilates the read buffer, so1472// we can't drop out and back in again.1473// Leaving this note here to spend more time on a fix now that1474// tokenize_command() is gone and strings are fixed.14751476// TODO: this isn't a performance sensitive section of the code (these1477// commands are all rare), but a hash table could speed things up.1478typedef void (*text_cmd_func)(conn *c, mcp_parser_t *pr);14791480struct text_cmd_entry {1481    const char *s; // top level command1482    text_cmd_func func; // function to call1483};14841485enum text_cmds {1486    text_cmd_shutdown = 0,1487    text_cmd_slabs,1488    text_cmd_cache_memlimit,1489#ifdef MEMCACHED_DEBUG1490    text_cmd_debugtime,1491    text_cmd_debugitem,1492    text_cmd_misbehave,1493#endif1494    text_cmd_flush_all,1495    text_cmd_lru_crawler,1496    text_cmd_verbosity,1497    text_cmd_lru,1498#ifdef EXTSTORE1499    text_cmd_extstore,1500#endif1501#ifdef TLS1502    text_cmd_refresh_certs,1503#endif1504    text_cmd_final,1505};15061507static const struct text_cmd_entry text_cmd_entries[] = {1508    [text_cmd_shutdown] = {"shutdown", process_shutdown_command},1509    [text_cmd_slabs] = {"slabs",  process_slabs_command},1510    [text_cmd_cache_memlimit] = {"cache_memlimit", process_memlimit_command},1511#ifdef MEMCACHED_DEBUG1512    [text_cmd_debugtime] = {"debugtime", process_debugtime_command},1513    [text_cmd_debugitem] = {"debugitem", process_debugitem_command},1514    [text_cmd_misbehave] = {"misbehave", process_misbehave_command},1515#endif1516    [text_cmd_flush_all] = {"flush_all",  process_flush_all_command},1517    [text_cmd_lru_crawler] = {"lru_crawler", process_lru_crawler_command},1518    [text_cmd_verbosity] = {"verbosity", process_verbosity_command},1519    [text_cmd_lru] = {"lru", process_lru_command},1520#ifdef EXTSTORE1521    [text_cmd_extstore] = {"extstore", process_extstore_command},1522#endif1523#ifdef TLS1524    [text_cmd_refresh_certs] = {"refresh_certs", process_refresh_certs_command},1525#endif1526    [text_cmd_final] = {NULL, NULL},1527};15281529void process_command_ascii(conn *c, char *command, size_t cmdlen) {1530    mcp_parser_t pr = {0};1531    LIBEVENT_THREAD *t = c->thread;1532    // Prep the response object for this query.1533    if (!resp_start(c)) {1534        conn_set_state(c, conn_closing);1535        return;1536    }15371538    t->cur_sfd = c->sfd; // cuddle sfd for logging.1539    int ret = process_request(&pr, command, cmdlen);1540    c->resp->noreply = pr.noreply;1541    if (settings.verbose > 1) {1542        fprintf(stderr, "<%d %.*s\n", t->cur_sfd, (int)cmdlen-2, command);1543    }1544    if (ret == PROCESS_REQUEST_OK) {1545        mc_resp *resp = c->resp;1546        switch (pr.command) {1547            case CMD_MA:1548                process_marithmetic_cmd(t, &pr, resp);1549                conn_set_state(c, conn_new_cmd);1550                break;1551             case CMD_MD:1552                process_mdelete_cmd(t, &pr, resp);1553                conn_set_state(c, conn_new_cmd);1554                break;1555             case CMD_MG:1556                process_mget_cmd(t, &pr, resp, storage_get_item);1557                if (resp->io_pending) {1558                    resp->io_pending->c = c;1559                    conn_resp_suspend(c, resp);1560                } else {1561                    conn_set_state(c, conn_new_cmd);1562                }1563                break;1564            case CMD_MN:1565                out_string(c, "MN");1566                // mn command forces immediate writeback flush.1567                conn_set_state(c, conn_mwrite);1568                break;1569            case CMD_MS:1570                process_mset_command(c, &pr, resp);1571                break;1572            case CMD_ME:1573                process_meta_command(c, &pr);1574                break;1575            case CMD_GET:1576                process_get_command(c, t, &pr, storage_get_item, _NO_CAS, _NO_TOUCH);1577                break;1578            case CMD_GETS:1579                process_get_command(c, t, &pr, storage_get_item, _DO_CAS, _NO_TOUCH);1580                break;1581            case CMD_GAT:1582                process_get_command(c, t, &pr, storage_get_item, _NO_CAS, _DO_TOUCH);1583                break;1584            case CMD_GATS:1585                process_get_command(c, t, &pr, storage_get_item, _DO_CAS, _DO_TOUCH);1586                break;1587             case CMD_DELETE:1588                process_delete_cmd(t, &pr, resp);1589                conn_set_state(c, conn_new_cmd);1590                break;1591             case CMD_INCR:1592                process_arithmetic_cmd(t, &pr, resp, true);1593                conn_set_state(c, conn_new_cmd);1594                break;1595             case CMD_DECR:1596                process_arithmetic_cmd(t, &pr, resp, false);1597                conn_set_state(c, conn_new_cmd);1598                break;1599             case CMD_TOUCH:1600                process_touch_cmd(t, &pr, resp);1601                conn_set_state(c, conn_new_cmd);1602                break;1603             case CMD_SET:1604                process_update_command(c, &pr, resp, NREAD_SET, false);1605                break;1606            case CMD_ADD:1607                process_update_command(c, &pr, resp, NREAD_ADD, false);1608                break;1609            case CMD_APPEND:1610                process_update_command(c, &pr, resp, NREAD_APPEND, false);1611                break;1612            case CMD_PREPEND:1613                process_update_command(c, &pr, resp, NREAD_PREPEND, false);1614                break;1615            case CMD_REPLACE:1616                process_update_command(c, &pr, resp, NREAD_REPLACE, false);1617                break;1618            case CMD_CAS:1619                process_update_command(c, &pr, resp, NREAD_CAS, true);1620                break;1621            case CMD_QUIT:1622                process_quit_command(c, &pr);1623                break;1624            case CMD_VERSION:1625                process_version_command(c, &pr);1626                break;1627            case CMD_STATS:1628                process_stat(c, &pr);1629                break;1630            case CMD_WATCH:1631                process_watch_command(c, &pr);1632                break;1633            default:1634                fprintf(stderr, "COMMAND: %s\n", command);1635                assert(true == false);1636                break;1637        }16381639        return;1640    } else if (ret == PROCESS_REQUEST_CMD_NOT_FOUND) {1641        if (pr.tok.ntokens > 0) {1642            int len = 0;1643            const char *cm = mcmc_token_get(pr.request, &pr.tok, 0, &len);1644            for (int x = 0; text_cmd_entries[x].s; x++) {1645                const struct text_cmd_entry *e = &text_cmd_entries[x];1646                if (strlen(e->s) == len && strncmp(e->s, cm, len) == 0) {1647                    e->func(c, &pr);1648                    return;1649                }1650            }1651        }16521653        if (pr.tok.ntokens > 1) {1654            int len = 0;1655            const char *subcm = mcmc_token_get(pr.request, &pr.tok, pr.tok.ntokens-1, &len);1656            if (len >= 5 && strncmp(subcm, "HTTP/", 5) == 0) {1657                conn_set_state(c, conn_closing);1658                c->close_reason = ERROR_CLOSE;1659                return;1660            }1661        }1662    } else if (ret == PROCESS_REQUEST_BAD_FORMAT) {1663        out_string(c, "CLIENT_ERROR bad command line format");1664        return;1665    }16661667    out_string(c, "ERROR");1668}

Code quality findings 38

Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
warning correctness malloc-unchecked
if (!rbuf_switch_to_malloc(c)) {
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
int l;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
char *buf;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
int tlen;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
double ratio;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
int x;
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
if (mcmc_token_get_u32(pr->request, &pr->tok, 1, &memlimit) != MCMC_OK) {
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
} else {
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
if (memlimit < 8) {
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
} else {
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
if (memlimit > 1000000000) {
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
double hot_factor;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
double factor;
Warning: Variable declared without initialization. Uninitialized local variables contain indeterminate values, which can lead to undefined behavior.
warning correctness uninitialized-variable
int rv;
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(ch->used != 0);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(ch->prev);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(ch->used == 1);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(1 == 0);
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 (c->rbytes > 2048) {
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 (c->rbytes > 2048) {
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 (ptr - c->rcurr > 100 ||
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(cont <= (c->rcurr + c->rbytes));
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c->rcurr <= (c->rbuf + c->rsize));
Info: Function has many parameters (>= 6). Consider using a struct to group related parameters for better readability.
info maintainability long-parameter-list
static void process_get_command(conn *c, LIBEVENT_THREAD *t, mcp_parser_t *pr, parser_storage_get_cb storage_cb, bool return_cas, bool should_touch) {
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: Use of 'goto' can make code harder to read and maintain. Prefer structured control flow (loops, functions).
info maintainability goto-usage
goto error;
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
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 (level < 3 || level > 1800) {
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(c != NULL);
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 (memlimit > 1000000000) {
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 (pct_hot + pct_warm > 80) {
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 (tosleep > 1000000) {
Info: assert() macros are typically disabled in production builds (NDEBUG). Do not rely on them for error handling or security checks in release code.
info correctness assert-in-production
assert(true == false);

Security findings 15

Warning: Potential format string vulnerability if the format argument is not a literal string. Ensure format strings are constant or come from trusted sources.
security format-string-vuln
ret = snprintf(resp->wbuf + total, WRITE_BUFFER_SIZE - (it->nkey + 12),
Warning: system() calls can be dangerous if the command includes untrusted input. Prefer exec* family functions or ensure input is rigorously sanitized.
security system-command
i = system("sleep 0");
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, "HD", 2);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, "EX", 2);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, "NF", 2);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, "NS", 2);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, "\r\n", 2);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(wbuf, errstr, strlen(errstr));
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(wbuf, c->resp->wbuf, l);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(resp->wbuf, "ME ", 3);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(resp->wbuf + total, ITEM_key(it), it->nkey);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(p, &pr->request[pr->tok.tokens[i]], tlen);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);
Info: memcpy() does not handle overlapping memory regions. If source and destination may overlap, use memmove() as a safer alternative.
security memcpy-overlap
memcpy(sc_buf, slabclass, len > sizeof(sc_buf)-1 ? sizeof(sc_buf)-1 : len);

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.