mirror of
https://github.com/samba-team/samba.git
synced 2025-02-03 13:47:25 +03:00
232054c09b
lib/util/safe_string.h is similar to source3/include/safe_string.h, but the former has fewer checks. It is missing bcopy, strcasecmp, and strncasecmp. Add the missing elements to lib/util/safe_string.h remove the other safe_string.h which is in the source3-specific path. To accomodate existing uses of str(n?)casecmp, add #undef lines to source files where they are used. Signed-off-by: Matthew DeVore <matvore@google.com> Reviewed-by: David Mulder <dmulder@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org> Autobuild-User(master): Jeremy Allison <jra@samba.org> Autobuild-Date(master): Fri Aug 28 02:18:40 UTC 2020 on sn-devel-184
645 lines
16 KiB
C
645 lines
16 KiB
C
/*
|
|
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
|
|
* absense 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);
|
|
}
|