diff --git a/src/box/box.cc b/src/box/box.cc index a3782415c77563fc86a314d34d1c8e781ebd47bf..b2a795b30c7ac136cb010014910d2224ea716bca 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -67,6 +67,7 @@ #include "gc.h" #include "checkpoint.h" #include "systemd.h" +#include "call.h" static char status[64] = "unknown"; diff --git a/src/box/box.h b/src/box/box.h index 5164054327c9ceb70617257c7137bc04c64ad071..460c064772d372b8df141c1aff94f6a40a73a4f4 100644 --- a/src/box/box.h +++ b/src/box/box.h @@ -154,11 +154,6 @@ void box_update_vinyl_options(void); extern "C" { #endif /* defined(__cplusplus) */ -struct box_function_ctx { - struct request *request; - struct port *port; -}; - typedef struct tuple box_tuple_t; /* box_select is private and used only by FFI */ diff --git a/src/box/call.cc b/src/box/call.cc index 286b34ca71ceffdbef8f93b8d0f9ceda5c9437a9..c6fd46c36d166cdcce70e76ccd47e67c65fc466b 100644 --- a/src/box/call.cc +++ b/src/box/call.cc @@ -43,6 +43,71 @@ #include "rmean.h" #include "small/obuf.h" +static const char empty_args[] = { (char)0x90 }; + +void +call_request_decode_xc(struct call_request *request, uint8_t type, + uint64_t sync, const char *data, uint32_t len) +{ + assert(type == IPROTO_CALL || type == IPROTO_CALL_16 || type == IPROTO_EVAL); + const char *end = data + len; + uint32_t map_size; + if (mp_typeof(*data) != MP_MAP || mp_check_map(data, end) > 0) + tnt_raise(ClientError, ER_INVALID_MSGPACK, "packet body"); + + map_size = mp_decode_map(&data); + request->name = NULL; + request->args = NULL; + request->args_end = NULL; + request->type = type; + request->sync = sync; + for (uint32_t i = 0; i < map_size; ++i) { + uint8_t key = *data; + if (key != IPROTO_EXPR && key != IPROTO_FUNCTION_NAME && + key != IPROTO_TUPLE) { + /* Skip key + value. */ + mp_check(&data, end); + mp_check(&data, end); + continue; + } + const char *value = ++data; + if (mp_check(&data, end) != 0) { + tnt_raise(ClientError, ER_INVALID_MSGPACK, + "packet body"); + } + switch(key) { + case IPROTO_EXPR: + request->expr = value; + break; + case IPROTO_FUNCTION_NAME: + request->name = value; + break; + case IPROTO_TUPLE: + request->args = value; + request->args_end = data; + break; + default: + unreachable(); + } + } + if (type == IPROTO_EVAL) { + if (request->expr == NULL) { + tnt_raise(ClientError, ER_MISSING_REQUEST_FIELD, + iproto_key_name(IPROTO_EXPR)); + } + } else if (request->name == NULL) { + assert(type == IPROTO_CALL_16 || type == IPROTO_CALL); + tnt_raise(ClientError, ER_MISSING_REQUEST_FIELD, + iproto_key_name(IPROTO_FUNCTION_NAME)); + } + if (request->args == NULL) { + request->args = empty_args; + request->args_end = empty_args + sizeof(empty_args); + } + if (data != end) + tnt_raise(ClientError, ER_INVALID_MSGPACK, "packet body"); +} + static inline struct func * access_check_func(const char *name, uint32_t name_len) { @@ -69,7 +134,7 @@ access_check_func(const char *name, uint32_t name_len) } static int -func_call(struct func *func, struct request *request, struct obuf *out) +func_call(struct func *func, struct call_request *request, struct obuf *out) { assert(func != NULL && func->def->language == FUNC_LANGUAGE_C); if (func->func == NULL) @@ -85,7 +150,7 @@ func_call(struct func *func, struct request *request, struct obuf *out) diag_clear(&fiber()->diag); assert(!in_txn()); /* transaction is not started */ /* Call function from the shared library */ - int rc = func->func(&ctx, request->tuple, request->tuple_end); + int rc = func->func(&ctx, request->args, request->args_end); if (rc != 0) { if (diag_last_error(&fiber()->diag) == NULL) { /* Stored procedure forget to set diag */ @@ -105,7 +170,7 @@ func_call(struct func *func, struct request *request, struct obuf *out) obuf_rollback_to_svp(out, &svp); goto error; } - iproto_reply_select(out, &svp, request->header->sync, + iproto_reply_select(out, &svp, request->sync, ::schema_version, port.size); } else { assert(request->type == IPROTO_CALL); @@ -118,7 +183,7 @@ func_call(struct func *func, struct request *request, struct obuf *out) obuf_rollback_to_svp(out, &svp); goto error; } - iproto_reply_select(out, &svp, request->header->sync, + iproto_reply_select(out, &svp, request->sync, ::schema_version, 1); } @@ -130,13 +195,14 @@ func_call(struct func *func, struct request *request, struct obuf *out) } void -box_process_call(struct request *request, struct obuf *out) +box_process_call(struct call_request *request, struct obuf *out) { rmean_collect(rmean_box, IPROTO_CALL, 1); /** * Find the function definition and check access. */ - const char *name = request->key; + const char *name = request->name; + assert(name != NULL); uint32_t name_len = mp_decode_strl(&name); struct func *func = access_check_func(name, name_len); /* @@ -192,7 +258,7 @@ box_process_call(struct request *request, struct obuf *out) } void -box_process_eval(struct request *request, struct obuf *out) +box_process_eval(struct call_request *request, struct obuf *out) { rmean_collect(rmean_box, IPROTO_EVAL, 1); /* Check permissions */ @@ -204,7 +270,8 @@ box_process_eval(struct request *request, struct obuf *out) if (in_txn()) { /* The procedure forgot to call box.commit() */ - const char *expr = request->key; + const char *expr = request->expr; + assert(expr != NULL); uint32_t expr_len = mp_decode_strl(&expr); say_warn("a transaction is active at return from EVAL '%.*s'", expr_len, expr); diff --git a/src/box/call.h b/src/box/call.h index 5213de778ed27d0e11449a5419931f22884a15eb..1c1cc6c4ba44072d404ceac27f87c5d6cef58e08 100644 --- a/src/box/call.h +++ b/src/box/call.h @@ -31,13 +31,50 @@ * SUCH DAMAGE. */ -struct request; +#include <stdint.h> + struct obuf; +/** Request details of call and eval. */ +struct call_request { + /** + * Set either name to function name of call request or + * set expr to lua expression for eval request. + */ + union { + const char *name; + const char *expr; + }; + /** Call/eval parameters. */ + const char *args; + const char *args_end; + /** IPROTO_CALL/IPROTO_CALL_16/IPROTO_EVAL */ + uint8_t type; + /** Request sync. @sa xrow_header. */ + uint64_t sync; +}; + +struct box_function_ctx { + struct call_request *request; + struct port *port; +}; + +/** + * Decode call request from a given MessagePack map. + * @param[out] call_request Request to decode to. + * @param type Request type - either CALL or CALL_16 or EVAL. + * @param sync Request sync. + * @param data Request MessagePack encoded body. + * @param len @data length. + */ +void +call_request_decode_xc(struct call_request *request, uint8_t type, + uint64_t sync, const char *data, uint32_t len); + void -box_process_call(struct request *request, struct obuf *out); +box_process_call(struct call_request *request, struct obuf *out); void -box_process_eval(struct request *request, struct obuf *out); +box_process_eval(struct call_request *request, struct obuf *out); #endif /* INCLUDES_TARANTOOL_MOD_BOX_CALL_H */ diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 6c31a6772e54e316102559c184541edcd7fd7cb5..74c5a9e244aed0a2fc3de6cf868e196daa872c5e 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -76,8 +76,12 @@ struct iproto_msg: public cmsg /* --- Box msgs - actual requests for the transaction processor --- */ /* Request message code and sync. */ struct xrow_header header; - /* Box request, if this is a DML */ - struct request request; + union { + /* Box request, if this is a DML */ + struct request dml_request; + /* Box request, if this is misc (call, eval). */ + struct call_request call_request; + }; /* * Remember the active iobuf of the connection, * in which the request is stored. The response @@ -611,20 +615,20 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend, { xrow_header_decode_xc(&msg->header, pos, reqend); assert(*pos == reqend); - request_create(&msg->request, msg->header.type); - msg->request.header = &msg->header; + const char *body; + uint8_t type = msg->header.type; + uint64_t sync = msg->header.sync; - switch (msg->header.type) { + switch (type) { case IPROTO_SELECT: case IPROTO_INSERT: case IPROTO_REPLACE: case IPROTO_UPDATE: case IPROTO_DELETE: - case IPROTO_CALL_16: - case IPROTO_CALL: case IPROTO_AUTH: - case IPROTO_EVAL: case IPROTO_UPSERT: + request_create(&msg->dml_request, type); + msg->dml_request.header = &msg->header; /* * This is a common request which can be parsed with * request_decode(). Parse it before putting it into @@ -632,16 +636,24 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend, * requests are parsed in tx thread into request type- * specific objects. */ - if (msg->header.bodycnt == 0) { - tnt_raise(ClientError, ER_INVALID_MSGPACK, - "missing request body"); - } - request_decode_xc(&msg->request, - (const char *) msg->header.body[0].iov_base, - msg->header.body[0].iov_len, - request_key_map(msg->header.type)); - assert(msg->header.type < sizeof(dml_route)/sizeof(*dml_route)); - cmsg_init(msg, dml_route[msg->header.type]); + if (msg->header.bodycnt == 0) + goto err_no_body; + body = (const char *) msg->header.body[0].iov_base; + request_decode_xc(&msg->dml_request, body, + msg->header.body[0].iov_len, + request_key_map(type)); + assert(type < sizeof(dml_route)/sizeof(*dml_route)); + cmsg_init(msg, dml_route[type]); + break; + case IPROTO_CALL_16: + case IPROTO_CALL: + case IPROTO_EVAL: + if (msg->header.bodycnt == 0) + goto err_no_body; + body = (const char *) msg->header.body[0].iov_base; + call_request_decode_xc(&msg->call_request, type, sync, body, + msg->header.body[0].iov_len); + cmsg_init(msg, misc_route); break; case IPROTO_PING: cmsg_init(msg, misc_route); @@ -653,9 +665,12 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend, break; default: tnt_raise(ClientError, ER_UNKNOWN_REQUEST_TYPE, - (uint32_t) msg->header.type); + (uint32_t) type); break; } + return; +err_no_body: + tnt_raise(ClientError, ER_INVALID_MSGPACK, "missing request body"); } /** Enqueue all requests which were read up. */ @@ -940,7 +955,7 @@ tx_process1(struct cmsg *m) struct tuple *tuple; struct obuf_svp svp; - if (box_process1(&msg->request, &tuple) || + if (box_process1(&msg->dml_request, &tuple) || iproto_prepare_select(out, &svp)) goto error; if (tuple && tuple_to_obuf(tuple, out)) @@ -963,7 +978,7 @@ tx_process_select(struct cmsg *m) struct obuf_svp svp; struct port port; int rc; - struct request *req = &msg->request; + struct request *req = &msg->dml_request; tx_fiber_init(msg->connection->session, msg->header.sync); @@ -1009,16 +1024,16 @@ tx_process_misc(struct cmsg *m) switch (msg->header.type) { case IPROTO_CALL: case IPROTO_CALL_16: - assert(msg->request.type == msg->header.type); - box_process_call(&msg->request, out); + assert(msg->call_request.type == msg->header.type); + box_process_call(&msg->call_request, out); break; case IPROTO_EVAL: - assert(msg->request.type == msg->header.type); - box_process_eval(&msg->request, out); + assert(msg->call_request.type == msg->header.type); + box_process_eval(&msg->call_request, out); break; case IPROTO_AUTH: - assert(msg->request.type == msg->header.type); - box_process_auth(&msg->request, out); + assert(msg->dml_request.type == msg->header.type); + box_process_auth(&msg->dml_request, out); break; case IPROTO_PING: iproto_reply_ok_xc(out, msg->header.sync, diff --git a/src/box/lua/call.c b/src/box/lua/call.c index bfcab912f6f47af39d365dc8ed27cde388283163..62fc549b5d0a4237cc8318d173b56c6ec36af86e 100644 --- a/src/box/lua/call.c +++ b/src/box/lua/call.c @@ -29,6 +29,7 @@ * SUCH DAMAGE. */ #include "box/lua/call.h" +#include "box/call.h" #include "box/error.h" #include "fiber.h" @@ -243,7 +244,7 @@ luamp_encode_call(lua_State *L, struct luaL_serializer *cfg, } struct lua_function_ctx { - struct request *request; + struct call_request *request; struct obuf *out; struct obuf_svp svp; /* true if `out' was changed and `svp' can be used for rollback */ @@ -259,12 +260,12 @@ execute_lua_call(lua_State *L) { struct lua_function_ctx *ctx = (struct lua_function_ctx *) lua_topointer(L, 1); - struct request *request = ctx->request; + struct call_request *request = ctx->request; struct obuf *out = ctx->out; struct obuf_svp *svp = &ctx->svp; lua_settop(L, 0); /* clear the stack to simplify the logic below */ - const char *name = request->key; + const char *name = request->name; uint32_t name_len = mp_decode_strl(&name); int oc = 0; /* how many objects are on stack after box_lua_find */ @@ -272,7 +273,7 @@ execute_lua_call(lua_State *L) oc = box_lua_find(L, name, name + name_len); /* Push the rest of args (a tuple). */ - const char *args = request->tuple; + const char *args = request->args; uint32_t arg_count = mp_decode_array(&args); luaL_checkstack(L, arg_count, "call: out of stack"); @@ -322,8 +323,7 @@ execute_lua_call(lua_State *L) } mpstream_flush(&stream); - iproto_reply_select(out, svp, request->header->sync, schema_version, - count); + iproto_reply_select(out, svp, request->sync, schema_version, count); return 0; /* truncate Lua stack */ } @@ -332,13 +332,13 @@ execute_lua_eval(lua_State *L) { struct lua_function_ctx *ctx = (struct lua_function_ctx *) lua_topointer(L, 1); - struct request *request = ctx->request; + struct call_request *request = ctx->request; struct obuf *out = ctx->out; struct obuf_svp *svp = &ctx->svp; lua_settop(L, 0); /* clear the stack to simplify the logic below */ /* Compile expression */ - const char *expr = request->key; + const char *expr = request->expr; uint32_t expr_len = mp_decode_strl(&expr); if (luaL_loadbuffer(L, expr, expr_len, "=eval")) { diag_set(LuajitError, lua_tostring(L, -1)); @@ -346,7 +346,7 @@ execute_lua_eval(lua_State *L) } /* Unpack arguments */ - const char *args = request->tuple; + const char *args = request->args; uint32_t arg_count = mp_decode_array(&args); luaL_checkstack(L, arg_count, "eval: out of stack"); for (uint32_t i = 0; i < arg_count; i++) { @@ -368,14 +368,13 @@ execute_lua_eval(lua_State *L) luamp_encode(L, luaL_msgpack_default, &stream, k); } mpstream_flush(&stream); - iproto_reply_select(out, svp, request->header->sync, schema_version, - nrets); + iproto_reply_select(out, svp, request->sync, schema_version, nrets); return 0; } static inline int -box_process_lua(struct request *request, struct obuf *out, lua_CFunction handler) +box_process_lua(struct call_request *request, struct obuf *out, lua_CFunction handler) { struct lua_function_ctx ctx = { request, out, {0, 0, 0}, false }; @@ -402,13 +401,13 @@ box_process_lua(struct request *request, struct obuf *out, lua_CFunction handler } int -box_lua_call(struct request *request, struct obuf *out) +box_lua_call(struct call_request *request, struct obuf *out) { return box_process_lua(request, out, execute_lua_call); } int -box_lua_eval(struct request *request, struct obuf *out) +box_lua_eval(struct call_request *request, struct obuf *out) { return box_process_lua(request, out, execute_lua_eval); } diff --git a/src/box/lua/call.h b/src/box/lua/call.h index 4e1a3522dda847081a745980832ccf83bdbb4487..655ff893c682ffa1726fd339dc34f2cd0af72dec 100644 --- a/src/box/lua/call.h +++ b/src/box/lua/call.h @@ -43,7 +43,7 @@ struct lua_State; void box_lua_call_init(struct lua_State *L); -struct request; +struct call_request; struct obuf; /** @@ -51,10 +51,10 @@ struct obuf; * (implementation of 'CALL' command code). */ int -box_lua_call(struct request *request, struct obuf *out); +box_lua_call(struct call_request *request, struct obuf *out); int -box_lua_eval(struct request *request, struct obuf *out); +box_lua_eval(struct call_request *request, struct obuf *out); #if defined(__cplusplus) } /* extern "C" */ diff --git a/src/box/xrow.h b/src/box/xrow.h index ee1e9f09db3eeba706a70d40651707637c38692f..072bd41b69c64fc5c0c71932675076e329e31e49 100644 --- a/src/box/xrow.h +++ b/src/box/xrow.h @@ -254,7 +254,7 @@ struct request uint32_t offset; uint32_t limit; uint32_t iterator; - /** Search key or proc name. */ + /** Search key. */ const char *key; const char *key_end; /** Insert/replace/upsert tuple or proc argument or update operations. */ diff --git a/test/box-py/iproto.result b/test/box-py/iproto.result index 3d1111c6d0b3f92e6fd33e05e335124a40347c23..95b39e56fa2c7b9ef81a38cae489753c3ff5d6a7 100644 --- a/test/box-py/iproto.result +++ b/test/box-py/iproto.result @@ -190,6 +190,17 @@ box.schema.user.grant('guest', 'read,write,execute', 'space', 'test_index_base') - [1, 0, 2, -2] +function kek() return 'kek' end +--- +... +box.schema.user.grant('guest', 'read,write,execute', 'universe') +--- +... +Sync: 100 +Retcode: [['kek']] +box.schema.user.revoke('guest', 'read,write,execute', 'universe') +--- +... space:drop() --- ... diff --git a/test/box-py/iproto.test.py b/test/box-py/iproto.test.py index d242a7fed8531fcedf6a955511563d9b8c944978..f545453ae7ef3b3a308562cd047760c2b7d84af0 100644 --- a/test/box-py/iproto.test.py +++ b/test/box-py/iproto.test.py @@ -390,5 +390,26 @@ response = Response(c, c._read_response()) print response.__str__() c.close() + +# +# gh-2619 follow up: allow empty args for call/eval. +# +admin("function kek() return 'kek' end") +admin("box.schema.user.grant('guest', 'read,write,execute', 'universe')") + +c = Connection('localhost', server.iproto.port) +c.connect() +s = c._socket + +header = { IPROTO_CODE: REQUEST_TYPE_CALL, IPROTO_SYNC: 100 } +body = { IPROTO_FUNCTION_NAME: 'kek' } +resp = test_request(header, body) +print "Sync: ", resp['header'][IPROTO_SYNC] +print "Retcode: ", resp['body'][IPROTO_DATA] + +c.close() + +admin("box.schema.user.revoke('guest', 'read,write,execute', 'universe')") + admin("space:drop()")