]> git.ipfire.org Git - thirdparty/squid.git/blame - src/store_digest.cc
Docs: Copyright updates for 2018 (#114)
[thirdparty/squid.git] / src / store_digest.cc
CommitLineData
8638fc66 1/*
5b74111a 2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
8638fc66 7 */
8
bbc27441
AJ
9/* DEBUG: section 71 Store Digest Manager */
10
6168bccd 11/*
12 * TODO: We probably do not track all the cases when
13 * storeDigestNoteStoreReady() must be called; this may prevent
14 * storeDigestRebuild/write schedule to be activated
15 */
16
582c2af2
FC
17#include "squid.h"
18#include "Debug.h"
a553a5a3 19#include "event.h"
582c2af2 20#include "globals.h"
8822ebee 21#include "mgr/Registration.h"
35a28a37 22#include "store_digest.h"
d6fd3381 23
b814e8d4
FC
24#if USE_CACHE_DIGESTS
25#include "CacheDigest.h"
528b2c61 26#include "HttpReply.h"
582c2af2 27#include "HttpRequest.h"
308e60be 28#include "internal.h"
528b2c61 29#include "MemObject.h"
aa839030 30#include "PeerDigest.h"
c6f15d40 31#include "refresh.h"
4d5904f7 32#include "SquidConfig.h"
985c86bc 33#include "SquidTime.h"
582c2af2 34#include "Store.h"
c8f4eac4 35#include "StoreSearch.h"
ed6e9fb9 36#include "util.h"
528b2c61 37
074d6a40 38#include <cmath>
582c2af2 39
d6fd3381 40/*
41 * local types
42 */
12784378 43
c8f4eac4 44class StoreDigestState
62e76326 45{
c8f4eac4 46
47public:
12784378 48 StoreDigestCBlock cblock;
f53969cc
SM
49 int rebuild_lock; /* bucket number */
50 StoreEntry * rewrite_lock; /* points to store entry with the digest */
c8f4eac4 51 StoreSearchPointer theSearch;
12784378 52 int rewrite_offset;
53 int rebuild_count;
b644367b 54 int rewrite_count;
c8f4eac4 55};
12784378 56
26ac0430 57typedef struct {
f53969cc
SM
58 int del_count; /* #store entries deleted from store_digest */
59 int del_lost_count; /* #store entries not found in store_digest on delete */
60 int add_count; /* #store entries accepted to store_digest */
61 int add_coll_count; /* #accepted entries that collided with existing ones */
62 int rej_count; /* #store entries not accepted to store_digest */
63 int rej_coll_count; /* #not accepted entries that collided with existing ones */
2fadd50d 64} StoreDigestStats;
6168bccd 65
462f66d2 66/* local vars */
12784378 67static StoreDigestState sd_state;
6168bccd 68static StoreDigestStats sd_stats;
12784378 69
70/* local prototypes */
6168bccd 71static void storeDigestRebuildStart(void *datanotused);
d6fd3381 72static void storeDigestRebuildResume(void);
73static void storeDigestRebuildFinish(void);
12784378 74static void storeDigestRebuildStep(void *datanotused);
d6fd3381 75static void storeDigestRewriteStart(void *);
76static void storeDigestRewriteResume(void);
b644367b 77static void storeDigestRewriteFinish(StoreEntry * e);
d6f51e3c 78static EVH storeDigestSwapOutStep;
b644367b 79static void storeDigestCBlockSwapOut(StoreEntry * e);
d6fd3381 80static void storeDigestAdd(const StoreEntry *);
12784378 81
831e953c
AJ
82/// calculates digest capacity
83static uint64_t
84storeDigestCalcCap()
85{
86 /*
87 * To-Do: Bloom proved that the optimal filter utilization is 50% (half of
88 * the bits are off). However, we do not have a formula to calculate the
89 * number of _entries_ we want to pre-allocate for.
90 */
91 const uint64_t hi_cap = Store::Root().maxSize() / Config.Store.avgObjectSize;
92 const uint64_t lo_cap = 1 + Store::Root().currentSize() / Config.Store.avgObjectSize;
93 const uint64_t e_count = StoreEntry::inUseCount();
94 uint64_t cap = e_count ? e_count : hi_cap;
95 debugs(71, 2, "have: " << e_count << ", want " << cap <<
96 " entries; limits: [" << lo_cap << ", " << hi_cap << "]");
97
98 if (cap < lo_cap)
99 cap = lo_cap;
100
101 /* do not enforce hi_cap limit, average-based estimation may be wrong
102 *if (cap > hi_cap)
103 * cap = hi_cap;
104 */
105
106 // Bug 4534: we still have to set an upper-limit at some reasonable value though.
107 // this matches cacheDigestCalcMaskSize doing (cap*bpe)+7 < INT_MAX
108 const uint64_t absolute_max = (INT_MAX -8) / Config.digest.bits_per_entry;
109 if (cap > absolute_max) {
6008010b
AJ
110 static time_t last_loud = 0;
111 if (last_loud < squid_curtime - 86400) {
112 debugs(71, DBG_IMPORTANT, "WARNING: Cache Digest cannot store " << cap << " entries. Limiting to " << absolute_max);
113 last_loud = squid_curtime;
114 } else {
115 debugs(71, 3, "WARNING: Cache Digest cannot store " << cap << " entries. Limiting to " << absolute_max);
116 }
831e953c
AJ
117 cap = absolute_max;
118 }
119
120 return cap;
121}
122#endif /* USE_CACHE_DIGESTS */
12784378 123
8638fc66 124void
d6fd3381 125storeDigestInit(void)
8638fc66 126{
8f0386af 127 Mgr::RegisterAction("store_digest", "Store Digest", storeDigestReport, 0, 1);
d120ed12 128
6cfa8966 129#if USE_CACHE_DIGESTS
7e3ce7b9 130 if (!Config.onoff.digest_generation) {
62e76326 131 store_digest = NULL;
bf8fe701 132 debugs(71, 3, "Local cache digest generation disabled");
62e76326 133 return;
7e3ce7b9 134 }
62e76326 135
831e953c 136 const uint64_t cap = storeDigestCalcCap();
e04fc9d3 137 store_digest = new CacheDigest(cap, Config.digest.bits_per_entry);
e0236918 138 debugs(71, DBG_IMPORTANT, "Local cache digest enabled; rebuild/rewrite every " <<
bf8fe701 139 (int) Config.digest.rebuild_period << "/" <<
140 (int) Config.digest.rewrite_period << " sec");
141
12784378 142 memset(&sd_state, 0, sizeof(sd_state));
d6fd3381 143#else
144 store_digest = NULL;
bf8fe701 145 debugs(71, 3, "Local cache digest is 'off'");
d6fd3381 146#endif
8638fc66 147}
148
6168bccd 149/* called when store_rebuild completes */
8638fc66 150void
d6fd3381 151storeDigestNoteStoreReady(void)
12784378 152{
d6fd3381 153#if USE_CACHE_DIGESTS
62e76326 154
7e3ce7b9 155 if (Config.onoff.digest_generation) {
62e76326 156 storeDigestRebuildStart(NULL);
157 storeDigestRewriteStart(NULL);
7e3ce7b9 158 }
62e76326 159
d6fd3381 160#endif
161}
162
35a28a37 163//TODO: this seems to be dead code. Is it needed?
d6fd3381 164void
165storeDigestDel(const StoreEntry * entry)
166{
167#if USE_CACHE_DIGESTS
62e76326 168
7e3ce7b9 169 if (!Config.onoff.digest_generation) {
62e76326 170 return;
7e3ce7b9 171 }
62e76326 172
d6fd3381 173 assert(entry && store_digest);
bf8fe701 174 debugs(71, 6, "storeDigestDel: checking entry, key: " << entry->getMD5Text());
62e76326 175
d46a87a8 176 if (!EBIT_TEST(entry->flags, KEY_PRIVATE)) {
6fc4e508 177 if (!store_digest->contains(static_cast<const cache_key *>(entry->key))) {
5db6bf73 178 ++sd_stats.del_lost_count;
bf8fe701 179 debugs(71, 6, "storeDigestDel: lost entry, key: " << entry->getMD5Text() << " url: " << entry->url() );
62e76326 180 } else {
5db6bf73 181 ++sd_stats.del_count;
fbba122c 182 store_digest->remove(static_cast<const cache_key *>(entry->key));
bf8fe701 183 debugs(71, 6, "storeDigestDel: deled entry, key: " << entry->getMD5Text());
62e76326 184 }
d6fd3381 185 }
35a28a37 186#endif //USE_CACHE_DIGESTS
12784378 187}
188
12784378 189void
d6fd3381 190storeDigestReport(StoreEntry * e)
191{
192#if USE_CACHE_DIGESTS
62e76326 193
7e3ce7b9 194 if (!Config.onoff.digest_generation) {
62e76326 195 return;
7e3ce7b9 196 }
62e76326 197
d6fd3381 198 if (store_digest) {
62e76326 199 cacheDigestReport(store_digest, "store", e);
200 storeAppendPrintf(e, "\t added: %d rejected: %d ( %.2f %%) del-ed: %d\n",
201 sd_stats.add_count,
202 sd_stats.rej_count,
203 xpercent(sd_stats.rej_count, sd_stats.rej_count + sd_stats.add_count),
204 sd_stats.del_count);
205 storeAppendPrintf(e, "\t collisions: on add: %.2f %% on rej: %.2f %%\n",
206 xpercent(sd_stats.add_coll_count, sd_stats.add_count),
207 xpercent(sd_stats.rej_coll_count, sd_stats.rej_count));
d6fd3381 208 } else {
62e76326 209 storeAppendPrintf(e, "store digest: disabled.\n");
d6fd3381 210 }
62e76326 211
35a28a37 212#endif //USE_CACHE_DIGESTS
d6fd3381 213}
214
215/*
216 * LOCAL FUNCTIONS
217 */
218
219#if USE_CACHE_DIGESTS
220
c68e9c6b 221/* should we digest this entry? used by storeDigestAdd() */
222static int
223storeDigestAddable(const StoreEntry * e)
224{
225 /* add some stats! XXX */
226
bf8fe701 227 debugs(71, 6, "storeDigestAddable: checking entry, key: " << e->getMD5Text());
c68e9c6b 228
3900307b 229 /* check various entry flags (mimics StoreEntry::checkCachable XXX) */
62e76326 230
c68e9c6b 231 if (EBIT_TEST(e->flags, KEY_PRIVATE)) {
bf8fe701 232 debugs(71, 6, "storeDigestAddable: NO: private key");
62e76326 233 return 0;
c68e9c6b 234 }
62e76326 235
c68e9c6b 236 if (EBIT_TEST(e->flags, ENTRY_NEGCACHED)) {
bf8fe701 237 debugs(71, 6, "storeDigestAddable: NO: negative cached");
62e76326 238 return 0;
c68e9c6b 239 }
62e76326 240
c68e9c6b 241 if (EBIT_TEST(e->flags, RELEASE_REQUEST)) {
bf8fe701 242 debugs(71, 6, "storeDigestAddable: NO: release requested");
62e76326 243 return 0;
c68e9c6b 244 }
62e76326 245
c68e9c6b 246 if (e->store_status == STORE_OK && EBIT_TEST(e->flags, ENTRY_BAD_LENGTH)) {
bf8fe701 247 debugs(71, 6, "storeDigestAddable: NO: wrong content-length");
62e76326 248 return 0;
c68e9c6b 249 }
62e76326 250
c68e9c6b 251 /* do not digest huge objects */
47f6e231 252 if (e->swap_file_sz > (uint64_t )Config.Store.maxObjectSize) {
bf8fe701 253 debugs(71, 6, "storeDigestAddable: NO: too big");
62e76326 254 return 0;
c68e9c6b 255 }
62e76326 256
c68e9c6b 257 /* still here? check staleness */
258 /* Note: We should use the time of the next rebuild, not (cur_time+period) */
7e3ce7b9 259 if (refreshCheckDigest(e, Config.digest.rebuild_period)) {
4a7a3d56 260 debugs(71, 6, "storeDigestAdd: entry expires within " << Config.digest.rebuild_period << " secs, ignoring");
62e76326 261 return 0;
c68e9c6b 262 }
62e76326 263
17a80fc2 264 /*
265 * idea: how about also skipping very fresh (thus, potentially
266 * unstable) entries? Should be configurable through
267 * cd_refresh_pattern, of course.
268 */
269 /*
270 * idea: skip objects that are going to be purged before the next
271 * update.
272 */
c68e9c6b 273 return 1;
274}
275
d6fd3381 276static void
6168bccd 277storeDigestAdd(const StoreEntry * entry)
278{
6168bccd 279 assert(entry && store_digest);
c68e9c6b 280
281 if (storeDigestAddable(entry)) {
5db6bf73 282 ++sd_stats.add_count;
62e76326 283
6fc4e508 284 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
5db6bf73 285 ++sd_stats.add_coll_count;
62e76326 286
fbba122c 287 store_digest->add(static_cast<const cache_key *>(entry->key));
62e76326 288
bf8fe701 289 debugs(71, 6, "storeDigestAdd: added entry, key: " << entry->getMD5Text());
6168bccd 290 } else {
5db6bf73 291 ++sd_stats.rej_count;
62e76326 292
6fc4e508 293 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
5db6bf73 294 ++sd_stats.rej_coll_count;
6168bccd 295 }
12784378 296}
297
12784378 298/* rebuilds digest from scratch */
299static void
6168bccd 300storeDigestRebuildStart(void *datanotused)
8638fc66 301{
302 assert(store_digest);
12784378 303 /* prevent overlapping if rebuild schedule is too tight */
62e76326 304
12784378 305 if (sd_state.rebuild_lock) {
e0236918 306 debugs(71, DBG_IMPORTANT, "storeDigestRebuildStart: overlap detected, consider increasing rebuild period");
62e76326 307 return;
12784378 308 }
62e76326 309
12784378 310 sd_state.rebuild_lock = 1;
bf8fe701 311 debugs(71, 2, "storeDigestRebuildStart: rebuild #" << sd_state.rebuild_count + 1);
62e76326 312
6168bccd 313 if (sd_state.rewrite_lock) {
bf8fe701 314 debugs(71, 2, "storeDigestRebuildStart: waiting for Rewrite to finish.");
62e76326 315 return;
6168bccd 316 }
62e76326 317
6168bccd 318 storeDigestRebuildResume();
319}
320
831e953c
AJ
321/// \returns true if we actually resized the digest
322static bool
323storeDigestResize()
324{
325 const uint64_t cap = storeDigestCalcCap();
326 assert(store_digest);
3faf5197
AJ
327 uint64_t diff;
328 if (cap > store_digest->capacity)
329 diff = cap - store_digest->capacity;
330 else
331 diff = store_digest->capacity - cap;
831e953c
AJ
332 debugs(71, 2, store_digest->capacity << " -> " << cap << "; change: " <<
333 diff << " (" << xpercentInt(diff, store_digest->capacity) << "%)" );
334 /* avoid minor adjustments */
335
336 if (diff <= store_digest->capacity / 10) {
337 debugs(71, 2, "small change, will not resize.");
338 return false;
339 } else {
340 debugs(71, 2, "big change, resizing.");
341 store_digest->updateCapacity(cap);
342 }
343 return true;
344}
345
6168bccd 346/* called be Rewrite to push Rebuild forward */
347static void
d6fd3381 348storeDigestRebuildResume(void)
6168bccd 349{
350 assert(sd_state.rebuild_lock);
351 assert(!sd_state.rewrite_lock);
2745fea5 352 sd_state.theSearch = Store::Root().search();
304b267e 353 /* resize or clear */
62e76326 354
304b267e 355 if (!storeDigestResize())
28faff32 356 store_digest->clear(); /* not clean()! */
62e76326 357
6168bccd 358 memset(&sd_stats, 0, sizeof(sd_stats));
62e76326 359
c43f5247 360 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, NULL, 0.0, 1);
12784378 361}
362
363/* finishes swap out sequence for the digest; schedules next rebuild */
364static void
d6fd3381 365storeDigestRebuildFinish(void)
12784378 366{
367 assert(sd_state.rebuild_lock);
368 sd_state.rebuild_lock = 0;
5db6bf73 369 ++sd_state.rebuild_count;
bf8fe701 370 debugs(71, 2, "storeDigestRebuildFinish: done.");
7e3ce7b9 371 eventAdd("storeDigestRebuildStart", storeDigestRebuildStart, NULL, (double)
62e76326 372 Config.digest.rebuild_period, 1);
6168bccd 373 /* resume pending Rewrite if any */
62e76326 374
12784378 375 if (sd_state.rewrite_lock)
62e76326 376 storeDigestRewriteResume();
12784378 377}
378
379/* recalculate a few hash buckets per invocation; schedules next step */
380static void
381storeDigestRebuildStep(void *datanotused)
382{
c8f4eac4 383 /* TODO: call Store::Root().size() to determine this.. */
384 int count = Config.Store.objectsPerBucket * (int) ceil((double) store_hash_buckets *
385 (double) Config.digest.rebuild_chunk_percentage / 100.0);
12784378 386 assert(sd_state.rebuild_lock);
62e76326 387
bf8fe701 388 debugs(71, 3, "storeDigestRebuildStep: buckets: " << store_hash_buckets << " entries to check: " << count);
62e76326 389
c8f4eac4 390 while (count-- && !sd_state.theSearch->isDone() && sd_state.theSearch->next())
391 storeDigestAdd(sd_state.theSearch->currentItem());
62e76326 392
12784378 393 /* are we done ? */
c8f4eac4 394 if (sd_state.theSearch->isDone())
62e76326 395 storeDigestRebuildFinish();
12784378 396 else
62e76326 397 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, NULL, 0.0, 1);
12784378 398}
399
12784378 400/* starts swap out sequence for the digest */
401static void
6168bccd 402storeDigestRewriteStart(void *datanotused)
12784378 403{
12784378 404 assert(store_digest);
405 /* prevent overlapping if rewrite schedule is too tight */
62e76326 406
12784378 407 if (sd_state.rewrite_lock) {
e0236918 408 debugs(71, DBG_IMPORTANT, "storeDigestRewrite: overlap detected, consider increasing rewrite period");
62e76326 409 return;
12784378 410 }
62e76326 411
bf8fe701 412 debugs(71, 2, "storeDigestRewrite: start rewrite #" << sd_state.rewrite_count + 1);
8babada0
AJ
413
414 const char *url = internalLocalUri("/squid-internal-periodic/", SBuf(StoreDigestFileName));
415 const MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initCacheDigest);
416 auto req = HttpRequest::FromUrl(url, mx);
417
418 RequestFlags flags;
e857372a 419 flags.cachable = true;
8babada0
AJ
420
421 StoreEntry *e = storeCreateEntry(url, url, flags, Http::METHOD_GET);
28c60158 422 assert(e);
aa839030 423 sd_state.rewrite_lock = e;
bf8fe701 424 debugs(71, 3, "storeDigestRewrite: url: " << url << " key: " << e->getMD5Text());
8babada0 425 e->mem_obj->request = req;
62e76326 426
8babada0 427 /* wait for rebuild (if any) to finish */
6168bccd 428 if (sd_state.rebuild_lock) {
bf8fe701 429 debugs(71, 2, "storeDigestRewriteStart: waiting for rebuild to finish.");
62e76326 430 return;
6168bccd 431 }
62e76326 432
6168bccd 433 storeDigestRewriteResume();
434}
435
436static void
d6fd3381 437storeDigestRewriteResume(void)
6168bccd 438{
28c60158 439 StoreEntry *e;
6168bccd 440
441 assert(sd_state.rewrite_lock);
442 assert(!sd_state.rebuild_lock);
aa839030 443 e = sd_state.rewrite_lock;
12784378 444 sd_state.rewrite_offset = 0;
d46a87a8 445 EBIT_SET(e->flags, ENTRY_SPECIAL);
462f66d2 446 /* setting public key will purge old digest entry if any */
d88e3c49 447 e->setPublicKey();
462f66d2 448 /* fake reply */
06a5ae20 449 HttpReply *rep = new HttpReply;
955394ce 450 rep->setHeaders(Http::scOkay, "Cache Digest OK",
11992b6f
AJ
451 "application/cache-digest", (store_digest->mask_size + sizeof(sd_state.cblock)),
452 squid_curtime, (squid_curtime + Config.digest.rewrite_period) );
26ac0430 453 debugs(71, 3, "storeDigestRewrite: entry expires on " << rep->expires <<
bf8fe701 454 " (" << std::showpos << (int) (rep->expires - squid_curtime) << ")");
3900307b 455 e->buffer();
db237875 456 e->replaceHttpReply(rep);
12784378 457 storeDigestCBlockSwapOut(e);
3900307b 458 e->flush();
aa839030 459 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, sd_state.rewrite_lock, 0.0, 1, false);
12784378 460}
461
462/* finishes swap out sequence for the digest; schedules next rewrite */
463static void
b644367b 464storeDigestRewriteFinish(StoreEntry * e)
12784378 465{
aa839030 466 assert(e == sd_state.rewrite_lock);
528b2c61 467 e->complete();
3900307b 468 e->timestampsSet();
26ac0430 469 debugs(71, 2, "storeDigestRewriteFinish: digest expires at " << e->expires <<
bf8fe701 470 " (" << std::showpos << (int) (e->expires - squid_curtime) << ")");
6168bccd 471 /* is this the write order? @?@ */
528b2c61 472 e->mem_obj->unlinkRequest();
acc5dc4c 473 e->unlock("storeDigestRewriteFinish");
feefc1d9 474 sd_state.rewrite_lock = NULL;
5db6bf73 475 ++sd_state.rewrite_count;
7e3ce7b9 476 eventAdd("storeDigestRewriteStart", storeDigestRewriteStart, NULL, (double)
62e76326 477 Config.digest.rewrite_period, 1);
6168bccd 478 /* resume pending Rebuild if any */
62e76326 479
6168bccd 480 if (sd_state.rebuild_lock)
62e76326 481 storeDigestRebuildResume();
12784378 482}
483
484/* swaps out one digest "chunk" per invocation; schedules next swap out */
485static void
52040193 486storeDigestSwapOutStep(void *data)
12784378 487{
aa839030 488 StoreEntry *e = static_cast<StoreEntry *>(data);
7e3ce7b9 489 int chunk_size = Config.digest.swapout_chunk_size;
aa839030 490 assert(e == sd_state.rewrite_lock);
12784378 491 assert(e);
12784378 492 /* _add_ check that nothing bad happened while we were waiting @?@ @?@ */
62e76326 493
831e953c 494 if (static_cast<uint32_t>(sd_state.rewrite_offset + chunk_size) > store_digest->mask_size)
62e76326 495 chunk_size = store_digest->mask_size - sd_state.rewrite_offset;
496
3900307b 497 e->append(store_digest->mask + sd_state.rewrite_offset, chunk_size);
62e76326 498
e4049756 499 debugs(71, 3, "storeDigestSwapOutStep: size: " << store_digest->mask_size <<
500 " offset: " << sd_state.rewrite_offset << " chunk: " <<
501 chunk_size << " bytes");
62e76326 502
12784378 503 sd_state.rewrite_offset += chunk_size;
62e76326 504
12784378 505 /* are we done ? */
831e953c 506 if (static_cast<uint32_t>(sd_state.rewrite_offset) >= store_digest->mask_size)
62e76326 507 storeDigestRewriteFinish(e);
12784378 508 else
aa839030 509 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, data, 0.0, 1, false);
12784378 510}
511
512static void
b644367b 513storeDigestCBlockSwapOut(StoreEntry * e)
12784378 514{
12784378 515 memset(&sd_state.cblock, 0, sizeof(sd_state.cblock));
462f66d2 516 sd_state.cblock.ver.current = htons(CacheDigestVer.current);
517 sd_state.cblock.ver.required = htons(CacheDigestVer.required);
518 sd_state.cblock.capacity = htonl(store_digest->capacity);
519 sd_state.cblock.count = htonl(store_digest->count);
520 sd_state.cblock.del_count = htonl(store_digest->del_count);
521 sd_state.cblock.mask_size = htonl(store_digest->mask_size);
831e953c 522 sd_state.cblock.bits_per_entry = Config.digest.bits_per_entry;
6168bccd 523 sd_state.cblock.hash_func_count = (unsigned char) CacheDigestHashFuncCount;
3900307b 524 e->append((char *) &sd_state.cblock, sizeof(sd_state.cblock));
8638fc66 525}
526
d6fd3381 527#endif /* USE_CACHE_DIGESTS */
f53969cc 528