diff options
author | Pavel Březina <pbrezina@redhat.com> | 2016-01-15 13:00:45 +0100 |
---|---|---|
committer | Jakub Hrozek <jhrozek@redhat.com> | 2016-06-20 14:48:47 +0200 |
commit | d3dee2a07f1a8ee9ae6f94e149ced754ef76c248 (patch) | |
tree | dcb92cf97dd70a4183d05258b9db0414b91d60a8 /src/providers/data_provider/dp_request.c | |
parent | 565b9955cc439ade58cc24a98168060a60f33e7a (diff) | |
download | sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.gz sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.tar.xz sssd-d3dee2a07f1a8ee9ae6f94e149ced754ef76c248.zip |
DP: Introduce new interface for backend
Terminology:
* Backend: Implemenation of domain
* Data Provider: interface between backend and responders
* Module: ldap/ipa/ad/... dlopened library that implements dp interface
* Target: id/autofs/sudo/... functionality of module
Benefits over current code:
* data provider is a black box completely separated from backend
* method handlers are just simple tevent requests on backend side
* no need of spy on be_client
* simplified and error proof adding of new responders
* simplified adding of new methods
* reply to D-Bus message is completely handled by DP code
* each target can have several methods defined
* properties can be added on objects
* each method can have output parameters
* modules now support constructor
* improved debugging
* clear memory hierarchy
* ability to chain requests
* type safe private data
Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Jakub Hrozek <jhrozek@redhat.com>
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
Diffstat (limited to 'src/providers/data_provider/dp_request.c')
-rw-r--r-- | src/providers/data_provider/dp_request.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/src/providers/data_provider/dp_request.c b/src/providers/data_provider/dp_request.c new file mode 100644 index 000000000..6c0a0b72d --- /dev/null +++ b/src/providers/data_provider/dp_request.c @@ -0,0 +1,460 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2016 Red Hat + + 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 <talloc.h> +#include <tevent.h> +#include <dbus/dbus.h> + +#include "sbus/sssd_dbus_errors.h" +#include "providers/data_provider/dp_private.h" +#include "providers/backend.h" +#include "util/dlinklist.h" +#include "util/util.h" + +struct dp_req { + struct data_provider *provider; + struct dp_client *client; + uint32_t dp_flags; + + struct sss_domain_info *domain; + + enum dp_targets target; + enum dp_methods method; + struct dp_method *execute; + const char *name; + uint32_t num; + + struct tevent_req *req; + struct tevent_req *handler_req; + void *request_data; + + /* Active request list. */ + struct dp_req *prev; + struct dp_req *next; +}; + +static bool check_data_type(const char *expected, + const char *description, + void *ptr) +{ + void *tmp; + + /* If ptr is NULL we still return true since it is valid case. */ + tmp = talloc_check_name(ptr, expected); + if (tmp != NULL || ptr == NULL) { + return true; + } + + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid %s data type provided. Expected [%s], " + "got [%s].\n", description, expected, talloc_get_name(ptr)); + + return false; +} + +static bool check_method_data(struct dp_method *method, + void *request_data) +{ + if (!check_data_type(method->method_dtype, "method", method->method_data)) { + return false; + } + + if (!check_data_type(method->request_dtype, "request", request_data)) { + return false; + } + + return true; +} + +static int dp_req_destructor(struct dp_req *dp_req) +{ + DLIST_REMOVE(dp_req->provider->requests.active, dp_req); + + if (dp_req->provider->requests.num_active == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: there are no active requests!\n"); + return 0; + } + + dp_req->provider->requests.num_active--; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, "Request removed."); + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + dp_req->provider->requests.num_active); + + return 0; +} + +static errno_t dp_attach_req(struct dp_req *dp_req, + struct data_provider *provider, + const char *name, + uint32_t dp_flags) +{ + /* If we run out of numbers we simply overflow. */ + dp_req->num = provider->requests.index++; + dp_req->name = talloc_asprintf(dp_req, "%s #%u", name, dp_req->num); + if (dp_req->name == NULL) { + return ENOMEM; + } + + /* Attach this request to active request list. */ + DLIST_ADD(provider->requests.active, dp_req); + provider->requests.num_active++; + + talloc_set_destructor(dp_req, dp_req_destructor); + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, dp_req->name, + "New request. Flags [%#.4x].", dp_flags); + + DEBUG(SSSDBG_TRACE_FUNC, "Number of active DP request: %u\n", + provider->requests.num_active); + + return EOK; +} + +static errno_t +dp_req_new(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domainname, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req *dp_req; + struct be_ctx *be_ctx; + errno_t ret; + + /* We set output even for error to simplify code flow in the caller. */ + *_dp_req = NULL; + + dp_req = talloc_zero(mem_ctx, struct dp_req); + if (dp_req == NULL) { + return ENOMEM; + } + + dp_req->provider = provider; + dp_req->client = dp_cli; + dp_req->dp_flags = dp_flags; + dp_req->target = target; + dp_req->method = method; + dp_req->request_data = request_data; + dp_req->req = req; + + ret = dp_attach_req(dp_req, provider, name, dp_flags); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create DP request " + "[%s] [%d]: %s\n", name, ret, sss_strerror(ret)); + talloc_free(dp_req); + return ret; + } + + /* Now the request is created. We will return it even in case of error + * so we can get better debug messages. */ + + talloc_steal(dp_req, dp_req->request_data); + *_dp_req = dp_req; + + be_ctx = provider->be_ctx; + dp_req->domain = be_ctx->domain; + if (domainname != NULL) { + dp_req->domain = find_domain_by_name(be_ctx->domain, domainname, true); + if (dp_req->domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown domain: %s\n", domainname); + return ERR_DOMAIN_NOT_FOUND; + } + } + + ret = dp_find_method(provider, target, method, &dp_req->execute); + + return ret; +} + +static errno_t +file_dp_request(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domainname, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + struct tevent_req *req, + struct dp_req **_dp_req) +{ + struct dp_req_params *dp_params; + dp_req_send_fn send_fn; + struct dp_req *dp_req; + struct be_ctx *be_ctx; + errno_t ret; + + be_ctx = provider->be_ctx; + + ret = dp_req_new(mem_ctx, provider, dp_cli, domainname, name, target, + method, dp_flags, request_data, req, &dp_req); + if (ret != EOK) { + *_dp_req = dp_req; + goto done; + } + + /* DP request is already created. We will always return it to get nice + * debug messages. */ + *_dp_req = dp_req; + + /* Check that provided data are of correct type. */ + + if (!check_method_data(dp_req->execute, dp_req->request_data)) { + ret = ERR_INVALID_DATA_TYPE; + goto done; + } + + /* Process data provider flags */ + + if (dp_flags & DP_FAST_REPLY && be_is_offline(be_ctx)) { + ret = ERR_OFFLINE; + goto done; + } + + /* File request */ + + dp_params = talloc_zero(dp_req, struct dp_req_params); + if (dp_params == NULL) { + ret = ENOMEM; + goto done; + } + + dp_params->ev = provider->ev; + dp_params->be_ctx = be_ctx; + dp_params->domain = dp_req->domain; + dp_params->target = dp_req->target; + dp_params->method = dp_req->method; + + send_fn = dp_req->execute->send_fn; + dp_req->handler_req = send_fn(dp_req, dp_req->execute->method_data, + dp_req->request_data, dp_params); + if (dp_req->handler_req == NULL) { + ret = ENOMEM; + goto done; + } + + *_dp_req = dp_req; + + ret = EOK; + +done: + return ret; +} + +struct dp_req_state { + struct dp_req *dp_req; + dp_req_recv_fn recv_fn; + void *output_data; +}; + +static void dp_req_done(struct tevent_req *subreq); + +struct tevent_req *dp_req_send(TALLOC_CTX *mem_ctx, + struct data_provider *provider, + struct dp_client *dp_cli, + const char *domain, + const char *name, + enum dp_targets target, + enum dp_methods method, + uint32_t dp_flags, + void *request_data, + const char **_request_name) +{ + struct dp_req_state *state; + const char *request_name; + struct tevent_req *req; + struct dp_req *dp_req; + errno_t ret; + + req = tevent_req_create(mem_ctx, &state, struct dp_req_state); + if (req == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n"); + return NULL; + } + + ret = file_dp_request(state, provider, dp_cli, domain, name, target, + method, dp_flags, request_data, req, &dp_req); + + if (dp_req == NULL) { + /* An error ocurred before request could be created. */ + if (_request_name != NULL) { + *_request_name = "Request Not Yet Created"; + } + + goto immediately; + } + + state->dp_req = dp_req; + if (_request_name != NULL) { + request_name = talloc_strdup(mem_ctx, dp_req->name); + if (request_name == NULL) { + *_request_name = "Request Not Yet Created"; + ret = ENOMEM; + goto immediately; + } + *_request_name = request_name; + } + + if (ret != EOK) { + goto immediately; + } + + state->recv_fn = dp_req->execute->recv_fn; + state->output_data = talloc_zero_size(state, dp_req->execute->output_size); + if (state->output_data == NULL) { + ret = ENOMEM; + goto immediately; + } + + talloc_set_name_const(state->output_data, dp_req->execute->output_dtype); + + tevent_req_set_callback(dp_req->handler_req, dp_req_done, req); + + return req; + +immediately: + if (ret == EOK) { + tevent_req_done(req); + } else { + tevent_req_error(req, ret); + } + tevent_req_post(req, provider->ev); + + return req; +} + +static void dp_req_done(struct tevent_req *subreq) +{ + struct dp_req_state *state; + struct tevent_req *req; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + state = tevent_req_data(req, struct dp_req_state); + + ret = state->recv_fn(state->output_data, subreq, state->output_data); + + /* subreq is the same as dp_req->handler_req */ + talloc_zfree(subreq); + state->dp_req->handler_req = NULL; + + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Request handler finished [%d]: %s", ret, sss_strerror(ret)); + + if (ret != EOK) { + tevent_req_error(req, ret); + return; + } + + tevent_req_done(req); +} + +errno_t _dp_req_recv(TALLOC_CTX *mem_ctx, + struct tevent_req *req, + const char *output_dtype, + void **_output_data) +{ + struct dp_req_state *state; + + state = tevent_req_data(req, struct dp_req_state); + + if (state->dp_req != NULL) { + DP_REQ_DEBUG(SSSDBG_TRACE_FUNC, state->dp_req->name, + "Receiving request data."); + } else { + /* dp_req may be NULL in case we error when filing request */ + DEBUG(SSSDBG_TRACE_FUNC, + "Receiving data of prematurely interrupted request!\n"); + } + + TEVENT_REQ_RETURN_ON_ERROR(req); + + if (!check_data_type(output_dtype, "output", state->output_data)) { + return ERR_INVALID_DATA_TYPE; + } + + *_output_data = talloc_steal(mem_ctx, state->output_data); + + return EOK; +} + +static void dp_terminate_request(struct dp_req *dp_req) +{ + if (dp_req->handler_req == NULL) { + /* This may occur when the handler already finished but the caller + * of dp request did not yet recieved data/free dp_req. We just + * return here. */ + return; + } + + /* We will end the handler request and mark dp request as terminated. */ + + DP_REQ_DEBUG(SSSDBG_TRACE_ALL, dp_req->name, "Terminating."); + + talloc_zfree(dp_req->handler_req); + tevent_req_error(dp_req->req, ERR_TERMINATED); +} + +static void dp_terminate_request_list(struct data_provider *provider, + const char *domain) +{ + struct dp_req *next; + struct dp_req *cur; + + if (provider == NULL || provider->requests.active == NULL) { + return; + } + + for (cur = provider->requests.active; cur != NULL; cur = next) { + next = cur->next; + if (domain == NULL || strcmp(cur->domain->name, domain) == 0) { + dp_terminate_request(cur); + } + } +} + +void dp_terminate_active_requests(struct data_provider *provider) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests\n"); + + dp_terminate_request_list(provider, NULL); +} + +void dp_terminate_domain_requests(struct data_provider *provider, + const char *domain) +{ + DEBUG(SSSDBG_TRACE_FUNC, "Terminating active data provider requests " + "for domain [%s]\n", domain); + + if (domain == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: domain is NULL!\n"); + return; + } + + dp_terminate_request_list(provider, domain); +} |