diff options
author | Amitay Isaacs <amitay@gmail.com> | 2017-06-29 15:10:11 +1000 |
---|---|---|
committer | Martin Schwenke <martins@samba.org> | 2017-09-01 08:52:07 +0200 |
commit | dcc1eaf542e279bf05c338fd03bc9307084cadc5 (patch) | |
tree | d5bfe2bdf80da99005448efe57b97fe2dc4582ca /ctdb/common | |
parent | dfa87862fb3f2c17094bb8ac921b369c344c556d (diff) | |
download | samba-dcc1eaf542e279bf05c338fd03bc9307084cadc5.tar.gz |
ctdb-common: Add sock_client abstraction
This sets up boilerplate required for a client code connecting to a
server over unix domain socket. The communication between client
and server is "request" from client to server and "reply" from
server to client.
Signed-off-by: Amitay Isaacs <amitay@gmail.com>
Reviewed-by: Martin Schwenke <martin@meltin.net>
Diffstat (limited to 'ctdb/common')
-rw-r--r-- | ctdb/common/sock_client.c | 332 | ||||
-rw-r--r-- | ctdb/common/sock_client.h | 129 |
2 files changed, 461 insertions, 0 deletions
diff --git a/ctdb/common/sock_client.c b/ctdb/common/sock_client.c new file mode 100644 index 00000000000..e5f993e085c --- /dev/null +++ b/ctdb/common/sock_client.c @@ -0,0 +1,332 @@ +/* + A client based on unix domain socket + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#include "replace.h" +#include "system/filesys.h" +#include "system/network.h" + +#include <talloc.h> +#include <tevent.h> + +#include "lib/util/debug.h" +#include "lib/util/time.h" +#include "lib/util/tevent_unix.h" + +#include "common/logging.h" +#include "common/reqid.h" +#include "common/comm.h" +#include "common/sock_client.h" + +struct sock_client_context { + struct sock_client_proto_funcs *funcs; + void *private_data; + + void (*disconnect_callback)(void *private_data); + void *disconnect_data; + + int fd; + struct comm_context *comm; + struct reqid_context *idr; +}; + +/* + * connect to a unix domain socket + */ + +static int socket_connect(const char *sockpath) +{ + struct sockaddr_un addr; + size_t len; + int fd, ret; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlcpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); + if (len >= sizeof(addr.sun_path)) { + D_ERR("socket path too long: %s\n", sockpath); + return -1; + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + D_ERR("socket create failed - %s\n", sockpath); + return -1; + } + + ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret != 0) { + D_ERR("socket connect failed - %s\n", sockpath); + close(fd); + return -1; + } + + return fd; +} + +/* + * Socket client + */ + +static int sock_client_context_destructor(struct sock_client_context *sockc); +static void sock_client_read_handler(uint8_t *buf, size_t buflen, + void *private_data); +static void sock_client_dead_handler(void *private_data); + +static void sock_client_msg_reply(struct sock_client_context *sockc, + uint8_t *buf, size_t buflen); + +int sock_client_setup(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const char *sockpath, + struct sock_client_proto_funcs *funcs, + void *private_data, + struct sock_client_context **result) +{ + struct sock_client_context *sockc; + int ret; + + if (sockpath == NULL) { + return EINVAL; + } + + if (funcs == NULL || funcs->request_push == NULL || + funcs->reply_pull == NULL || funcs->reply_reqid == NULL) { + return EINVAL; + } + + sockc = talloc_zero(mem_ctx, struct sock_client_context); + if (sockc == NULL) { + return ENOMEM; + } + + sockc->funcs = funcs; + sockc->private_data = private_data; + + sockc->fd = socket_connect(sockpath); + if (sockc->fd == -1) { + talloc_free(sockc); + return EIO; + } + + ret = comm_setup(sockc, ev, sockc->fd, + sock_client_read_handler, sockc, + sock_client_dead_handler, sockc, + &sockc->comm); + if (ret != 0) { + D_ERR("comm_setup() failed, ret=%d\n", ret); + close(sockc->fd); + talloc_free(sockc); + return ret; + } + + ret = reqid_init(sockc, INT_MAX-200, &sockc->idr); + if (ret != 0) { + D_ERR("reqid_init() failed, ret=%d\n", ret); + close(sockc->fd); + talloc_free(sockc); + return ret; + } + + talloc_set_destructor(sockc, sock_client_context_destructor); + + *result = sockc; + return 0; +} + +static int sock_client_context_destructor(struct sock_client_context *sockc) +{ + TALLOC_FREE(sockc->comm); + if (sockc->fd != -1) { + close(sockc->fd); + sockc->fd = -1; + } + return 0; +} + + +static void sock_client_read_handler(uint8_t *buf, size_t buflen, + void *private_data) +{ + struct sock_client_context *sockc = talloc_get_type_abort( + private_data, struct sock_client_context); + + sock_client_msg_reply(sockc, buf, buflen); +} + +static void sock_client_dead_handler(void *private_data) +{ + struct sock_client_context *sockc = talloc_get_type_abort( + private_data, struct sock_client_context); + + if (sockc->disconnect_callback != NULL) { + sockc->disconnect_callback(sockc->disconnect_data); + talloc_free(sockc); + return; + } + + D_NOTICE("connection to daemon closed, exiting\n"); + exit(1); +} + +void sock_client_set_disconnect_callback(struct sock_client_context *sockc, + sock_client_callback_func_t callback, + void *private_data) +{ + sockc->disconnect_callback = callback; + sockc->disconnect_data = private_data; +} + + +struct sock_client_msg_state { + struct sock_client_context *sockc; + uint32_t reqid; + struct tevent_req *req; + void *reply; +}; + +static int sock_client_msg_state_destructor( + struct sock_client_msg_state *state); +static void sock_client_msg_done(struct tevent_req *subreq); + +struct tevent_req *sock_client_msg_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *sockc, + struct timeval timeout, + void *request) +{ + struct tevent_req *req, *subreq; + struct sock_client_msg_state *state; + uint8_t *buf; + size_t buflen; + int ret; + + req = tevent_req_create(mem_ctx, &state, struct sock_client_msg_state); + if (req == NULL) { + return NULL; + } + + state->sockc = sockc; + + state->reqid = reqid_new(sockc->idr, state); + if (state->reqid == REQID_INVALID) { + talloc_free(req); + return NULL; + } + + state->req = req; + + talloc_set_destructor(state, sock_client_msg_state_destructor); + + ret = sockc->funcs->request_push(request, state->reqid, state, + &buf, &buflen, sockc->private_data); + if (ret != 0) { + tevent_req_error(req, ret); + return tevent_req_post(req, ev); + } + + subreq = comm_write_send(state, ev, sockc->comm, buf, buflen); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, sock_client_msg_done, req); + + if (! timeval_is_zero(&timeout)) { + tevent_req_set_endtime(req, ev, timeout); + } + + return req; +} + +static int sock_client_msg_state_destructor( + struct sock_client_msg_state *state) +{ + reqid_remove(state->sockc->idr, state->reqid); + return 0; +} + +static void sock_client_msg_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + bool status; + + status = comm_write_recv(subreq, &ret); + TALLOC_FREE(subreq); + if (! status) { + tevent_req_error(req, ret); + return; + } + + /* wait for the reply or timeout */ +} + +static void sock_client_msg_reply(struct sock_client_context *sockc, + uint8_t *buf, size_t buflen) +{ + struct sock_client_msg_state *state; + uint32_t reqid; + int ret; + + ret = sockc->funcs->reply_reqid(buf, buflen, &reqid, + sockc->private_data); + if (ret != 0) { + D_WARNING("Invalid packet received, ret=%d\n", ret); + return; + } + + state = reqid_find(sockc->idr, reqid, struct sock_client_msg_state); + if (state == NULL) { + return; + } + + if (reqid != state->reqid) { + return; + } + + ret = sockc->funcs->reply_pull(buf, buflen, state, &state->reply, + sockc->private_data); + if (ret != 0) { + tevent_req_error(state->req, ret); + return; + } + + tevent_req_done(state->req); +} + +bool sock_client_msg_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, void **reply) +{ + struct sock_client_msg_state *state = tevent_req_data( + req, struct sock_client_msg_state); + int ret; + + if (tevent_req_is_unix_error(req, &ret)) { + if (perr != NULL) { + *perr = ret; + } + return false; + } + + if (reply != NULL) { + *reply = talloc_steal(mem_ctx, state->reply); + } + + return true; +} diff --git a/ctdb/common/sock_client.h b/ctdb/common/sock_client.h new file mode 100644 index 00000000000..c640767c3a7 --- /dev/null +++ b/ctdb/common/sock_client.h @@ -0,0 +1,129 @@ +/* + A client based on unix domain socket + + Copyright (C) Amitay Isaacs 2017 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __CTDB_SOCK_CLIENT_H__ +#define __CTDB_SOCK_CLIENT_H__ + +#include <talloc.h> +#include <tevent.h> + +/** + * @file sock_client.h + * + * @brief A framework for a client based on unix-domain sockets. + * + * This abstraction allows to build clients that communicate using + * unix-domain sockets. It takes care of the common boilerplate. + */ + +/** + * @brief The abstract socket daemon context + */ +struct sock_client_context; + +/** + * @brief callback function + * + * This function can be registered to be called in case daemon goes away. + */ +typedef void (*sock_client_callback_func_t)(void *private_data); + +/** + * @brief Protocol marshalling functions + * + * The typical protocol packet will have a header and a payload. + * Header will contain at least 2 fields: length and reqid + * + * request_push() is called when the request packet needs to be marshalled + * + * reply_pull() is called to unmarshall data into a reply packet + * + * reply_reqid() is called to extract request id from a reply packet + */ +struct sock_client_proto_funcs { + int (*request_push)(void *request, uint32_t reqid, + TALLOC_CTX *mem_ctx, + uint8_t **buf, size_t *buflen, + void *private_data); + + int (*reply_pull)(uint8_t *buf, size_t buflen, + TALLOC_CTX *mem_ctx, void **reply, + void *private_data); + + int (*reply_reqid)(uint8_t *buf, size_t buflen, + uint32_t *reqid, void *private_data); +}; + +/** + * @brief Create a new socket client + * + * @param[in] mem_ctx Talloc memory context + * @param[in] ev Tevent context + * @param[in] sockpath Unix domain socket path + * @param[in] funcs Protocol marshalling functions + * @param[in] private_data Private data for protocol functions + * @param[out] result New socket client context + * @return 0 on success, errno on failure + */ +int sock_client_setup(TALLOC_CTX *mem_ctx, struct tevent_context *ev, + const char *sockpath, + struct sock_client_proto_funcs *funcs, + void *private_data, + struct sock_client_context **result); + +/** + * @brief Register a callback in case of client disconnection + * + * @param[in] sockc Socket client context + * @param[in] callback Callback function + * @param[in] private_data Private data for callback function + */ +void sock_client_set_disconnect_callback(struct sock_client_context *sockc, + sock_client_callback_func_t callback, + void *private_data); + +/** + * @brief Async computation to send data to the daemon + * + * @param[in] mem_ctx Talloc memory context + * @param[in] ev Tevent context + * @param[in] sockc The socket client context + * @param[in] timeout How long to wait for + * @param[in] request Requeset packet to be sent + * @return new tevent request, or NULL on failure + */ +struct tevent_req *sock_client_msg_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct sock_client_context *sockc, + struct timeval timeout, + void *request); + +/** + * @brief Async computation end to send data to the daemon + * + * @param[in] req Tevent request + * @param[out] perr errno in case of failure + * @param[in] mem_ctx Talloc memory context + * @param[out] reply Reply received from server + * @return true on success, false on failure + */ +bool sock_client_msg_recv(struct tevent_req *req, int *perr, + TALLOC_CTX *mem_ctx, void **reply); + +#endif /* __CTDB_SOCK_CLIENT_H__ */ |