diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:20:00 +0000 |
commit | 8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch) | |
tree | 4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/dsdb/samdb/ldb_modules/count_attrs.c | |
parent | Initial commit. (diff) | |
download | samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip |
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/count_attrs.c')
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/count_attrs.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/count_attrs.c b/source4/dsdb/samdb/ldb_modules/count_attrs.c new file mode 100644 index 0000000..6d7d30a --- /dev/null +++ b/source4/dsdb/samdb/ldb_modules/count_attrs.c @@ -0,0 +1,644 @@ +/* + ldb database library + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019 + + 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/>. +*/ + +/* + * Count how often different attributes are searched for, for performance + * analysis. The counts are stored in tdb files in the 'debug' subdirectory of + * Samba installation's private directory, and can be read using + * script/attr_count_read. + */ + +#include "includes.h" +#include "ldb_module.h" +#include "param/param.h" +#include "lib/tdb_wrap/tdb_wrap.h" +#include "system/filesys.h" + +#define NULL_ATTRS "__null_attrs__" +#define EMPTY_ATTRS "__empty_attrs__" +#define UNKNOWN_ATTR "__unknown_attribute__" +#define STAR_ATTR "*" + +#define NULL_REQ_PSEUDO_N -2LL; +#define STAR_REQ_PSEUDO_N -4LL; + +#undef strcasecmp + +struct count_attrs_private { + struct tdb_wrap *requested; + struct tdb_wrap *duplicates; + struct tdb_wrap *found; + struct tdb_wrap *not_found; + struct tdb_wrap *unwanted; + struct tdb_wrap *star_match; + struct tdb_wrap *null_req; + struct tdb_wrap *empty_req; + struct tdb_wrap *req_vs_found; +}; + + +struct count_attrs_context { + struct ldb_module *module; + struct ldb_request *req; + bool has_star; + bool is_null; + const char **requested_attrs; + size_t n_attrs; +}; + + +static int add_key(struct tdb_context *tdb, + struct TDB_DATA key) +{ + int ret; + uint32_t one = 1; + struct TDB_DATA value = { + .dptr = (uint8_t *)&one, + .dsize = sizeof(one) + }; + ret = tdb_store(tdb, + key, + value, + 0); + return ret; +} + +static int increment_attr_count(struct tdb_context *tdb, + const char *attr) +{ + /* + * Note that as we don't lock the database, there is a small window + * between the fetch and store in which identical updates from + * separate processes can race to clobber each other. If this happens + * the stored count will be one less than it should be. + * + * We don't worry about that because it should be quite rare and + * agnostic as to which counts are affected, meaning the overall + * statistical truth is preserved. + */ + int ret; + uint32_t *val; + TDB_DATA key = { + .dptr = discard_const(attr), + .dsize = strlen(attr) + }; + + TDB_DATA data = tdb_fetch(tdb, key); + if (data.dptr == NULL) { + ret = tdb_error(tdb); + if (ret != TDB_ERR_NOEXIST) { + const char *errstr = tdb_errorstr(tdb); + DBG_ERR("tdb fetch error: %s\n", errstr); + return LDB_ERR_OPERATIONS_ERROR; + } + /* this key is unknown. We'll add it and get out of here. */ + ret = add_key(tdb, key); + if (ret != 0) { + DBG_ERR("could not add %s: %d\n", attr, ret); + } + return ret; + } + + val = (uint32_t *)data.dptr; + (*val)++; + + ret = tdb_store(tdb, + key, + data, + 0); + + if (ret != 0) { + const char *errstr = tdb_errorstr(tdb); + DBG_ERR("tdb store error: %s\n", errstr); + free(data.dptr); + return LDB_ERR_OPERATIONS_ERROR; + } + free(data.dptr); + return LDB_SUCCESS; +} + + +static int increment_req_vs_found(struct tdb_context *tdb, + struct count_attrs_context *ac, + size_t n_found) +{ + /* + * Here we record the number of elements in each reply along with the + * number of attributes in the corresponding request. Requests for + * NULL and "*" are arbitrarily given the attribute counts -2 and -4 + * respectively. This leads them to be plotted as two stacks on the + * left hand side of the scatter plot. + */ + int ret; + ssize_t k[2]; + uint32_t *val = NULL; + TDB_DATA key = { + .dptr = (unsigned char *)k, + .dsize = sizeof(k) + }; + TDB_DATA data = {0}; + ssize_t n_req = ac->n_attrs; + if (ac->is_null) { + n_req = NULL_REQ_PSEUDO_N; + } else if (ac->has_star) { + n_req = STAR_REQ_PSEUDO_N; + } + k[0] = n_req; + k[1] = n_found; + + data = tdb_fetch(tdb, key); + if (data.dptr == NULL) { + ret = tdb_error(tdb); + if (ret != TDB_ERR_NOEXIST) { + const char *errstr = tdb_errorstr(tdb); + DBG_ERR("req vs found fetch error: %s\n", errstr); + return LDB_ERR_OPERATIONS_ERROR; + } + /* unknown key */ + ret = add_key(tdb, key); + if (ret != 0) { + DBG_ERR("could not add req vs found %zu:%zu: %d\n", + n_req, n_found, ret); + } + return ret; + } + + val = (uint32_t *)data.dptr; + (*val)++; + + ret = tdb_store(tdb, key, data, 0); + if (ret != 0) { + const char *errstr = tdb_errorstr(tdb); + DBG_ERR("req vs found store error: %s\n", errstr); + free(data.dptr); + return LDB_ERR_OPERATIONS_ERROR; + } + free(data.dptr); + return LDB_SUCCESS; +} + + +static int strcasecmp_ptr(const char **a, const char **b) +{ + return strcasecmp(*a, *b); +} + + +static const char **get_sorted_attrs(TALLOC_CTX *mem_ctx, + const char * const *unsorted_attrs, + size_t n_attrs) +{ + size_t i; + const char **attrs = talloc_array(mem_ctx, + const char *, + n_attrs); + + if (attrs == NULL) { + return NULL; + } + for (i = 0; i < n_attrs; i++) { + const char *a = unsorted_attrs[i]; + if (a == NULL) { + DBG_ERR("attrs have disappeared! " + "wanted %zu; got %zu\n", + n_attrs, i); + talloc_free(attrs); + return NULL; + } + attrs[i] = a; + } + + qsort(attrs, n_attrs, sizeof(char *), QSORT_CAST strcasecmp_ptr); + return attrs; +} + + + +static int count_attrs_search_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct count_attrs_private *priv = NULL; + struct ldb_message *msg = NULL; + size_t i, j; + int ret; + + struct count_attrs_context *ac = \ + talloc_get_type(req->context, + struct count_attrs_context); + + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + + priv = talloc_get_type_abort(ldb_module_get_private(ac->module), + struct count_attrs_private); + + if (ares == NULL) { + DBG_ERR("ares is NULL\n"); + return ldb_module_done(ac->req, NULL, NULL, + LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + DBG_INFO("ares error %d\n", ares->error); + return ldb_module_done(ac->req, ares->controls, + ares->response, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_REFERRAL: + return ldb_module_send_referral(ac->req, ares->referral); + + case LDB_REPLY_DONE: + return ldb_module_done(ac->req, ares->controls, + ares->response, LDB_SUCCESS); + + case LDB_REPLY_ENTRY: + msg = ares->message; + if (ac->is_null || ac->n_attrs == 0) { + struct tdb_context *tdb = NULL; + /* + * Note when attributes are found when the requested + * list was empty or NULL + */ + if (ac->is_null) { + tdb = priv->null_req->tdb; + } else { + tdb = priv->empty_req->tdb; + } + for (i = 0; i < msg->num_elements; i++) { + const char *name = msg->elements[i].name; + ret = increment_attr_count(tdb, name); + if (ret != LDB_SUCCESS) { + talloc_free(ares); + DBG_ERR("inc failed\n"); + return ret; + } + } + } else { + /* + * We make sorted lists of the requested and found + * elements, which makes it easy to find missing or + * intruding values. + */ + struct tdb_context *found_tdb = priv->found->tdb; + struct tdb_context *unwanted_tdb = \ + priv->unwanted->tdb; + struct tdb_context *star_match_tdb = \ + priv->star_match->tdb; + struct tdb_context *not_found_tdb = \ + priv->not_found->tdb; + + const char **requested_attrs = ac->requested_attrs; + const char **found_attrs = \ + talloc_array(ac, const char *, + msg->num_elements); + if (found_attrs == NULL) { + return ldb_oom(ldb); + } + + for (i = 0; i < msg->num_elements; i++) { + found_attrs[i] = msg->elements[i].name; + } + + qsort(found_attrs, msg->num_elements, sizeof(char *), + QSORT_CAST strcasecmp_ptr); + + + /* find and report duplicates */ + for (i = 1; i < msg->num_elements; i++) { + if (strcasecmp(found_attrs[i], + found_attrs[i - 1]) == 0) { + DBG_ERR("duplicate element: %s!\n", + found_attrs[i]); + /* + * If this happens it will muck up our + * counts, but probably have worse + * effects on the rest of the module + * stack. */ + } + } + + /* + * This next bit is like the merge stage of a + * mergesort, but instead of merging we only detect + * absence or presence. + */ + i = 0; + j = 0; + while (i < ac->n_attrs || + j < msg->num_elements) { + int cmp; + if (i >= ac->n_attrs) { + cmp = 1; + } else if (j >= msg->num_elements) { + cmp = -1; + } else { + cmp = strcasecmp(requested_attrs[i], + found_attrs[j] + ); + } + + if (cmp < 0) { + /* We did not find the element */ + ret = increment_attr_count( + not_found_tdb, + requested_attrs[i]); + i++; + } else if (cmp > 0) { + /* + * We found the element, but didn't + * specifically ask for it. + */ + if (ac->has_star) { + ret = increment_attr_count( + star_match_tdb, + found_attrs[j]); + } else { + ret = increment_attr_count( + unwanted_tdb, + found_attrs[j]); + } + j++; + } else { + /* We got what we asked for. */ + ret = increment_attr_count( + found_tdb, + found_attrs[j]); + i++; + j++; + } + if (ret != LDB_SUCCESS) { + talloc_free(ares); + DBG_ERR("inc failed\n"); + return ret; + } + } + } + ret = increment_req_vs_found(priv->req_vs_found->tdb, + ac, + msg->num_elements); + + if (ret != LDB_SUCCESS) { + talloc_free(ares); + DBG_ERR("inc of req vs found failed\n"); + return ret; + } + + return ldb_module_send_entry( + ac->req, + ares->message, + ares->controls); + } + + talloc_free(ares); + return LDB_SUCCESS; +} + + +static int count_attrs_search(struct ldb_module *module, + struct ldb_request *req) +{ + int ret; + const char * const *attrs = req->op.search.attrs; + struct count_attrs_private *count_attrs_private = NULL; + struct tdb_context *tdb = NULL; + struct ldb_request *down_req = NULL; + struct count_attrs_context *ac = NULL; + bool has_star = false; + bool is_null = false; + size_t n_attrs = 0; + const char **sorted_attrs = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(module); + + + void *untyped_private = ldb_module_get_private(module); + if (untyped_private == NULL) { + /* + * There are some cases (in early start up, and during a + * backup restore) in which we get a NULL private object, in + * which case all we can do is ignore it and pass the request + * on unexamined. + */ + return ldb_next_request(module, req); + } + + count_attrs_private = talloc_get_type_abort(untyped_private, + struct count_attrs_private); + tdb = count_attrs_private->requested->tdb; + + ac = talloc_zero(req, struct count_attrs_context); + if (ac == NULL) { + return ldb_oom(ldb); + } + + if (attrs == NULL) { + ret = increment_attr_count(tdb, NULL_ATTRS); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + is_null = true; + } else if (attrs[0] == NULL) { + ret = increment_attr_count(tdb, EMPTY_ATTRS); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + } else { + size_t i, j; + for (i = 0; attrs[i] != NULL; i++) { + ret = increment_attr_count(tdb, attrs[i]); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + if (strcmp("*", attrs[i]) == 0) { + has_star = true; + } + } + n_attrs = i; + sorted_attrs = get_sorted_attrs(req, + attrs, + n_attrs); + /* + * Find, report, and remove duplicates. Duplicate attrs in + * requests are allowed, but don't work well with our + * merge-count algorithm. + */ + j = 0; + for (i = 1; i < n_attrs; i++) { + if (strcasecmp(sorted_attrs[i], + sorted_attrs[j]) == 0) { + ret = increment_attr_count( + count_attrs_private->duplicates->tdb, + sorted_attrs[i]); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return ret; + } + } else { + j++; + if (j != i) { + sorted_attrs[j] = sorted_attrs[i]; + } + } + } + n_attrs = j; + } + + ac->module = module; + ac->req = req; + ac->has_star = has_star; + ac->is_null = is_null; + ac->n_attrs = n_attrs; + ac->requested_attrs = sorted_attrs; + + ret = ldb_build_search_req_ex(&down_req, + ldb, + ac, + req->op.search.base, + req->op.search.scope, + req->op.search.tree, + req->op.search.attrs, + req->controls, + ac, + count_attrs_search_callback, + req); + if (ret != LDB_SUCCESS) { + return ldb_operr(ldb); + } + + return ldb_next_request(module, down_req); +} + + +static struct tdb_wrap * open_private_tdb(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *name) +{ + struct tdb_wrap *store = NULL; + char *filename = lpcfg_private_path(mem_ctx, lp_ctx, name); + + if (filename == NULL) { + return NULL; + } + + store = tdb_wrap_open(mem_ctx, filename, 1000, + TDB_CLEAR_IF_FIRST, + O_RDWR | O_CREAT, + 0660); + if (store == NULL) { + DBG_ERR("failed to open tdb at %s\n", filename); + } + TALLOC_FREE(filename); + return store; +} + +static int make_private_dir(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *name) +{ + int ret; + char *dirname = lpcfg_private_path(mem_ctx, lp_ctx, name); + if (dirname == NULL) { + return -1; + } + ret = mkdir(dirname, 0755); + TALLOC_FREE(dirname); + return ret; +} + + +static int count_attrs_init(struct ldb_module *module) +{ + struct ldb_context *ldb = NULL; + struct count_attrs_private *data = NULL; + struct loadparm_context *lp_ctx = NULL; + int ret; + + ldb = ldb_module_get_ctx(module); + + data = talloc_zero(module, struct count_attrs_private); + if (data == NULL) { + return ldb_oom(ldb); + } + + lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"), + struct loadparm_context); + + ret = make_private_dir(data, lp_ctx, "debug"); + if (ret != 0) { + goto no_private_dir; + } + data->requested = open_private_tdb(data, lp_ctx, + "debug/attr_counts_requested.tdb"); + data->duplicates = \ + open_private_tdb(data, lp_ctx, + "debug/attr_counts_duplicates.tdb"); + data->found = open_private_tdb(data, lp_ctx, + "debug/attr_counts_found.tdb"); + data->not_found = open_private_tdb(data, lp_ctx, + "debug/attr_counts_not_found.tdb"); + data->unwanted = open_private_tdb(data, lp_ctx, + "debug/attr_counts_unwanted.tdb"); + data->star_match = open_private_tdb(data, lp_ctx, + "debug/attr_counts_star_match.tdb"); + data->null_req = open_private_tdb(data, lp_ctx, + "debug/attr_counts_null_req.tdb"); + data->empty_req = open_private_tdb(data, lp_ctx, + "debug/attr_counts_empty_req.tdb"); + data->req_vs_found = \ + open_private_tdb(data, lp_ctx, + "debug/attr_counts_req_vs_found.tdb"); + if (data->requested == NULL || + data->duplicates == NULL || + data->found == NULL || + data->not_found == NULL || + data->unwanted == NULL || + data->star_match == NULL || + data->null_req == NULL || + data->empty_req == NULL || + data->req_vs_found == NULL) { + goto no_private_dir; + } + + ldb_module_set_private(module, data); + return ldb_next_init(module); + + no_private_dir: + /* + * If we leave the private data NULL, the search function knows not to + * do anything. + */ + DBG_WARNING("the count_attrs module could not open its databases\n"); + DBG_WARNING("attributes will not be counted.\n"); + TALLOC_FREE(data); + ldb_module_set_private(module, NULL); + return ldb_next_init(module); +} + +static const struct ldb_module_ops ldb_count_attrs_module_ops = { + .name = "count_attrs", + .search = count_attrs_search, + .init_context = count_attrs_init +}; + +int ldb_count_attrs_init(const char *version) +{ + LDB_MODULE_CHECK_VERSION(version); + return ldb_register_module(&ldb_count_attrs_module_ops); +} |