1 #include <stdlib.h>
2 #include <sys/socket.h>
3 #include <sys/types.h>
4 #include <netdb.h>
5 #include <unistd.h>
6 #include <fcntl.h>
7 #include <string.h>
8 #include <errno.h>
9 #include <stdio.h>
10 #include <limits.h>
11 #include <ctype.h>
12
13 // TODO: move these structs into mcmc.h, but only expose them if
14 // MCMC_EXPOSE_INTERNALS is defined... for tests and this thing.
15 #include "mcmc.h"
16
17 // TODO: if there's a parse error or unknown status code, we likely have a
18 // protocol desync and need to disconnect.
19
20 // A "reasonable" minimum buffer size to work with.
21 // Callers are allowed to create a buffer of any size larger than this.
22 // TODO: Put the math/documentation in here.
23 // This is essentially the largest return value status line possible.
24 // at least doubled for wiggle room.
25 #define MIN_BUFFER_SIZE 2048
26
27 #define FLAG_BUF_WANTED_READ 0x4
28
29 #define STATE_DEFAULT 0 // looking for any kind of response
30 #define STATE_GET_RESP 1 // processing VALUE's until END
31 #define STATE_STAT_RESP 2 // processing STAT's until END
32 #define STATE_STAT_RESP_DONE 3
33
34 typedef struct mcmc_ctx {
35 int fd;
36 int gai_status; // getaddrinfo() last status.
37 int sent_bytes_partial; // note for partially sent buffers.
38 int error; // latest error code.
39 uint32_t status_flags; // internal only flags.
40
41 // FIXME: s/buffer_used/buffer_filled/ ?
42 size_t buffer_used; // amount of bytes read into the buffer so far.
43 char *buffer_head; // buffer pointer currently in use.
44 } mcmc_ctx_t;
45
46 // INTERNAL FUNCTIONS
47
48 #define TOKENIZER_MAXLEN USHRT_MAX
49
50 // Find the starting offsets of each token; ignoring length.
51 // This creates a fast small (<= cacheline) index into the request,
52 // where we later scan or directly feed data into API's.
53 #define _mcmc_tokenize(t, line, len, mstart, max) _mcmc_tokenize_meta(t, line, len, UINT8_MAX, max)
54
55 // specialized tokenizer for meta protocol commands or responses, fills in the
56 // bitflags while scanning.
57 // Function _assumes_ const char *line ends with \n or \r\n, but will not
58 // break so long as the passed in 'len' is reasonable.
_mcmc_tokenize_meta(mcmc_tokenizer_t * t,const char * line,size_t len,const int mstart,const int max)59 MCMC_STATIC int _mcmc_tokenize_meta(mcmc_tokenizer_t *t, const char *line, size_t len, const int mstart, const int max) {
60 const char *s = line;
61 t->metaflags = 0;
62
63 // since multigets can be huge, we can't purely judge reqlen against this
64 // limit, but we also can't index past it since the tokens are shorts.
65 if (len > TOKENIZER_MAXLEN) {
66 len = TOKENIZER_MAXLEN;
67 }
68
69 if (line[len-2] == '\r') {
70 len -= 2;
71 } else {
72 len -= 1;
73 }
74
75 const char *end = s + len;
76 int curtoken = 0;
77
78 int state = 0;
79 while (s != end) {
80 switch (state) {
81 case 0:
82 // scanning for first non-space to find a token.
83 if (*s != ' ') {
84 if (curtoken >= mstart) {
85 if (*s > 64 && *s < 123) {
86 t->metaflags |= (uint64_t)1 << (*s - 65);
87 } else if (isdigit(*s) == 0) {
88 return MCMC_NOK;
89 }
90 }
91 t->tokens[curtoken] = s - line;
92 if (++curtoken == max) {
93 s++;
94 state = 2;
95 break;
96 }
97 state = 1;
98 }
99 s++;
100 break;
101 case 1:
102 // advance over a token
103 if (*s != ' ') {
104 s++;
105 } else {
106 state = 0;
107 }
108 break;
109 case 2:
110 // hit max tokens before end of the line.
111 // keep advancing so we can place endcap token.
112 if (*s == ' ') {
113 goto endloop;
114 }
115 s++;
116 break;
117 }
118 }
119 endloop:
120
121 // endcap token so we can quickly find the length of any token by looking
122 // at the next one.
123 t->tokens[curtoken] = s - line;
124 t->ntokens = curtoken;
125 t->mstart = mstart;
126
127 return MCMC_OK;
128 }
129
_mcmc_token_len(const char * line,mcmc_tokenizer_t * t,size_t token)130 __attribute__((unused)) MCMC_STATIC int _mcmc_token_len(const char *line, mcmc_tokenizer_t *t, size_t token) {
131 const char *s = line + t->tokens[token];
132 const char *e = line + t->tokens[token+1];
133 // start of next token is after any space delimiters, so back those out.
134 while (*(e-1) == ' ') {
135 e--;
136 }
137 return e - s;
138 }
139
_mcmc_token(const char * line,mcmc_tokenizer_t * t,size_t token,int * len)140 __attribute__((unused)) MCMC_STATIC const char *_mcmc_token(const char *line, mcmc_tokenizer_t *t, size_t token, int *len) {
141 const char *s = line + t->tokens[token];
142 if (len != NULL) {
143 const char *e = line + t->tokens[token+1];
144 while (*(e-1) == ' ') {
145 e--;
146 }
147 *len = e - s;
148 }
149 return s;
150 }
151
_mcmc_parse_value_line(const char * buf,size_t read,mcmc_resp_t * r)152 static int _mcmc_parse_value_line(const char *buf, size_t read, mcmc_resp_t *r) {
153 // we know that "VALUE " has matched, so skip that.
154 const char *p = buf+6;
155 size_t l = r->reslen;
156
157 // <key> <flags> <bytes> [<cas unique>]
158 const char *key = p;
159 int keylen;
160 p = memchr(p, ' ', l - 6);
161 if (p == NULL) {
162 return -MCMC_ERR_VALUE;
163 }
164
165 keylen = p - key;
166
167 // convert flags into something useful.
168 // FIXME: do we need to prevent overruns in strtoul?
169 // we know for sure the line will eventually end in a \n.
170 char *n = NULL;
171 errno = 0;
172 uint32_t flags = strtoul(p, &n, 10);
173 if ((errno == ERANGE) || (p == n) || (*n != ' ')) {
174 return -MCMC_ERR_VALUE;
175 }
176 p = n;
177
178 errno = 0;
179 uint32_t bytes = strtoul(p, &n, 10);
180 if ((errno == ERANGE) || (p == n)) {
181 return -MCMC_ERR_VALUE;
182 }
183 p = n;
184
185 // If next byte is a space, we read the optional CAS value.
186 uint64_t cas = 0;
187 if (*n == ' ') {
188 errno = 0;
189 cas = strtoull(p, &n, 10);
190 if ((errno == ERANGE) || (p == n)) {
191 return -MCMC_ERR_VALUE;
192 }
193 }
194
195 // If we made it this far, we've parsed everything, stuff the details into
196 // the context for fetching later.
197 r->vlen = bytes + 2; // add in the \r\n
198 int buffer_remain = read - r->reslen;
199 if (buffer_remain >= r->vlen) {
200 r->vlen_read = r->vlen;
201 } else {
202 r->vlen_read = buffer_remain;
203 }
204 r->key = key;
205 r->klen = keylen;
206 r->flags = flags;
207 r->cas = cas;
208 r->type = MCMC_RESP_GET;
209
210 // NOTE: if value_offset < read, has part of the value in the
211 // buffer already.
212
213 return MCMC_CODE_OK;
214 }
215
_mcmc_parse_stat_line(const char * buf,mcmc_resp_t * r)216 static int _mcmc_parse_stat_line(const char *buf, mcmc_resp_t *r) {
217 const char *p = buf+5; // pass "STAT "
218 size_t l = r->reslen;
219
220 // STAT key value
221 const char *sname = p;
222 p = memchr(p, ' ', l-5);
223 if (p == NULL) {
224 return -MCMC_ERR_VALUE;
225 }
226
227 int snamelen = p - sname;
228 while (*p == ' ') {
229 p++;
230 }
231 const char *stat = p;
232 int statlen = l - (p - buf) - 2;
233
234 r->sname = sname;
235 r->snamelen = snamelen;
236 r->stat = stat;
237 r->statlen = statlen;
238
239 return MCMC_CODE_OK;
240 }
241
242 // FIXME: This is broken for ASCII multiget.
243 // if we get VALUE back, we need to stay in ASCII GET read mode until an END
244 // is seen.
_mcmc_parse_response(const char * buf,size_t read,mcmc_resp_t * r)245 static int _mcmc_parse_response(const char *buf, size_t read, mcmc_resp_t *r) {
246 const char *cur = buf;
247 int rlen; // response code length.
248 int more = 0;
249 r->type = MCMC_RESP_FAIL;
250
251 // walk until the \r\n
252 // we can't enter this function without there being a '\n' in the buffer.
253 while (*cur != '\r' && *cur != '\n') {
254 if (*cur == ' ') {
255 more = 1;
256 break;
257 }
258 cur++;
259 }
260 rlen = cur - buf;
261
262 // incr/decr returns a number with no code :(
263 // not checking length first since buf must have at least one char to
264 // enter this function.
265 if (buf[0] >= '0' && buf[0] <= '9') {
266 // TODO: parse it as a number on request.
267 // TODO: validate whole thing as digits here?
268 r->type = MCMC_RESP_NUMERIC;
269 r->code = MCMC_CODE_OK;
270 return MCMC_OK;
271 }
272
273 if (rlen < 2) {
274 r->code = MCMC_ERR_SHORT;
275 return MCMC_ERR;
276 }
277
278 int code = MCMC_ERR;
279 switch (rlen) {
280 case 2:
281 // meta, "OK"
282 // FIXME: adding new return codes would make the client completely
283 // fail. The rest of the client is agnostic to requests/flags for
284 // meta.
285 // can we make it agnostic for return codes outside of "read this
286 // data" types?
287 // As-is it should fail down to the "send the return code to the
288 // user". not sure that's right.
289 r->type = MCMC_RESP_META;
290 switch (buf[0]) {
291 case 'E':
292 if (buf[1] == 'N') {
293 code = MCMC_CODE_END;
294 } else if (buf[1] == 'X') {
295 code = MCMC_CODE_EXISTS;
296 }
297 break;
298 case 'H':
299 if (buf[1] == 'D') {
300 // typical meta response.
301 code = MCMC_CODE_OK;
302 }
303 break;
304 case 'M':
305 if (buf[1] == 'N') {
306 // specific return code so user can see pipeline end.
307 code = MCMC_CODE_NOP;
308 } else if (buf[1] == 'E') {
309 // ME is the debug output line.
310 // TODO: this just gets returned as an rline?
311 // specific code? specific type?
312 // ME <key> <key=value debug line>
313 code = MCMC_CODE_OK;
314 }
315 break;
316 case 'N':
317 if (buf[1] == 'F') {
318 code = MCMC_CODE_NOT_FOUND;
319 } else if (buf[1] == 'S') {
320 code = MCMC_CODE_NOT_STORED;
321 }
322 break;
323 case 'O':
324 if (buf[1] == 'K') {
325 // Used by many random management commands
326 r->type = MCMC_RESP_GENERIC;
327 code = MCMC_CODE_OK;
328 }
329 break;
330 case 'V':
331 if (buf[1] == 'A') {
332 // VA <size> <flags>*\r\n
333 if (more) {
334 errno = 0;
335 char *n = NULL;
336 uint32_t vsize = strtoul(cur, &n, 10);
337 if ((errno == ERANGE) || (cur == n)) {
338 r->type = MCMC_RESP_FAIL;
339 code = -MCMC_ERR_PARSE;
340 } else {
341 r->vlen = vsize + 2; // tag in the \r\n.
342 // FIXME: macro.
343 int buffer_remain = read - r->reslen;
344 if (buffer_remain >= r->vlen) {
345 r->vlen_read = r->vlen;
346 } else {
347 r->vlen_read = buffer_remain;
348 }
349 cur = n;
350 if (*cur != ' ') {
351 more = 0;
352 }
353 code = MCMC_CODE_OK;
354 }
355 } else {
356 r->type = MCMC_RESP_FAIL;
357 code = -MCMC_ERR_PARSE;
358 }
359 }
360 break;
361 }
362 // maybe: if !rv and !fail, do something special?
363 // if (more), there are flags. shove them in the right place.
364 if (more) {
365 // walk until not space
366 while (*cur == ' ') {
367 cur++;
368 }
369 r->rline = cur;
370 r->rlen = r->reslen - (cur - buf);
371 // cut \n or \r\n
372 if (buf[r->reslen-2] == '\r') {
373 r->rlen -= 2;
374 } else {
375 r->rlen--;
376 }
377 } else {
378 r->rline = NULL;
379 r->rlen = 0;
380 }
381 break;
382 case 3:
383 if (memcmp(buf, "END", 3) == 0) {
384 // Either end of STAT results, or end of ascii GET key list.
385 code = MCMC_CODE_END;
386 r->type = MCMC_RESP_END;
387 }
388 break;
389 case 4:
390 if (memcmp(buf, "STAT", 4) == 0) {
391 r->type = MCMC_RESP_STAT;
392 code = _mcmc_parse_stat_line(buf, r);
393 }
394 break;
395 case 5:
396 if (memcmp(buf, "VALUE", 5) == 0) {
397 if (more) {
398 // <key> <flags> <bytes> [<cas unique>]
399 code = _mcmc_parse_value_line(buf, read, r);
400 } else {
401 code = -MCMC_ERR_PARSE;
402 }
403 } else if (memcmp(buf, "ERROR", 5) == 0) {
404 r->type = MCMC_RESP_ERRMSG;
405 code = -MCMC_CODE_ERROR;
406 }
407 break;
408 case 6:
409 if (memcmp(buf, "STORED", 6) == 0) {
410 r->type = MCMC_RESP_GENERIC;
411 code = MCMC_CODE_STORED;
412 } else if (memcmp(buf, "EXISTS", 6) == 0) {
413 r->type = MCMC_RESP_GENERIC;
414 code = MCMC_CODE_EXISTS;
415 // TODO: type -> ASCII?
416 }
417 break;
418 case 7:
419 if (memcmp(buf, "DELETED", 7) == 0) {
420 r->type = MCMC_RESP_GENERIC;
421 code = MCMC_CODE_DELETED;
422 } else if (memcmp(buf, "TOUCHED", 7) == 0) {
423 r->type = MCMC_RESP_GENERIC;
424 code = MCMC_CODE_TOUCHED;
425 } else if (memcmp(buf, "VERSION", 7) == 0) {
426 code = MCMC_CODE_VERSION;
427 r->type = MCMC_RESP_VERSION;
428 // TODO: prep the version line for return
429 }
430 break;
431 case 9:
432 if (memcmp(buf, "NOT_FOUND", 9) == 0) {
433 r->type = MCMC_RESP_GENERIC;
434 code = MCMC_CODE_NOT_FOUND;
435 }
436 break;
437 case 10:
438 if (memcmp(buf, "NOT_STORED", 10) == 0) {
439 r->type = MCMC_RESP_GENERIC;
440 code = MCMC_CODE_NOT_STORED;
441 }
442 break;
443 default:
444 // Unknown code, assume error.
445 if (memcmp(buf, "SERVER_ERROR", 12) == 0) {
446 r->type = MCMC_RESP_ERRMSG;
447 code = -MCMC_CODE_SERVER_ERROR;
448 } else if (memcmp(buf, "CLIENT_ERROR", 12) == 0) {
449 r->type = MCMC_RESP_ERRMSG;
450 code = -MCMC_CODE_CLIENT_ERROR;
451 }
452 break;
453 }
454
455 if (code < MCMC_OK) {
456 r->code = -code;
457 return MCMC_ERR;
458 } else {
459 r->code = code;
460 return MCMC_OK;
461 }
462 }
463
464 // TODO: possible to codegen the 32 vs 64 variants
465 // easy with a macro I just hate how that looks
466 #define MCMC_TOKTO32_MAX 11
467 #define MCMC_TOKTO64_MAX 22
468
469 // TODO: test if __builtin_add_overflow exists and use that instead for
470 // hardware boost.
471 // just need to split the mul and the add? if (__builtin_mul_overflow(etc))
472 // - need a method to force compile both functions for the test suite.
mcmc_toktou32(const char * t,size_t len,uint32_t * out)473 MCMC_STATIC int mcmc_toktou32(const char *t, size_t len, uint32_t *out) {
474 uint32_t sum = 0;
475 const char *pos = t;
476 // We clamp the possible length to make input length errors less likely to
477 // go for a walk through memory.
478 if (len > MCMC_TOKTO32_MAX) {
479 return MCMC_TOKTO_ELONG;
480 }
481 while (len--) {
482 char num = pos[0] - '0';
483 if (num > -1 && num < 10) {
484 uint32_t lim = (UINT32_MAX - num) / 10;
485 if (sum > lim) {
486 return MCMC_TOKTO_ERANGE;
487 }
488 sum = sum * 10 + num;
489 } else {
490 return MCMC_TOKTO_EINVALID;
491 }
492 pos++;
493 }
494 *out = sum;
495 return MCMC_OK;
496 }
497
mcmc_toktou64(const char * t,size_t len,uint64_t * out)498 MCMC_STATIC int mcmc_toktou64(const char *t, size_t len, uint64_t *out) {
499 uint64_t sum = 0;
500 const char *pos = t;
501 if (len > MCMC_TOKTO64_MAX) {
502 return MCMC_TOKTO_ELONG;
503 }
504 while (len--) {
505 char num = pos[0] - '0';
506 if (num > -1 && num < 10) {
507 uint64_t lim = (UINT64_MAX - num) / 10;
508 if (sum > lim) {
509 return MCMC_TOKTO_ERANGE;
510 }
511 sum = sum * 10 + num;
512 } else {
513 return MCMC_TOKTO_EINVALID;
514 }
515 pos++;
516 }
517 *out = sum;
518 return MCMC_OK;
519 }
520
521 // TODO: these funcs aren't defending against len == 0
522 // note that the command strings _must_ end in a \n so it _should_ be
523 // impossible to land after the buffer.
524 // However this should be adjusted next time I work on it:
525 // - instead of len, calculate end.
526 // - only do '-' check if pos != end
527 // - check pos against end in the while loop and just incr pos
528
mcmc_tokto32(const char * t,size_t len,int32_t * out)529 MCMC_STATIC int mcmc_tokto32(const char *t, size_t len, int32_t *out) {
530 int32_t sum = 0;
531 const char *pos = t;
532 int is_sig = 0;
533 if (len > MCMC_TOKTO32_MAX) {
534 return MCMC_TOKTO_ELONG;
535 }
536 // If we're negative the first character must be -
537 if (pos[0] == '-') {
538 len--;
539 pos++;
540 is_sig = 1;
541 }
542 while (len--) {
543 char num = pos[0] - '0';
544 if (num > -1 && num < 10) {
545 if (is_sig) {
546 int32_t lim = (INT32_MIN + num) / 10;
547 if (sum < lim) {
548 return MCMC_TOKTO_ERANGE;
549 }
550 sum = sum * 10 - num;
551 } else {
552 int32_t lim = (INT32_MAX - num) / 10;
553 if (sum > lim) {
554 return MCMC_TOKTO_ERANGE;
555 }
556 sum = sum * 10 + num;
557 }
558 } else {
559 return MCMC_TOKTO_EINVALID;
560 }
561 pos++;
562 }
563 *out = sum;
564 return MCMC_OK;
565 }
566
mcmc_tokto64(const char * t,size_t len,int64_t * out)567 MCMC_STATIC int mcmc_tokto64(const char *t, size_t len, int64_t *out) {
568 int64_t sum = 0;
569 const char *pos = t;
570 int is_sig = 0;
571 if (len > MCMC_TOKTO64_MAX) {
572 return MCMC_TOKTO_ELONG;
573 }
574 // If we're negative the first character must be -
575 if (pos[0] == '-') {
576 len--;
577 pos++;
578 is_sig = 1;
579 }
580 while (len--) {
581 char num = pos[0] - '0';
582 if (num > -1 && num < 10) {
583 if (is_sig) {
584 int64_t lim = (INT64_MIN + num) / 10;
585 if (sum < lim) {
586 return MCMC_TOKTO_ERANGE;
587 }
588 sum = sum * 10 - num;
589 } else {
590 int64_t lim = (INT64_MAX - num) / 10;
591 if (sum > lim) {
592 return MCMC_TOKTO_ERANGE;
593 }
594 sum = sum * 10 + num;
595 }
596 } else {
597 return MCMC_TOKTO_EINVALID;
598 }
599 pos++;
600 }
601 *out = sum;
602 return MCMC_OK;
603 }
604
605 // END INTERNAL FUNCTIONS
606
607 // Context-less bare API.
608
609 //MCMC_STATIC int _mcmc_tokenize_meta(mcmc_tokenizer_t *t, const char *line, size_t len, const int mstart, const int max) {
610
611 // TODO: ideally this does a macro test of t->ntokens before calling a
612 // function. are compilers smart enough that I don't have to do this tho?
mcmc_tokenize_res(const char * l,size_t len,mcmc_tokenizer_t * t)613 int mcmc_tokenize_res(const char *l, size_t len, mcmc_tokenizer_t *t) {
614 if (t->ntokens == 0) {
615 return _mcmc_tokenize_meta(t, l, len, 1, MCMC_PARSER_MAX_TOKENS-1);
616 }
617 return 0;
618 }
619
mcmc_token_get(const char * l,mcmc_tokenizer_t * t,int idx,int * len)620 const char *mcmc_token_get(const char *l, mcmc_tokenizer_t *t, int idx, int *len) {
621 if (idx > 0 && idx < t->ntokens) {
622 return _mcmc_token(l, t, idx, len);
623 } else {
624 return NULL;
625 }
626 }
627
628 #define X(n, p, c) \
629 int n(const char *l, mcmc_tokenizer_t *t, int idx, p *val) { \
630 if (idx > 0 && idx < t->ntokens) { \
631 int tlen = 0; \
632 const char *tok = _mcmc_token(l, t, idx, &tlen); \
633 return c(tok, tlen, val); \
634 } else { \
635 return MCMC_ERR; \
636 } \
637 }
638
X(mcmc_token_get_u32,uint32_t,mcmc_toktou32)639 X(mcmc_token_get_u32, uint32_t, mcmc_toktou32)
640 X(mcmc_token_get_u64, uint64_t, mcmc_toktou64)
641 X(mcmc_token_get_32, int32_t, mcmc_tokto32)
642 X(mcmc_token_get_64, int64_t, mcmc_tokto64)
643
644 #undef X
645
646 int mcmc_token_has_flag(const char *l, mcmc_tokenizer_t *t, char flag) {
647 if (flag < 65 || flag > 122) {
648 return MCMC_ERR;
649 }
650 uint64_t flagbit = (uint64_t)1 << (flag-65);
651 if (t->metaflags & flagbit) {
652 return MCMC_OK;
653 } else {
654 return MCMC_NOK;
655 }
656 }
657
658 // User can optionally call has_flag or has_flag_bit before calling these
659 // functions. Is optional because it can be either wasteful to check if the
660 // flag will always be there, or it's useful to the caller to know the flag
661 // exists but there wasn't a token attached or we failed to parse it.
mcmc_token_get_flag(const char * l,mcmc_tokenizer_t * t,char flag,int * len)662 const char *mcmc_token_get_flag(const char *l, mcmc_tokenizer_t *t, char flag, int *len) {
663 for (int x = t->mstart; x < t->ntokens; x++) {
664 const char *tflag = l + t->tokens[x];
665 if (tflag[0] == flag) {
666 int tlen = _mcmc_token_len(l, t, x) - 1; // subtract the flag.
667 // ensure the flag actually has a token.
668 if (tlen > 0) {
669 if (len) {
670 *len = tlen;
671 }
672 return tflag+1;
673 } else {
674 break;
675 }
676 }
677 }
678
679 return NULL;
680 }
681
mcmc_token_get_flag_u32(const char * l,mcmc_tokenizer_t * t,char flag,uint32_t * val)682 int mcmc_token_get_flag_u32(const char *l, mcmc_tokenizer_t *t, char flag, uint32_t *val) {
683 int tlen = 0;
684 const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
685 if (tok) {
686 return mcmc_toktou32(tok, tlen, val);
687 }
688
689 return MCMC_NOK;
690 }
691
mcmc_token_get_flag_u64(const char * l,mcmc_tokenizer_t * t,char flag,uint64_t * val)692 int mcmc_token_get_flag_u64(const char *l, mcmc_tokenizer_t *t, char flag, uint64_t *val) {
693 int tlen = 0;
694 const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
695 if (tok) {
696 return mcmc_toktou64(tok, tlen, val);
697 }
698
699 return MCMC_NOK;
700 }
701
mcmc_token_get_flag_32(const char * l,mcmc_tokenizer_t * t,char flag,int32_t * val)702 int mcmc_token_get_flag_32(const char *l, mcmc_tokenizer_t *t, char flag, int32_t *val) {
703 int tlen = 0;
704 const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
705 if (tok) {
706 return mcmc_tokto32(tok, tlen, val);
707 }
708
709 return MCMC_NOK;
710 }
711
mcmc_token_get_flag_64(const char * l,mcmc_tokenizer_t * t,char flag,int64_t * val)712 int mcmc_token_get_flag_64(const char *l, mcmc_tokenizer_t *t, char flag, int64_t *val) {
713 int tlen = 0;
714 const char *tok = mcmc_token_get_flag(l, t, flag, &tlen);
715 if (tok) {
716 return mcmc_tokto64(tok, tlen, val);
717 }
718
719 return MCMC_NOK;
720 }
721
mcmc_token_get_flag_idx(const char * l,mcmc_tokenizer_t * t,char flag)722 int mcmc_token_get_flag_idx(const char *l, mcmc_tokenizer_t *t, char flag) {
723 for (int x = t->mstart; x < t->ntokens; x++) {
724 const char *tflag = l + t->tokens[x];
725 if (tflag[0] == flag) {
726 return x;
727 }
728 }
729
730 return -1;
731 }
732
733 // Directly parse a buffer with read data of size len.
734 // r->reslen + r->vlen_read is the bytes consumed from the buffer read.
735 // Caller manages how to retry if MCMC_WANT_READ or an error happens.
mcmc_parse_buf(const char * buf,size_t read,mcmc_resp_t * r)736 int mcmc_parse_buf(const char *buf, size_t read, mcmc_resp_t *r) {
737 char *el;
738
739 memset(r, 0, sizeof(*r));
740 el = memchr(buf, '\n', read);
741 if (el == NULL) {
742 r->code = MCMC_WANT_READ;
743 return MCMC_ERR;
744 }
745
746 // Consume through the newline, note where the value would start if exists
747 r->value = el+1;
748 r->reslen = r->value - buf;
749
750 // FIXME: the server must be stricter in what it sends back. should always
751 // have a \r. check for it and fail?
752
753 return _mcmc_parse_response(buf, read, r);
754 }
755
756 // Context-ful API.
757
mcmc_fd(void * c)758 int mcmc_fd(void *c) {
759 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
760 return ctx->fd;
761 }
762
mcmc_size(int options)763 size_t mcmc_size(int options) {
764 return sizeof(mcmc_ctx_t);
765 }
766
767 // Allow returning this dynamically based on options set.
768 // FIXME: it might be more flexible to call this after mcmc_connect()...
769 // but this is probably more convenient for the caller if it's less dynamic.
mcmc_min_buffer_size(int options)770 size_t mcmc_min_buffer_size(int options) {
771 return MIN_BUFFER_SIZE;
772 }
773
774 /*** Functions wrapping syscalls **/
775
776 // TODO: should be able to flip between block and nonblock.
777
778 // used for checking on async connections.
mcmc_check_nonblock_connect(void * c,int * err)779 int mcmc_check_nonblock_connect(void *c, int *err) {
780 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
781 socklen_t errsize = sizeof(*err);
782 if (getsockopt(ctx->fd, SOL_SOCKET, SO_ERROR, err, &errsize) == 0) {
783 if (*err == 0) {
784 return MCMC_OK;
785 }
786 } else {
787 // getsockopt failed. still need to pass up the error.
788 *err = errno;
789 }
790
791 return MCMC_ERR;
792 }
793
794 // TODO:
795 // - option for connecting 4 -> 6 or 6 -> 4
796 // connect_unix()
797 // connect_bind_tcp()
798 // ^ fill an internal struct from the stack and call into this central
799 // connect?
mcmc_connect(void * c,char * host,char * port,int options)800 int mcmc_connect(void *c, char *host, char *port, int options) {
801 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
802
803 int s;
804 int sock;
805 int res = MCMC_CONNECTED;
806 struct addrinfo hints;
807 struct addrinfo *ai = NULL;
808 struct addrinfo *next = NULL;
809
810 // Since our cx memory was likely malloc'ed, ensure we start clear.
811 memset(ctx, 0, sizeof(mcmc_ctx_t));
812 memset(&hints, 0, sizeof(hints));
813 hints.ai_family = AF_INET;
814 hints.ai_socktype = SOCK_STREAM;
815 ctx->fd = 0;
816
817 s = getaddrinfo(host, port, &hints, &ai);
818
819 if (s != 0) {
820 hints.ai_family = AF_INET6;
821 s = getaddrinfo(host, port, &hints, &ai);
822 if (s != 0) {
823 // TODO: gai_strerror(s)
824 ctx->gai_status = s;
825 res = MCMC_ERR;
826 goto end;
827 }
828 }
829
830 for (next = ai; next != NULL; next = next->ai_next) {
831 sock = socket(next->ai_family, next->ai_socktype,
832 next->ai_protocol);
833 if (sock == -1)
834 continue;
835
836 if (options & MCMC_OPTION_TCP_KEEPALIVE) {
837 int optval = 1;
838 if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) < 0) {
839 res = MCMC_ERR;
840 close(sock);
841 goto end;
842 }
843 }
844
845 if (options & MCMC_OPTION_NONBLOCK) {
846 int flags = fcntl(sock, F_GETFL);
847 if (flags < 0) {
848 res = MCMC_ERR;
849 close(sock);
850 goto end;
851 }
852 if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
853 res = MCMC_ERR;
854 close(sock);
855 goto end;
856 }
857 res = MCMC_CONNECTING;
858
859 if (connect(sock, next->ai_addr, next->ai_addrlen) != -1) {
860 if (errno == EINPROGRESS) {
861 break; // We're good, stop the loop.
862 }
863 }
864
865 break;
866 } else {
867 // TODO: BIND local port.
868 if (connect(sock, next->ai_addr, next->ai_addrlen) != -1)
869 break;
870 }
871
872 close(sock);
873 }
874
875 // TODO: cache last connect status code?
876 if (next == NULL) {
877 res = MCMC_ERR;
878 goto end;
879 }
880
881 ctx->fd = sock;
882 end:
883 if (ai) {
884 freeaddrinfo(ai);
885 }
886 return res;
887 }
888
889 // NOTE: if WANT_WRITE returned, call with same arguments.
890 // FIXME: len -> size_t?
891 // TODO: rename to mcmc_request_send
mcmc_send_request(void * c,const char * request,int len,int count)892 int mcmc_send_request(void *c, const char *request, int len, int count) {
893 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
894
895 // adjust our send buffer by how much has already been sent.
896 const char *r = request + ctx->sent_bytes_partial;
897 int l = len - ctx->sent_bytes_partial;
898 int sent = send(ctx->fd, r, l, 0);
899 if (sent == -1) {
900 // implicitly handle nonblock mode.
901 if (errno == EAGAIN || errno == EWOULDBLOCK) {
902 return MCMC_WANT_WRITE;
903 } else {
904 return MCMC_ERR;
905 }
906 }
907
908 if (sent < len) {
909 // can happen anytime, but mostly in nonblocking mode.
910 ctx->sent_bytes_partial += sent;
911 return MCMC_WANT_WRITE;
912 } else {
913 ctx->sent_bytes_partial = 0;
914 }
915
916 return MCMC_OK;
917 }
918
919 // TODO: pretty sure I don't want this function chewing on a submitted iov
920 // stack, but it might make for less client code :(
921 // so for now, lets not.
mcmc_request_writev(void * c,const struct iovec * iov,int iovcnt,ssize_t * sent,int count)922 int mcmc_request_writev(void *c, const struct iovec *iov, int iovcnt, ssize_t *sent, int count) {
923 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
924 // need to track sent vs tosend to know when to update counters.
925 ssize_t tosend = 0;
926 for (int i = 0; i < iovcnt; i++) {
927 tosend += iov[i].iov_len;
928 }
929
930 *sent = writev(ctx->fd, iov, iovcnt);
931 if (*sent == -1) {
932 // implicitly handle nonblock mode.
933 if (errno == EAGAIN || errno == EWOULDBLOCK) {
934 return MCMC_WANT_WRITE;
935 } else {
936 return MCMC_ERR;
937 }
938 }
939
940 if (*sent < tosend) {
941 // can happen anytime, but mostly in nonblocking mode.
942 return MCMC_WANT_WRITE;
943 }
944
945 return MCMC_OK;
946 }
947
948 // TODO: return consumed bytes and end ptr?
949 // FIXME: mcmc no longer tracks the buffer inbetween commands, which was
950 // causing issues with the API and bugs.
951 // This function wraps the recv call, so it needs to understand the buffer a
952 // little bit. Since memcached doesn't currently use this function I'm
953 // commenting it out with this note so it can be rewritten in terms of an
954 // external buffer later.
955 /*
956 int mcmc_read(void *c, char *buf, size_t bufsize, mcmc_resp_t *r) {
957 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
958 char *el;
959 memset(r, 0, sizeof(*r));
960
961 // If there's still data in the buffer try to use it before potentially
962 // hanging on the network read.
963 // Also skip this check if we specifically wanted more bytes from net.
964 if (ctx->buffer_used && !(ctx->status_flags & FLAG_BUF_WANTED_READ)) {
965 el = memchr(buf, '\n', ctx->buffer_used);
966 if (el) {
967 goto parse;
968 }
969 }
970
971 // adjust buffer by how far we've already consumed.
972 char *b = buf + ctx->buffer_used;
973 size_t l = bufsize - ctx->buffer_used;
974
975 int read = recv(ctx->fd, b, l, 0);
976 if (read == 0) {
977 return MCMC_NOT_CONNECTED;
978 } else if (read == -1) {
979 // implicitly handle nonblocking configurations.
980 if (errno == EAGAIN || errno == EWOULDBLOCK) {
981 return MCMC_WANT_READ;
982 } else {
983 return MCMC_ERR;
984 }
985 }
986
987 ctx->buffer_used += read;
988
989 // Always scan from the start of the original buffer.
990 el = memchr(buf, '\n', ctx->buffer_used);
991 if (!el) {
992 // FIXME: error if buffer is full but no \n is found.
993 ctx->status_flags |= FLAG_BUF_WANTED_READ;
994 return MCMC_WANT_READ;
995 }
996 parse:
997 // Consume through the newline.
998 r->value = el+1;
999
1000 // FIXME: the server must be stricter in what it sends back. should always
1001 // have a \r. check for it and fail?
1002 ctx->buffer_request_len = r->value - buf;
1003 // leave the \r\n in the line end cache.
1004 ctx->buffer_head = buf;
1005 // TODO: handling for nonblock case.
1006
1007 // We have a result line. Now pass it through the parser.
1008 // Then we indicate to the user that a response is ready.
1009 return _mcmc_parse_response(ctx, r);
1010 }
1011 */
1012
mcmc_get_error(void * c,char * code,size_t clen,char * msg,size_t mlen)1013 void mcmc_get_error(void *c, char *code, size_t clen, char *msg, size_t mlen) {
1014 code[0] = '\0';
1015 msg[0] = '\0';
1016 }
1017
1018 // read into the buffer, up to a max size of vsize.
1019 // will read (vsize-read) into the buffer pointed to by (val+read).
1020 // you are able to stream the value into different buffers, or process the
1021 // value and reuse the same buffer, by adjusting vsize and *read between
1022 // calls.
1023 // vsize must not be larger than the remaining value size pending read.
1024 /* TODO: see notes on mcmc_read()
1025 int mcmc_read_value(void *c, char *val, mcmc_resp_t *r, int *read) {
1026 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
1027 size_t l;
1028
1029 // If the distance between tail/head is smaller than what we read into the
1030 // main buffer, we have some value to copy out.
1031 size_t vsize = r->vlen;
1032 if (*read < r->vlen_read) {
1033 memcpy(val + *read, r->value, r->vlen_read);
1034 *read += r->vlen_read;
1035 if (r->vlen_read >= r->vlen) {
1036 return MCMC_OK;
1037 }
1038 }
1039
1040 char *v = val + *read;
1041 l = vsize - *read;
1042
1043 int rd = recv(ctx->fd, v, l, 0);
1044 if (rd == 0) {
1045 // TODO: some internal disconnect work?
1046 return MCMC_NOT_CONNECTED;
1047 }
1048 // FIXME: EAGAIN || EWOULDBLOCK!
1049 if (rd == -1) {
1050 return MCMC_ERR;
1051 }
1052
1053 *read += rd;
1054
1055 if (*read < vsize) {
1056 return MCMC_WANT_READ;
1057 } else {
1058 return MCMC_OK;
1059 }
1060 }
1061 */
1062
mcmc_disconnect(void * c)1063 int mcmc_disconnect(void *c) {
1064 mcmc_ctx_t *ctx = (mcmc_ctx_t *)c;
1065
1066 // FIXME: I forget if 0 can be valid.
1067 if (ctx->fd != 0) {
1068 close(ctx->fd);
1069 ctx->fd = 0;
1070 return MCMC_OK;
1071 } else {
1072 return MCMC_NOT_CONNECTED;
1073 }
1074 }
1075