Line data Source code
1 : /*
2 : ldb database library
3 :
4 : Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019
5 :
6 : This program is free software; you can redistribute it and/or modify
7 : it under the terms of the GNU General Public License as published by
8 : the Free Software Foundation; either version 3 of the License, or
9 : (at your option) any later version.
10 :
11 : This program is distributed in the hope that it will be useful,
12 : but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : GNU General Public License for more details.
15 :
16 : You should have received a copy of the GNU General Public License
17 : along with this program. If not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : /*
21 : * Count how often different attributes are searched for, for performance
22 : * analysis. The counts are stored in tdb files in the 'debug' subdirectory of
23 : * Samba installation's private directory, and can be read using
24 : * script/attr_count_read.
25 : */
26 :
27 : #include "includes.h"
28 : #include "ldb_module.h"
29 : #include "param/param.h"
30 : #include "lib/tdb_wrap/tdb_wrap.h"
31 : #include "system/filesys.h"
32 :
33 : #define NULL_ATTRS "__null_attrs__"
34 : #define EMPTY_ATTRS "__empty_attrs__"
35 : #define UNKNOWN_ATTR "__unknown_attribute__"
36 : #define STAR_ATTR "*"
37 :
38 : #define NULL_REQ_PSEUDO_N -2LL;
39 : #define STAR_REQ_PSEUDO_N -4LL;
40 :
41 : #undef strcasecmp
42 :
43 : struct count_attrs_private {
44 : struct tdb_wrap *requested;
45 : struct tdb_wrap *duplicates;
46 : struct tdb_wrap *found;
47 : struct tdb_wrap *not_found;
48 : struct tdb_wrap *unwanted;
49 : struct tdb_wrap *star_match;
50 : struct tdb_wrap *null_req;
51 : struct tdb_wrap *empty_req;
52 : struct tdb_wrap *req_vs_found;
53 : };
54 :
55 :
56 : struct count_attrs_context {
57 : struct ldb_module *module;
58 : struct ldb_request *req;
59 : bool has_star;
60 : bool is_null;
61 : const char **requested_attrs;
62 : size_t n_attrs;
63 : };
64 :
65 :
66 0 : static int add_key(struct tdb_context *tdb,
67 : struct TDB_DATA key)
68 : {
69 0 : int ret;
70 0 : uint32_t one = 1;
71 0 : struct TDB_DATA value = {
72 : .dptr = (uint8_t *)&one,
73 : .dsize = sizeof(one)
74 : };
75 0 : ret = tdb_store(tdb,
76 : key,
77 : value,
78 : 0);
79 0 : return ret;
80 : }
81 :
82 0 : static int increment_attr_count(struct tdb_context *tdb,
83 : const char *attr)
84 : {
85 : /*
86 : * Note that as we don't lock the database, there is a small window
87 : * between the fetch and store in which identical updates from
88 : * separate processes can race to clobber each other. If this happens
89 : * the stored count will be one less than it should be.
90 : *
91 : * We don't worry about that because it should be quite rare and
92 : * agnostic as to which counts are affected, meaning the overall
93 : * statistical truth is preserved.
94 : */
95 0 : int ret;
96 0 : uint32_t *val;
97 0 : TDB_DATA key = {
98 : .dptr = discard_const(attr),
99 0 : .dsize = strlen(attr)
100 : };
101 :
102 0 : TDB_DATA data = tdb_fetch(tdb, key);
103 0 : if (data.dptr == NULL) {
104 0 : ret = tdb_error(tdb);
105 0 : if (ret != TDB_ERR_NOEXIST) {
106 0 : const char *errstr = tdb_errorstr(tdb);
107 0 : DBG_ERR("tdb fetch error: %s\n", errstr);
108 0 : return LDB_ERR_OPERATIONS_ERROR;
109 : }
110 : /* this key is unknown. We'll add it and get out of here. */
111 0 : ret = add_key(tdb, key);
112 0 : if (ret != 0) {
113 0 : DBG_ERR("could not add %s: %d\n", attr, ret);
114 : }
115 0 : return ret;
116 : }
117 :
118 0 : val = (uint32_t *)data.dptr;
119 0 : (*val)++;
120 :
121 0 : ret = tdb_store(tdb,
122 : key,
123 : data,
124 : 0);
125 :
126 0 : if (ret != 0) {
127 0 : const char *errstr = tdb_errorstr(tdb);
128 0 : DBG_ERR("tdb store error: %s\n", errstr);
129 0 : free(data.dptr);
130 0 : return LDB_ERR_OPERATIONS_ERROR;
131 : }
132 0 : free(data.dptr);
133 0 : return LDB_SUCCESS;
134 : }
135 :
136 :
137 0 : static int increment_req_vs_found(struct tdb_context *tdb,
138 : struct count_attrs_context *ac,
139 : size_t n_found)
140 : {
141 : /*
142 : * Here we record the number of elements in each reply along with the
143 : * number of attributes in the corresponding request. Requests for
144 : * NULL and "*" are arbitrarily given the attribute counts -2 and -4
145 : * respectively. This leads them to be plotted as two stacks on the
146 : * left hand side of the scatter plot.
147 : */
148 0 : int ret;
149 0 : ssize_t k[2];
150 0 : uint32_t *val = NULL;
151 0 : TDB_DATA key = {
152 : .dptr = (unsigned char *)k,
153 : .dsize = sizeof(k)
154 : };
155 0 : TDB_DATA data = {0};
156 0 : ssize_t n_req = ac->n_attrs;
157 0 : if (ac->is_null) {
158 0 : n_req = NULL_REQ_PSEUDO_N;
159 0 : } else if (ac->has_star) {
160 0 : n_req = STAR_REQ_PSEUDO_N;
161 : }
162 0 : k[0] = n_req;
163 0 : k[1] = n_found;
164 :
165 0 : data = tdb_fetch(tdb, key);
166 0 : if (data.dptr == NULL) {
167 0 : ret = tdb_error(tdb);
168 0 : if (ret != TDB_ERR_NOEXIST) {
169 0 : const char *errstr = tdb_errorstr(tdb);
170 0 : DBG_ERR("req vs found fetch error: %s\n", errstr);
171 0 : return LDB_ERR_OPERATIONS_ERROR;
172 : }
173 : /* unknown key */
174 0 : ret = add_key(tdb, key);
175 0 : if (ret != 0) {
176 0 : DBG_ERR("could not add req vs found %zu:%zu: %d\n",
177 : n_req, n_found, ret);
178 : }
179 0 : return ret;
180 : }
181 :
182 0 : val = (uint32_t *)data.dptr;
183 0 : (*val)++;
184 :
185 0 : ret = tdb_store(tdb, key, data, 0);
186 0 : if (ret != 0) {
187 0 : const char *errstr = tdb_errorstr(tdb);
188 0 : DBG_ERR("req vs found store error: %s\n", errstr);
189 0 : free(data.dptr);
190 0 : return LDB_ERR_OPERATIONS_ERROR;
191 : }
192 0 : free(data.dptr);
193 0 : return LDB_SUCCESS;
194 : }
195 :
196 :
197 0 : static int strcasecmp_ptr(const char **a, const char **b)
198 : {
199 0 : return strcasecmp(*a, *b);
200 : }
201 :
202 :
203 0 : static const char **get_sorted_attrs(TALLOC_CTX *mem_ctx,
204 : const char * const *unsorted_attrs,
205 : size_t n_attrs)
206 : {
207 0 : size_t i;
208 0 : const char **attrs = talloc_array(mem_ctx,
209 : const char *,
210 : n_attrs);
211 :
212 0 : if (attrs == NULL) {
213 0 : return NULL;
214 : }
215 0 : for (i = 0; i < n_attrs; i++) {
216 0 : const char *a = unsorted_attrs[i];
217 0 : if (a == NULL) {
218 0 : DBG_ERR("attrs have disappeared! "
219 : "wanted %zu; got %zu\n",
220 : n_attrs, i);
221 0 : talloc_free(attrs);
222 0 : return NULL;
223 : }
224 0 : attrs[i] = a;
225 : }
226 :
227 0 : qsort(attrs, n_attrs, sizeof(char *), QSORT_CAST strcasecmp_ptr);
228 0 : return attrs;
229 : }
230 :
231 :
232 :
233 0 : static int count_attrs_search_callback(struct ldb_request *req,
234 : struct ldb_reply *ares)
235 : {
236 0 : struct count_attrs_private *priv = NULL;
237 0 : struct ldb_message *msg = NULL;
238 0 : size_t i, j;
239 0 : int ret;
240 :
241 0 : struct count_attrs_context *ac = \
242 0 : talloc_get_type(req->context,
243 : struct count_attrs_context);
244 :
245 0 : struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
246 :
247 0 : priv = talloc_get_type_abort(ldb_module_get_private(ac->module),
248 : struct count_attrs_private);
249 :
250 0 : if (ares == NULL) {
251 0 : DBG_ERR("ares is NULL\n");
252 0 : return ldb_module_done(ac->req, NULL, NULL,
253 : LDB_ERR_OPERATIONS_ERROR);
254 : }
255 0 : if (ares->error != LDB_SUCCESS) {
256 0 : DBG_INFO("ares error %d\n", ares->error);
257 0 : return ldb_module_done(ac->req, ares->controls,
258 : ares->response, ares->error);
259 : }
260 :
261 0 : switch (ares->type) {
262 0 : case LDB_REPLY_REFERRAL:
263 0 : return ldb_module_send_referral(ac->req, ares->referral);
264 :
265 0 : case LDB_REPLY_DONE:
266 0 : return ldb_module_done(ac->req, ares->controls,
267 : ares->response, LDB_SUCCESS);
268 :
269 0 : case LDB_REPLY_ENTRY:
270 0 : msg = ares->message;
271 0 : if (ac->is_null || ac->n_attrs == 0) {
272 0 : struct tdb_context *tdb = NULL;
273 : /*
274 : * Note when attributes are found when the requested
275 : * list was empty or NULL
276 : */
277 0 : if (ac->is_null) {
278 0 : tdb = priv->null_req->tdb;
279 : } else {
280 0 : tdb = priv->empty_req->tdb;
281 : }
282 0 : for (i = 0; i < msg->num_elements; i++) {
283 0 : const char *name = msg->elements[i].name;
284 0 : ret = increment_attr_count(tdb, name);
285 0 : if (ret != LDB_SUCCESS) {
286 0 : talloc_free(ares);
287 0 : DBG_ERR("inc failed\n");
288 0 : return ret;
289 : }
290 : }
291 : } else {
292 : /*
293 : * We make sorted lists of the requested and found
294 : * elements, which makes it easy to find missing or
295 : * intruding values.
296 : */
297 0 : struct tdb_context *found_tdb = priv->found->tdb;
298 0 : struct tdb_context *unwanted_tdb = \
299 0 : priv->unwanted->tdb;
300 0 : struct tdb_context *star_match_tdb = \
301 0 : priv->star_match->tdb;
302 0 : struct tdb_context *not_found_tdb = \
303 0 : priv->not_found->tdb;
304 :
305 0 : const char **requested_attrs = ac->requested_attrs;
306 0 : const char **found_attrs = \
307 0 : talloc_array(ac, const char *,
308 : msg->num_elements);
309 0 : if (found_attrs == NULL) {
310 0 : return ldb_oom(ldb);
311 : }
312 :
313 0 : for (i = 0; i < msg->num_elements; i++) {
314 0 : found_attrs[i] = msg->elements[i].name;
315 : }
316 :
317 0 : qsort(found_attrs, msg->num_elements, sizeof(char *),
318 : QSORT_CAST strcasecmp_ptr);
319 :
320 :
321 : /* find and report duplicates */
322 0 : for (i = 1; i < msg->num_elements; i++) {
323 0 : if (strcasecmp(found_attrs[i],
324 0 : found_attrs[i - 1]) == 0) {
325 0 : DBG_ERR("duplicate element: %s!\n",
326 : found_attrs[i]);
327 : /*
328 : * If this happens it will muck up our
329 : * counts, but probably have worse
330 : * effects on the rest of the module
331 : * stack. */
332 : }
333 : }
334 :
335 : /*
336 : * This next bit is like the merge stage of a
337 : * mergesort, but instead of merging we only detect
338 : * absence or presence.
339 : */
340 0 : i = 0;
341 0 : j = 0;
342 0 : while (i < ac->n_attrs ||
343 0 : j < msg->num_elements) {
344 0 : int cmp;
345 0 : if (i >= ac->n_attrs) {
346 0 : cmp = 1;
347 0 : } else if (j >= msg->num_elements) {
348 0 : cmp = -1;
349 : } else {
350 0 : cmp = strcasecmp(requested_attrs[i],
351 0 : found_attrs[j]
352 : );
353 : }
354 :
355 0 : if (cmp < 0) {
356 : /* We did not find the element */
357 0 : ret = increment_attr_count(
358 : not_found_tdb,
359 0 : requested_attrs[i]);
360 0 : i++;
361 0 : } else if (cmp > 0) {
362 : /*
363 : * We found the element, but didn't
364 : * specifically ask for it.
365 : */
366 0 : if (ac->has_star) {
367 0 : ret = increment_attr_count(
368 : star_match_tdb,
369 0 : found_attrs[j]);
370 : } else {
371 0 : ret = increment_attr_count(
372 : unwanted_tdb,
373 0 : found_attrs[j]);
374 : }
375 0 : j++;
376 : } else {
377 : /* We got what we asked for. */
378 0 : ret = increment_attr_count(
379 : found_tdb,
380 0 : found_attrs[j]);
381 0 : i++;
382 0 : j++;
383 : }
384 0 : if (ret != LDB_SUCCESS) {
385 0 : talloc_free(ares);
386 0 : DBG_ERR("inc failed\n");
387 0 : return ret;
388 : }
389 : }
390 : }
391 0 : ret = increment_req_vs_found(priv->req_vs_found->tdb,
392 : ac,
393 0 : msg->num_elements);
394 :
395 0 : if (ret != LDB_SUCCESS) {
396 0 : talloc_free(ares);
397 0 : DBG_ERR("inc of req vs found failed\n");
398 0 : return ret;
399 : }
400 :
401 0 : return ldb_module_send_entry(
402 : ac->req,
403 : ares->message,
404 : ares->controls);
405 : }
406 :
407 0 : talloc_free(ares);
408 0 : return LDB_SUCCESS;
409 : }
410 :
411 :
412 0 : static int count_attrs_search(struct ldb_module *module,
413 : struct ldb_request *req)
414 : {
415 0 : int ret;
416 0 : const char * const *attrs = req->op.search.attrs;
417 0 : struct count_attrs_private *count_attrs_private = NULL;
418 0 : struct tdb_context *tdb = NULL;
419 0 : struct ldb_request *down_req = NULL;
420 0 : struct count_attrs_context *ac = NULL;
421 0 : bool has_star = false;
422 0 : bool is_null = false;
423 0 : size_t n_attrs = 0;
424 0 : const char **sorted_attrs = NULL;
425 0 : struct ldb_context *ldb = ldb_module_get_ctx(module);
426 :
427 :
428 0 : void *untyped_private = ldb_module_get_private(module);
429 0 : if (untyped_private == NULL) {
430 : /*
431 : * There are some cases (in early start up, and during a
432 : * backup restore) in which we get a NULL private object, in
433 : * which case all we can do is ignore it and pass the request
434 : * on unexamined.
435 : */
436 0 : return ldb_next_request(module, req);
437 : }
438 :
439 0 : count_attrs_private = talloc_get_type_abort(untyped_private,
440 : struct count_attrs_private);
441 0 : tdb = count_attrs_private->requested->tdb;
442 :
443 0 : ac = talloc_zero(req, struct count_attrs_context);
444 0 : if (ac == NULL) {
445 0 : return ldb_oom(ldb);
446 : }
447 :
448 0 : if (attrs == NULL) {
449 0 : ret = increment_attr_count(tdb, NULL_ATTRS);
450 0 : if (ret != LDB_SUCCESS) {
451 0 : talloc_free(ac);
452 0 : return ret;
453 : }
454 0 : is_null = true;
455 0 : } else if (attrs[0] == NULL) {
456 0 : ret = increment_attr_count(tdb, EMPTY_ATTRS);
457 0 : if (ret != LDB_SUCCESS) {
458 0 : talloc_free(ac);
459 0 : return ret;
460 : }
461 : } else {
462 : size_t i, j;
463 0 : for (i = 0; attrs[i] != NULL; i++) {
464 0 : ret = increment_attr_count(tdb, attrs[i]);
465 0 : if (ret != LDB_SUCCESS) {
466 0 : talloc_free(ac);
467 0 : return ret;
468 : }
469 0 : if (strcmp("*", attrs[i]) == 0) {
470 0 : has_star = true;
471 : }
472 : }
473 0 : n_attrs = i;
474 0 : sorted_attrs = get_sorted_attrs(req,
475 : attrs,
476 : n_attrs);
477 : /*
478 : * Find, report, and remove duplicates. Duplicate attrs in
479 : * requests are allowed, but don't work well with our
480 : * merge-count algorithm.
481 : */
482 0 : j = 0;
483 0 : for (i = 1; i < n_attrs; i++) {
484 0 : if (strcasecmp(sorted_attrs[i],
485 0 : sorted_attrs[j]) == 0) {
486 0 : ret = increment_attr_count(
487 0 : count_attrs_private->duplicates->tdb,
488 0 : sorted_attrs[i]);
489 0 : if (ret != LDB_SUCCESS) {
490 0 : talloc_free(ac);
491 0 : return ret;
492 : }
493 : } else {
494 0 : j++;
495 0 : if (j != i) {
496 0 : sorted_attrs[j] = sorted_attrs[i];
497 : }
498 : }
499 : }
500 0 : n_attrs = j;
501 : }
502 :
503 0 : ac->module = module;
504 0 : ac->req = req;
505 0 : ac->has_star = has_star;
506 0 : ac->is_null = is_null;
507 0 : ac->n_attrs = n_attrs;
508 0 : ac->requested_attrs = sorted_attrs;
509 :
510 0 : ret = ldb_build_search_req_ex(&down_req,
511 : ldb,
512 : ac,
513 : req->op.search.base,
514 : req->op.search.scope,
515 : req->op.search.tree,
516 : req->op.search.attrs,
517 : req->controls,
518 : ac,
519 : count_attrs_search_callback,
520 : req);
521 0 : if (ret != LDB_SUCCESS) {
522 0 : return ldb_operr(ldb);
523 : }
524 :
525 0 : return ldb_next_request(module, down_req);
526 : }
527 :
528 :
529 0 : static struct tdb_wrap * open_private_tdb(TALLOC_CTX *mem_ctx,
530 : struct loadparm_context *lp_ctx,
531 : const char *name)
532 : {
533 0 : struct tdb_wrap *store = NULL;
534 0 : char *filename = lpcfg_private_path(mem_ctx, lp_ctx, name);
535 :
536 0 : if (filename == NULL) {
537 0 : return NULL;
538 : }
539 :
540 0 : store = tdb_wrap_open(mem_ctx, filename, 1000,
541 : TDB_CLEAR_IF_FIRST,
542 : O_RDWR | O_CREAT,
543 : 0660);
544 0 : if (store == NULL) {
545 0 : DBG_ERR("failed to open tdb at %s\n", filename);
546 : }
547 0 : TALLOC_FREE(filename);
548 0 : return store;
549 : }
550 :
551 0 : static int make_private_dir(TALLOC_CTX *mem_ctx,
552 : struct loadparm_context *lp_ctx,
553 : const char *name)
554 : {
555 0 : int ret;
556 0 : char *dirname = lpcfg_private_path(mem_ctx, lp_ctx, name);
557 0 : if (dirname == NULL) {
558 0 : return -1;
559 : }
560 0 : ret = mkdir(dirname, 0755);
561 0 : TALLOC_FREE(dirname);
562 0 : return ret;
563 : }
564 :
565 :
566 0 : static int count_attrs_init(struct ldb_module *module)
567 : {
568 0 : struct ldb_context *ldb = NULL;
569 0 : struct count_attrs_private *data = NULL;
570 0 : struct loadparm_context *lp_ctx = NULL;
571 0 : int ret;
572 :
573 0 : ldb = ldb_module_get_ctx(module);
574 :
575 0 : data = talloc_zero(module, struct count_attrs_private);
576 0 : if (data == NULL) {
577 0 : return ldb_oom(ldb);
578 : }
579 :
580 0 : lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
581 : struct loadparm_context);
582 :
583 0 : ret = make_private_dir(data, lp_ctx, "debug");
584 0 : if (ret != 0) {
585 0 : goto no_private_dir;
586 : }
587 0 : data->requested = open_private_tdb(data, lp_ctx,
588 : "debug/attr_counts_requested.tdb");
589 0 : data->duplicates = \
590 0 : open_private_tdb(data, lp_ctx,
591 : "debug/attr_counts_duplicates.tdb");
592 0 : data->found = open_private_tdb(data, lp_ctx,
593 : "debug/attr_counts_found.tdb");
594 0 : data->not_found = open_private_tdb(data, lp_ctx,
595 : "debug/attr_counts_not_found.tdb");
596 0 : data->unwanted = open_private_tdb(data, lp_ctx,
597 : "debug/attr_counts_unwanted.tdb");
598 0 : data->star_match = open_private_tdb(data, lp_ctx,
599 : "debug/attr_counts_star_match.tdb");
600 0 : data->null_req = open_private_tdb(data, lp_ctx,
601 : "debug/attr_counts_null_req.tdb");
602 0 : data->empty_req = open_private_tdb(data, lp_ctx,
603 : "debug/attr_counts_empty_req.tdb");
604 0 : data->req_vs_found = \
605 0 : open_private_tdb(data, lp_ctx,
606 : "debug/attr_counts_req_vs_found.tdb");
607 0 : if (data->requested == NULL ||
608 0 : data->duplicates == NULL ||
609 0 : data->found == NULL ||
610 0 : data->not_found == NULL ||
611 0 : data->unwanted == NULL ||
612 0 : data->star_match == NULL ||
613 0 : data->null_req == NULL ||
614 0 : data->empty_req == NULL ||
615 0 : data->req_vs_found == NULL) {
616 0 : goto no_private_dir;
617 : }
618 :
619 0 : ldb_module_set_private(module, data);
620 0 : return ldb_next_init(module);
621 :
622 0 : no_private_dir:
623 : /*
624 : * If we leave the private data NULL, the search function knows not to
625 : * do anything.
626 : */
627 0 : DBG_WARNING("the count_attrs module could not open its databases\n");
628 0 : DBG_WARNING("attributes will not be counted.\n");
629 0 : TALLOC_FREE(data);
630 0 : ldb_module_set_private(module, NULL);
631 0 : return ldb_next_init(module);
632 : }
633 :
634 : static const struct ldb_module_ops ldb_count_attrs_module_ops = {
635 : .name = "count_attrs",
636 : .search = count_attrs_search,
637 : .init_context = count_attrs_init
638 : };
639 :
640 6040 : int ldb_count_attrs_init(const char *version)
641 : {
642 6040 : LDB_MODULE_CHECK_VERSION(version);
643 6040 : return ldb_register_module(&ldb_count_attrs_module_ops);
644 : }
|