summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/protocol.txt12
-rw-r--r--proto_text.c51
-rw-r--r--t/metaget.t53
3 files changed, 60 insertions, 56 deletions
diff --git a/doc/protocol.txt b/doc/protocol.txt
index 49c12d8..bbaabef 100644
--- a/doc/protocol.txt
+++ b/doc/protocol.txt
@@ -96,17 +96,20 @@ noted in the 'Commands' section above.
All meta commands follow a basic syntax:
-<cm> <key> <flag1> <flag2> <...>\r\n
+<cm> <key> <datalen*> <flag1> <flag2> <...>\r\n
Where <cm> is a 2 character command code.
+<datalen> is only for commands with payloads, which is set.
Responses look like:
-<RC> <flag1> <flag2> <...>\r\n
+<RC> <datalen*> <flag1> <flag2> <...>\r\n
Where <RC> is a 2 character return code. The number of flags returned are
based off of the flags supplied.
+<datalen> is only for responses with payloads, with the return code 'VA'.
+
Flags are single character codes, ie 'q' or 'k' or 'I', which adjust the
behavior of the command. The flags are reflected in the response. The order of
which tokens are consumed or returned depend on the order of the flags given.
@@ -671,10 +674,12 @@ The meta set command a generic command for storing data to memcached. Based
on the flags supplied, it can replace all storage commands (see token M) as
well as adds new options.
-ms <key> <flags>*\r\n
+ms <key> <datalen> <flags>*\r\n
- <key> means one key string.
+- <datalen> is the length of the payload data.
+
- <flags> are a set of single character codes ended with a space or newline.
flags may have strings after the initial character.
@@ -713,7 +718,6 @@ The flags used by the 'ms' command are:
- k: return key as a token
- O(token): opaque value, consumes a token and copies back with response
- q: use noreply semantics for return codes
-- S(token): size of <data block> to store
- T(token): Time-To-Live for item, see "Expiration" above.
- M(token): mode switch to change behavior to add, replace, append, prepend
diff --git a/proto_text.c b/proto_text.c
index 8cbd216..f572a7a 100644
--- a/proto_text.c
+++ b/proto_text.c
@@ -900,21 +900,20 @@ struct _meta_flags {
rel_time_t exptime;
rel_time_t autoviv_exptime;
rel_time_t recache_time;
- int32_t value_len;
uint32_t client_flags;
uint64_t req_cas_id;
uint64_t delta; // ma
uint64_t initial; // ma
};
-static int _meta_flag_preparse(token_t *tokens, const size_t ntokens,
+static int _meta_flag_preparse(token_t *tokens, const size_t start,
struct _meta_flags *of, char **errstr) {
unsigned int i;
size_t ret;
int32_t tmp_int;
uint8_t seen[127] = {0};
// Start just past the key token. Look at first character of each token.
- for (i = KEY_TOKEN+1; i < ntokens-1; i++) {
+ for (i = start; tokens[i].length != 0; i++) {
uint8_t o = (uint8_t)tokens[i].value[0];
// zero out repeat flags so we don't over-parse for return data.
if (o >= 127 || seen[o] != 0) {
@@ -998,20 +997,6 @@ static int _meta_flag_preparse(token_t *tokens, const size_t ntokens,
of->has_error = true;
}
break;
- case 'S':
- if (!safe_strtol(tokens[i].value+1, &tmp_int)) {
- of->has_error = true;
- } else {
- // Size is adjusted for underflow or overflow once the
- // \r\n terminator is added.
- if (tmp_int < 0 || tmp_int > (INT_MAX - 2)) {
- *errstr = "CLIENT_ERROR invalid length";
- of->has_error = true;
- } else {
- of->value_len = tmp_int + 2; // \r\n
- }
- }
- break;
case 'C': // mset, mdelete, marithmetic
if (!safe_strtoull(tokens[i].value+1, &of->req_cas_id)) {
*errstr = "CLIENT_ERROR bad token in command line format";
@@ -1090,7 +1075,8 @@ static void process_mget_command(conn *c, token_t *tokens, const size_t ntokens)
}
// scrubs duplicated options and sets flags for how to load the item.
- if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+ // we pass in the first token that should be a flag.
+ if (_meta_flag_preparse(tokens, 2, &of, &errstr) != 0) {
out_errstring(c, errstr);
return;
}
@@ -1358,7 +1344,8 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
short comm = NREAD_SET;
struct _meta_flags of = {0}; // option bitflags.
char *errstr = "CLIENT_ERROR bad command line format";
- uint32_t hv;
+ uint32_t hv; // cached hash value.
+ int vlen = 0; // value from data line.
mc_resp *resp = c->resp;
char *p = resp->wbuf;
@@ -1385,9 +1372,21 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
// final buffer in complete_nread_ascii.
p = resp->wbuf;
+ if (!safe_strtol(tokens[KEY_TOKEN + 1].value, (int32_t*)&vlen)) {
+ out_errstring(c, "CLIENT_ERROR bad command line format");
+ return;
+ }
+
+ if (vlen < 0 || vlen > (INT_MAX - 2)) {
+ out_errstring(c, "CLIENT_ERROR bad command line format");
+ return;
+ }
+ vlen += 2;
+
// We need to at least try to get the size to properly slurp bad bytes
// after an error.
- if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+ // we pass in the first token that should be a flag.
+ if (_meta_flag_preparse(tokens, 3, &of, &errstr) != 0) {
goto error;
}
@@ -1459,13 +1458,13 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
if (has_error)
goto error;
- it = item_alloc(key, nkey, of.client_flags, of.exptime, of.value_len);
+ it = item_alloc(key, nkey, of.client_flags, of.exptime, vlen);
if (it == 0) {
enum store_item_type status;
// TODO: These could be normalized codes (TL and OM). Need to
// reorganize the output stuff a bit though.
- if (! item_size_ok(nkey, of.client_flags, of.value_len)) {
+ if (! item_size_ok(nkey, of.client_flags, vlen)) {
errstr = "SERVER_ERROR object too large for cache";
status = TOO_LARGE;
} else {
@@ -1519,7 +1518,7 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
return;
error:
/* swallow the data line */
- c->sbytes = of.value_len;
+ c->sbytes = vlen;
// Note: no errors possible after the item was successfully allocated.
// So we're just looking at dumping error codes and returning.
@@ -1556,8 +1555,9 @@ static void process_mdelete_command(conn *c, token_t *tokens, const size_t ntoke
}
// scrubs duplicated options and sets flags for how to load the item.
+ // we pass in the first token that should be a flag.
// FIXME: not using the preparse errstr?
- if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+ if (_meta_flag_preparse(tokens, 2, &of, &errstr) != 0) {
out_errstring(c, "CLIENT_ERROR invalid or duplicate flag");
return;
}
@@ -1685,7 +1685,8 @@ static void process_marithmetic_command(conn *c, token_t *tokens, const size_t n
}
// scrubs duplicated options and sets flags for how to load the item.
- if (_meta_flag_preparse(tokens, ntokens, &of, &errstr) != 0) {
+ // we pass in the first token that should be a flag.
+ if (_meta_flag_preparse(tokens, 2, &of, &errstr) != 0) {
out_errstring(c, "CLIENT_ERROR invalid or duplicate flag");
return;
}
diff --git a/t/metaget.t b/t/metaget.t
index a9c3dfe..3293d9e 100644
--- a/t/metaget.t
+++ b/t/metaget.t
@@ -43,7 +43,7 @@ my $sock = $server->sock;
# - X: object is stale
# - Z: object has sent a winning token
#
-# ms [key] [flags]\r\n
+# ms [key] [valuelen] [flags]\r\n
# value\r\n
# response:
# OK [flags]\r\n
@@ -53,7 +53,6 @@ my $sock = $server->sock;
# - q: noreply
# - F(token): set client flags
# - C(token): compare CAS value
-# - S(token): item size
# - T(token): TTL
# - O(token): opaque to copy back.
# - k: return key
@@ -152,7 +151,7 @@ my $sock = $server->sock;
diag "encoded binary keys";
# 44OG44K544OI is "tesuto" in katakana
my $tesuto = "44OG44K544OI";
- print $sock "ms $tesuto S2 b\r\npo\r\n";
+ print $sock "ms $tesuto 2 b\r\npo\r\n";
like(scalar <$sock>, qr/^OK/, "set with encoded key");
my $res = mget($sock, $tesuto, 'v');
@@ -235,7 +234,7 @@ my $sock = $server->sock;
{
diag "mset mode switch";
- print $sock "ms modedefault S2 T120\r\naa\r\n";
+ print $sock "ms modedefault 2 T120\r\naa\r\n";
like(scalar <$sock>, qr/^OK/, "default set mode");
mget_is({ sock => $sock,
flags => 's v',
@@ -243,10 +242,10 @@ my $sock = $server->sock;
'modedefault', 'aa', "retrieved test value");
# Fail an add
- print $sock "ms modedefault S2 T120 ME\r\naa\r\n";
+ print $sock "ms modedefault 2 T120 ME\r\naa\r\n";
like(scalar <$sock>, qr/^NS/, "add mode gets NOT_STORED");
# Win an add
- print $sock "ms modetest S2 T120 ME\r\nbb\r\n";
+ print $sock "ms modetest 2 T120 ME\r\nbb\r\n";
like(scalar <$sock>, qr/^OK/, "add mode");
mget_is({ sock => $sock,
flags => 's v',
@@ -254,14 +253,14 @@ my $sock = $server->sock;
'modetest', 'bb', "retrieved test value");
# append
- print $sock "ms modetest S2 T120 MA\r\ncc\r\n";
+ print $sock "ms modetest 2 T120 MA\r\ncc\r\n";
like(scalar <$sock>, qr/^OK/, "append mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's4' },
'modetest', 'bbcc', "retrieved test value");
# prepend
- print $sock "ms modetest S2 T120 MP\r\naa\r\n";
+ print $sock "ms modetest 2 T120 MP\r\naa\r\n";
like(scalar <$sock>, qr/^OK/, "append mode");
mget_is({ sock => $sock,
flags => 's v',
@@ -269,9 +268,9 @@ my $sock = $server->sock;
'modetest', 'aabbcc', "retrieved test value");
# replace
- print $sock "ms modereplace S2 T120 MR\r\nzz\r\n";
+ print $sock "ms modereplace 2 T120 MR\r\nzz\r\n";
like(scalar <$sock>, qr/^NS/, "fail replace mode");
- print $sock "ms modetest S2 T120 MR\r\nxx\r\n";
+ print $sock "ms modetest 2 T120 MR\r\nxx\r\n";
like(scalar <$sock>, qr/^OK/, "replace mode");
mget_is({ sock => $sock,
flags => 's v',
@@ -279,11 +278,11 @@ my $sock = $server->sock;
'modetest', 'xx', "retrieved test value");
# explicit set
- print $sock "ms modetest S2 T120 MS\r\nyy\r\n";
+ print $sock "ms modetest 2 T120 MS\r\nyy\r\n";
like(scalar <$sock>, qr/^OK/, "force set mode");
# invalid mode
- print $sock "ms modetest S2 T120 MZ\r\ntt\r\n";
+ print $sock "ms modetest 2 T120 MZ\r\ntt\r\n";
like(scalar <$sock>, qr/^CLIENT_ERROR /, "invalid mode");
}
@@ -315,13 +314,13 @@ my $sock = $server->sock;
}
# set back with the wrong CAS
- print $sock "ms needwin C5000 S2 T120\r\nnu\r\n";
+ print $sock "ms needwin 2 C5000 T120\r\nnu\r\n";
like(scalar <$sock>, qr/^EX/, "failed to SET: CAS didn't match");
# again, but succeed.
# TODO: the actual CAS command should work here too?
my $cas = get_flag($res, 'c');
- print $sock "ms needwin C$cas S2 T120\r\nmu\r\n";
+ print $sock "ms needwin 2 C$cas T120\r\nmu\r\n";
like(scalar <$sock>, qr/^OK/, "SET: CAS matched");
# now we repeat the original mget, but the data should be different.
@@ -353,7 +352,7 @@ my $sock = $server->sock;
# again, but succeed.
$cas = get_flag($res, 'c');
- print $sock "ms needwin C$cas S4 T300\r\nzuuu\r\n";
+ print $sock "ms needwin 4 C$cas T300\r\nzuuu\r\n";
like(scalar <$sock>, qr/^OK/, "SET: CAS matched");
# now we repeat the original mget, but the data should be different.
@@ -371,7 +370,7 @@ my $sock = $server->sock;
# test get-and-touch mode
{
# Set key with lower initial TTL.
- print $sock "ms gatkey S4 T100\r\nooom\r\n";
+ print $sock "ms gatkey 4 T100\r\nooom\r\n";
like(scalar <$sock>, qr/^OK/, "set gatkey");
# Coolish side feature and/or bringer of bugs: 't' before 'T' gives TTL
@@ -387,7 +386,7 @@ my $sock = $server->sock;
# test no-value mode
{
# Set key with lower initial TTL.
- print $sock "ms hidevalue S4 T100\r\nhide\r\n";
+ print $sock "ms hidevalue 4 T100\r\nhide\r\n";
like(scalar <$sock>, qr/^OK/, "set hidevalue");
my $res = mget($sock, 'hidevalue', 's t');
@@ -401,7 +400,7 @@ my $sock = $server->sock;
# test hit-before? flag
{
- print $sock "ms hitflag S3 T100\r\nhit\r\n";
+ print $sock "ms hitflag 3 T100\r\nhit\r\n";
like(scalar <$sock>, qr/^OK/, "set hitflag");
my $res = mget($sock, 'hitflag', 's t h');
@@ -415,7 +414,7 @@ my $sock = $server->sock;
# test no-update flag
{
- print $sock "ms noupdate S3 T100\r\nhit\r\n";
+ print $sock "ms noupdate 3 T100\r\nhit\r\n";
like(scalar <$sock>, qr/^OK/, "set noupdate");
my $res = mget($sock, 'noupdate', 's t u h');
@@ -433,7 +432,7 @@ my $sock = $server->sock;
# test last-access time
{
- print $sock "ms la_test S2 T100\r\nla\r\n";
+ print $sock "ms la_test 2 T100\r\nla\r\n";
like(scalar <$sock>, qr/^OK/, "set la_test");
sleep 2;
@@ -482,10 +481,10 @@ my $sock = $server->sock;
is($res->{val}, "moo", "value matches");
diag "trying to fail then stale set via mset";
- print $sock "ms toinv S1 T90 C0\r\nf\r\n";
+ print $sock "ms toinv 1 T90 C0\r\nf\r\n";
like(scalar <$sock>, qr/^EX/, "failed to SET: low CAS didn't match");
- print $sock "ms toinv S1 I T90 C1\r\nf\r\n";
+ print $sock "ms toinv 1 I T90 C1\r\nf\r\n";
like(scalar <$sock>, qr/^OK/, "SET an invalid/stale item");
diag "confirm item still stale, and TTL wasn't raised.";
@@ -500,7 +499,7 @@ my $sock = $server->sock;
diag "do valid mset";
$cas = get_flag($res, 'c');
- print $sock "ms toinv S1 T90 C$cas\r\ng\r\n";
+ print $sock "ms toinv 1 T90 C$cas\r\ng\r\n";
like(scalar <$sock>, qr/^OK/, "SET over the stale item");
$res = mget($sock, 'toinv', 's t c v');
@@ -519,7 +518,7 @@ my $sock = $server->sock;
# mget's with hits should return real data.
{
diag "testing quiet flag";
- print $sock "ms quiet q S2\r\nmo\r\n";
+ print $sock "ms quiet 2 q\r\nmo\r\n";
print $sock "md quiet q\r\n";
print $sock "mg quiet s v q\r\n";
diag "now purposefully cause an error\r\n";
@@ -527,7 +526,7 @@ my $sock = $server->sock;
like(scalar <$sock>, qr/^CLIENT_ERROR/, "resp not OK, or EN");
# Now try a pipelined get. Throw an mnop at the end
- print $sock "ms quiet q S2\r\nbo\r\n";
+ print $sock "ms quiet 2 q\r\nbo\r\n";
print $sock "mg quiet v q\r\nmg quiet v q\r\nmg quietmiss v q\r\nmn\r\n";
# Should get back VA/data/VA/data/EN
like(scalar <$sock>, qr/^VA 2/, "get response");
@@ -545,7 +544,7 @@ my $sock = $server->sock;
{
my $k = 'otest';
diag "testing mget opaque";
- print $sock "ms $k S2 T100\r\nra\r\n";
+ print $sock "ms $k 2 T100\r\nra\r\n";
like(scalar <$sock>, qr/^OK/, "set $k");
my $res = mget($sock, $k, 't v Oopaque');
@@ -560,7 +559,7 @@ my $sock = $server->sock;
{
diag "pipeline test";
- print $sock "ms foo S2 T100\r\nna\r\n";
+ print $sock "ms foo 2 T100\r\nna\r\n";
like(scalar <$sock>, qr/^OK/, "set foo");
print $sock "mg foo s\r\nmg foo s\r\nquit\r\nmg foo s\r\n";
like(scalar <$sock>, qr/^OK /, "got resp");