Warning: Allocation result must be checked for NULL before use to prevent null pointer dereference.
if (!rbuf_switch_to_malloc(c)) {
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}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.