diff --git a/.gitignore b/.gitignore index bc52d4c37c8193135f7e3dd58f23a220156fb54a..b93b4ae7d529c72261985802220c863d573daa69 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ lcov test/var test/lib/*.pyc test/lib/*/*.pyc +test/box/protocol Makefile CMakeFiles CMakeCache.txt diff --git a/connector/c/client.c b/connector/c/client.c index 4597f2feeefa23c31bbd36c72f0d879b62ad986b..5b39911e5d52d67cc9120d9cdac214aac80684b8 100644 --- a/connector/c/client.c +++ b/connector/c/client.c @@ -32,54 +32,106 @@ #include <netinet/in.h> #include <netinet/tcp.h> +/* + * Please keep as many data structures defined in .c as possible + * to increase forward ABI compatibility. + */ + + +struct tnt_connection +{ + /** The socket used to get connected to the server. */ + int data_port; +}; + + +/** A helper to get the DNS resolution done. + */ + static struct sockaddr_in -get_sockaddr_in(const char *hostname, unsigned short port) { - struct sockaddr_in result; +get_sockaddr_in(const char *hostname, unsigned short port) +{ + struct sockaddr_in result; - memset((void*)(&result), 0, sizeof(result)); - result.sin_family = AF_INET; - result.sin_port = htons(port); + memset((void*)(&result), 0, sizeof(result)); + result.sin_family = AF_INET; + result.sin_port = htons(port); - struct hostent *host = gethostbyname(hostname); - if (host != 0) - memcpy((void*)(&result.sin_addr), - (void*)(host->h_addr), host->h_length); + /* @todo: start using gethostbyname_r */ + struct hostent *host = gethostbyname(hostname); + if (host != 0) + memcpy((void*)(&result.sin_addr), + (void*)(host->h_addr), host->h_length); - return result; + return result; } -struct tnt_connection *tnt_connect(const char *hostname, int port) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) - return NULL; - int opt = 1; - if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1) - return NULL; +struct tnt_connection *tnt_connect(const char *hostname, int port) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) + return NULL; + + /* + * We set TCP_NODELAY since we're not strictly + * request/response. + */ + int opt = 1; + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) == -1) + return NULL; - struct sockaddr_in addr = get_sockaddr_in(hostname, port); - if (connect(fd, (struct sockaddr*)&addr, sizeof addr)) - return NULL; + struct sockaddr_in addr = get_sockaddr_in(hostname, port); + if (connect(fd, (struct sockaddr*)&addr, sizeof addr)) + return NULL; - struct tnt_connection *conn = malloc(sizeof(struct tnt_connection)); - conn->data_port = fd; - return conn; + struct tnt_connection *tnt = malloc(sizeof(struct tnt_connection)); + if (tnt == NULL) { + close(fd); + return NULL; + } + tnt->data_port = fd; + return tnt; } -void tnt_disconnect(struct tnt_connection *conn) { - close(conn->data_port); - free(conn); + +void tnt_disconnect(struct tnt_connection *tnt) +{ + close(tnt->data_port); + free(tnt); } -int tnt_execute_raw(struct tnt_connection *conn, const char *message, - size_t len) { - if (send(conn->data_port, message, len, 0) < 0) - return 3; - char buf[2048]; - if (recv(conn->data_port, buf, 2048, 0) < 16) - return 3; +/** Send the binary blob message to the server, read the response + * and learn as much from it as possible. + * + * @return 0 on success, 1 on error. + */ + +int tnt_execute_raw(struct tnt_connection *tnt, const char *message, + size_t len, struct tnt_result *tnt_res) +{ + if (send(tnt->data_port, message, len, 0) < 0) + return -1; + + char buf[2048]; + + if (recv(tnt->data_port, buf, 2048, 0) < 16) + return -1; + + if (tnt_res) { + memset(tnt_res, 0, sizeof *tnt_res); + + int ret_code = buf[12]; + int b = 256; + int i = 13; + while (i < 16) { + ret_code += (buf[i++] * b); + b *= 256; + } - return buf[12]; // return_code: 0,1,2 + tnt_res->errcode = ret_code; /* see iproto.h */ + } + return 0; } diff --git a/connector/c/client.h b/connector/c/client.h index 034ba5cc00b756f02e1b858e35d22385e3ea586d..f54cf7f77d2b30e0a84eca7abd11c1aa86d9b42a 100644 --- a/connector/c/client.h +++ b/connector/c/client.h @@ -28,14 +28,24 @@ */ #include <stddef.h> +#include <inttypes.h> /** * A connection with a Tarantool server. */ -struct tnt_connection { - int data_port; +struct tnt_connection; + + +/** Result of an operation, on an established + * connection. + */ +struct tnt_result +{ + /** Server error or 0. */ + uint32_t errcode; }; + /** * Open a connection with a Tarantool server. * @@ -46,24 +56,39 @@ struct tnt_connection { */ struct tnt_connection *tnt_connect(const char *hostname, int port); + /** * Close a connection. * - * @param conn the connection. + * @param tnt the connection. */ -void tnt_disconnect(struct tnt_connection *conn); +void tnt_disconnect(struct tnt_connection *tnt); + /** - * Execute a statement on a connection + * Execute a statement on a Tarantool server. * - * @param conn the connection. - * @param message a raw message following Tarantool protocol. - * @param len the length of the message. + * @param tnt the connection. + * @param message a raw message following Tarantool + * protocol. + * @param len the length of the message. + * @param[out] tnt_res optional, can be NULL. If given, + * contains the server response. * - * @return the status code: 0,1,2 if the statement was successufully - * sent (see Tarantool protocol) or 3 if an error occured. + * @retval 0 if the statement was successfully sent (see Tarantool + * protocol) and a response from the server was successfully + * received. + * @retval -1 if a client error occurred. Server error can be + * queried by looking into tnt_res. + */ + +int tnt_execute_raw(struct tnt_connection *tnt, const char *message, + size_t len, struct tnt_result *tnt_res); + + +/** Return the *server* error code of the last error (see + * errcode.h), or 0 if there were no server error. */ -int tnt_execute_raw(struct tnt_connection *conn, const char *message, - size_t len); +uint32_t tnt_get_errcode(struct tnt_result *tnt_res); #endif /* TARANTOOL_CONNECTOR_CLIENT_H_INCLUDED */ diff --git a/include/errcode.h b/include/errcode.h index 7f3d4d2c77ffa9e84e7da6d2a4529f34ff0a145f..f1609ad8916cfc9abcdc90690eb7d6d00ceee968 100644 --- a/include/errcode.h +++ b/include/errcode.h @@ -14,7 +14,7 @@ struct errcode_record { #define ERROR_CODES(_) \ /* 0 */_(ERR_CODE_OK, 0, "OK") \ /* 1 */_(ERR_CODE_NONMASTER, 2, "Non master connection, but it should be") \ - /* 2 */_(ERR_CODE_ILLEGAL_PARAMS, 2, "Illegal parametrs") \ + /* 2 */_(ERR_CODE_ILLEGAL_PARAMS, 2, "Illegal parameters") \ /* 3 */_(ERR_CODE_BAD_UID, 2, "Uid is not from this storage range") \ /* 4 */_(ERR_CODE_NODE_IS_RO, 1, "Node is marked as read-only") \ /* 5 */_(ERR_CODE_NODE_IS_NOT_LOCKED, 1, "Node isn't locked") \ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5b6ade761ef95634f94b8ee65a28e55c15e34ab4..bed1434831b04845990de315d56dad3130fddbba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,7 +3,8 @@ add_custom_target(test ) add_executable(box/protocol - ${CMAKE_SOURCE_DIR}/test/box/protocol.c) + ${CMAKE_SOURCE_DIR}/test/box/protocol.c + ${CMAKE_SOURCE_DIR}/core/errcode.c) target_link_libraries (box/protocol client) install (PROGRAMS tarantool DESTINATION bin) diff --git a/test/box/protocol.c b/test/box/protocol.c index fa2d6d5ddcf5ac926b6c701bef238feae7dac34c..5463ca5f0449616638100f9bfef63e15e3ec8ef1 100644 --- a/test/box/protocol.c +++ b/test/box/protocol.c @@ -1,4 +1,5 @@ #include <connector/c/client.h> +#include <include/errcode.h> #include <stdio.h> /** Server connection. Reused between tests. */ @@ -12,7 +13,7 @@ void test_ping() 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x1, 0x0, 0x0, 0x0 }; - int res = tnt_execute_raw(conn, message, sizeof message); + int res = tnt_execute_raw(conn, message, sizeof message, 0); printf("return_code: %d\n", res); /* =0 */ } @@ -28,8 +29,11 @@ void test_bug702397() 0x11, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0 }; - int res = tnt_execute_raw(conn, message, sizeof message); - printf("return_code: %d\n", res); // =2 + struct tnt_result tnt_res; + int res = tnt_execute_raw(conn, message, sizeof message, &tnt_res); + printf("return_code: %s, %s\n", + tnt_errcode_str(tnt_res.errcode >> 8), + tnt_errcode_desc(tnt_res.errcode >> 8)); } int main() @@ -42,5 +46,5 @@ int main() test_bug702397(); tnt_disconnect(conn); - return 0; + return 0; } diff --git a/test/box/protocol.result b/test/box/protocol.result index 309dfed10be212e98be605ace8498f3947ff30c9..34a50786e7282da11da9e42f027ea52366d3e419 100644 --- a/test/box/protocol.result +++ b/test/box/protocol.result @@ -1,2 +1,2 @@ return_code: 0 -return_code: 2 +return_code: ERR_CODE_ILLEGAL_PARAMS, "Illegal parameters"