summaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
authordormando <dormando@rydia.net>2020-06-10 22:56:07 -0700
committerdormando <dormando@rydia.net>2021-10-05 12:21:25 -0700
commitd22b66483bce8843110795609386edc6ebf65b69 (patch)
treeac2107f9450c857d9ed125a17aef79a4158da7f9 /vendor
parent56dc81db316a0b957415e371d20c683fea9d7d2f (diff)
downloadmemcached-d22b66483bce8843110795609386edc6ebf65b69.tar.gz
proxy: initial commit.
See BUILD for compilation details. See t/startfile.lua for configuration examples. (see also https://github.com/memcached/memcached-proxylibs for extensions, config libraries, more examples) NOTE: io_uring mode is _not stable_, will crash. As of this commit it is not recommended to run the proxy in production. If you are interested please let us know, as we are actively stabilizing for production use.
Diffstat (limited to 'vendor')
-rw-r--r--vendor/.gitignore4
-rw-r--r--vendor/Makefile10
-rwxr-xr-xvendor/fetch.sh5
-rw-r--r--vendor/lua/.gitignore2
-rw-r--r--vendor/mcmc/LICENSE30
-rw-r--r--vendor/mcmc/Makefile13
-rw-r--r--vendor/mcmc/README.md52
-rw-r--r--vendor/mcmc/example.c214
-rw-r--r--vendor/mcmc/mcmc.c728
-rw-r--r--vendor/mcmc/mcmc.h91
10 files changed, 1149 insertions, 0 deletions
diff --git a/vendor/.gitignore b/vendor/.gitignore
new file mode 100644
index 0000000..6ed69cb
--- /dev/null
+++ b/vendor/.gitignore
@@ -0,0 +1,4 @@
+README.md
+/mcmc/example
+!/mcmc/Makefile
+!/Makefile
diff --git a/vendor/Makefile b/vendor/Makefile
new file mode 100644
index 0000000..9d51aee
--- /dev/null
+++ b/vendor/Makefile
@@ -0,0 +1,10 @@
+all:
+ cd lua && $(MAKE) all && cd ..
+ cd mcmc && $(MAKE) all && cd ..
+
+clean:
+ cd lua && $(MAKE) clean && cd ..
+ cd mcmc && $(MAKE) clean && cd ..
+
+dist: clean
+distdir: clean
diff --git a/vendor/fetch.sh b/vendor/fetch.sh
new file mode 100755
index 0000000..2d54142
--- /dev/null
+++ b/vendor/fetch.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+HASH="44a55dee1d41c3ae92524df9f0dd8a747db79f04"
+wget https://github.com/memcached/memcached-vendor/archive/${HASH}.tar.gz
+tar -zxf ./${HASH}.tar.gz --strip-components=1
+rm ${HASH}.tar.gz
diff --git a/vendor/lua/.gitignore b/vendor/lua/.gitignore
new file mode 100644
index 0000000..d6b7ef3
--- /dev/null
+++ b/vendor/lua/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/vendor/mcmc/LICENSE b/vendor/mcmc/LICENSE
new file mode 100644
index 0000000..656c8f7
--- /dev/null
+++ b/vendor/mcmc/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2021 Cache Forge LLC.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+
+ * Neither the name of the Danga Interactive nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/mcmc/Makefile b/vendor/mcmc/Makefile
new file mode 100644
index 0000000..8ac6290
--- /dev/null
+++ b/vendor/mcmc/Makefile
@@ -0,0 +1,13 @@
+# gcc -g -Wall -Werror -pedantic -o example example.c mcmc.c
+PREFIX=/usr/local
+
+all:
+ gcc -g -O2 -Wall -Werror -pedantic -o example example.c mcmc.c
+ gcc -g -O2 -Wall -Werror -pedantic -c mcmc.c
+
+clean:
+ rm -f example mcmc.o
+
+dist: clean
+
+distdir:
diff --git a/vendor/mcmc/README.md b/vendor/mcmc/README.md
new file mode 100644
index 0000000..10d4880
--- /dev/null
+++ b/vendor/mcmc/README.md
@@ -0,0 +1,52 @@
+# Minimal (C) Client for MemCached
+
+WARNING: WORK IN PROGRESS. Missing features or testing!
+
+MCMC is a minimalistic allocation-free modern client for memcached. It uses a
+generic response parser, allowing a single code path regardless of the command
+sent to memcached. It has no 3rd party dependencies and is designed to
+integrate as a building block into full clients.
+
+MCMC does not (yet) include a typical memcached "selector". Meaning the
+ability to add many servers to a hash table of some kind and routing keys to
+specific servers. The MCMC base client is designed to be an object that
+selector objects hold and then issue commands against.
+
+Allocation-free (aside from a call to `getaddrinfo()`) means it does not
+_internally_ do any allocations, relying only on the stack. It requires you
+malloc a small structure and some buffers, but you are then free to manage
+them yourselves. Clients do not hold onto buffers when idle, cutting their
+memory overhead to a handful of bytes plus the TCP socket.
+
+MCMC is designed to be a building block for users designing full clients.
+For example:
+
+* A client author wants to implement the "get" command
+* They write a function in their native language's wrapper which accepts the
+ key to fetch and embeds that into a text buffer to look like `get [key]\r\n`
+* They then call mcmc's functions to send and read the response, parsing and
+ returning it to the client.
+
+This should be the same, if not less, code than wrapping a full C client with
+every possible command broken out. It also means 3rd party clients can (and
+should!) embed mcmc.c/mcmc.h (and any selector code they want) rather than be
+dependent on system distribution of a more complex client.
+
+The allocation-free nature also makes unit testing the client code easier,
+hopefully leading to higher quality.
+
+Caveats:
+
+* Care should be taken when handling the buffers mcmc requires to operate.
+ Since there are few operators you should only have to pay attention once :)
+* It does not support the various maintenance/settings commands (ie; `lru_crawler`).
+ It may gain some generic support for this, but those commands were not
+designed with consistent response codes and are hard to implement.
+* Does not support the binary protocol, which has been deprecated as of 1.6.0.
+
+As of this writing the code is being released _early_ (perhaps too early?). It
+may not have proper makefiles, tests, or a fully implemented API. The code has
+been posted so client authors and users can give early feedback on the API in
+hopes of prodiving something high quality and stable.
+
+Again, looking for feedback! Open an issue or let me know what you think.
diff --git a/vendor/mcmc/example.c b/vendor/mcmc/example.c
new file mode 100644
index 0000000..958c351
--- /dev/null
+++ b/vendor/mcmc/example.c
@@ -0,0 +1,214 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdint.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/uio.h>
+
+#include "mcmc.h"
+
+static void show_response(void *c, char *rbuf, size_t bufsize) {
+ int status;
+ // buffer shouldn't change until the read is completed.
+ mcmc_resp_t resp;
+ int go = 1;
+ while (go) {
+ go = 0;
+ status = mcmc_read(c, rbuf, bufsize, &resp);
+ if (status == MCMC_OK) {
+ // OK means a response of some kind was read.
+ char *val;
+ // NOTE: is "it's not a miss, and vlen is 0" enough to indicate that
+ // a 0 byte value was returned?
+ if (resp.vlen != 0) {
+ if (resp.vlen == resp.vlen_read) {
+ val = resp.value;
+ } else {
+ val = malloc(resp.vlen);
+ int read = 0;
+ do {
+ status = mcmc_read_value(c, val, resp.vlen, &read);
+ } while (status == MCMC_WANT_READ);
+ }
+ if (resp.vlen > 0) {
+ val[resp.vlen-1] = '\0';
+ printf("Response value: %s\n", val);
+ }
+ }
+ switch (resp.type) {
+ case MCMC_RESP_GET:
+ // GET's need to continue until END is seen.
+ printf("in GET mode\n");
+ go = 1;
+ break;
+ case MCMC_RESP_END: // ascii done-with-get's
+ printf("END seen\n");
+ break;
+ case MCMC_RESP_META: // any meta command. they all return the same.
+ printf("META response seen\n");
+ if (resp.rlen > 0) {
+ resp.rline[resp.rlen-1] = '\0';
+ printf("META response line: %s\n", resp.rline);
+ }
+ break;
+ case MCMC_RESP_STAT:
+ // STAT responses. need to call mcmc_read() in loop until
+ // we get an end signal.
+ go = 1;
+ break;
+ default:
+ // TODO: type -> str func.
+ fprintf(stderr, "unknown response type: %d\n", resp.type);
+ break;
+ }
+ } else {
+ // some kind of command specific error code (management commands)
+ // or protocol error status.
+ char code[MCMC_ERROR_CODE_MAX];
+ char msg[MCMC_ERROR_MSG_MAX];
+ mcmc_get_error(c, code, MCMC_ERROR_CODE_MAX, msg, MCMC_ERROR_MSG_MAX);
+ fprintf(stderr, "Got error from mc: status [%d] code [%s] msg: [%s]\n", status, code, msg);
+ // some errors don't have a msg. in this case msg[0] will be \0
+ }
+
+ int remain = 0;
+ // advance us to the next command in the buffer, or ready for the next
+ // mc_read().
+ char *newbuf = mcmc_buffer_consume(c, &remain);
+ printf("remains in buffer: %d\n", remain);
+ if (remain == 0) {
+ assert(newbuf == NULL);
+ // we're done.
+ } else {
+ // there're still some bytes unconsumed by the client.
+ // ensure the next time we call the client, the buffer has those
+ // bytes at the front still.
+ // NOTE: this _could_ be an entirely different buffer if we copied
+ // the data off. The client is just tracking the # of bytes it
+ // didn't gobble.
+ // In this case we shuffle the bytes back to the front of our read
+ // buffer.
+ memmove(rbuf, newbuf, remain);
+ }
+ }
+}
+
+int main (int argc, char *agv[]) {
+ // TODO: detect if C is pre-C11?
+ printf("C version: %ld\n", __STDC_VERSION__);
+
+ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
+ perror("signal");
+ exit(1);
+ }
+
+ void *c = malloc(mcmc_size(MCMC_OPTION_BLANK));
+ // we only "need" the minimum buf size.
+ // buffers large enough to fit return values result in fewer syscalls.
+ size_t bufsize = mcmc_min_buffer_size(MCMC_OPTION_BLANK) * 2;
+ // buffers are also generally agnostic to clients. The buffer must be
+ // held and re-used when required by the API. When the buffer is empty,
+ // it may be released to a pool or reused with other connections.
+ char *rbuf = malloc(bufsize);
+
+ int status;
+
+ // API is blocking by default.
+ status = mcmc_connect(c, "127.0.0.1", "11211", MCMC_OPTION_BLANK);
+
+ if (status != MCMC_CONNECTED) {
+ // TODO: mc_strerr(c);
+ fprintf(stderr, "Failed to connect to memcached\n");
+ return -1;
+ }
+
+ char *requests[5] = {"get foo\r\n",
+ "get foob\r\n",
+ "mg foo s t v\r\n",
+ "mg doof s t v Omoo k\r\n",
+ ""};
+
+ for (int x = 0; strlen(requests[x]) != 0; x++) {
+ // provide a buffer, the buffer length, and the number of responses
+ // expected. ie; if pipelining many requests, or using noreply semantics.
+ // FIXME: not confident "number of expected responses" is worth tracking
+ // internally.
+ status = mcmc_send_request(c, requests[x], strlen(requests[x]), 1);
+ //printf("sent request: %s\n", requests[x]);
+
+ if (status != MCMC_OK) {
+ fprintf(stderr, "Failed to send request to memcached\n");
+ return -1;
+ }
+
+ // Regardless of what command we sent, this should print out the response.
+ show_response(c, rbuf, bufsize);
+
+ }
+
+ status = mcmc_disconnect(c);
+ // The only free'ing needed.
+ free(c);
+
+ // TODO: stats example.
+
+ // nonblocking example.
+ c = malloc(mcmc_size(MCMC_OPTION_BLANK));
+ // reuse bufsize/rbuf.
+ status = mcmc_connect(c, "127.0.0.1", "11211", MCMC_OPTION_NONBLOCK);
+ printf("nonblock connecting...\n");
+ struct pollfd pfds[1];
+ if (status == MCMC_CONNECTING) {
+ // need to wait for socket to become writeable.
+ pfds[0].fd = mcmc_fd(c);
+ pfds[0].events = POLLOUT;
+ if (poll(pfds, 1, 1000) != 1) {
+ fprintf(stderr, "poll on connect timed out or failed\n");
+ return -1;
+ }
+ int err = 0;
+ if (pfds[0].revents & POLLOUT && mcmc_check_nonblock_connect(c, &err) == MCMC_OK) {
+ printf("asynchronous connection completed: %d\n", err);
+ } else {
+ printf("failed to connect: %s\n", strerror(err));
+ return -1;
+ }
+ } else {
+ perror("connect");
+ fprintf(stderr, "bad response to nonblock connection: %d\n", status);
+ return -1;
+ }
+
+ // TODO: check socket for errors.
+
+ // TODO: send request
+ status = mcmc_send_request(c, requests[0], strlen(requests[0]), 1);
+ //printf("sent request: %s\n", requests[x]);
+
+ if (status != MCMC_OK) {
+ fprintf(stderr, "Failed to send request to memcached\n");
+ return -1;
+ }
+
+ mcmc_resp_t resp;
+ status = mcmc_read(c, rbuf, bufsize, &resp);
+ // this could race and fail, depending on the system.
+ if (status == MCMC_WANT_READ) {
+ printf("got MCMC_WANT_READ from a too-fast read as expected\n");
+ pfds[0].fd = mcmc_fd(c);
+ pfds[0].events = POLLIN;
+ if (poll(pfds, 1, 1000) != 1) {
+ fprintf(stderr, "poll on connect timed out or failed\n");
+ return -1;
+ }
+ if (pfds[0].revents & POLLIN) {
+ printf("asynchronous read ready\n");
+ }
+
+ show_response(c, rbuf, bufsize);
+ }
+
+ return 0;
+}
diff --git a/vendor/mcmc/mcmc.c b/vendor/mcmc/mcmc.c
new file mode 100644
index 0000000..65b7e68
--- /dev/null
+++ b/vendor/mcmc/mcmc.c
@@ -0,0 +1,728 @@
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "mcmc.h"
+
+// TODO: if there's a parse error or unknown status code, we likely have a
+// protocol desync and need to disconnect.
+
+// NOTE: this _will_ change a bit for adding TLS support.
+
+// A "reasonable" minimum buffer size to work with.
+// Callers are allowed to create a buffer of any size larger than this.
+// TODO: Put the math/documentation in here.
+// This is essentially the largest return value status line possible.
+// at least doubled for wiggle room.
+#define MIN_BUFFER_SIZE 2048
+
+#define FLAG_BUF_IS_ERROR 0x1
+#define FLAG_BUF_IS_NUMERIC 0x2
+#define FLAG_BUF_WANTED_READ 0x4
+
+#define STATE_DEFAULT 0 // looking for any kind of response
+#define STATE_GET_RESP 1 // processing VALUE's until END
+#define STATE_STAT_RESP 2 // processing STAT's until END
+#define STATE_STAT_RESP_DONE 3
+
+typedef struct mcmc_ctx {
+ int fd;
+ int gai_status; // getaddrinfo() last status.
+ int last_sys_error; // last syscall error (connect/etc?)
+ int sent_bytes_partial; // note for partially sent buffers.
+ int request_queue; // supposed outstanding replies.
+ int fail_code; // recent failure reason.
+ int error; // latest error code.
+ uint32_t status_flags; // internal only flags.
+ int state;
+
+ // FIXME: s/buffer_used/buffer_filled/ ?
+ size_t buffer_used; // amount of bytes read into the buffer so far.
+ size_t buffer_request_len; // cached endpoint for current request
+ char *buffer_head; // buffer pointer currently in use.
+ char *buffer_tail; // consumed tail of the buffer.
+
+ // request response detail.
+ mcmc_resp_t *resp;
+} mcmc_ctx_t;
+
+// INTERNAL FUNCTIONS
+
+static int _mcmc_parse_value_line(mcmc_ctx_t *ctx) {
+ char *buf = ctx->buffer_head;
+ // we know that "VALUE " has matched, so skip that.
+ char *p = buf+6;
+ size_t l = ctx->buffer_request_len;
+
+ // <key> <flags> <bytes> [<cas unique>]
+ char *key = p;
+ int keylen;
+ p = memchr(p, ' ', l - 6);
+ if (p == NULL) {
+ // FIXME: these should return MCMC_ERR and set the internal parse
+ // error code.
+ return MCMC_PARSE_ERROR;
+ }
+
+ keylen = p - key;
+
+ // convert flags into something useful.
+ // FIXME: do we need to prevent overruns in strtoul?
+ // we know for sure the line will eventually end in a \n.
+ char *n = NULL;
+ errno = 0;
+ uint32_t flags = strtoul(p, &n, 10);
+ if ((errno == ERANGE) || (p == n) || (*n != ' ')) {
+ return MCMC_PARSE_ERROR;
+ }
+ p = n;
+
+ errno = 0;
+ uint32_t bytes = strtoul(p, &n, 10);
+ if ((errno == ERANGE) || (p == n)) {
+ return MCMC_PARSE_ERROR;
+ }
+ p = n;
+
+ // If next byte is a space, we read the optional CAS value.
+ uint64_t cas = 0;
+ if (*n == ' ') {
+ errno = 0;
+ cas = strtoull(p, &n, 10);
+ if ((errno == ERANGE) || (p == n)) {
+ return MCMC_PARSE_ERROR;
+ }
+ }
+
+ // If we made it this far, we've parsed everything, stuff the details into
+ // the context for fetching later.
+ mcmc_resp_t *r = ctx->resp;
+ // FIXME: set to NULL if we don't have the value?
+ r->value = ctx->buffer_tail;
+ r->vlen = bytes + 2; // add in the \r\n
+ int buffer_remain = ctx->buffer_used - (ctx->buffer_tail - ctx->buffer_head);
+ if (buffer_remain >= r->vlen) {
+ r->vlen_read = r->vlen;
+ ctx->buffer_tail += r->vlen;
+ } else {
+ r->vlen_read = buffer_remain;
+ }
+ r->key = key;
+ r->klen = keylen;
+ r->flags = flags;
+ r->cas = cas;
+ r->type = MCMC_RESP_GET;
+ ctx->state = STATE_GET_RESP;
+
+ // NOTE: if value_offset < buffer_used, has part of the value in the
+ // buffer already.
+
+ return MCMC_OK;
+}
+
+// FIXME: This is broken for ASCII multiget.
+// if we get VALUE back, we need to stay in ASCII GET read mode until an END
+// is seen.
+static int _mcmc_parse_response(mcmc_ctx_t *ctx) {
+ char *buf = ctx->buffer_head;
+ char *cur = buf;
+ size_t l = ctx->buffer_request_len;
+ int rlen; // response code length.
+ int more = 0;
+ mcmc_resp_t *r = ctx->resp;
+ r->reslen = ctx->buffer_request_len;
+ r->type = MCMC_RESP_GENERIC;
+
+ // walk until the \r\n
+ while (l-- > 2) {
+ if (*cur == ' ') {
+ more = 1;
+ break;
+ }
+ cur++;
+ }
+ rlen = cur - buf;
+
+ // incr/decr returns a number with no code :(
+ // not checking length first since buf must have at least one char to
+ // enter this function.
+ if (buf[0] >= '0' && buf[0] <= '9') {
+ // TODO: parse it as a number on request.
+ // TODO: validate whole thing as digits here?
+ ctx->status_flags |= FLAG_BUF_IS_NUMERIC;
+ r->type = MCMC_RESP_NUMERIC;
+ return MCMC_OK;
+ }
+
+ if (rlen < 2) {
+ ctx->error = MCMC_PARSE_ERROR_SHORT;
+ return MCMC_ERR;
+ }
+
+ int rv = MCMC_OK;
+ int code = MCMC_CODE_OK;
+ switch (rlen) {
+ case 2:
+ // meta, "OK"
+ // FIXME: adding new return codes would make the client completely
+ // fail. The rest of the client is agnostic to requests/flags for
+ // meta.
+ // can we make it agnostic for return codes outside of "read this
+ // data" types?
+ // As-is it should fail down to the "send the return code to the
+ // user". not sure that's right.
+ r->type = MCMC_RESP_META;
+ switch (buf[0]) {
+ case 'E':
+ if (buf[1] == 'N') {
+ code = MCMC_CODE_MISS;
+ // TODO: RESP type
+ } else if (buf[1] == 'X') {
+ code = MCMC_CODE_EXISTS;
+ }
+ break;
+ case 'H':
+ if (buf[1] == 'D') {
+ // typical meta response.
+ code = MCMC_CODE_OK;
+ }
+ break;
+ case 'M':
+ if (buf[1] == 'N') {
+ // specific return code so user can see pipeline end.
+ code = MCMC_CODE_NOP;
+ } else if (buf[1] == 'E') {
+ // ME is the debug output line.
+ // TODO: this just gets returned as an rline?
+ // specific code? specific type?
+ // ME <key> <key=value debug line>
+ rv = MCMC_OK;
+ }
+ break;
+ case 'N':
+ if (buf[1] == 'F') {
+ code = MCMC_CODE_NOT_FOUND;
+ } else if (buf[1] == 'S') {
+ code = MCMC_CODE_NOT_STORED;
+ }
+ break;
+ case 'O':
+ if (buf[1] == 'K') {
+ // Used by many random management commands
+ r->type = MCMC_RESP_GENERIC;
+ }
+ break;
+ case 'V':
+ if (buf[1] == 'A') {
+ // VA <size> <flags>*\r\n
+ if (more) {
+ errno = 0;
+ char *n = NULL;
+ uint32_t vsize = strtoul(cur, &n, 10);
+ if ((errno == ERANGE) || (cur == n)) {
+ rv = MCMC_ERR;
+ } else {
+ r->value = ctx->buffer_tail;
+ r->vlen = vsize + 2; // tag in the \r\n.
+ // FIXME: macro.
+ int buffer_remain = ctx->buffer_used - (ctx->buffer_tail - ctx->buffer_head);
+ if (buffer_remain >= r->vlen) {
+ r->vlen_read = r->vlen;
+ ctx->buffer_tail += r->vlen;
+ } else {
+ r->vlen_read = buffer_remain;
+ }
+ cur = n;
+ if (*cur != ' ') {
+ more = 0;
+ }
+ }
+ } else {
+ rv = MCMC_ERR;
+ }
+ }
+ break;
+ }
+ // maybe: if !rv and !fail, do something special?
+ // if (more), there are flags. shove them in the right place.
+ if (more) {
+ r->rline = cur+1; // eat the space.
+ r->rlen = l-1;
+ } else {
+ r->rline = NULL;
+ r->rlen = 0;
+ }
+ break;
+ case 3:
+ if (memcmp(buf, "END", 3) == 0) {
+ // Either end of STAT results, or end of ascii GET key list.
+ ctx->state = STATE_DEFAULT;
+ // FIXME: caller needs to understand if this is a real miss.
+ code = MCMC_CODE_MISS;
+ r->type = MCMC_RESP_END;
+ rv = MCMC_OK;
+ }
+ break;
+ case 4:
+ if (memcmp(buf, "STAT", 4) == 0) {
+ r->type = MCMC_RESP_STAT;
+ ctx->state = STATE_STAT_RESP;
+ // TODO: initialize stat reader mode.
+ }
+ break;
+ case 5:
+ if (memcmp(buf, "VALUE", 5) == 0) {
+ if (more) {
+ // <key> <flags> <bytes> [<cas unique>]
+ rv = _mcmc_parse_value_line(ctx);
+ } else {
+ rv = MCMC_ERR; // FIXME: parse error.
+ }
+ }
+ break;
+ case 6:
+ if (memcmp(buf, "STORED", 6) == 0) {
+ code = MCMC_CODE_STORED;
+ } else if (memcmp(buf, "EXISTS", 6) == 0) {
+ code = MCMC_CODE_EXISTS;
+ // TODO: type -> ASCII?
+ }
+ break;
+ case 7:
+ if (memcmp(buf, "DELETED", 7) == 0) {
+ code = MCMC_CODE_DELETED;
+ } else if (memcmp(buf, "TOUCHED", 7) == 0) {
+ code = MCMC_CODE_TOUCHED;
+ } else if (memcmp(buf, "VERSION", 7) == 0) {
+ code = MCMC_CODE_VERSION;
+ r->type = MCMC_RESP_VERSION;
+ // TODO: prep the version line for return
+ }
+ break;
+ case 9:
+ if (memcmp(buf, "NOT_FOUND", 9) == 0) {
+ code = MCMC_CODE_NOT_FOUND;
+ }
+ break;
+ case 10:
+ if (memcmp(buf, "NOT_STORED", 10) == 0) {
+ code = MCMC_CODE_NOT_STORED;
+ }
+ break;
+ default:
+ // Unknown code, assume error.
+ break;
+ }
+
+ r->code = code;
+ if (rv == -1) {
+ // TODO: Finish this.
+ ctx->status_flags |= FLAG_BUF_IS_ERROR;
+ rv = MCMC_ERR;
+ }
+
+ return rv;
+}
+
+// EXTERNAL API
+
+int mcmc_fd(void *c) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ return ctx->fd;
+}
+
+size_t mcmc_size(int options) {
+ return sizeof(mcmc_ctx_t);
+}
+
+// Allow returning this dynamically based on options set.
+// FIXME: it might be more flexible to call this after mcmc_connect()...
+// but this is probably more convenient for the caller if it's less dynamic.
+size_t mcmc_min_buffer_size(int options) {
+ return MIN_BUFFER_SIZE;
+}
+
+char *mcmc_read_prep(void *c, char *buf, size_t bufsize, size_t *bufremain) {
+ mcmc_ctx_t *ctx = c;
+ char *b = buf + ctx->buffer_used;
+ *bufremain = bufsize - ctx->buffer_used;
+ return b;
+}
+
+// Directly parse a buffer with read data of size len.
+// r->reslen + r->vlen_read is the bytes consumed from the buffer read.
+// Caller manages how to retry if MCMC_WANT_READ or an error happens.
+// FIXME: not sure if to keep this command to a fixed buffer size, or continue
+// to use the ctx->buffer_used bits... if we keep the buffer_used stuff caller can
+// loop without memmove'ing the buffer?
+int mcmc_parse_buf(void *c, char *buf, size_t read, mcmc_resp_t *r) {
+ mcmc_ctx_t *ctx = c;
+ char *el;
+ ctx->buffer_used += read;
+
+ el = memchr(buf, '\n', ctx->buffer_used);
+ if (el == NULL) {
+ return MCMC_WANT_READ;
+ }
+
+ memset(r, 0, sizeof(*r));
+
+ // Consume through the newline.
+ // buffer_tail now points to where value could start.
+ // FIXME: ctx->value ?
+ ctx->buffer_tail = el+1;
+
+ // FIXME: the server must be stricter in what it sends back. should always
+ // have a \r. check for it and fail?
+ ctx->buffer_request_len = ctx->buffer_tail - buf;
+ // leave the \r\n in the line end cache.
+ ctx->buffer_head = buf;
+ // TODO: handling for nonblock case.
+
+ // We have a result line. Now pass it through the parser.
+ // Then we indicate to the user that a response is ready.
+ ctx->resp = r;
+ return _mcmc_parse_response(ctx);
+}
+
+/*** Functions wrapping syscalls **/
+
+// TODO: should be able to flip between block and nonblock.
+
+// used for checking on async connections.
+int mcmc_check_nonblock_connect(void *c, int *err) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ socklen_t errsize = sizeof(*err);
+ if (getsockopt(ctx->fd, SOL_SOCKET, SO_ERROR, err, &errsize) == 0) {
+ if (*err == 0) {
+ return MCMC_OK;
+ }
+ } else {
+ // getsockopt failed. still need to pass up the error.
+ *err = errno;
+ }
+
+ return MCMC_ERR;
+}
+
+// TODO:
+// - option for connecting 4 -> 6 or 6 -> 4
+// connect_unix()
+// connect_bind_tcp()
+// ^ fill an internal struct from the stack and call into this central
+// connect?
+int mcmc_connect(void *c, char *host, char *port, int options) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+
+ int s;
+ int sock;
+ int res = MCMC_CONNECTED;
+ struct addrinfo hints;
+ struct addrinfo *ai;
+ struct addrinfo *next;
+
+ // Since our cx memory was likely malloc'ed, ensure we start clear.
+ memset(ctx, 0, sizeof(mcmc_ctx_t));
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ s = getaddrinfo(host, port, &hints, &ai);
+
+ if (s != 0) {
+ hints.ai_family = AF_INET6;
+ s = getaddrinfo(host, port, &hints, &ai);
+ if (s != 0) {
+ // TODO: gai_strerror(s)
+ ctx->gai_status = s;
+ res = MCMC_ERR;
+ goto end;
+ }
+ }
+
+ for (next = ai; next != NULL; next = next->ai_next) {
+ sock = socket(next->ai_family, next->ai_socktype,
+ next->ai_protocol);
+ if (sock == -1)
+ continue;
+
+ // TODO: NONBLOCK
+ if (options & MCMC_OPTION_NONBLOCK) {
+ int flags = fcntl(sock, F_GETFL);
+ if (flags < 0) {
+ res = MCMC_ERR;
+ close(sock);
+ goto end;
+ }
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
+ res = MCMC_ERR;
+ close(sock);
+ goto end;
+ }
+ res = MCMC_CONNECTING;
+
+ if (connect(sock, next->ai_addr, next->ai_addrlen) != -1) {
+ if (errno == EINPROGRESS) {
+ break; // We're good, stop the loop.
+ }
+ }
+
+ break;
+ } else {
+ // TODO: BIND local port.
+ if (connect(sock, next->ai_addr, next->ai_addrlen) != -1)
+ break;
+ }
+
+ close(sock);
+ }
+
+ // TODO: cache last connect status code?
+ if (next == NULL) {
+ res = MCMC_ERR;
+ goto end;
+ }
+
+ ctx->fd = sock;
+end:
+ if (ai) {
+ freeaddrinfo(ai);
+ }
+ return res;
+}
+
+// NOTE: if WANT_WRITE returned, call with same arguments.
+// FIXME: len -> size_t?
+// TODO: rename to mcmc_request_send
+int mcmc_send_request(void *c, const char *request, int len, int count) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+
+ // adjust our send buffer by how much has already been sent.
+ const char *r = request + ctx->sent_bytes_partial;
+ int l = len - ctx->sent_bytes_partial;
+ int sent = send(ctx->fd, r, l, 0);
+ if (sent == -1) {
+ // implicitly handle nonblock mode.
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return MCMC_WANT_WRITE;
+ } else {
+ return MCMC_ERR;
+ }
+ }
+
+ if (sent < len) {
+ // can happen anytime, but mostly in nonblocking mode.
+ ctx->sent_bytes_partial += sent;
+ return MCMC_WANT_WRITE;
+ } else {
+ ctx->request_queue += count;
+ ctx->sent_bytes_partial = 0;
+ }
+
+ return MCMC_OK;
+}
+
+// TODO: pretty sure I don't want this function chewing on a submitted iov
+// stack, but it might make for less client code :(
+// so for now, lets not.
+int mcmc_request_writev(void *c, const struct iovec *iov, int iovcnt, ssize_t *sent, int count) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ // need to track sent vs tosend to know when to update counters.
+ ssize_t tosend = 0;
+ for (int i = 0; i < iovcnt; i++) {
+ tosend += iov[i].iov_len;
+ }
+
+ *sent = writev(ctx->fd, iov, iovcnt);
+ if (*sent == -1) {
+ // implicitly handle nonblock mode.
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return MCMC_WANT_WRITE;
+ } else {
+ return MCMC_ERR;
+ }
+ }
+
+ if (*sent < tosend) {
+ // can happen anytime, but mostly in nonblocking mode.
+ return MCMC_WANT_WRITE;
+ } else {
+ // FIXME: user has to keep submitting the same count value...
+ // should decide on whether or not to give up on this.
+ ctx->request_queue += count;
+ }
+
+ return MCMC_OK;
+}
+
+// TODO: avoid recv if we have bytes in the buffer.
+int mcmc_read(void *c, char *buf, size_t bufsize, mcmc_resp_t *r) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ char *el;
+ memset(r, 0, sizeof(*r));
+
+ // If there's still data in the buffer try to use it before potentially
+ // hanging on the network read.
+ // Also skip this check if we specifically wanted more bytes from net.
+ if (ctx->buffer_used && !(ctx->status_flags & FLAG_BUF_WANTED_READ)) {
+ el = memchr(buf, '\n', ctx->buffer_used);
+ if (el) {
+ goto parse;
+ }
+ }
+
+ // adjust buffer by how far we've already consumed.
+ char *b = buf + ctx->buffer_used;
+ size_t l = bufsize - ctx->buffer_used;
+
+ int read = recv(ctx->fd, b, l, 0);
+ if (read == 0) {
+ return MCMC_NOT_CONNECTED;
+ } else if (read == -1) {
+ // implicitly handle nonblocking configurations.
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return MCMC_WANT_READ;
+ } else {
+ return MCMC_ERR;
+ }
+ }
+
+ ctx->buffer_used += read;
+
+ // Always scan from the start of the original buffer.
+ el = memchr(buf, '\n', ctx->buffer_used);
+ if (!el) {
+ // FIXME: error if buffer is full but no \n is found.
+ ctx->status_flags |= FLAG_BUF_WANTED_READ;
+ return MCMC_WANT_READ;
+ }
+parse:
+ // Consume through the newline.
+ // buffer_tail now points to where a value could start.
+ ctx->buffer_tail = el+1;
+
+ // FIXME: the server must be stricter in what it sends back. should always
+ // have a \r. check for it and fail?
+ ctx->buffer_request_len = ctx->buffer_tail - buf;
+ // leave the \r\n in the line end cache.
+ ctx->buffer_head = buf;
+ // TODO: handling for nonblock case.
+
+ // We have a result line. Now pass it through the parser.
+ // Then we indicate to the user that a response is ready.
+ ctx->resp = r;
+ return _mcmc_parse_response(ctx);
+}
+
+void mcmc_get_error(void *c, char *code, size_t clen, char *msg, size_t mlen) {
+ code[0] = '\0';
+ msg[0] = '\0';
+}
+
+int mcmc_read_value_buf(void *c, char *val, const size_t vsize, int *read) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+
+ // If the distance between tail/head is smaller than what we read into the
+ // main buffer, we have some value to copy out.
+ int leftover = ctx->buffer_used - (ctx->buffer_tail - ctx->buffer_head);
+ if (leftover > 0) {
+ int tocopy = leftover > vsize ? vsize : leftover;
+ memcpy(val + *read, ctx->buffer_tail, tocopy);
+ ctx->buffer_tail += tocopy;
+ *read += tocopy;
+ if (leftover > tocopy) {
+ // FIXME: think we need a specific code for "value didn't fit"
+ return MCMC_WANT_READ;
+ }
+ }
+
+ return MCMC_OK;
+}
+
+// read into the buffer, up to a max size of vsize.
+// will read (vsize-read) into the buffer pointed to by (val+read).
+// you are able to stream the value into different buffers, or process the
+// value and reuse the same buffer, by adjusting vsize and *read between
+// calls.
+// vsize must not be larger than the remaining value size pending read.
+int mcmc_read_value(void *c, char *val, const size_t vsize, int *read) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ size_t l;
+
+ // If the distance between tail/head is smaller than what we read into the
+ // main buffer, we have some value to copy out.
+ int leftover = ctx->buffer_used - (ctx->buffer_tail - ctx->buffer_head);
+ if (leftover > 0) {
+ int tocopy = leftover > vsize ? vsize : leftover;
+ memcpy(val + *read, ctx->buffer_tail, tocopy);
+ ctx->buffer_tail += tocopy;
+ *read += tocopy;
+ if (leftover > tocopy) {
+ // FIXME: think we need a specific code for "value didn't fit"
+ return MCMC_WANT_READ;
+ }
+ }
+
+ char *v = val + *read;
+ l = vsize - *read;
+
+ int r = recv(ctx->fd, v, l, 0);
+ if (r == 0) {
+ // TODO: some internal disconnect work?
+ return MCMC_NOT_CONNECTED;
+ }
+ // FIXME: EAGAIN || EWOULDBLOCK!
+ if (r == -1) {
+ return MCMC_ERR;
+ }
+
+ *read += r;
+
+ if (*read < vsize) {
+ return MCMC_WANT_READ;
+ } else {
+ return MCMC_OK;
+ }
+}
+
+char *mcmc_buffer_consume(void *c, int *remain) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+ ctx->buffer_used -= ctx->buffer_tail - ctx->buffer_head;
+ int used = ctx->buffer_used;
+ char *newbuf = ctx->buffer_tail;
+
+ // FIXME: request_queue-- is in the wrong place.
+ // TODO: which of these _must_ be reset between requests? I think very
+ // little?
+ ctx->request_queue--;
+ ctx->status_flags = 0;
+ ctx->buffer_head = NULL;
+ ctx->buffer_tail = NULL;
+
+ if (used) {
+ *remain = used;
+ return newbuf;
+ } else {
+ return NULL;
+ }
+}
+
+int mcmc_disconnect(void *c) {
+ mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
+
+ // FIXME: I forget if 0 can be valid.
+ if (ctx->fd != 0) {
+ close(ctx->fd);
+ return MCMC_OK;
+ } else {
+ return MCMC_NOT_CONNECTED;
+ }
+}
diff --git a/vendor/mcmc/mcmc.h b/vendor/mcmc/mcmc.h
new file mode 100644
index 0000000..1769228
--- /dev/null
+++ b/vendor/mcmc/mcmc.h
@@ -0,0 +1,91 @@
+#ifndef MCMC_HEADER
+#define MCMC_HEADER
+
+#define MCMC_OK 0
+#define MCMC_ERR -1
+#define MCMC_NOT_CONNECTED 1
+#define MCMC_CONNECTED 2
+#define MCMC_CONNECTING 3 // nonblock mode.
+#define MCMC_WANT_WRITE 4
+#define MCMC_WANT_READ 5
+#define MCMC_HAS_RESULT 7
+// TODO: either internally set a flag for "ok" or "not ok" and use a func,
+// or use a bitflag here (1<<6) for "OK", (1<<5) for "FAIL", etc.
+// or, we directly return "OK" or "FAIL" and you can ask for specific error.
+#define MCMC_CODE_STORED 8
+#define MCMC_CODE_EXISTS 9
+#define MCMC_CODE_DELETED 10
+#define MCMC_CODE_TOUCHED 11
+#define MCMC_CODE_VERSION 12
+#define MCMC_CODE_NOT_FOUND 13
+#define MCMC_CODE_NOT_STORED 14
+#define MCMC_CODE_OK 15
+#define MCMC_CODE_NOP 16
+#define MCMC_PARSE_ERROR_SHORT 17
+#define MCMC_PARSE_ERROR 18
+#define MCMC_CODE_MISS 19 // FIXME
+
+
+// response types
+#define MCMC_RESP_GET 100
+#define MCMC_RESP_META 101
+#define MCMC_RESP_STAT 102
+#define MCMC_RESP_GENERIC 104
+#define MCMC_RESP_END 105
+#define MCMC_RESP_VERSION 106
+#define MCMC_RESP_NUMERIC 107 // for weird incr/decr syntax.
+
+#define MCMC_OPTION_BLANK 0
+#define MCMC_OPTION_NONBLOCK 1
+
+// convenience defines. if you want to save RAM you can set these smaller and
+// error handler will only copy what you ask for.
+#define MCMC_ERROR_CODE_MAX 32
+#define MCMC_ERROR_MSG_MAX 512
+
+typedef struct {
+ unsigned short type;
+ unsigned short code;
+ char *value; // pointer to start of value in buffer.
+ size_t reslen; // full length of the response line
+ size_t vlen_read; // amount of value that was in supplied buffer.
+ size_t vlen; // reslen + vlen is the full length of the response.
+ union {
+ // META response
+ struct {
+ char *rline; // start of meta response line.
+ size_t rlen;
+ };
+ // GET response
+ struct {
+ char *key;
+ size_t klen;
+ uint32_t flags;
+ uint64_t cas;
+ // TODO: value info
+ };
+ // STAT response
+ struct {
+ char *stat;
+ size_t slen;
+ };
+ };
+} mcmc_resp_t;
+
+int mcmc_fd(void *c);
+size_t mcmc_size(int options);
+size_t mcmc_min_buffer_size(int options);
+char *mcmc_read_prep(void *c, char *buf, size_t bufsize, size_t *bufremain);
+int mcmc_parse_buf(void *c, char *buf, size_t read, mcmc_resp_t *r);
+int mcmc_connect(void *c, char *host, char *port, int options);
+int mcmc_check_nonblock_connect(void *c, int *err);
+int mcmc_send_request(void *c, const char *request, int len, int count);
+int mcmc_request_writev(void *c, const struct iovec *iov, int iovcnt, ssize_t *sent, int count);
+int mcmc_read(void *c, char *buf, size_t bufsize, mcmc_resp_t *r);
+int mcmc_read_value(void *c, char *val, const size_t vsize, int *read);
+int mcmc_read_value_buf(void *c, char *val, const size_t vsize, int *read);
+char *mcmc_buffer_consume(void *c, int *remain);
+int mcmc_disconnect(void *c);
+void mcmc_get_error(void *c, char *code, size_t clen, char *msg, size_t mlen);
+
+#endif