]> git.ipfire.org Git - thirdparty/squid.git/blame - src/peer_digest.cc
Bug 4148: external_acl_type header format does not accept the new libformat syntax
[thirdparty/squid.git] / src / peer_digest.cc
CommitLineData
9b7de833 1/*
bbc27441 2 * Copyright (C) 1996-2014 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.
9b7de833 7 */
8
bbc27441
AJ
9/* DEBUG: section 72 Peer Digest Routines */
10
582c2af2 11#include "squid.h"
6cfa8966 12#if USE_CACHE_DIGESTS
b814e8d4 13#include "CacheDigest.h"
a011edee 14#include "CachePeer.h"
a553a5a3 15#include "event.h"
eb13c21e 16#include "FwdState.h"
af69c635 17#include "globals.h"
528b2c61 18#include "HttpReply.h"
582c2af2 19#include "HttpRequest.h"
308e60be 20#include "internal.h"
528b2c61 21#include "MemObject.h"
b6149797 22#include "mime_header.h"
602d9612 23#include "neighbors.h"
aa839030 24#include "PeerDigest.h"
985c86bc 25#include "SquidTime.h"
582c2af2 26#include "Store.h"
fb548aaf 27#include "store_key_md5.h"
598465f1 28#include "StoreClient.h"
4e540555 29#include "tools.h"
598465f1 30
9b7de833 31/* local types */
32
33/* local prototypes */
e13ee7ad 34static time_t peerDigestIncDelay(const PeerDigest * pd);
35static time_t peerDigestNewDelay(const StoreEntry * e);
36static void peerDigestSetCheck(PeerDigest * pd, time_t delay);
eb16313f 37static void peerDigestClean(PeerDigest *);
e13ee7ad 38static EVH peerDigestCheck;
39static void peerDigestRequest(PeerDigest * pd);
add2192d 40static STCB peerDigestHandleReply;
3dfaa3d2 41static int peerDigestFetchReply(void *, char *, ssize_t);
1f140227 42int peerDigestSwapInHeaders(void *, char *, ssize_t);
43int peerDigestSwapInCBlock(void *, char *, ssize_t);
44int peerDigestSwapInMask(void *, char *, ssize_t);
4b4cd312 45static int peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name);
551e60a9 46static void peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason);
e13ee7ad 47static void peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason);
48static void peerDigestReqFinish(DigestFetchState * fetch, char *buf, int, int, int, const char *reason, int err);
49static void peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err);
50static void peerDigestFetchFinish(DigestFetchState * fetch, int err);
51static void peerDigestFetchSetStats(DigestFetchState * fetch);
52static int peerDigestSetCBlock(PeerDigest * pd, const char *buf);
53static int peerDigestUseful(const PeerDigest * pd);
395b813e 54
9b7de833 55/* local constants */
aa839030 56Version const CacheDigestVer = { 5, 3 };
9d486b43 57
9b7de833 58#define StoreDigestCBlockSize sizeof(StoreDigestCBlock)
59
e13ee7ad 60/* min interval for requesting digests from a given peer */
61static const time_t PeerDigestReqMinGap = 5 * 60; /* seconds */
62/* min interval for requesting digests (cumulative request stream) */
63static const time_t GlobDigestReqMinGap = 1 * 60; /* seconds */
bd890734 64
65/* local vars */
9d486b43 66
8a6218c6 67static time_t pd_last_req_time = 0; /* last call to Check */
9b7de833 68
e13ee7ad 69/* initialize peer digest */
70static void
a3c6762c 71peerDigestInit(PeerDigest * pd, CachePeer * p)
9b7de833 72{
e13ee7ad 73 assert(pd && p);
74
75 memset(pd, 0, sizeof(*pd));
4e454ade 76 /*
77 * DPW 2007-04-12
78 * Lock on to the peer here. The corresponding cbdataReferenceDone()
79 * is in peerDigestDestroy().
80 */
81 pd->peer = cbdataReference(p);
e13ee7ad 82 /* if peer disappears, we will know it's name */
528b2c61 83 pd->host = p->host;
e13ee7ad 84
85 pd->times.initialized = squid_curtime;
9b7de833 86}
87
4b4cd312 88static void
8a6218c6 89peerDigestClean(PeerDigest * pd)
9b7de833 90{
e13ee7ad 91 assert(pd);
62e76326 92
e13ee7ad 93 if (pd->cd)
62e76326 94 cacheDigestDestroy(pd->cd);
95
30abd221 96 pd->host.clean();
9b7de833 97}
98
0353e724 99CBDATA_CLASS_INIT(PeerDigest);
100
e13ee7ad 101/* allocate new peer digest, call Init, and lock everything */
102PeerDigest *
a3c6762c 103peerDigestCreate(CachePeer * p)
e13ee7ad 104{
105 PeerDigest *pd;
8a6218c6 106 assert(p);
e13ee7ad 107
0353e724 108 pd = new PeerDigest;
e13ee7ad 109 peerDigestInit(pd, p);
e13ee7ad 110
fa80a8ef 111 /* XXX This does not look right, and the same thing again in the caller */
112 return cbdataReference(pd);
e13ee7ad 113}
114
115/* call Clean and free/unlock everything */
2d72d4fd 116static void
8a6218c6 117peerDigestDestroy(PeerDigest * pd)
e13ee7ad 118{
8abf232c 119 void *p;
e13ee7ad 120 assert(pd);
8abf232c 121 void * peerTmp = pd->peer;
e13ee7ad 122
4e454ade 123 /*
124 * DPW 2007-04-12
125 * We locked the peer in peerDigestInit(), this is
126 * where we unlock it. If the peer is still valid,
127 * tell it that the digest is gone.
128 */
8abf232c 129 if (cbdataReferenceValidDone(peerTmp, &p))
a3c6762c 130 peerNoteDigestGone((CachePeer *)p);
e13ee7ad 131
132 peerDigestClean(pd);
62e76326 133
00d77d6b 134 delete pd;
e13ee7ad 135}
136
137/* called by peer to indicate that somebody actually needs this digest */
138void
8a6218c6 139peerDigestNeeded(PeerDigest * pd)
e13ee7ad 140{
141 assert(pd);
142 assert(!pd->flags.needed);
143 assert(!pd->cd);
144
be4d35dc 145 pd->flags.needed = true;
e13ee7ad 146 pd->times.needed = squid_curtime;
8a6218c6 147 peerDigestSetCheck(pd, 0); /* check asap */
e13ee7ad 148}
149
150/* currently we do not have a reason to disable without destroying */
8a6218c6 151#if FUTURE_CODE
395b813e 152/* disables peer for good */
153static void
8a6218c6 154peerDigestDisable(PeerDigest * pd)
395b813e 155{
bf8fe701 156 debugs(72, 2, "peerDigestDisable: peer " << pd->host.buf() << " disabled for good");
e13ee7ad 157 pd->times.disabled = squid_curtime;
8a6218c6 158 pd->times.next_check = -1; /* never */
e13ee7ad 159 pd->flags.usable = 0;
160
161 if (pd->cd) {
62e76326 162 cacheDigestDestroy(pd->cd);
163 pd->cd = NULL;
e13ee7ad 164 }
62e76326 165
e13ee7ad 166 /* we do not destroy the pd itself to preserve its "history" and stats */
395b813e 167}
62e76326 168
e13ee7ad 169#endif
395b813e 170
e13ee7ad 171/* increment retry delay [after an unsuccessful attempt] */
395b813e 172static time_t
8a6218c6 173peerDigestIncDelay(const PeerDigest * pd)
395b813e 174{
e13ee7ad 175 assert(pd);
176 return pd->times.retry_delay > 0 ?
62e76326 177 2 * pd->times.retry_delay : /* exponential backoff */
178 PeerDigestReqMinGap; /* minimal delay */
395b813e 179}
180
62e76326 181/* artificially increases Expires: setting to avoid race conditions
e13ee7ad 182 * returns the delay till that [increased] expiration time */
00485c29 183static time_t
e13ee7ad 184peerDigestNewDelay(const StoreEntry * e)
00485c29 185{
e13ee7ad 186 assert(e);
62e76326 187
00485c29 188 if (e->expires > 0)
62e76326 189 return e->expires + PeerDigestReqMinGap - squid_curtime;
190
e13ee7ad 191 return PeerDigestReqMinGap;
00485c29 192}
193
e13ee7ad 194/* registers next digest verification */
395b813e 195static void
e13ee7ad 196peerDigestSetCheck(PeerDigest * pd, time_t delay)
395b813e 197{
e13ee7ad 198 eventAdd("peerDigestCheck", peerDigestCheck, pd, (double) delay, 1);
199 pd->times.next_check = squid_curtime + delay;
bb790702 200 debugs(72, 3, "peerDigestSetCheck: will check peer " << pd->host << " in " << delay << " secs");
e13ee7ad 201}
202
5385c86a 203/*
204 * called when peer is about to disappear or have already disappeared
205 */
e13ee7ad 206void
8a6218c6 207peerDigestNotePeerGone(PeerDigest * pd)
208{
e13ee7ad 209 if (pd->flags.requested) {
bb790702 210 debugs(72, 2, "peerDigest: peer " << pd->host << " gone, will destroy after fetch.");
62e76326 211 /* do nothing now, the fetching chain will notice and take action */
395b813e 212 } else {
bb790702 213 debugs(72, 2, "peerDigest: peer " << pd->host << " is gone, destroying now.");
62e76326 214 peerDigestDestroy(pd);
395b813e 215 }
216}
217
e13ee7ad 218/* callback for eventAdd() (with peer digest locked)
26ac0430 219 * request new digest if our copy is too old or if we lack one;
e13ee7ad 220 * schedule next check otherwise */
9b7de833 221static void
e13ee7ad 222peerDigestCheck(void *data)
9b7de833 223{
e6ccf245 224 PeerDigest *pd = (PeerDigest *)data;
e13ee7ad 225 time_t req_time;
226
e13ee7ad 227 assert(!pd->flags.requested);
5d9bb360 228
8a6218c6 229 pd->times.next_check = 0; /* unknown */
e13ee7ad 230
fa80a8ef 231 if (!cbdataReferenceValid(pd->peer)) {
62e76326 232 peerDigestNotePeerGone(pd);
233 return;
9b7de833 234 }
62e76326 235
cc192b50 236 debugs(72, 3, "peerDigestCheck: peer " << pd->peer->host << ":" << pd->peer->http_port);
26ac0430
AJ
237 debugs(72, 3, "peerDigestCheck: time: " << squid_curtime <<
238 ", last received: " << (long int) pd->times.received << " (" <<
bf8fe701 239 std::showpos << (int) (squid_curtime - pd->times.received) << ")");
e13ee7ad 240
241 /* decide when we should send the request:
242 * request now unless too close to other requests */
243 req_time = squid_curtime;
244
245 /* per-peer limit */
62e76326 246
e13ee7ad 247 if (req_time - pd->times.received < PeerDigestReqMinGap) {
b4197865 248 debugs(72, 2, "peerDigestCheck: " << pd->host <<
bf8fe701 249 ", avoiding close peer requests (" <<
250 (int) (req_time - pd->times.received) << " < " <<
251 (int) PeerDigestReqMinGap << " secs).");
252
62e76326 253 req_time = pd->times.received + PeerDigestReqMinGap;
bd890734 254 }
62e76326 255
e13ee7ad 256 /* global limit */
257 if (req_time - pd_last_req_time < GlobDigestReqMinGap) {
b4197865 258 debugs(72, 2, "peerDigestCheck: " << pd->host <<
bf8fe701 259 ", avoiding close requests (" <<
260 (int) (req_time - pd_last_req_time) << " < " <<
261 (int) GlobDigestReqMinGap << " secs).");
262
62e76326 263 req_time = pd_last_req_time + GlobDigestReqMinGap;
9b7de833 264 }
62e76326 265
e13ee7ad 266 if (req_time <= squid_curtime)
62e76326 267 peerDigestRequest(pd); /* will set pd->flags.requested */
e13ee7ad 268 else
62e76326 269 peerDigestSetCheck(pd, req_time - squid_curtime);
9b7de833 270}
271
2812146b 272CBDATA_TYPE(DigestFetchState);
273
e13ee7ad 274/* ask store for a digest */
9b7de833 275static void
e13ee7ad 276peerDigestRequest(PeerDigest * pd)
9b7de833 277{
a3c6762c 278 CachePeer *p = pd->peer;
9b7de833 279 StoreEntry *e, *old_e;
7e928efc 280 char *url = NULL;
9d486b43 281 const cache_key *key;
190154cf 282 HttpRequest *req;
9b7de833 283 DigestFetchState *fetch = NULL;
528b2c61 284 StoreIOBuffer tempBuffer;
e13ee7ad 285
286 pd->req_result = NULL;
be4d35dc 287 pd->flags.requested = true;
e13ee7ad 288
9b7de833 289 /* compute future request components */
62e76326 290
7e3ce7b9 291 if (p->digest_url)
62e76326 292 url = xstrdup(p->digest_url);
7e3ce7b9 293 else
ecafbc50 294 url = xstrdup(internalRemoteUri(p->host, p->http_port, "/squid-internal-periodic/", StoreDigestFileName));
7e3ce7b9 295
c21ad0f5 296 req = HttpRequest::CreateFromUrl(url);
62e76326 297
e13ee7ad 298 assert(req);
62e76326 299
f66a9ef4 300 key = storeKeyPublicByRequest(req);
62e76326 301
bf8fe701 302 debugs(72, 2, "peerDigestRequest: " << url << " key: " << storeKeyText(key));
e13ee7ad 303
9b7de833 304 /* add custom headers */
2246b732 305 assert(!req->header.len);
62e76326 306
a9925b40 307 req->header.putStr(HDR_ACCEPT, StoreDigestMimeStr);
62e76326 308
a9925b40 309 req->header.putStr(HDR_ACCEPT, "text/html");
62e76326 310
89000349
AJ
311 if (p->login &&
312 p->login[0] != '*' &&
b552ea1f
A
313 strcmp(p->login, "PASS") != 0 &&
314 strcmp(p->login, "PASSTHRU") != 0 &&
315 strcmp(p->login, "NEGOTIATE") != 0 &&
89000349 316 strcmp(p->login, "PROXYPASS") != 0) {
c486d50a 317 req->url.userInfo(SBuf(p->login)); // XXX: performance regression make peer login SBuf as well.
89000349 318 }
9b7de833 319 /* create fetch state structure */
2812146b 320 CBDATA_INIT_TYPE(DigestFetchState);
62e76326 321
72711e31 322 fetch = cbdataAlloc(DigestFetchState);
62e76326 323
b248c2a3
AJ
324 fetch->request = req;
325 HTTPMSGLOCK(fetch->request);
62e76326 326
fa80a8ef 327 fetch->pd = cbdataReference(pd);
62e76326 328
e13ee7ad 329 fetch->offset = 0;
62e76326 330
add2192d 331 fetch->state = DIGEST_READ_REPLY;
e13ee7ad 332
333 /* update timestamps */
9b7de833 334 fetch->start_time = squid_curtime;
62e76326 335
e13ee7ad 336 pd->times.requested = squid_curtime;
62e76326 337
e13ee7ad 338 pd_last_req_time = squid_curtime;
339
e857372a 340 req->flags.cachable = true;
62e76326 341
9b7de833 342 /* the rest is based on clientProcessExpired() */
e857372a 343 req->flags.refresh = true;
62e76326 344
c8f4eac4 345 old_e = fetch->old_entry = Store::Root().get(key);
62e76326 346
9b7de833 347 if (old_e) {
bf8fe701 348 debugs(72, 5, "peerDigestRequest: found old entry");
34266cde 349
acc5dc4c 350 old_e->lock("peerDigestRequest");
c877c0bc 351 old_e->createMemObject(url, url, req->method);
34266cde 352
62e76326 353 fetch->old_sc = storeClientListAdd(old_e, fetch);
9b7de833 354 }
62e76326 355
9b7de833 356 e = fetch->entry = storeCreateEntry(url, url, req->flags, req->method);
e13ee7ad 357 assert(EBIT_TEST(e->flags, KEY_PRIVATE));
06d2839d 358 fetch->sc = storeClientListAdd(e, fetch);
9b7de833 359 /* set lastmod to trigger IMS request if possible */
62e76326 360
9b7de833 361 if (old_e)
62e76326 362 e->lastmod = old_e->lastmod;
e13ee7ad 363
9b7de833 364 /* push towards peer cache */
bf8fe701 365 debugs(72, 3, "peerDigestRequest: forwarding to fwdStart...");
62e76326 366
e83cc785 367 FwdState::fwdStart(Comm::ConnectionPointer(), e, req);
62e76326 368
598465f1 369 tempBuffer.offset = 0;
62e76326 370
f95db407 371 tempBuffer.length = SM_PAGE_SIZE;
62e76326 372
598465f1 373 tempBuffer.data = fetch->buf;
62e76326 374
598465f1 375 storeClientCopy(fetch->sc, e, tempBuffer,
62e76326 376 peerDigestHandleReply, fetch);
7e928efc
AJ
377
378 safe_free(url);
9b7de833 379}
380
add2192d 381/* Handle the data copying .. */
382
383/*
384 * This routine handles the copy data and then redirects the
385 * copy to a bunch of subfunctions depending upon the copy state.
386 * It also tracks the buffer offset and "seen", since I'm actually
387 * not interested in rewriting everything to suit my little idea.
388 */
9b7de833 389static void
2324cda2 390peerDigestHandleReply(void *data, StoreIOBuffer receivedData)
add2192d 391{
e6ccf245 392 DigestFetchState *fetch = (DigestFetchState *)data;
add2192d 393 int retsize = -1;
394 digest_read_state_t prevstate;
395 int newsize;
396
2324cda2 397 assert(fetch->pd && receivedData.data);
398 /* The existing code assumes that the received pointer is
598465f1 399 * where we asked the data to be put
400 */
2324cda2 401 assert(fetch->buf + fetch->bufofs == receivedData.data);
add2192d 402
403 /* Update the buffer size */
2324cda2 404 fetch->bufofs += receivedData.length;
add2192d 405
406 assert(fetch->bufofs <= SM_PAGE_SIZE);
407
408 /* If we've fetched enough, return */
62e76326 409
add2192d 410 if (peerDigestFetchedEnough(fetch, fetch->buf, fetch->bufofs, "peerDigestHandleReply"))
62e76326 411 return;
add2192d 412
413 /* Call the right function based on the state */
414 /* (Those functions will update the state if needed) */
fa80a8ef 415
e2848e94 416 /* Give us a temporary reference. Some of the calls we make may
417 * try to destroy the fetch structure, and we like to know if they
418 * do
bac6d4bd 419 */
e2848e94 420 fetch = cbdataReference(fetch);
add2192d 421
422 /* Repeat this loop until we're out of data OR the state changes */
423 /* (So keep going if the state has changed and we still have data */
424 do {
62e76326 425 prevstate = fetch->state;
426
427 switch (fetch->state) {
428
429 case DIGEST_READ_REPLY:
430 retsize = peerDigestFetchReply(fetch, fetch->buf, fetch->bufofs);
431 break;
432
433 case DIGEST_READ_HEADERS:
434 retsize = peerDigestSwapInHeaders(fetch, fetch->buf, fetch->bufofs);
435 break;
436
437 case DIGEST_READ_CBLOCK:
438 retsize = peerDigestSwapInCBlock(fetch, fetch->buf, fetch->bufofs);
439 break;
440
441 case DIGEST_READ_MASK:
442 retsize = peerDigestSwapInMask(fetch, fetch->buf, fetch->bufofs);
443 break;
444
445 case DIGEST_READ_NONE:
446 break;
447
448 case DIGEST_READ_DONE:
449 goto finish;
450 break;
451
452 default:
453 fatal("Bad digest transfer mode!\n");
454 }
455
456 if (retsize < 0)
457 goto finish;
458
459 /*
460 * The returned size indicates how much of the buffer was read -
461 * so move the remainder of the buffer to the beginning
462 * and update the bufofs / bufsize
463 */
464 newsize = fetch->bufofs - retsize;
465
41d00cd3 466 memmove(fetch->buf, fetch->buf + retsize, fetch->bufofs - newsize);
62e76326 467
468 fetch->bufofs = newsize;
add2192d 469
e2848e94 470 } while (cbdataReferenceValid(fetch) && prevstate != fetch->state && fetch->bufofs > 0);
add2192d 471
472 /* Update the copy offset */
2324cda2 473 fetch->offset += receivedData.length;
add2192d 474
475 /* Schedule another copy */
fa80a8ef 476 if (cbdataReferenceValid(fetch)) {
62e76326 477 StoreIOBuffer tempBuffer;
478 tempBuffer.offset = fetch->offset;
479 tempBuffer.length = SM_PAGE_SIZE - fetch->bufofs;
480 tempBuffer.data = fetch->buf + fetch->bufofs;
481 storeClientCopy(fetch->sc, fetch->entry, tempBuffer,
482 peerDigestHandleReply, fetch);
add2192d 483 }
62e76326 484
485finish:
e2848e94 486 /* Get rid of our reference, we've finished with it for now */
487 cbdataReferenceDone(fetch);
add2192d 488}
489
add2192d 490/* wait for full http headers to be received then parse them */
491/*
492 * This routine handles parsing the reply line.
493 * If the reply line indicates an OK, the same data is thrown
494 * to SwapInHeaders(). If the reply line is a NOT_MODIFIED,
495 * we simply stop parsing.
496 */
497static int
9b7de833 498peerDigestFetchReply(void *data, char *buf, ssize_t size)
499{
e6ccf245 500 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 501 PeerDigest *pd = fetch->pd;
9bc73deb 502 size_t hdr_size;
e13ee7ad 503 assert(pd && buf);
9b7de833 504 assert(!fetch->offset);
e13ee7ad 505
add2192d 506 assert(fetch->state == DIGEST_READ_REPLY);
62e76326 507
9b7de833 508 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestFetchReply"))
62e76326 509 return -1;
e13ee7ad 510
9bc73deb 511 if ((hdr_size = headersEnd(buf, size))) {
62e76326 512 HttpReply const *reply = fetch->entry->getReply();
513 assert(reply);
9b769c67
AJ
514 assert(reply->sline.status() != Http::scNone);
515 const Http::StatusCode status = reply->sline.status();
b4197865 516 debugs(72, 3, "peerDigestFetchReply: " << pd->host << " status: " << status <<
26ac0430 517 ", expires: " << (long int) reply->expires << " (" << std::showpos <<
bf8fe701 518 (int) (reply->expires - squid_curtime) << ")");
62e76326 519
520 /* this "if" is based on clientHandleIMSReply() */
521
955394ce 522 if (status == Http::scNotModified) {
62e76326 523 /* our old entry is fine */
524 assert(fetch->old_entry);
525
b248c2a3
AJ
526 if (!fetch->old_entry->mem_obj->request) {
527 fetch->old_entry->mem_obj->request = fetch->entry->mem_obj->request;
528 HTTPMSGLOCK(fetch->old_entry->mem_obj->request);
529 }
62e76326 530
531 assert(fetch->old_entry->mem_obj->request);
532
07947ad8 533 HttpReply *old_rep = (HttpReply *) fetch->old_entry->getReply();
534
06a5ae20 535 old_rep->updateOnNotModified(reply);
62e76326 536
3900307b 537 fetch->old_entry->timestampsSet();
62e76326 538
539 /* get rid of 304 reply */
540 storeUnregister(fetch->sc, fetch->entry, fetch);
541
acc5dc4c 542 fetch->entry->unlock("peerDigestFetchReply 304");
62e76326 543
544 fetch->entry = fetch->old_entry;
545
546 fetch->old_entry = NULL;
547
548 /* preserve request -- we need its size to update counters */
549 /* requestUnlink(r); */
550 /* fetch->entry->mem_obj->request = NULL; */
955394ce 551 } else if (status == Http::scOkay) {
62e76326 552 /* get rid of old entry if any */
553
554 if (fetch->old_entry) {
bf8fe701 555 debugs(72, 3, "peerDigestFetchReply: got new digest, releasing old one");
62e76326 556 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
d88e3c49 557 fetch->old_entry->releaseRequest();
acc5dc4c 558 fetch->old_entry->unlock("peerDigestFetchReply 200");
62e76326 559 fetch->old_entry = NULL;
560 }
561 } else {
562 /* some kind of a bug */
9b769c67 563 peerDigestFetchAbort(fetch, buf, reply->sline.reason());
62e76326 564 return -1; /* XXX -1 will abort stuff in ReadReply! */
565 }
566
567 /* must have a ready-to-use store entry if we got here */
568 /* can we stay with the old in-memory digest? */
955394ce 569 if (status == Http::scNotModified && fetch->pd->cd) {
62e76326 570 peerDigestFetchStop(fetch, buf, "Not modified");
571 fetch->state = DIGEST_READ_DONE;
572 } else {
573 fetch->state = DIGEST_READ_HEADERS;
574 }
9b7de833 575 } else {
62e76326 576 /* need more data, do we have space? */
577
578 if (size >= SM_PAGE_SIZE)
579 peerDigestFetchAbort(fetch, buf, "reply header too big");
9b7de833 580 }
add2192d 581
582 /* We don't want to actually ack that we've handled anything,
583 * otherwise SwapInHeaders() won't get the reply line .. */
584 return 0;
9b7de833 585}
586
587/* fetch headers from disk, pass on to SwapInCBlock */
1f140227 588int
9b7de833 589peerDigestSwapInHeaders(void *data, char *buf, ssize_t size)
590{
e6ccf245 591 DigestFetchState *fetch = (DigestFetchState *)data;
9b7de833 592 size_t hdr_size;
e13ee7ad 593
add2192d 594 assert(fetch->state == DIGEST_READ_HEADERS);
62e76326 595
9b7de833 596 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInHeaders"))
62e76326 597 return -1;
e13ee7ad 598
9d486b43 599 assert(!fetch->offset);
62e76326 600
9b7de833 601 if ((hdr_size = headersEnd(buf, size))) {
62e76326 602 assert(fetch->entry->getReply());
9b769c67 603 assert(fetch->entry->getReply()->sline.status() != Http::scNone);
62e76326 604
9b769c67 605 if (fetch->entry->getReply()->sline.status() != Http::scOkay) {
e0236918 606 debugs(72, DBG_IMPORTANT, "peerDigestSwapInHeaders: " << fetch->pd->host <<
9b769c67 607 " status " << fetch->entry->getReply()->sline.status() <<
bf8fe701 608 " got cached!");
609
62e76326 610 peerDigestFetchAbort(fetch, buf, "internal status error");
611 return -1;
612 }
613
614 fetch->state = DIGEST_READ_CBLOCK;
615 return hdr_size; /* Say how much data we read */
9b7de833 616 } else {
62e76326 617 /* need more data, do we have space? */
618
619 if (size >= SM_PAGE_SIZE) {
620 peerDigestFetchAbort(fetch, buf, "stored header too big");
621 return -1;
622 } else {
623 return 0; /* We need to read more to parse .. */
624 }
9b7de833 625 }
62e76326 626
add2192d 627 fatal("peerDigestSwapInHeaders() - shouldn't get here!\n");
0b5f04b8 628 return 0; /* keep gcc happy */
9b7de833 629}
630
1f140227 631int
9b7de833 632peerDigestSwapInCBlock(void *data, char *buf, ssize_t size)
633{
e6ccf245 634 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 635
add2192d 636 assert(fetch->state == DIGEST_READ_CBLOCK);
62e76326 637
9b7de833 638 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInCBlock"))
62e76326 639 return -1;
e13ee7ad 640
e6ccf245 641 if (size >= (ssize_t)StoreDigestCBlockSize) {
62e76326 642 PeerDigest *pd = fetch->pd;
62e76326 643
e4a67a80 644 assert(pd && fetch->entry->getReply());
62e76326 645
646 if (peerDigestSetCBlock(pd, buf)) {
647 /* XXX: soon we will have variable header size */
648 /* switch to CD buffer and fetch digest guts */
649 buf = NULL;
650 assert(pd->cd->mask);
651 fetch->state = DIGEST_READ_MASK;
652 return StoreDigestCBlockSize;
653 } else {
654 peerDigestFetchAbort(fetch, buf, "invalid digest cblock");
655 return -1;
656 }
9b7de833 657 } else {
62e76326 658 /* need more data, do we have space? */
659
660 if (size >= SM_PAGE_SIZE) {
661 peerDigestFetchAbort(fetch, buf, "digest cblock too big");
662 return -1;
663 } else {
664 return 0; /* We need more data */
665 }
9b7de833 666 }
62e76326 667
add2192d 668 fatal("peerDigestSwapInCBlock(): shouldn't get here!\n");
0b5f04b8 669 return 0; /* keep gcc happy */
9b7de833 670}
671
1f140227 672int
9b7de833 673peerDigestSwapInMask(void *data, char *buf, ssize_t size)
674{
e6ccf245 675 DigestFetchState *fetch = (DigestFetchState *)data;
e13ee7ad 676 PeerDigest *pd;
677
e13ee7ad 678 pd = fetch->pd;
679 assert(pd->cd && pd->cd->mask);
9d486b43 680
add2192d 681 /*
682 * NOTENOTENOTENOTENOTE: buf doesn't point to pd->cd->mask anymore!
683 * we need to do the copy ourselves!
684 */
41d00cd3 685 memcpy(pd->cd->mask + fetch->mask_offset, buf, size);
add2192d 686
687 /* NOTE! buf points to the middle of pd->cd->mask! */
62e76326 688
add2192d 689 if (peerDigestFetchedEnough(fetch, NULL, size, "peerDigestSwapInMask"))
62e76326 690 return -1;
add2192d 691
da407def 692 fetch->mask_offset += size;
62e76326 693
57d55dfa 694 if (fetch->mask_offset >= pd->cd->mask_size) {
e4049756 695 debugs(72, 2, "peerDigestSwapInMask: Done! Got " <<
696 fetch->mask_offset << ", expected " << pd->cd->mask_size);
57d55dfa 697 assert(fetch->mask_offset == pd->cd->mask_size);
62e76326 698 assert(peerDigestFetchedEnough(fetch, NULL, 0, "peerDigestSwapInMask"));
699 return -1; /* XXX! */
e13ee7ad 700 } else {
62e76326 701 /* We always read everything, so return so */
702 return size;
9b7de833 703 }
62e76326 704
add2192d 705 fatal("peerDigestSwapInMask(): shouldn't get here!\n");
0b5f04b8 706 return 0; /* keep gcc happy */
9b7de833 707}
708
709static int
4b4cd312 710peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name)
9b7de833 711{
e13ee7ad 712 PeerDigest *pd = NULL;
8a6218c6 713 const char *host = "<unknown>"; /* peer host */
714 const char *reason = NULL; /* reason for completion */
715 const char *no_bug = NULL; /* successful completion if set */
e2848e94 716 const int pdcb_valid = cbdataReferenceValid(fetch->pd);
717 const int pcb_valid = cbdataReferenceValid(fetch->pd->peer);
e13ee7ad 718
719 /* test possible exiting conditions (the same for most steps!)
720 * cases marked with '?!' should not happen */
721
722 if (!reason) {
62e76326 723 if (!(pd = fetch->pd))
724 reason = "peer digest disappeared?!";
725
bac6d4bd 726#if DONT /* WHY NOT? /HNO */
62e76326 727
728 else if (!cbdataReferenceValid(pd))
729 reason = "invalidated peer digest?!";
730
5385c86a 731#endif
62e76326 732
733 else
b4197865 734 host = pd->host.termedBuf();
e13ee7ad 735 }
62e76326 736
e4049756 737 debugs(72, 6, step_name << ": peer " << host << ", offset: " <<
738 fetch->offset << " size: " << size << ".");
e13ee7ad 739
740 /* continue checking (with pd and host known and valid) */
62e76326 741
e13ee7ad 742 if (!reason) {
62e76326 743 if (!cbdataReferenceValid(pd->peer))
744 reason = "peer disappeared";
745 else if (size < 0)
746 reason = "swap failure";
747 else if (!fetch->entry)
748 reason = "swap aborted?!";
749 else if (EBIT_TEST(fetch->entry->flags, ENTRY_ABORTED))
750 reason = "swap aborted";
e13ee7ad 751 }
62e76326 752
e13ee7ad 753 /* continue checking (maybe-successful eof case) */
754 if (!reason && !size) {
62e76326 755 if (!pd->cd)
756 reason = "null digest?!";
47f6e231 757 else if (fetch->mask_offset != (int)pd->cd->mask_size)
62e76326 758 reason = "premature end of digest?!";
759 else if (!peerDigestUseful(pd))
760 reason = "useless digest";
761 else
762 reason = no_bug = "success";
e13ee7ad 763 }
62e76326 764
e13ee7ad 765 /* finish if we have a reason */
9b7de833 766 if (reason) {
62e76326 767 const int level = strstr(reason, "?!") ? 1 : 3;
bf8fe701 768 debugs(72, level, "" << step_name << ": peer " << host << ", exiting after '" << reason << "'");
62e76326 769 peerDigestReqFinish(fetch, buf,
770 1, pdcb_valid, pcb_valid, reason, !no_bug);
e13ee7ad 771 } else {
62e76326 772 /* paranoid check */
773 assert(pdcb_valid && pcb_valid);
e13ee7ad 774 }
62e76326 775
e13ee7ad 776 return reason != NULL;
777}
778
551e60a9 779/* call this when all callback data is valid and fetch must be stopped but
780 * no error has occurred (e.g. we received 304 reply and reuse old digest) */
781static void
782peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason)
783{
784 assert(reason);
b4197865 785 debugs(72, 2, "peerDigestFetchStop: peer " << fetch->pd->host << ", reason: " << reason);
551e60a9 786 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 0);
787}
788
e13ee7ad 789/* call this when all callback data is valid but something bad happened */
790static void
791peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason)
792{
551e60a9 793 assert(reason);
b4197865 794 debugs(72, 2, "peerDigestFetchAbort: peer " << fetch->pd->host << ", reason: " << reason);
e13ee7ad 795 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 1);
796}
797
798/* complete the digest transfer, update stats, unlock/release everything */
799static void
800peerDigestReqFinish(DigestFetchState * fetch, char *buf,
62e76326 801 int fcb_valid, int pdcb_valid, int pcb_valid,
802 const char *reason, int err)
e13ee7ad 803{
804 assert(reason);
805
806 /* must go before peerDigestPDFinish */
62e76326 807
e13ee7ad 808 if (pdcb_valid) {
be4d35dc 809 fetch->pd->flags.requested = false;
62e76326 810 fetch->pd->req_result = reason;
e13ee7ad 811 }
62e76326 812
e13ee7ad 813 /* schedule next check if peer is still out there */
814 if (pcb_valid) {
62e76326 815 PeerDigest *pd = fetch->pd;
816
817 if (err) {
818 pd->times.retry_delay = peerDigestIncDelay(pd);
819 peerDigestSetCheck(pd, pd->times.retry_delay);
820 } else {
821 pd->times.retry_delay = 0;
822 peerDigestSetCheck(pd, peerDigestNewDelay(fetch->entry));
823 }
9b7de833 824 }
62e76326 825
e13ee7ad 826 /* note: order is significant */
827 if (fcb_valid)
62e76326 828 peerDigestFetchSetStats(fetch);
829
e13ee7ad 830 if (pdcb_valid)
62e76326 831 peerDigestPDFinish(fetch, pcb_valid, err);
832
e13ee7ad 833 if (fcb_valid)
62e76326 834 peerDigestFetchFinish(fetch, err);
9b7de833 835}
836
e13ee7ad 837/* destroys digest if peer disappeared
838 * must be called only when fetch and pd cbdata are valid */
9b7de833 839static void
e13ee7ad 840peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err)
9b7de833 841{
e13ee7ad 842 PeerDigest *pd = fetch->pd;
b4197865 843 const char *host = pd->host.termedBuf();
e13ee7ad 844
845 pd->times.received = squid_curtime;
846 pd->times.req_delay = fetch->resp_time;
8a6218c6 847 kb_incr(&pd->stats.sent.kbytes, (size_t) fetch->sent.bytes);
848 kb_incr(&pd->stats.recv.kbytes, (size_t) fetch->recv.bytes);
e13ee7ad 849 pd->stats.sent.msgs += fetch->sent.msg;
850 pd->stats.recv.msgs += fetch->recv.msg;
851
852 if (err) {
e0236918 853 debugs(72, DBG_IMPORTANT, "" << (pcb_valid ? "temporary " : "" ) << "disabling (" << pd->req_result << ") digest from " << host);
62e76326 854
855 if (pd->cd) {
856 cacheDigestDestroy(pd->cd);
857 pd->cd = NULL;
858 }
859
be4d35dc 860 pd->flags.usable = false;
62e76326 861
862 if (!pcb_valid)
863 peerDigestNotePeerGone(pd);
e13ee7ad 864 } else {
62e76326 865 assert(pcb_valid);
866
be4d35dc 867 pd->flags.usable = true;
e13ee7ad 868
62e76326 869 /* XXX: ugly condition, but how? */
e13ee7ad 870
62e76326 871 if (fetch->entry->store_status == STORE_OK)
bf8fe701 872 debugs(72, 2, "re-used old digest from " << host);
62e76326 873 else
bf8fe701 874 debugs(72, 2, "received valid digest from " << host);
d1cdaa16 875 }
62e76326 876
fa80a8ef 877 cbdataReferenceDone(fetch->pd);
e13ee7ad 878}
879
880/* free fetch state structures
881 * must be called only when fetch cbdata is valid */
882static void
883peerDigestFetchFinish(DigestFetchState * fetch, int err)
884{
885 assert(fetch->entry && fetch->request);
886
9b7de833 887 if (fetch->old_entry) {
48e7baac 888 debugs(72, 3, "peerDigestFetchFinish: deleting old entry");
8121ba82 889 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
d88e3c49 890 fetch->old_entry->releaseRequest();
acc5dc4c 891 fetch->old_entry->unlock("peerDigestFetchFinish old");
62e76326 892 fetch->old_entry = NULL;
9b7de833 893 }
62e76326 894
1543ab6c 895 /* update global stats */
83704487 896 kb_incr(&statCounter.cd.kbytes_sent, (size_t) fetch->sent.bytes);
62e76326 897
83704487 898 kb_incr(&statCounter.cd.kbytes_recv, (size_t) fetch->recv.bytes);
62e76326 899
83704487 900 statCounter.cd.msgs_sent += fetch->sent.msg;
62e76326 901
83704487 902 statCounter.cd.msgs_recv += fetch->recv.msg;
e13ee7ad 903
1543ab6c 904 /* unlock everything */
06d2839d 905 storeUnregister(fetch->sc, fetch->entry, fetch);
62e76326 906
acc5dc4c 907 fetch->entry->unlock("peerDigestFetchFinish new");
62e76326 908
6dd9f4bd 909 HTTPMSGUNLOCK(fetch->request);
62e76326 910
9b7de833 911 fetch->entry = NULL;
62e76326 912
3855c318 913 assert(fetch->pd == NULL);
62e76326 914
9b7de833 915 cbdataFree(fetch);
9b7de833 916}
917
e13ee7ad 918/* calculate fetch stats after completion */
919static void
920peerDigestFetchSetStats(DigestFetchState * fetch)
921{
922 MemObject *mem;
923 assert(fetch->entry && fetch->request);
924
925 mem = fetch->entry->mem_obj;
926 assert(mem);
927
928 /* XXX: outgoing numbers are not precise */
929 /* XXX: we must distinguish between 304 hits and misses here */
e11fe29a 930 fetch->sent.bytes = fetch->request->prefixLen();
528b2c61 931 /* XXX: this is slightly wrong: we don't KNOW that the entire memobject
932 * was fetched. We only know how big it is
933 */
934 fetch->recv.bytes = mem->size();
e13ee7ad 935 fetch->sent.msg = fetch->recv.msg = 1;
936 fetch->expires = fetch->entry->expires;
937 fetch->resp_time = squid_curtime - fetch->start_time;
938
bf8fe701 939 debugs(72, 3, "peerDigestFetchFinish: recv " << fetch->recv.bytes <<
940 " bytes in " << (int) fetch->resp_time << " secs");
941
942 debugs(72, 3, "peerDigestFetchFinish: expires: " <<
943 (long int) fetch->expires << " (" << std::showpos <<
944 (int) (fetch->expires - squid_curtime) << "), lmt: " <<
945 std::noshowpos << (long int) fetch->entry->lastmod << " (" <<
946 std::showpos << (int) (fetch->entry->lastmod - squid_curtime) <<
947 ")");
948
e13ee7ad 949}
950
9b7de833 951static int
8a6218c6 952peerDigestSetCBlock(PeerDigest * pd, const char *buf)
9b7de833 953{
954 StoreDigestCBlock cblock;
955 int freed_size = 0;
b4197865 956 const char *host = pd->host.termedBuf();
e13ee7ad 957
41d00cd3 958 memcpy(&cblock, buf, sizeof(cblock));
9b7de833 959 /* network -> host conversions */
960 cblock.ver.current = ntohs(cblock.ver.current);
961 cblock.ver.required = ntohs(cblock.ver.required);
962 cblock.capacity = ntohl(cblock.capacity);
963 cblock.count = ntohl(cblock.count);
964 cblock.del_count = ntohl(cblock.del_count);
965 cblock.mask_size = ntohl(cblock.mask_size);
bf8fe701 966 debugs(72, 2, "got digest cblock from " << host << "; ver: " <<
967 (int) cblock.ver.current << " (req: " << (int) cblock.ver.required <<
968 ")");
969
26ac0430
AJ
970 debugs(72, 2, "\t size: " <<
971 cblock.mask_size << " bytes, e-cnt: " <<
972 cblock.count << ", e-util: " <<
bf8fe701 973 xpercentInt(cblock.count, cblock.capacity) << "%" );
6106c6fc 974 /* check version requirements (both ways) */
62e76326 975
9b7de833 976 if (cblock.ver.required > CacheDigestVer.current) {
e0236918 977 debugs(72, DBG_IMPORTANT, "" << host << " digest requires version " <<
bf8fe701 978 cblock.ver.required << "; have: " << CacheDigestVer.current);
979
62e76326 980 return 0;
9b7de833 981 }
62e76326 982
6106c6fc 983 if (cblock.ver.current < CacheDigestVer.required) {
e0236918 984 debugs(72, DBG_IMPORTANT, "" << host << " digest is version " <<
bf8fe701 985 cblock.ver.current << "; we require: " <<
986 CacheDigestVer.required);
987
62e76326 988 return 0;
6106c6fc 989 }
62e76326 990
9b7de833 991 /* check consistency */
4b4cd312 992 if (cblock.ver.required > cblock.ver.current ||
62e76326 993 cblock.mask_size <= 0 || cblock.capacity <= 0 ||
994 cblock.bits_per_entry <= 0 || cblock.hash_func_count <= 0) {
fa84c01d 995 debugs(72, DBG_CRITICAL, "" << host << " digest cblock is corrupted.");
62e76326 996 return 0;
9b7de833 997 }
62e76326 998
d1cdaa16 999 /* check consistency further */
e6ccf245 1000 if ((size_t)cblock.mask_size != cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)) {
fa84c01d 1001 debugs(72, DBG_CRITICAL, host << " digest cblock is corrupted " <<
e4049756 1002 "(mask size mismatch: " << cblock.mask_size << " ? " <<
1003 cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)
1004 << ").");
62e76326 1005 return 0;
d1cdaa16 1006 }
62e76326 1007
d1cdaa16 1008 /* there are some things we cannot do yet */
1009 if (cblock.hash_func_count != CacheDigestHashFuncCount) {
fa84c01d 1010 debugs(72, DBG_CRITICAL, "" << host << " digest: unsupported #hash functions: " <<
bf8fe701 1011 cblock.hash_func_count << " ? " << CacheDigestHashFuncCount << ".");
62e76326 1012 return 0;
d1cdaa16 1013 }
62e76326 1014
9b7de833 1015 /*
1016 * no cblock bugs below this point
1017 */
1018 /* check size changes */
e6ccf245 1019 if (pd->cd && cblock.mask_size != (ssize_t)pd->cd->mask_size) {
e4049756 1020 debugs(72, 2, host << " digest changed size: " << cblock.mask_size <<
1021 " -> " << pd->cd->mask_size);
62e76326 1022 freed_size = pd->cd->mask_size;
1023 cacheDigestDestroy(pd->cd);
1024 pd->cd = NULL;
9b7de833 1025 }
62e76326 1026
e13ee7ad 1027 if (!pd->cd) {
26ac0430
AJ
1028 debugs(72, 2, "creating " << host << " digest; size: " << cblock.mask_size << " (" <<
1029 std::showpos << (int) (cblock.mask_size - freed_size) << ") bytes");
62e76326 1030 pd->cd = cacheDigestCreate(cblock.capacity, cblock.bits_per_entry);
1031
1032 if (cblock.mask_size >= freed_size)
1033 kb_incr(&statCounter.cd.memory, cblock.mask_size - freed_size);
9b7de833 1034 }
62e76326 1035
e13ee7ad 1036 assert(pd->cd);
9b7de833 1037 /* these assignments leave us in an inconsistent state until we finish reading the digest */
e13ee7ad 1038 pd->cd->count = cblock.count;
1039 pd->cd->del_count = cblock.del_count;
9b7de833 1040 return 1;
1041}
1042
9b7de833 1043static int
8a6218c6 1044peerDigestUseful(const PeerDigest * pd)
9b7de833 1045{
d1cdaa16 1046 /* TODO: we should calculate the prob of a false hit instead of bit util */
e13ee7ad 1047 const int bit_util = cacheDigestBitUtil(pd->cd);
62e76326 1048
e13ee7ad 1049 if (bit_util > 65) {
fa84c01d 1050 debugs(72, DBG_CRITICAL, "Warning: " << pd->host <<
bf8fe701 1051 " peer digest has too many bits on (" << bit_util << "%%).");
1052
62e76326 1053 return 0;
9b7de833 1054 }
62e76326 1055
9b7de833 1056 return 1;
1057}
7f6eb0fe 1058
e13ee7ad 1059static int
1060saneDiff(time_t diff)
1061{
3db6fb54 1062 return abs((int) diff) > squid_curtime / 2 ? 0 : diff;
e13ee7ad 1063}
1064
1065void
8a6218c6 1066peerDigestStatsReport(const PeerDigest * pd, StoreEntry * e)
e13ee7ad 1067{
1068#define f2s(flag) (pd->flags.flag ? "yes" : "no")
38650cc8 1069#define appendTime(tm) storeAppendPrintf(e, "%s\t %10ld\t %+d\t %+d\n", \
1070 ""#tm, (long int)pd->times.tm, \
e13ee7ad 1071 saneDiff(pd->times.tm - squid_curtime), \
1072 saneDiff(pd->times.tm - pd->times.initialized))
1073
e13ee7ad 1074 assert(pd);
1075
b4197865 1076 const char *host = pd->host.termedBuf();
e13ee7ad 1077 storeAppendPrintf(e, "\npeer digest from %s\n", host);
1078
1079 cacheDigestGuessStatsReport(&pd->stats.guess, e, host);
1080
1081 storeAppendPrintf(e, "\nevent\t timestamp\t secs from now\t secs from init\n");
1082 appendTime(initialized);
1083 appendTime(needed);
1084 appendTime(requested);
1085 appendTime(received);
1086 appendTime(next_check);
1087
1088 storeAppendPrintf(e, "peer digest state:\n");
1089 storeAppendPrintf(e, "\tneeded: %3s, usable: %3s, requested: %3s\n",
62e76326 1090 f2s(needed), f2s(usable), f2s(requested));
e13ee7ad 1091 storeAppendPrintf(e, "\n\tlast retry delay: %d secs\n",
62e76326 1092 (int) pd->times.retry_delay);
e13ee7ad 1093 storeAppendPrintf(e, "\tlast request response time: %d secs\n",
62e76326 1094 (int) pd->times.req_delay);
e13ee7ad 1095 storeAppendPrintf(e, "\tlast request result: %s\n",
62e76326 1096 pd->req_result ? pd->req_result : "(none)");
e13ee7ad 1097
1098 storeAppendPrintf(e, "\npeer digest traffic:\n");
1099 storeAppendPrintf(e, "\trequests sent: %d, volume: %d KB\n",
62e76326 1100 pd->stats.sent.msgs, (int) pd->stats.sent.kbytes.kb);
e13ee7ad 1101 storeAppendPrintf(e, "\treplies recv: %d, volume: %d KB\n",
62e76326 1102 pd->stats.recv.msgs, (int) pd->stats.recv.kbytes.kb);
e13ee7ad 1103
1104 storeAppendPrintf(e, "\npeer digest structure:\n");
62e76326 1105
e13ee7ad 1106 if (pd->cd)
62e76326 1107 cacheDigestReport(pd->cd, host, e);
e13ee7ad 1108 else
62e76326 1109 storeAppendPrintf(e, "\tno in-memory copy\n");
e13ee7ad 1110}
1111
7f6eb0fe 1112#endif