Coverage Report

Created: 2026-09-03 07:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/lib/tdb/common/summary.c
Line
Count
Source
1
 /*
2
   Trivial Database: human-readable summary code
3
   Copyright (C) Rusty Russell 2010
4
5
   This library is free software; you can redistribute it and/or
6
   modify it under the terms of the GNU Lesser General Public
7
   License as published by the Free Software Foundation; either
8
   version 3 of the License, or (at your option) any later version.
9
10
   This library is distributed in the hope that it will be useful,
11
   but WITHOUT ANY WARRANTY; without even the implied warranty of
12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
   Lesser General Public License for more details.
14
15
   You should have received a copy of the GNU Lesser General Public
16
   License along with this library; if not, see <http://www.gnu.org/licenses/>.
17
*/
18
#include "tdb_private.h"
19
20
#define SUMMARY_FORMAT \
21
  "Size of file/data: %llu/%zu\n" \
22
  "Header offset/logical size: %zu/%zu\n" \
23
  "Number of records: %zu\n" \
24
  "Incompatible hash: %s\n" \
25
  "Active/supported feature flags: 0x%08x/0x%08x\n" \
26
  "Robust mutexes locking: %s\n" \
27
  "Smallest/average/largest keys: %zu/%zu/%zu\n" \
28
  "Smallest/average/largest data: %zu/%zu/%zu\n" \
29
  "Smallest/average/largest padding: %zu/%zu/%zu\n" \
30
  "Number of dead records: %zu\n" \
31
  "Smallest/average/largest dead records: %zu/%zu/%zu\n" \
32
  "Number of free records: %zu\n" \
33
  "Smallest/average/largest free records: %zu/%zu/%zu\n" \
34
  "Number of hash chains: %zu\n" \
35
  "Smallest/average/largest hash chains: %zu/%zu/%zu\n" \
36
  "Number of uncoalesced records: %zu\n" \
37
  "Smallest/average/largest uncoalesced runs: %zu/%zu/%zu\n" \
38
  "Percentage keys/data/padding/free/dead/rechdrs&tailers/hashes: %.0f/%.0f/%.0f/%.0f/%.0f/%.0f/%.0f\n"
39
40
/* We don't use tally module, to keep upstream happy. */
41
struct tally {
42
  size_t min, max, total;
43
  size_t num;
44
};
45
46
static void tally_init(struct tally *tally)
47
0
{
48
0
  tally->total = 0;
49
0
  tally->num = 0;
50
0
  tally->min = tally->max = 0;
51
0
}
52
53
static void tally_add(struct tally *tally, size_t len)
54
0
{
55
0
  if (tally->num == 0)
56
0
    tally->max = tally->min = len;
57
0
  else if (len > tally->max)
58
0
    tally->max = len;
59
0
  else if (len < tally->min)
60
0
    tally->min = len;
61
0
  tally->num++;
62
0
  tally->total += len;
63
0
}
64
65
static size_t tally_mean(const struct tally *tally)
66
0
{
67
0
  if (!tally->num)
68
0
    return 0;
69
0
  return tally->total / tally->num;
70
0
}
71
72
static size_t get_hash_length(struct tdb_context *tdb, unsigned int i)
73
0
{
74
0
  tdb_off_t rec_ptr;
75
0
  struct tdb_chainwalk_ctx chainwalk;
76
0
  size_t count = 0;
77
78
0
  if (tdb_ofs_read(tdb, TDB_HASH_TOP(i), &rec_ptr) == -1)
79
0
    return 0;
80
81
0
  tdb_chainwalk_init(&chainwalk, rec_ptr);
82
83
  /* keep looking until we find the right record */
84
0
  while (rec_ptr) {
85
0
    struct tdb_record r;
86
0
    bool ok;
87
0
    ++count;
88
0
    if (tdb_rec_read(tdb, rec_ptr, &r) == -1)
89
0
      return 0;
90
0
    rec_ptr = r.next;
91
0
    ok = tdb_chainwalk_check(tdb, &chainwalk, rec_ptr);
92
0
    if (!ok) {
93
0
      return SIZE_MAX;
94
0
    }
95
0
  }
96
0
  return count;
97
0
}
98
99
_PUBLIC_ char *tdb_summary(struct tdb_context *tdb)
100
0
{
101
0
  off_t file_size;
102
0
  tdb_off_t off, rec_off;
103
0
  struct tally freet, keys, data, dead, extra, hashval, uncoal;
104
0
  struct tdb_record rec;
105
0
  char *ret = NULL;
106
0
  bool locked;
107
0
  size_t unc = 0;
108
0
  int len;
109
0
  struct tdb_record recovery;
110
111
  /* Read-only databases use no locking at all: it's best-effort.
112
   * We may have a write lock already, so skip that case too. */
113
0
  if (tdb->read_only || tdb->allrecord_lock.count != 0) {
114
0
    locked = false;
115
0
  } else {
116
0
    if (tdb_lockall_read(tdb) == -1)
117
0
      return NULL;
118
0
    locked = true;
119
0
  }
120
121
0
  if (tdb_recovery_area(tdb, tdb->methods, &rec_off, &recovery) != 0) {
122
0
    goto unlock;
123
0
  }
124
125
0
  tally_init(&freet);
126
0
  tally_init(&keys);
127
0
  tally_init(&data);
128
0
  tally_init(&dead);
129
0
  tally_init(&extra);
130
0
  tally_init(&hashval);
131
0
  tally_init(&uncoal);
132
133
0
  for (off = TDB_DATA_START(tdb->hash_size);
134
0
       off < tdb->map_size - 1;
135
0
       off += sizeof(rec) + rec.rec_len) {
136
0
    if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec),
137
0
             DOCONV()) == -1)
138
0
      goto unlock;
139
0
    switch (rec.magic) {
140
0
    case TDB_MAGIC:
141
0
      tally_add(&keys, rec.key_len);
142
0
      tally_add(&data, rec.data_len);
143
0
      tally_add(&extra, rec.rec_len - (rec.key_len
144
0
               + rec.data_len));
145
0
      if (unc > 1)
146
0
        tally_add(&uncoal, unc - 1);
147
0
      unc = 0;
148
0
      break;
149
0
    case TDB_FREE_MAGIC:
150
0
      tally_add(&freet, rec.rec_len);
151
0
      unc++;
152
0
      break;
153
    /* If we crash after ftruncate, we can get zeroes or fill. */
154
0
    case TDB_RECOVERY_INVALID_MAGIC:
155
0
    case 0x42424242:
156
0
      unc++;
157
      /* If it's a valid recovery, we can trust rec_len. */
158
0
      if (off != rec_off) {
159
0
        rec.rec_len = tdb_dead_space(tdb, off)
160
0
          - sizeof(rec);
161
0
      }
162
163
0
      FALL_THROUGH;
164
0
    case TDB_DEAD_MAGIC:
165
0
      tally_add(&dead, rec.rec_len);
166
0
      break;
167
0
    default:
168
0
      TDB_LOG((tdb, TDB_DEBUG_ERROR,
169
0
         "Unexpected record magic 0x%x at offset %u\n",
170
0
         rec.magic, off));
171
0
      goto unlock;
172
0
    }
173
0
  }
174
0
  if (unc > 1)
175
0
    tally_add(&uncoal, unc - 1);
176
177
0
  for (off = 0; off < tdb->hash_size; off++)
178
0
    tally_add(&hashval, get_hash_length(tdb, off));
179
180
0
  file_size = tdb->hdr_ofs + tdb->map_size;
181
182
0
  len = asprintf(&ret, SUMMARY_FORMAT,
183
0
     (unsigned long long)file_size, keys.total+data.total,
184
0
     (size_t)tdb->hdr_ofs, (size_t)tdb->map_size,
185
0
     keys.num,
186
0
     (tdb->hash_fn == tdb_jenkins_hash)?"yes":"no",
187
0
     (unsigned)tdb->feature_flags, TDB_SUPPORTED_FEATURE_FLAGS,
188
0
     (tdb->feature_flags & TDB_FEATURE_FLAG_MUTEX)?"yes":"no",
189
0
     keys.min, tally_mean(&keys), keys.max,
190
0
     data.min, tally_mean(&data), data.max,
191
0
     extra.min, tally_mean(&extra), extra.max,
192
0
     dead.num,
193
0
     dead.min, tally_mean(&dead), dead.max,
194
0
     freet.num,
195
0
     freet.min, tally_mean(&freet), freet.max,
196
0
     hashval.num,
197
0
     hashval.min, tally_mean(&hashval), hashval.max,
198
0
     uncoal.total,
199
0
     uncoal.min, tally_mean(&uncoal), uncoal.max,
200
0
     keys.total * 100.0 / file_size,
201
0
     data.total * 100.0 / file_size,
202
0
     extra.total * 100.0 / file_size,
203
0
     freet.total * 100.0 / file_size,
204
0
     dead.total * 100.0 / file_size,
205
0
     (keys.num + freet.num + dead.num)
206
0
     * (sizeof(struct tdb_record) + sizeof(uint32_t))
207
0
     * 100.0 / file_size,
208
0
     tdb->hash_size * sizeof(tdb_off_t)
209
0
     * 100.0 / file_size);
210
0
  if (len == -1) {
211
0
    goto unlock;
212
0
  }
213
214
0
unlock:
215
0
  if (locked) {
216
0
    tdb_unlockall_read(tdb);
217
0
  }
218
0
  return ret;
219
0
}